import 'package:flutter/material.dart'; import 'dart:math' as math; 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; // Optional: cap the effective viewport width used by the table final double? maxWidthCap; const ZipcodeTable({ super.key, required this.zipcodes, required this.currentPage, required this.totalPages, required this.onPageChanged, required this.onSelect, this.maxWidthCap, }); 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: LayoutBuilder( builder: (context, constraints) { // 고정폭 + 마지막 filler const double minW = 160 + 180 + 180 + 320 + 140 + 24; final double viewportW = constraints.maxWidth.isFinite ? constraints.maxWidth : MediaQuery.sizeOf(context).width; final double cappedW = maxWidthCap != null ? math.min(viewportW, maxWidthCap!) : viewportW; final double tableW = cappedW >= minW ? cappedW : minW; const double etcW = 320.0; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: tableW, child: ShadTable.list( columnSpanExtent: (index) { switch (index) { case 0: return const FixedTableSpanExtent(160); // 우편번호 case 1: return const FixedTableSpanExtent(180); // 시도 case 2: return const FixedTableSpanExtent(180); // 구/군 case 3: return const FixedTableSpanExtent(etcW); // 상세주소 case 4: return const FixedTableSpanExtent(140); // 작업 case 5: return const RemainingTableSpanExtent(); // filler default: return const FixedTableSpanExtent(100); } }, header: [ const ShadTableCell.header(child: Text('우편번호')), const ShadTableCell.header(child: Text('시도')), const ShadTableCell.header(child: Text('구/군')), ShadTableCell.header(child: SizedBox(width: etcW, child: const Text('상세주소'))), const ShadTableCell.header(child: Text('작업')), const ShadTableCell.header(child: SizedBox.shrink()), ], children: zipcodes.map((zipcode) { return [ // 우편번호 (오버플로우 방지) ShadTableCell( child: SizedBox( width: 160, child: FittedBox( alignment: Alignment.centerLeft, fit: BoxFit.scaleDown, child: Row( mainAxisSize: MainAxisSize.min, 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), ), ], ), ), ), ), // 시도 ShadTableCell( child: Row( children: [ Icon(Icons.location_city, size: 16, color: theme.colorScheme.mutedForeground), const SizedBox(width: 6), Flexible(child: Text(zipcode.sido, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.w500))), ], ), ), // 구/군 ShadTableCell( child: Row( children: [ Icon(Icons.location_on, size: 16, color: theme.colorScheme.mutedForeground), const SizedBox(width: 6), Flexible(child: Text(zipcode.gu, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.w500))), ], ), ), // 상세주소 (가변 폭) ShadTableCell( child: SizedBox( width: etcW, 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), ), ], ), ), ), // 작업 ShadTableCell( child: ShadButton( onPressed: () => onSelect(zipcode), size: ShadButtonSize.sm, child: const Text('선택', style: TextStyle(fontSize: 11)), ), ), const ShadTableCell(child: SizedBox.shrink()), ]; }).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, ), ], ), ), ], ); } }