feat(ui): full‑width ShadTable across app; fix rent dialog width; correct equipment pagination
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- ShadTable: ensure full-width via LayoutBuilder+ConstrainedBox minWidth
- BaseListScreen: default data area padding = 0 for table edge-to-edge
- Vendor/Model/User/Company/Inventory/Zipcode: set columnSpanExtent per column
  and add final filler column to absorb remaining width; pin date/status/actions
  widths; ensure date text is single-line
- Equipment: unify card/border style; define fixed column widths + filler;
  increase checkbox column to 56px to avoid overflow
- Rent list: migrate to ShadTable.list with fixed widths + filler column
- Rent form dialog: prevent infinite width by bounding ShadProgress with
  SizedBox and remove Expanded from option rows; add safe selectedOptionBuilder
- Admin list: fix const with non-const argument in table column extents
- Services/Controller: remove hardcoded perPage=10; use BaseListController
  perPage; trust server meta (total/totalPages) in equipment pagination
- widgets/shad_table: ConstrainedBox(minWidth=viewport) so table stretches

Run: flutter analyze → 0 errors (warnings remain).
This commit is contained in:
JiWoong Sul
2025-09-09 22:38:08 +09:00
parent 655d473413
commit 49b203d366
67 changed files with 2305 additions and 1933 deletions

View File

@@ -94,6 +94,7 @@ class _ModelListScreenState extends State<ModelListScreen> {
isLoading: controller.isLoading,
error: controller.errorMessage,
onRefresh: () => controller.loadInitialData(),
dataAreaPadding: EdgeInsets.zero,
);
},
),
@@ -245,41 +246,105 @@ class _ModelListScreenState extends State<ModelListScreen> {
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: ShadTable.list(
header: const [
ShadTableCell.header(child: Text('ID')),
ShadTableCell.header(child: Text('제조사')),
ShadTableCell.header(child: Text('모델명')),
ShadTableCell.header(child: Text('등록일')),
ShadTableCell.header(child: Text('상태')),
ShadTableCell.header(child: Text('작업')),
],
children: currentPageModels.map((model) {
final vendor = _controller.getVendorById(model.vendorsId);
return [
ShadTableCell(child: Text(model.id.toString(), style: ShadcnTheme.bodySmall)),
ShadTableCell(child: Text(vendor?.name ?? '알 수 없음', overflow: TextOverflow.ellipsis)),
ShadTableCell(child: Text(model.name, overflow: TextOverflow.ellipsis, style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500))),
ShadTableCell(child: Text(model.registeredAt != null ? DateFormat('yyyy-MM-dd').format(model.registeredAt) : '-', style: ShadcnTheme.bodySmall)),
ShadTableCell(child: _buildStatusChip(model.isDeleted)),
ShadTableCell(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ShadButton.ghost(
onPressed: () => _showEditDialog(model),
child: const Icon(Icons.edit, size: 16),
),
const SizedBox(width: ShadcnTheme.spacing1),
ShadButton.ghost(
onPressed: () => _showDeleteConfirmDialog(model),
child: Icon(Icons.delete, size: 16, color: ShadcnTheme.destructive),
),
child: LayoutBuilder(
builder: (context, constraints) {
// 고정폭 + 마지막 filler 컬럼이 남는 폭을 흡수
const double actionsW = 200.0;
const double minTableWidth = 80 + 260 + 320 + 120 + 100 + actionsW + 24;
final double tableWidth = constraints.maxWidth >= minTableWidth
? constraints.maxWidth
: minTableWidth;
const double modelColumnWidth = 320.0;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: tableWidth,
child: ShadTable.list(
columnSpanExtent: (index) {
switch (index) {
case 0:
return const FixedTableSpanExtent(80); // ID
case 1:
return const FixedTableSpanExtent(260); // 제조사
case 2:
return const FixedTableSpanExtent(modelColumnWidth); // 모델명
case 3:
return const FixedTableSpanExtent(120); // 등록일
case 4:
return const FixedTableSpanExtent(100); // 상태
case 5:
return const FixedTableSpanExtent(actionsW); // 작업
case 6:
return const RemainingTableSpanExtent(); // filler
default:
return const FixedTableSpanExtent(100);
}
},
header: [
const ShadTableCell.header(child: Text('ID')),
const ShadTableCell.header(child: Text('제조사')),
ShadTableCell.header(child: SizedBox(width: modelColumnWidth, child: const Text('모델명'))),
ShadTableCell.header(child: SizedBox(width: 120, child: const Text('등록일'))),
ShadTableCell.header(child: SizedBox(width: 100, child: const Text('상태'))),
ShadTableCell.header(child: SizedBox(width: actionsW, child: const Text('작업'))),
const ShadTableCell.header(child: SizedBox.shrink()),
],
children: currentPageModels.map((model) {
final vendor = _controller.getVendorById(model.vendorsId);
return [
ShadTableCell(child: Text(model.id.toString(), style: ShadcnTheme.bodySmall)),
ShadTableCell(child: Text(vendor?.name ?? '알 수 없음', overflow: TextOverflow.ellipsis)),
ShadTableCell(
child: SizedBox(
width: modelColumnWidth,
child: Text(
model.name,
overflow: TextOverflow.ellipsis,
style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500),
),
),
),
ShadTableCell(
child: SizedBox(
width: 120,
child: Text(
model.registeredAt != null ? DateFormat('yyyy-MM-dd').format(model.registeredAt) : '-',
style: ShadcnTheme.bodySmall,
),
),
),
ShadTableCell(child: SizedBox(width: 100, child: _buildStatusChip(model.isDeleted))),
ShadTableCell(
child: SizedBox(
width: actionsW,
child: FittedBox(
alignment: Alignment.centerLeft,
fit: BoxFit.scaleDown,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ShadButton.ghost(
onPressed: () => _showEditDialog(model),
child: const Icon(Icons.edit, size: 16),
),
const SizedBox(width: ShadcnTheme.spacing1),
ShadButton.ghost(
onPressed: () => _showDeleteConfirmDialog(model),
child: Icon(Icons.delete, size: 16, color: ShadcnTheme.destructive),
),
],
),
),
),
),
const ShadTableCell(child: SizedBox.shrink()),
];
}).toList(),
),
),
];
}).toList(),
);
},
),
),
);
@@ -290,27 +355,6 @@ class _ModelListScreenState extends State<ModelListScreen> {
final totalCount = controller.models.length;
final totalPages = _getTotalPages();
if (totalCount <= _pageSize) {
return Container(
padding: const EdgeInsets.all(ShadcnTheme.spacing3),
decoration: BoxDecoration(
color: ShadcnTheme.card,
border: Border(
top: BorderSide(color: ShadcnTheme.border),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$totalCount개 모델',
style: ShadcnTheme.bodySmall,
),
],
),
);
}
return Container(
padding: const EdgeInsets.all(ShadcnTheme.spacing3),
decoration: BoxDecoration(