import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:get_it/get_it.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/screens/common/widgets/pagination.dart'; import 'package:superport/screens/common/widgets/standard_action_bar.dart'; import 'package:superport/screens/common/widgets/standard_states.dart'; import 'package:superport/screens/maintenance/controllers/maintenance_controller.dart'; import 'package:superport/screens/maintenance/maintenance_form_dialog.dart'; import 'package:superport/data/models/maintenance_dto.dart'; import 'package:superport/domain/usecases/maintenance_usecase.dart'; /// shadcn/ui 스타일로 설계된 유지보수 관리 화면 class MaintenanceList extends StatefulWidget { const MaintenanceList({super.key}); @override State createState() => _MaintenanceListState(); } class _MaintenanceListState extends State { late final MaintenanceController _controller; bool _showDetailedColumns = true; final TextEditingController _searchController = TextEditingController(); final ScrollController _horizontalScrollController = ScrollController(); final Set _selectedItems = {}; @override void initState() { super.initState(); _controller = MaintenanceController( maintenanceUseCase: GetIt.instance(), ); // 초기 데이터 로드 WidgetsBinding.instance.addPostFrameCallback((_) { _controller.loadMaintenances(refresh: true); _controller.loadExpiringMaintenances(); }); } @override void dispose() { _searchController.dispose(); _horizontalScrollController.dispose(); _controller.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); _adjustColumnsForScreenSize(); } /// 화면 크기에 따라 컬럼 표시 조정 void _adjustColumnsForScreenSize() { final width = MediaQuery.of(context).size.width; setState(() { _showDetailedColumns = width > 1000; }); } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _controller, child: Scaffold( backgroundColor: ShadcnTheme.background, body: Column( children: [ _buildActionBar(), _buildFilterBar(), Expanded(child: _buildMainContent()), _buildBottomBar(), ], ), ), ); } /// 상단 액션바 Widget _buildActionBar() { return Consumer( builder: (context, controller, child) { return StandardActionBar( totalCount: controller.totalCount, selectedCount: _selectedItems.length, leftActions: const [ Text('유지보수 관리', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ], rightActions: [ // 만료 예정 알림 if (controller.expiringMaintenances.isNotEmpty) ShadButton.outline( child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.notification_important, size: 16), const SizedBox(width: 4), Text('만료 예정 ${controller.expiringMaintenances.length}건'), ], ), onPressed: () => _showExpiringMaintenances(), ), // 새로운 유지보수 등록 ShadButton( child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add, size: 16), SizedBox(width: 4), Text('유지보수 등록'), ], ), onPressed: () => _showMaintenanceForm(), ), // 선택된 항목 삭제 if (_selectedItems.isNotEmpty) ShadButton.destructive( child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.delete, size: 16), const SizedBox(width: 4), Text('삭제 (${_selectedItems.length})'), ], ), onPressed: () => _showDeleteConfirmation(), ), ], ); }, ); } /// 필터바 Widget _buildFilterBar() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: ShadcnTheme.card, border: Border( bottom: BorderSide(color: ShadcnTheme.border), ), ), child: Row( children: [ // 유지보수 타입 필터 Expanded( flex: 2, child: ShadSelect( placeholder: const Text('유지보수 타입'), options: [ const ShadOption(value: 'all', child: Text('전체')), ...MaintenanceType.typeOptions.map((option) => ShadOption( value: option['value']!, child: Text(option['label']!), ), ), ], selectedOptionBuilder: (context, value) { if (value == 'all') return const Text('전체'); final option = MaintenanceType.typeOptions .firstWhere((o) => o['value'] == value, orElse: () => {'label': value}); return Text(option['label']!); }, onChanged: (value) { _controller.setMaintenanceTypeFilter( value == 'all' ? null : value, ); }, ), ), const SizedBox(width: 12), // 상태 필터 Expanded( flex: 2, child: ShadSelect( placeholder: const Text('상태'), options: const [ ShadOption(value: 'all', child: Text('전체')), ShadOption(value: 'active', child: Text('활성')), ShadOption(value: 'expired', child: Text('만료')), ShadOption(value: 'expiring', child: Text('만료 예정')), ], selectedOptionBuilder: (context, value) { switch (value) { case 'active': return const Text('활성'); case 'expired': return const Text('만료'); case 'expiring': return const Text('만료 예정'); default: return const Text('전체'); } }, onChanged: (value) { _applyStatusFilter(value ?? 'all'); }, ), ), const SizedBox(width: 12), // 검색 Expanded( flex: 3, child: ShadInput( controller: _searchController, placeholder: const Text('장비 시리얼 번호 또는 모델명 검색...'), onSubmitted: (_) => _performSearch(), ), ), const SizedBox(width: 12), // 필터 초기화 ShadButton.outline( child: const Icon(Icons.refresh, size: 16), onPressed: _resetFilters, ), ], ), ); } /// 메인 컨텐츠 Widget _buildMainContent() { return Consumer( builder: (context, controller, child) { if (controller.isLoading && controller.maintenances.isEmpty) { return const StandardLoadingState(message: '유지보수 목록을 불러오는 중...'); } if (controller.error != null) { return StandardErrorState( message: controller.error!, onRetry: () => controller.loadMaintenances(refresh: true), ); } if (controller.maintenances.isEmpty) { return const StandardEmptyState( icon: Icons.build_circle_outlined, title: '유지보수가 없습니다', message: '새로운 유지보수를 등록해보세요.', ); } return _buildDataTable(controller); }, ); } /// 데이터 테이블 Widget _buildDataTable(MaintenanceController controller) { return SingleChildScrollView( scrollDirection: Axis.horizontal, controller: _horizontalScrollController, child: DataTable( columns: _buildHeaders(), rows: _buildRows(controller.maintenances), ), ); } /// 테이블 헤더 List _buildHeaders() { return [ const DataColumn(label: Text('선택')), const DataColumn(label: Text('ID')), const DataColumn(label: Text('장비 정보')), const DataColumn(label: Text('유지보수 타입')), const DataColumn(label: Text('시작일')), const DataColumn(label: Text('종료일')), if (_showDetailedColumns) ...[ const DataColumn(label: Text('주기')), const DataColumn(label: Text('상태')), const DataColumn(label: Text('남은 일수')), ], const DataColumn(label: Text('작업')), ]; } /// 테이블 로우 List _buildRows(List maintenances) { return maintenances.map((maintenance) { final isSelected = _selectedItems.contains(maintenance.id); return DataRow( selected: isSelected, onSelectChanged: (_) => _showMaintenanceDetail(maintenance), cells: [ // 선택 체크박스 DataCell( Checkbox( value: isSelected, onChanged: (value) { setState(() { if (value == true) { _selectedItems.add(maintenance.id!); } else { _selectedItems.remove(maintenance.id!); } }); }, ), ), // ID DataCell(Text(maintenance.id?.toString() ?? '-')), // 장비 정보 DataCell( Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( maintenance.equipmentSerial ?? '시리얼 번호 없음', style: const TextStyle(fontWeight: FontWeight.w500), ), if (maintenance.equipmentModel != null) Text( maintenance.equipmentModel!, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), // 유지보수 타입 DataCell( Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getMaintenanceTypeColor(maintenance.maintenanceType), borderRadius: BorderRadius.circular(4), ), child: Text( MaintenanceType.getDisplayName(maintenance.maintenanceType), style: const TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500, ), ), ), ), // 시작일 DataCell(Text(DateFormat('yyyy-MM-dd').format(maintenance.startedAt))), // 종료일 DataCell(Text(DateFormat('yyyy-MM-dd').format(maintenance.endedAt))), // 상세 컬럼들 if (_showDetailedColumns) ...[ // 주기 DataCell(Text('${maintenance.periodMonth}개월')), // 상태 DataCell( Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _controller.getMaintenanceStatusColor(maintenance), borderRadius: BorderRadius.circular(4), ), child: Text( _controller.getMaintenanceStatusText(maintenance), style: const TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500, ), ), ), ), // 남은 일수 DataCell( Text( maintenance.daysRemaining != null ? '${maintenance.daysRemaining}일' : '-', style: TextStyle( color: maintenance.daysRemaining != null && maintenance.daysRemaining! <= 30 ? Colors.red : null, ), ), ), ], // 작업 버튼들 DataCell( Row( mainAxisSize: MainAxisSize.min, children: [ ShadButton.ghost( child: const Icon(Icons.edit, size: 16), onPressed: () => _showMaintenanceForm(maintenance: maintenance), ), const SizedBox(width: 4), ShadButton.ghost( child: Icon( Icons.delete, size: 16, color: Colors.red[400], ), onPressed: () => _deleteMaintenance(maintenance), ), ], ), ), ], ); }).toList(); } /// 하단바 (페이지네이션) Widget _buildBottomBar() { return Consumer( builder: (context, controller, child) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: ShadcnTheme.card, border: Border( top: BorderSide(color: ShadcnTheme.border), ), ), child: Row( children: [ // 선택된 항목 정보 if (_selectedItems.isNotEmpty) Text('${_selectedItems.length}개 선택됨'), const Spacer(), // 페이지네이션 Pagination( totalCount: controller.totalCount, currentPage: controller.currentPage, pageSize: 20, // MaintenanceController._perPage 상수값 onPageChanged: (page) => controller.goToPage(page), ), ], ), ); }, ); } // 유틸리티 메서드들 Color _getMaintenanceTypeColor(String type) { switch (type) { case MaintenanceType.warranty: return Colors.blue; case MaintenanceType.contract: return Colors.orange; case MaintenanceType.inspection: return Colors.green; default: return Colors.grey; } } void _applyStatusFilter(String status) { switch (status) { case 'active': _controller.setExpiredFilter(false); break; case 'expired': _controller.setExpiredFilter(true); break; case 'expiring': _controller.setExpiringDaysFilter(30); break; default: _controller.setExpiredFilter(null); _controller.setExpiringDaysFilter(null); } } void _performSearch() { // TODO: 장비 시리얼/모델 검색 구현 // 백엔드에 검색 API가 추가되면 구현 } void _resetFilters() { setState(() { _searchController.clear(); }); _controller.clearFilters(); } // 다이얼로그 메서드들 void _showMaintenanceForm({MaintenanceDto? maintenance}) { showDialog( context: context, builder: (context) => ChangeNotifierProvider.value( value: _controller, child: MaintenanceFormDialog(maintenance: maintenance), ), ).then((_) { // 폼 닫힌 후 목록 새로고침 _controller.loadMaintenances(refresh: true); }); } void _showMaintenanceDetail(MaintenanceDto maintenance) { _controller.selectMaintenance(maintenance); _showMaintenanceForm(maintenance: maintenance); } void _showExpiringMaintenances() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('만료 예정 유지보수'), content: SizedBox( width: 600, height: 400, child: ListView.builder( itemCount: _controller.expiringMaintenances.length, itemBuilder: (context, index) { final maintenance = _controller.expiringMaintenances[index]; return ListTile( title: Text(maintenance.equipmentSerial ?? '시리얼 번호 없음'), subtitle: Text( '${MaintenanceType.getDisplayName(maintenance.maintenanceType)} - ' '${DateFormat('yyyy-MM-dd').format(maintenance.endedAt)} 만료', ), trailing: maintenance.daysRemaining != null ? Text( '${maintenance.daysRemaining}일 남음', style: TextStyle( color: maintenance.daysRemaining! <= 7 ? Colors.red : Colors.orange, fontWeight: FontWeight.bold, ), ) : null, onTap: () { Navigator.pop(context); _showMaintenanceDetail(maintenance); }, ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('닫기'), ), ], ), ); } void _deleteMaintenance(MaintenanceDto maintenance) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('유지보수 삭제'), content: Text( '${maintenance.equipmentSerial ?? "선택된"} 장비의 유지보수를 삭제하시겠습니까?\n' '삭제된 데이터는 복구할 수 있습니다.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('취소'), ), TextButton( onPressed: () async { Navigator.pop(context); final success = await _controller.deleteMaintenance(maintenance.id!); if (success && mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('유지보수가 삭제되었습니다')), ); } }, child: const Text('삭제', style: TextStyle(color: Colors.red)), ), ], ), ); } void _showDeleteConfirmation() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('선택된 유지보수 삭제'), content: Text('선택된 ${_selectedItems.length}개의 유지보수를 삭제하시겠습니까?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('취소'), ), TextButton( onPressed: () async { Navigator.pop(context); // TODO: 일괄 삭제 구현 setState(() { _selectedItems.clear(); }); }, child: const Text('삭제', style: TextStyle(color: Colors.red)), ), ], ), ); } }