import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport/data/models/zipcode_dto.dart'; class ZipcodeTable extends StatelessWidget { final List zipcodes; final int currentPage; final int totalPages; final Function(int) onPageChanged; final Function(ZipcodeDto) onSelect; const ZipcodeTable({ super.key, required this.zipcodes, required this.currentPage, required this.totalPages, required this.onPageChanged, required this.onSelect, }); void _copyToClipboard(BuildContext context, String text, String label) { Clipboard.setData(ClipboardData(text: text)); ShadToaster.of(context).show( ShadToast( title: Text('$label 복사됨'), description: Text(text), duration: const Duration(seconds: 2), ), ); } @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Column( children: [ Expanded( child: ShadCard( child: SingleChildScrollView( child: DataTable( horizontalMargin: 16, columnSpacing: 24, columns: const [ DataColumn( label: Text( '우편번호', style: TextStyle(fontWeight: FontWeight.w600), ), ), DataColumn( label: Text( '시도', style: TextStyle(fontWeight: FontWeight.w600), ), ), DataColumn( label: Text( '구/군', style: TextStyle(fontWeight: FontWeight.w600), ), ), DataColumn( label: Text( '상세주소', style: TextStyle(fontWeight: FontWeight.w600), ), ), DataColumn( label: Text( '작업', style: TextStyle(fontWeight: FontWeight.w600), ), ), ], rows: zipcodes.map((zipcode) { return DataRow( cells: [ // 우편번호 DataCell( Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: theme.colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: Text( zipcode.zipcode.toString().padLeft(5, '0'), style: TextStyle( fontWeight: FontWeight.w600, color: theme.colorScheme.primary, fontFamily: 'monospace', ), ), ), const SizedBox(width: 8), ShadButton.ghost( onPressed: () => _copyToClipboard( context, zipcode.zipcode.toString().padLeft(5, '0'), '우편번호' ), size: ShadButtonSize.sm, child: Icon( Icons.copy, size: 14, color: theme.colorScheme.mutedForeground, ), ), ], ), ), // 시도 DataCell( Row( children: [ Icon( Icons.location_city, size: 16, color: theme.colorScheme.mutedForeground, ), const SizedBox(width: 6), Text( zipcode.sido, style: const TextStyle(fontWeight: FontWeight.w500), ), ], ), ), // 구/군 DataCell( Row( children: [ Icon( Icons.location_on, size: 16, color: theme.colorScheme.mutedForeground, ), const SizedBox(width: 6), Text( zipcode.gu, style: const TextStyle(fontWeight: FontWeight.w500), ), ], ), ), // 상세주소 DataCell( ConstrainedBox( constraints: const BoxConstraints(maxWidth: 300), child: Row( children: [ Expanded( child: Text( zipcode.etc, overflow: TextOverflow.ellipsis, style: TextStyle( color: theme.colorScheme.foreground, ), ), ), const SizedBox(width: 8), ShadButton.ghost( onPressed: () => _copyToClipboard( context, zipcode.fullAddress, '전체주소' ), size: ShadButtonSize.sm, child: Icon( Icons.copy, size: 14, color: theme.colorScheme.mutedForeground, ), ), ], ), ), ), // 작업 DataCell( Row( mainAxisSize: MainAxisSize.min, children: [ ShadButton( onPressed: () => onSelect(zipcode), size: ShadButtonSize.sm, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check, size: 14), SizedBox(width: 4), Text('선택'), ], ), ), const SizedBox(width: 6), ShadButton.outline( onPressed: () => _showAddressDetails(context, zipcode), size: ShadButtonSize.sm, child: const Icon(Icons.info_outline, size: 14), ), ], ), ), ], ); }).toList(), ), ), ), ), // 페이지네이션 if (totalPages > 1) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border( top: BorderSide( color: theme.colorScheme.border, width: 1, ), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ShadButton.ghost( onPressed: currentPage > 1 ? () => onPageChanged(currentPage - 1) : null, size: ShadButtonSize.sm, child: const Icon(Icons.chevron_left, size: 16), ), const SizedBox(width: 8), // 페이지 번호 표시 ...List.generate( totalPages > 7 ? 7 : totalPages, (index) { int pageNumber; if (totalPages <= 7) { pageNumber = index + 1; } else if (currentPage <= 4) { pageNumber = index + 1; } else if (currentPage >= totalPages - 3) { pageNumber = totalPages - 6 + index; } else { pageNumber = currentPage - 3 + index; } if (pageNumber > totalPages) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: pageNumber == currentPage ? ShadButton( onPressed: () => onPageChanged(pageNumber), size: ShadButtonSize.sm, child: Text(pageNumber.toString()), ) : ShadButton.outline( onPressed: () => onPageChanged(pageNumber), size: ShadButtonSize.sm, child: Text(pageNumber.toString()), ), ); }, ), const SizedBox(width: 8), ShadButton.ghost( onPressed: currentPage < totalPages ? () => onPageChanged(currentPage + 1) : null, size: ShadButtonSize.sm, child: const Icon(Icons.chevron_right, size: 16), ), const SizedBox(width: 24), // 페이지 정보 Text( '페이지 $currentPage / $totalPages', style: theme.textTheme.muted, ), ], ), ), ], ); } void _showAddressDetails(BuildContext context, ZipcodeDto zipcode) { showDialog( context: context, builder: (context) => ShadDialog( title: const Text('우편번호 상세정보'), description: const Text('선택한 우편번호의 상세 정보입니다'), child: Container( width: 400, padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 우편번호 _buildInfoRow( context, '우편번호', zipcode.zipcode.toString().padLeft(5, '0'), Icons.local_post_office, ), const SizedBox(height: 12), // 시도 _buildInfoRow( context, '시도', zipcode.sido, Icons.location_city, ), const SizedBox(height: 12), // 구/군 _buildInfoRow( context, '구/군', zipcode.gu, Icons.location_on, ), const SizedBox(height: 12), // 상세주소 _buildInfoRow( context, '상세주소', zipcode.etc, Icons.home, ), const SizedBox(height: 12), // 전체주소 _buildInfoRow( context, '전체주소', zipcode.fullAddress, Icons.place, ), const SizedBox(height: 20), // 액션 버튼 Row( children: [ Expanded( child: ShadButton.outline( onPressed: () => _copyToClipboard( context, zipcode.fullAddress, '전체주소' ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.copy, size: 16), SizedBox(width: 6), Text('주소 복사'), ], ), ), ), const SizedBox(width: 8), Expanded( child: ShadButton( onPressed: () { Navigator.of(context).pop(); onSelect(zipcode); }, child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.check, size: 16), SizedBox(width: 6), Text('선택'), ], ), ), ), ], ), ], ), ), ), ); } Widget _buildInfoRow(BuildContext context, String label, String value, IconData icon) { final theme = ShadTheme.of(context); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( icon, size: 16, color: theme.colorScheme.mutedForeground, ), const SizedBox(width: 8), SizedBox( width: 60, child: Text( label, style: theme.textTheme.small.copyWith( fontWeight: FontWeight.w600, color: theme.colorScheme.mutedForeground, ), ), ), const SizedBox(width: 8), Expanded( child: Text( value, style: theme.textTheme.small.copyWith( fontWeight: FontWeight.w500, ), ), ), ], ); } }