import 'package:flutter/material.dart'; import 'dart:math' as math; 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, }); // copy helpers removed per UX request (no copy icons in popup) @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Column( children: [ Expanded( child: ShadCard( child: LayoutBuilder( builder: (context, constraints) { // 고정폭 + 마지막 filler // Standard widths const double zipStd = 160.0; const double sidoStd = 180.0; const double guStd = 180.0; const double etcStd = 320.0; const double actionsStd = 140.0; const double fudge = 24.0; // internal paddings/margins final double viewportW = constraints.maxWidth.isFinite ? constraints.maxWidth : MediaQuery.sizeOf(context).width; final double targetW = maxWidthCap != null ? math.min(viewportW, maxWidthCap!) : viewportW; final double stdMin = zipStd + sidoStd + guStd + etcStd + actionsStd + fudge; final bool compact = targetW < stdMin; // Effective widths double zipW; double sidoW; double guW; double etcW; double actionsW; bool includeFiller; if (!compact) { zipW = zipStd; sidoW = sidoStd; guW = guStd; etcW = etcStd; actionsW = actionsStd; includeFiller = true; // use remaining width as filler column } else { // Compact widths to fit inside targetW without horizontal scroll zipW = 110.0; sidoW = 120.0; guW = 120.0; actionsW = 110.0; final double remaining = targetW - fudge - (zipW + sidoW + guW + actionsW); etcW = math.max(180.0, remaining); includeFiller = false; // no filler in compact mode } final double tableW = targetW; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: tableW, child: ShadTable.list( columnSpanExtent: (index) { switch (index) { case 0: return FixedTableSpanExtent(zipW); // 우편번호 case 1: return FixedTableSpanExtent(sidoW); // 시도 case 2: return FixedTableSpanExtent(guW); // 구/군 case 3: return FixedTableSpanExtent(etcW); // 상세주소 case 4: return FixedTableSpanExtent(actionsW); // 작업 case 5: return includeFiller ? const RemainingTableSpanExtent() : const FixedTableSpanExtent(0); 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('상세주소'))), ShadTableCell.header(child: SizedBox(width: actionsW, child: const Text('작업'))), if (includeFiller) const ShadTableCell.header(child: SizedBox.shrink()), ], children: zipcodes.map((zipcode) { return [ // 우편번호 (오버플로우 방지) ShadTableCell( child: SizedBox( width: zipW, 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', ), ), ), // copy icon removed per spec ], ), ), ), ), // 시도 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: Text(zipcode.etc, overflow: TextOverflow.ellipsis, style: TextStyle(color: theme.colorScheme.foreground)), ), ), // 작업 ShadTableCell( child: SizedBox( width: actionsW, child: ShadButton( onPressed: () => onSelect(zipcode), size: ShadButtonSize.sm, child: const Text('선택', style: TextStyle(fontSize: 11)), ), ), ), if (includeFiller) 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, ), ], ), ), ], ); } }