import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport/data/models/model/model_dto.dart'; import 'package:superport/screens/model/controllers/model_controller.dart'; import 'package:superport/screens/model/model_form_dialog.dart'; import 'package:superport/screens/common/layouts/base_list_screen.dart'; import 'package:superport/screens/common/widgets/standard_action_bar.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/injection_container.dart' as di; class ModelListScreen extends StatefulWidget { const ModelListScreen({super.key}); @override State createState() => _ModelListScreenState(); } class _ModelListScreenState extends State { late final ModelController _controller; @override void initState() { super.initState(); _controller = di.sl(); WidgetsBinding.instance.addPostFrameCallback((_) { _controller.loadInitialData(); }); } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _controller, child: Scaffold( backgroundColor: ShadcnTheme.background, appBar: AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '모델 관리', style: ShadcnTheme.headingH4, ), Text( '장비 모델 정보를 관리합니다', style: ShadcnTheme.bodySmall, ), ], ), backgroundColor: ShadcnTheme.background, elevation: 0, ), body: Consumer( builder: (context, controller, child) { return BaseListScreen( headerSection: _buildStatisticsCards(controller), searchBar: _buildSearchBar(controller), actionBar: _buildActionBar(), dataTable: _buildDataTable(controller), pagination: _buildPagination(controller), isLoading: controller.isLoading, error: controller.errorMessage, onRefresh: () => controller.loadInitialData(), ); }, ), ), ); } Widget _buildStatisticsCards(ModelController controller) { if (controller.isLoading) return const SizedBox(); return Row( children: [ _buildStatCard( '전체 모델', controller.models.length.toString(), Icons.category, ShadcnTheme.primary, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '제조사', controller.vendors.length.toString(), Icons.business, ShadcnTheme.success, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '활성 모델', controller.models.where((m) => !m.isDeleted).length.toString(), Icons.check_circle, ShadcnTheme.info, ), ], ); } Widget _buildSearchBar(ModelController controller) { return Row( children: [ Expanded( flex: 2, child: ShadInput( placeholder: const Text('모델명 검색...'), onChanged: controller.setSearchQuery, ), ), const SizedBox(width: ShadcnTheme.spacing4), Expanded( child: ShadSelect( placeholder: const Text('제조사 선택'), options: [ const ShadOption( value: null, child: Text('전체'), ), ...controller.vendors.map( (vendor) => ShadOption( value: vendor.id, child: Text(vendor.name), ), ), ], selectedOptionBuilder: (context, value) { if (value == null) { return const Text('전체'); } try { final vendor = controller.vendors.firstWhere((v) => v.id == value); return Text(vendor.name); } catch (_) { return const Text('전체'); } }, onChanged: controller.setVendorFilter, ), ), ], ); } Widget _buildActionBar() { return StandardActionBar( totalCount: _controller.totalCount, leftActions: const [ Text('모델 목록', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ], rightActions: [ ShadButton.outline( onPressed: () => _controller.refreshModels(), child: const Icon(Icons.refresh, size: 16), ), const SizedBox(width: ShadcnTheme.spacing2), ShadButton( onPressed: _showCreateDialog, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add, size: 16), SizedBox(width: ShadcnTheme.spacing1), Text('새 모델 등록'), ], ), ), ], ); } /// 헤더 셀 빌더 Widget _buildHeaderCell( String text, { required int flex, required bool useExpanded, required double minWidth, }) { final child = Container( alignment: Alignment.centerLeft, child: Text( text, style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500), ), ); if (useExpanded) { return Expanded(flex: flex, child: child); } else { return SizedBox(width: minWidth, child: child); } } /// 데이터 셀 빌더 Widget _buildDataCell( Widget child, { required int flex, required bool useExpanded, required double minWidth, }) { final container = Container( alignment: Alignment.centerLeft, child: child, ); if (useExpanded) { return Expanded(flex: flex, child: container); } else { return SizedBox(width: minWidth, child: container); } } /// 헤더 셀 리스트 List _buildHeaderCells() { return [ _buildHeaderCell('ID', flex: 0, useExpanded: false, minWidth: 60), _buildHeaderCell('제조사', flex: 2, useExpanded: true, minWidth: 100), _buildHeaderCell('모델명', flex: 3, useExpanded: true, minWidth: 120), _buildHeaderCell('등록일', flex: 2, useExpanded: true, minWidth: 100), _buildHeaderCell('상태', flex: 0, useExpanded: false, minWidth: 80), _buildHeaderCell('작업', flex: 0, useExpanded: false, minWidth: 100), ]; } /// 테이블 행 빌더 Widget _buildTableRow(ModelDto model, int index) { final vendor = _controller.getVendorById(model.vendorsId); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: index.isEven ? ShadcnTheme.muted.withValues(alpha: 0.1) : null, border: const Border( bottom: BorderSide(color: Colors.black), ), ), child: Row( children: [ _buildDataCell( Text( model.id.toString(), style: ShadcnTheme.bodySmall, ), flex: 0, useExpanded: false, minWidth: 60, ), _buildDataCell( Text( vendor?.name ?? '알 수 없음', style: ShadcnTheme.bodyMedium, ), flex: 2, useExpanded: true, minWidth: 100, ), _buildDataCell( Text( model.name, style: ShadcnTheme.bodyMedium.copyWith( fontWeight: FontWeight.w500, ), ), flex: 3, useExpanded: true, minWidth: 120, ), _buildDataCell( Text( model.registeredAt != null ? DateFormat('yyyy-MM-dd').format(model.registeredAt) : '-', style: ShadcnTheme.bodySmall, ), flex: 2, useExpanded: true, minWidth: 100, ), _buildDataCell( _buildStatusChip(model.isDeleted), flex: 0, useExpanded: false, minWidth: 80, ), _buildDataCell( 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: const Icon(Icons.delete, size: 16), ), ], ), flex: 0, useExpanded: false, minWidth: 100, ), ], ), ); } Widget _buildDataTable(ModelController controller) { final models = controller.models; return Container( width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: Column( children: [ // 고정 헤더 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: ShadcnTheme.muted.withValues(alpha: 0.3), border: Border(bottom: BorderSide(color: Colors.black)), ), child: Row(children: _buildHeaderCells()), ), // 스크롤 바디 Expanded( child: models.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.category_outlined, size: 64, color: ShadcnTheme.mutedForeground, ), const SizedBox(height: 16), Text( '등록된 모델이 없습니다', style: ShadcnTheme.bodyMedium.copyWith( color: ShadcnTheme.mutedForeground, ), ), ], ), ) : ListView.builder( itemCount: models.length, itemBuilder: (context, index) => _buildTableRow(models[index], index), ), ), ], ), ); } Widget _buildPagination(ModelController controller) { // 모델 목록은 현재 페이지네이션이 없는 것 같으니 빈 위젯 반환 return const SizedBox(); } Widget _buildStatCard( String title, String value, IconData icon, Color color, ) { return Expanded( child: ShadCard( child: Padding( padding: const EdgeInsets.all(ShadcnTheme.spacing4), child: Row( children: [ Container( padding: const EdgeInsets.all(ShadcnTheme.spacing3), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg), ), child: Icon( icon, color: color, size: 20, ), ), const SizedBox(width: ShadcnTheme.spacing3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: ShadcnTheme.bodySmall, ), Text( value, style: ShadcnTheme.headingH6.copyWith( color: color, ), ), ], ), ), ], ), ), ), ); } Widget _buildStatusChip(bool isDeleted) { if (isDeleted) { return ShadBadge.destructive( child: const Text('비활성'), ); } else { return ShadBadge.secondary( child: const Text('활성'), ); } } void _showCreateDialog() { showDialog( context: context, builder: (context) => ModelFormDialog( controller: _controller, ), ); } void _showEditDialog(ModelDto model) { showDialog( context: context, builder: (context) => ModelFormDialog( controller: _controller, model: model, ), ); } void _showDeleteConfirmDialog(ModelDto model) { showDialog( context: context, builder: (context) => ShadDialog( title: const Text('모델 삭제'), description: Text('${model.name} 모델을 삭제하시겠습니까?'), actions: [ ShadButton.outline( onPressed: () => Navigator.of(context).pop(), child: const Text('취소'), ), ShadButton.destructive( onPressed: () async { Navigator.of(context).pop(); await _controller.deleteModel(model.id); }, child: const Text('삭제'), ), ], ), ); } }