import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../../data/models/maintenance_dto.dart'; import 'controllers/maintenance_controller.dart'; import 'maintenance_form_dialog.dart'; class MaintenanceAlertDashboard extends StatefulWidget { const MaintenanceAlertDashboard({super.key}); @override State createState() => _MaintenanceAlertDashboardState(); } class _MaintenanceAlertDashboardState extends State { @override void initState() { super.initState(); // 초기 데이터 로드 WidgetsBinding.instance.addPostFrameCallback((_) { final controller = context.read(); controller.loadAlerts(); controller.loadMaintenances(refresh: true); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], body: Consumer( builder: (context, controller, child) { if (controller.isLoading && controller.upcomingAlerts.isEmpty) { return const Center(child: CircularProgressIndicator()); } return RefreshIndicator( onRefresh: () async { await controller.loadAlerts(); }, child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(controller), const SizedBox(height: 24), _buildAlertSections(controller), const SizedBox(height: 24), _buildQuickActions(controller), ], ), ), ); }, ), ); } Widget _buildHeader(MaintenanceController controller) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [Theme.of(context).primaryColor, Theme.of(context).primaryColor.withValues(alpha: 0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Theme.of(context).primaryColor.withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '유지보수 알림 대시보드', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( '${DateFormat('yyyy년 MM월 dd일').format(DateTime.now())} 기준', style: TextStyle( fontSize: 14, color: Colors.white.withValues(alpha: 0.9), ), ), ], ), IconButton( icon: const Icon(Icons.refresh, color: Colors.white), onPressed: () { controller.loadAlerts(); }, tooltip: '새로고침', ), ], ), const SizedBox(height: 16), Row( children: [ _buildHeaderStat( Icons.warning_amber, '긴급', controller.overdueAlerts.length.toString(), Colors.red[300]!, ), const SizedBox(width: 16), _buildHeaderStat( Icons.schedule, '예정', controller.upcomingAlerts.length.toString(), Colors.orange[300]!, ), const SizedBox(width: 16), _buildHeaderStat( Icons.check_circle, '완료', '0', // 통계 API가 없어 고정값 Colors.green[300]!, ), ], ), ], ), ); } Widget _buildHeaderStat(IconData icon, String label, String value, Color color) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(icon, color: color, size: 24), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: Colors.white.withValues(alpha: 0.9), ), ), Text( value, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ], ), ), ); } Widget _buildStatCard(String title, String value, IconData icon, Color color) { return Column( children: [ Icon(icon, color: color, size: 32), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( title, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ); } Widget _buildAlertSections(MaintenanceController controller) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 지연된 유지보수 if (controller.overdueAlerts.isNotEmpty) ...[ _buildAlertSection( '⚠️ 지연된 유지보수', controller.overdueAlerts, Colors.red, true, ), const SizedBox(height: 20), ], // 예정된 유지보수 if (controller.upcomingAlerts.isNotEmpty) ...[ _buildAlertSection( '📅 예정된 유지보수', controller.upcomingAlerts, Colors.orange, false, ), ], // 알림이 없는 경우 if (controller.overdueAlerts.isEmpty && controller.upcomingAlerts.isEmpty) Center( child: Container( padding: const EdgeInsets.all(40), child: Column( children: [ Icon(Icons.check_circle_outline, size: 64, color: Colors.green[400]), const SizedBox(height: 16), Text( '모든 유지보수가 정상입니다', style: TextStyle( fontSize: 18, color: Colors.grey[600], ), ), ], ), ), ), ], ); } Widget _buildAlertSection( String title, List alerts, Color color, bool isOverdue, ) { // 우선순위별로 정렬 final sortedAlerts = List.from(alerts) ..sort((a, b) { // MaintenanceDto에는 priority와 daysUntilDue가 없으므로 등록일순으로 정렬 return b.registeredAt.compareTo(a.registeredAt); }); return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: color, ), ), Chip( label: Text( '${alerts.length}건', style: const TextStyle(color: Colors.white, fontSize: 12), ), backgroundColor: color, padding: const EdgeInsets.symmetric(horizontal: 8), ), ], ), ), ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: sortedAlerts.length > 5 ? 5 : sortedAlerts.length, separatorBuilder: (context, index) => const Divider(height: 1), itemBuilder: (context, index) { final alert = sortedAlerts[index]; return _buildAlertCard(alert, isOverdue); }, ), if (sortedAlerts.length > 5) Container( padding: const EdgeInsets.all(12), child: Center( child: TextButton( onPressed: () => _showAllAlerts(context, sortedAlerts, title), child: Text('${sortedAlerts.length - 5}개 더 보기'), ), ), ), ], ), ); } Widget _buildAlertCard(MaintenanceDto alert, bool isOverdue) { // MaintenanceDto 필드에 맞게 수정 final typeColor = alert.maintenanceType == 'O' ? Colors.blue : Colors.green; final typeIcon = alert.maintenanceType == 'O' ? Icons.build : Icons.computer; // 예상 마감일 계산 (startedAt + periodMonth) DateTime? scheduledDate; scheduledDate = DateTime(alert.startedAt.year, alert.startedAt.month + alert.periodMonth, alert.startedAt.day); int daysUntil = scheduledDate.difference(DateTime.now()).inDays; return ListTile( leading: CircleAvatar( backgroundColor: typeColor.withValues(alpha: 0.2), child: Icon(typeIcon, color: typeColor, size: 20), ), title: Text( 'Equipment History #${alert.equipmentHistoryId}', style: const TextStyle(fontWeight: FontWeight.w500), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), if (scheduledDate != null) Text( isOverdue ? '${daysUntil.abs()}일 지연' : '$daysUntil일 후 예정', style: TextStyle( color: isOverdue ? Colors.red : Colors.orange, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), if (scheduledDate != null) Text( '예정일: ${DateFormat('yyyy-MM-dd').format(scheduledDate)}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), if (alert.periodMonth != null) Text( '주기: ${alert.periodMonth}개월', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Chip( label: Text( alert.maintenanceType == 'O' ? '현장' : '원격', style: const TextStyle(fontSize: 11, color: Colors.white), ), backgroundColor: typeColor, padding: EdgeInsets.zero, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox(height: 4), Text( '비용: 미지원', // 백엔드에 비용 필드 없음 style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), onTap: () => _showMaintenanceDetails(alert.id!), ); } Widget _buildQuickActions(MaintenanceController controller) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '빠른 작업', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildActionButton( '새 유지보수 등록', Icons.add_circle, Colors.blue, () => _showCreateMaintenanceDialog(), ), ), const SizedBox(width: 12), Expanded( child: _buildActionButton( '일정 보기', Icons.calendar_month, Colors.green, () => Navigator.pushNamed(context, '/maintenance/schedule'), ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildActionButton( '보고서 생성', Icons.description, Colors.orange, () => _generateReport(controller), ), ), const SizedBox(width: 12), Expanded( child: _buildActionButton( '엑셀 내보내기', Icons.file_download, Colors.purple, () => _exportToExcel(controller), ), ), ], ), ], ), ); } Widget _buildActionButton( String label, IconData icon, Color color, VoidCallback onPressed, ) { return ElevatedButton.icon( onPressed: onPressed, icon: Icon(icon, size: 20), label: Text(label), style: ElevatedButton.styleFrom( backgroundColor: color, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ); } void _showAllAlerts(BuildContext context, List alerts, String title) { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => DraggableScrollableSheet( initialChildSize: 0.7, minChildSize: 0.5, maxChildSize: 0.95, expand: false, builder: (context, scrollController) => Container( padding: const EdgeInsets.all(16), child: Column( children: [ Text( title, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), Expanded( child: ListView.separated( controller: scrollController, itemCount: alerts.length, separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { final alert = alerts[index]; return _buildAlertCard(alert, title.contains('지연')); }, ), ), ], ), ), ), ); } void _showMaintenanceDetails(int maintenanceId) { final controller = context.read(); final maintenance = controller.maintenances.firstWhere( (m) => m.id == maintenanceId, orElse: () => MaintenanceDto( equipmentHistoryId: 0, startedAt: DateTime.now(), endedAt: DateTime.now(), periodMonth: 0, maintenanceType: 'O', registeredAt: DateTime.now(), ), ); if (maintenance.id != 0) { showDialog( context: context, builder: (context) => MaintenanceFormDialog(maintenance: maintenance), ).then((result) { if (result == true) { controller.loadAlerts(); controller.loadMaintenances(refresh: true); } }); } } void _showCreateMaintenanceDialog() { showDialog( context: context, builder: (context) => const MaintenanceFormDialog(), ).then((result) { if (result == true) { final controller = context.read(); controller.loadAlerts(); controller.loadMaintenances(refresh: true); } }); } void _generateReport(MaintenanceController controller) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('보고서 생성 기능은 준비 중입니다'), backgroundColor: Colors.orange, ), ); } void _exportToExcel(MaintenanceController controller) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('엑셀 내보내기 기능은 준비 중입니다'), backgroundColor: Colors.purple, ), ); } }