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/widgets/pagination.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; // 클라이언트 사이드 페이지네이션 int _currentPage = 1; static const int _pageSize = 10; @override void initState() { super.initState(); _controller = di.sl(); WidgetsBinding.instance.addPostFrameCallback((_) { _controller.loadInitialData(); }); } // 현재 페이지의 모델 목록 반환 List _getCurrentPageModels() { final allModels = _controller.models; final startIndex = (_currentPage - 1) * _pageSize; final endIndex = startIndex + _pageSize; if (startIndex >= allModels.length) return []; if (endIndex >= allModels.length) return allModels.sublist(startIndex); return allModels.sublist(startIndex, endIndex); } // 총 페이지 수 계산 int _getTotalPages() { return (_controller.models.length / _pageSize).ceil(); } // 페이지 이동 void _goToPage(int page) { final totalPages = _getTotalPages(); if (page < 1 || page > totalPages) return; setState(() { _currentPage = page; }); } @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.companyCustomer, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '제조사', controller.vendors.length.toString(), Icons.business, ShadcnTheme.companyPartner, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '활성 모델', controller.models.where((m) => !m.isDeleted).length.toString(), Icons.check_circle, ShadcnTheme.equipmentIn, ), ], ); } Widget _buildSearchBar(ModelController controller) { return Row( children: [ Expanded( flex: 2, child: ShadInput( placeholder: const Text('모델명 검색...'), onChanged: (value) { setState(() { _currentPage = 1; // 검색 시 첫 페이지로 리셋 }); controller.setSearchQuery(value); }, ), ), 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: (value) { setState(() { _currentPage = 1; // 필터 변경 시 첫 페이지로 리셋 }); controller.setVendorFilter(value); }, ), ), ], ); } 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 _buildDataTable(ModelController controller) { final allModels = controller.models; final currentPageModels = _getCurrentPageModels(); if (allModels.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.category_outlined, size: 64, color: ShadcnTheme.mutedForeground, ), const SizedBox(height: ShadcnTheme.spacing4), Text( '등록된 모델이 없습니다', style: ShadcnTheme.bodyMedium.copyWith( color: ShadcnTheme.mutedForeground, ), ), ], ), ); } return Container( decoration: BoxDecoration( border: Border.all(color: ShadcnTheme.border), borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), 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), ), ], ), ), ]; }).toList(), ), ), ); } Widget _buildPagination(ModelController controller) { 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( color: ShadcnTheme.card, border: Border( top: BorderSide(color: ShadcnTheme.border), ), ), child: Pagination( totalCount: totalCount, currentPage: _currentPage, pageSize: _pageSize, onPageChanged: _goToPage, ), ); } 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( backgroundColor: ShadcnTheme.equipmentDisposal.withValues(alpha: 0.1), foregroundColor: ShadcnTheme.equipmentDisposal, child: const Text('비활성'), ); } else { return ShadBadge( backgroundColor: ShadcnTheme.equipmentIn.withValues(alpha: 0.1), foregroundColor: ShadcnTheme.equipmentIn, 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('삭제'), ), ], ), ); } }