import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../data/models/maintenance_dto.dart'; import '../../domain/entities/maintenance_schedule.dart'; import 'controllers/maintenance_controller.dart'; import 'maintenance_form_dialog.dart'; import 'components/maintenance_calendar.dart'; class MaintenanceScheduleScreen extends StatefulWidget { const MaintenanceScheduleScreen({super.key}); @override State createState() => _MaintenanceScheduleScreenState(); } class _MaintenanceScheduleScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; bool _isCalendarView = false; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); // 초기 데이터 로드 WidgetsBinding.instance.addPostFrameCallback((_) { final controller = context.read(); controller.loadMaintenances(refresh: true); controller.loadAlerts(); }); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], body: Column( children: [ _buildHeader(), _buildFilterBar(), Expanded( child: Consumer( builder: (context, controller, child) { if (controller.isLoading && controller.maintenances.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (controller.error != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '오류 발생', style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Text(controller.error!), const SizedBox(height: 16), ShadButton( onPressed: () => controller.loadMaintenances(refresh: true), child: const Text('다시 시도'), ), ], ), ); } return _isCalendarView ? MaintenanceCalendar( maintenances: controller.maintenances, onDateSelected: (date) { // 날짜 선택시 해당 날짜의 유지보수 표시 _showMaintenancesForDate(date, controller.maintenances); }, ) : _buildListView(controller); }, ), ), ], ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withValues(alpha: 0.1), spreadRadius: 1, blurRadius: 3, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '유지보수 일정 관리', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Consumer( builder: (context, controller, child) { return Text( '총 ${controller.totalCount}건 | ' '예정 ${controller.upcomingCount}건 | ' '지연 ${controller.overdueCount}건', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.grey[600], ), ); }, ), ], ), Row( children: [ IconButton( icon: Icon( _isCalendarView ? Icons.list : Icons.calendar_month, ), onPressed: () { setState(() { _isCalendarView = !_isCalendarView; }); }, tooltip: _isCalendarView ? '리스트 보기' : '캘린더 보기', ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.refresh), onPressed: () { context.read().loadMaintenances( refresh: true, ); }, tooltip: '새로고침', ), ], ), ], ), const SizedBox(height: 16), _buildStatisticsCards(), ], ), ); } Widget _buildStatisticsCards() { return Consumer( builder: (context, controller, child) { // 백엔드에 통계 API가 없으므로 빈 위젯 반환 return const SizedBox.shrink(); }, ); } Widget _buildStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, color: color, size: 32), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: color, ), ), ], ), ), ], ), ); } Widget _buildFilterBar() { return Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( color: Colors.white, border: Border(bottom: BorderSide(color: Colors.grey[300]!)), ), child: Row( children: [ Expanded( child: ShadInput( placeholder: const Text('장비명, 일련번호로 검색'), onChanged: (value) { context.read().setSearchQuery(value); }, ), ), const SizedBox(width: 12), PopupMenuButton( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8), ), child: Row( children: const [ Icon(Icons.filter_list), SizedBox(width: 8), Text('상태 필터'), ], ), ), onSelected: (status) { context.read().setMaintenanceFilter(status); }, itemBuilder: (context) => [ const PopupMenuItem(value: null, child: Text('전체')), const PopupMenuItem( value: 'active', child: Text('진행중 (시작됨, 완료되지 않음)'), ), const PopupMenuItem( value: 'completed', child: Text('완료됨'), ), const PopupMenuItem( value: 'upcoming', child: Text('예정됨'), ), ], ), const SizedBox(width: 12), PopupMenuButton( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8), ), child: Row( children: const [ Icon(Icons.sort), SizedBox(width: 8), Text('정렬'), ], ), ), onSelected: (value) { final parts = value.split('_'); context.read().setSorting( parts[0], parts[1] == 'asc', ); }, itemBuilder: (context) => [ const PopupMenuItem( value: 'started_at_asc', child: Text('시작일 오름차순'), ), const PopupMenuItem( value: 'started_at_desc', child: Text('시작일 내림차순'), ), const PopupMenuItem( value: 'registered_at_desc', child: Text('최신 등록순'), ), const PopupMenuItem( value: 'period_month_desc', child: Text('주기 긴 순'), ), ], ), ], ), ); } Widget _buildListView(MaintenanceController controller) { if (controller.maintenances.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.build_circle_outlined, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( '등록된 유지보수가 없습니다', style: Theme.of( context, ).textTheme.titleLarge?.copyWith(color: Colors.grey[600]), ), const SizedBox(height: 8), Text('새로운 유지보수를 등록해주세요', style: TextStyle(color: Colors.grey[500])), ], ), ); } return ListView.builder( padding: const EdgeInsets.all(24), itemCount: controller.maintenances.length, itemBuilder: (context, index) { final maintenance = controller.maintenances[index]; return _buildMaintenanceCard(maintenance); }, ); } Widget _buildMaintenanceCard(MaintenanceDto maintenance) { final schedule = context .read() .getScheduleForMaintenance(maintenance.startedAt); // generateAlert is not available, using null for now final alert = null; // schedule?.generateAlert(); return Container( margin: const EdgeInsets.only(bottom: 12), child: ShadCard( child: InkWell( onTap: () => _showMaintenanceDetails(maintenance), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ _buildStatusChip(maintenance), const SizedBox(width: 8), _buildTypeChip(maintenance.maintenanceType), const SizedBox(width: 8), if (alert != null) _buildAlertChip(alert), const Spacer(), PopupMenuButton( onSelected: (value) => _handleMaintenanceAction(value, maintenance), itemBuilder: (context) => [ const PopupMenuItem(value: 'edit', child: Text('수정')), const PopupMenuItem( value: 'toggle', child: Text('상태 변경'), ), const PopupMenuItem( value: 'delete', child: Text('삭제'), ), ], ), ], ), const SizedBox(height: 12), Text( 'Equipment History #${maintenance.equipmentHistoryId}', style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Row( children: [ Icon(Icons.calendar_today, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text( '시작일: ${maintenance.startedAt ?? "미정"}', style: TextStyle(color: Colors.grey[600]), ), const SizedBox(width: 16), Icon(Icons.check_circle, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text( '완료일: ${maintenance.endedAt ?? "미완료"}', style: TextStyle(color: Colors.grey[600]), ), ], ), const SizedBox(height: 8), Row( children: [ Icon(Icons.repeat, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text( '주기: ${maintenance.periodMonth ?? 0}개월', style: TextStyle(color: Colors.grey[600]), ), const SizedBox(width: 16), Icon(Icons.settings, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text( '유형: ${maintenance.maintenanceType == 'O' ? '현장' : '원격'}', style: TextStyle(color: Colors.grey[600]), ), ], ), const SizedBox(height: 8), Row( children: [ Icon(Icons.schedule, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text( '등록일: ${DateFormat('yyyy-MM-dd').format(maintenance.registeredAt)}', style: TextStyle(color: Colors.grey[600]), ), ], ), ], ), ), ), ), ); } Widget _buildStatusChip(MaintenanceDto maintenance) { // 백엔드 스키마 기준으로 상태 판단 if (maintenance.endedAt != null) { return ShadBadge.secondary( child: const Text('완료'), ); } else if (maintenance.startedAt != null) { return ShadBadge( child: const Text('진행중'), ); } else { return ShadBadge.outline( child: const Text('예정'), ); } } Widget _buildTypeChip(String type) { return ShadBadge.destructive( child: Text(type == 'O' ? '현장' : '원격'), ); } Widget _buildAlertChip(MaintenanceAlert alert) { Color color; switch (alert.priority) { case AlertPriority.critical: color = Colors.red; break; case AlertPriority.high: color = Colors.orange; break; case AlertPriority.medium: color = Colors.yellow[700]!; break; case AlertPriority.low: color = Colors.blue; break; } return Chip( label: Text( '${alert.daysUntilDue < 0 ? "지연 " : ""}${alert.daysUntilDue.abs()}일', style: TextStyle(fontSize: 12, color: Colors.white), ), backgroundColor: color, padding: const EdgeInsets.symmetric(horizontal: 8), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ); } void _showCreateMaintenanceDialog() { showDialog( context: context, builder: (context) => const MaintenanceFormDialog(), ).then((result) { if (result == true) { context.read().loadMaintenances(refresh: true); } }); } void _showMaintenanceDetails(MaintenanceDto maintenance) { showDialog( context: context, builder: (context) => MaintenanceFormDialog(maintenance: maintenance), ).then((result) { if (result == true) { context.read().loadMaintenances(refresh: true); } }); } void _handleMaintenanceAction( String action, MaintenanceDto maintenance, ) async { final controller = context.read(); switch (action) { case 'edit': _showMaintenanceDetails(maintenance); break; case 'toggle': // TODO: 백엔드 스키마에 맞는 상태 변경 로직 구현 필요 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('상태 변경 기능은 준비 중입니다')), ); break; case 'delete': final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('유지보수 삭제'), content: const Text('정말로 이 유지보수를 삭제하시겠습니까?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('취소'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text( '삭제', style: TextStyle(color: Colors.red), ), ), ], ), ); if (confirm == true && maintenance.id != null) { await controller.deleteMaintenance(maintenance.id!); } break; } } void _showMaintenancesForDate( DateTime date, List maintenances, ) { final dateMaintenances = maintenances.where((m) { // nextMaintenanceDate 필드가 백엔드에 없으므로 startedAt~endedAt 기간으로 확인 final targetDate = DateTime(date.year, date.month, date.day); final startDate = DateTime(m.startedAt.year, m.startedAt.month, m.startedAt.day); final endDate = DateTime(m.endedAt.year, m.endedAt.month, m.endedAt.day); return (targetDate.isAfter(startDate) || targetDate.isAtSameMomentAs(startDate)) && (targetDate.isBefore(endDate) || targetDate.isAtSameMomentAs(endDate)); }).toList(); if (dateMaintenances.isEmpty) return; showModalBottomSheet( context: context, builder: (context) => Container( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${DateFormat('yyyy년 MM월 dd일').format(date)} 유지보수', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), ...dateMaintenances.map( (m) => ListTile( title: Text('Equipment History #${m.equipmentHistoryId}'), subtitle: Text( '${m.maintenanceType == "O" ? "현장" : "원격"} | ${m.periodMonth}개월 주기', ), trailing: Text( DateFormat('yyyy-MM-dd').format(m.endedAt), // 종료일로 대체 ), onTap: () { Navigator.of(context).pop(); _showMaintenanceDetails(m); }, ), ), ], ), ), ); } }