import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../common/theme_shadcn.dart'; import '../../injection_container.dart'; import '../common/widgets/standard_states.dart'; import 'controllers/rent_controller.dart'; import '../maintenance/controllers/maintenance_controller.dart'; import '../inventory/controllers/equipment_history_controller.dart'; import '../../data/models/rent_dto.dart'; import '../../data/models/maintenance_dto.dart'; import '../../data/models/stock_status_dto.dart'; class RentDashboard extends StatefulWidget { const RentDashboard({super.key}); @override State createState() => _RentDashboardState(); } class _RentDashboardState extends State { late final RentController _rentController; late final MaintenanceController _maintenanceController; late final EquipmentHistoryController _equipmentHistoryController; @override void initState() { super.initState(); _rentController = getIt(); _maintenanceController = getIt(); _equipmentHistoryController = getIt(); _loadData(); } Future _loadData() async { await Future.wait([ _rentController.loadActiveRents(), _maintenanceController.loadExpiringMaintenances(days: 30), _equipmentHistoryController.loadStockStatus(), ]); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: ShadcnTheme.background, body: MultiProvider( providers: [ ChangeNotifierProvider.value(value: _rentController), ChangeNotifierProvider.value(value: _maintenanceController), ChangeNotifierProvider.value(value: _equipmentHistoryController), ], child: Consumer3( builder: (context, rentController, maintenanceController, equipmentHistoryController, child) { bool isLoading = rentController.isLoading || maintenanceController.isLoading || equipmentHistoryController.isLoading; if (isLoading) { return const StandardLoadingState(message: '대시보드 정보를 불러오는 중...'); } if (rentController.hasError || maintenanceController.error != null || equipmentHistoryController.errorMessage != null) { String errorMsg = rentController.error ?? maintenanceController.error ?? equipmentHistoryController.errorMessage ?? '알 수 없는 오류'; return StandardErrorState( message: errorMsg, onRetry: _loadData, ); } return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 제목 Row( children: [ Icon(Icons.dashboard_outlined, size: 32, color: ShadcnTheme.primary), const SizedBox(width: 12), Text('ERP 대시보드', style: ShadcnTheme.headingH2), ], ), const SizedBox(height: 32), // 대시보드 카드들 _buildDashboardCards(rentController, maintenanceController, equipmentHistoryController), const SizedBox(height: 32), // 진행중 임대 현황 _buildActiveRentsSection(rentController), const SizedBox(height: 24), // 만료 예정 정비 _buildExpiringMaintenanceSection(maintenanceController), const SizedBox(height: 24), // 재고 현황 요약 _buildStockStatusSection(equipmentHistoryController), ], ), ); }, ), ), ); } /// 대시보드 카드들 Widget _buildDashboardCards(RentController rentController, MaintenanceController maintenanceController, EquipmentHistoryController equipmentHistoryController) { return Row( children: [ Expanded( child: _buildDashboardCard( '진행중 임대', rentController.rents.length.toString(), Icons.business, ShadcnTheme.primary, () => Navigator.pushNamed(context, '/rent/list'), ), ), const SizedBox(width: 16), Expanded( child: _buildDashboardCard( '만료 예정 정비', maintenanceController.expiringMaintenances.length.toString(), Icons.warning, Colors.orange, () => Navigator.pushNamed(context, '/maintenance/list'), ), ), const SizedBox(width: 16), Expanded( child: _buildDashboardCard( '재고 현황', equipmentHistoryController.stockStatus.length.toString(), Icons.inventory, Colors.green, () => Navigator.pushNamed(context, '/inventory/history'), ), ), ], ); } /// 대시보드 카드 단일 사용자 Widget _buildDashboardCard(String title, String count, IconData icon, Color color, VoidCallback onTap) { return ShadCard( child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(icon, size: 28, color: color), Text( count, style: ShadcnTheme.headingH1.copyWith(color: color), ), ], ), const SizedBox(height: 12), Text( title, style: ShadcnTheme.bodyLarge.copyWith( fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), Text( '자세히 보기', style: ShadcnTheme.bodySmall.copyWith( color: color, ), ), ], ), ), ), ); } /// 진행중 임대 섹션 Widget _buildActiveRentsSection(RentController controller) { return ShadCard( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('진행중 임대 현황', style: ShadcnTheme.headingH3), ShadButton.outline( onPressed: () => Navigator.pushNamed(context, '/rent/list'), child: const Text('전체 보기'), ), ], ), const SizedBox(height: 16), if (controller.rents.isEmpty) Center( child: Padding( padding: const EdgeInsets.all(32), child: Text('진행중인 임대가 없습니다.', style: ShadcnTheme.bodyMedium), ), ) else ...controller.rents.take(5).map((rent) => _buildRentItem(rent)), ], ), ), ); } /// 임대 아이템 Widget _buildRentItem(RentDto rent) { return Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: ShadcnTheme.border, width: 0.5, ), ), ), child: Row( children: [ // 장비 정보 Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rent.equipmentSerial ?? 'N/A', style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500), ), if (rent.equipmentModel != null) Text( rent.equipmentModel!, style: ShadcnTheme.bodySmall.copyWith(color: ShadcnTheme.foregroundMuted), ), ], ), ), // 임대 회사 Expanded( flex: 2, child: Text( rent.companyName ?? 'N/A', style: ShadcnTheme.bodyMedium, ), ), // 남은 일수 Expanded( flex: 1, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: _getRentStatusColor(rent).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Text( '${rent.daysRemaining ?? 0}일', style: ShadcnTheme.bodySmall.copyWith( color: _getRentStatusColor(rent), fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ), ], ), ); } /// 만료 예정 정비 섹션 Widget _buildExpiringMaintenanceSection(MaintenanceController controller) { return ShadCard( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('만료 예정 정비 (30일 이내)', style: ShadcnTheme.headingH3), ShadButton.outline( onPressed: () => Navigator.pushNamed(context, '/maintenance/list'), child: const Text('전체 보기'), ), ], ), const SizedBox(height: 16), if (controller.expiringMaintenances.isEmpty) Center( child: Padding( padding: const EdgeInsets.all(32), child: Text('만료 예정 정비가 없습니다.', style: ShadcnTheme.bodyMedium), ), ) else ...controller.expiringMaintenances.take(5).map((maintenance) => _buildMaintenanceItem(maintenance)), ], ), ), ); } /// 정비 아이템 Widget _buildMaintenanceItem(MaintenanceDto maintenance) { return Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: ShadcnTheme.border, width: 0.5, ), ), ), child: Row( children: [ // 정비 정보 Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _getMaintenanceTypeName(maintenance.maintenanceType), style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500), ), Text( '종료일: ${_formatDate(maintenance.endedAt)}', style: ShadcnTheme.bodySmall.copyWith(color: ShadcnTheme.foregroundMuted), ), ], ), ), // 남은 일수 Expanded( flex: 1, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Text( '${maintenance.daysRemaining ?? 0}일', style: ShadcnTheme.bodySmall.copyWith( color: Colors.orange, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ), ], ), ); } /// 재고 현황 섹션 Widget _buildStockStatusSection(EquipmentHistoryController controller) { return ShadCard( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('재고 현황 요약', style: ShadcnTheme.headingH3), ShadButton.outline( onPressed: () => Navigator.pushNamed(context, '/inventory/dashboard'), child: const Text('상세 보기'), ), ], ), const SizedBox(height: 16), if (controller.stockStatus.isEmpty) Center( child: Padding( padding: const EdgeInsets.all(32), child: Text('재고 정보가 없습니다.', style: ShadcnTheme.bodyMedium), ), ) else ...controller.stockStatus.take(5).map((stock) => _buildStockItem(stock)), ], ), ), ); } /// 재고 아이템 Widget _buildStockItem(StockStatusDto stock) { return Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: ShadcnTheme.border, width: 0.5, ), ), ), child: Row( children: [ // 장비 정보 Expanded( flex: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( stock.equipmentSerial, style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500), ), if (stock.modelName != null) Text( stock.modelName!, style: ShadcnTheme.bodySmall.copyWith(color: ShadcnTheme.foregroundMuted), ), ], ), ), // 창고 위치 Expanded( flex: 1, child: Text( stock.warehouseName, style: ShadcnTheme.bodyMedium, ), ), // 재고 수량 Expanded( flex: 1, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: stock.currentQuantity > 0 ? Colors.green.withValues(alpha: 0.1) : Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Text( '${stock.currentQuantity}', style: ShadcnTheme.bodySmall.copyWith( color: stock.currentQuantity > 0 ? Colors.green : Colors.red, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ), ], ), ); } // 유틸리티 메서드들 Color _getRentStatusColor(RentDto rent) { final remainingDays = rent.daysRemaining ?? 0; if (remainingDays <= 0) return Colors.red; if (remainingDays <= 7) return Colors.orange; return Colors.green; } String _getMaintenanceTypeName(String? type) { switch (type) { case 'WARRANTY': return '무상보증'; case 'CONTRACT': return '유상계약'; case 'INSPECTION': return '점검'; default: return '알수없음'; } } String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } }