import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../common/theme_shadcn.dart'; import '../../injection_container.dart'; import '../common/widgets/standard_states.dart'; import 'controllers/rent_controller.dart'; class RentDashboard extends StatefulWidget { const RentDashboard({super.key}); @override State createState() => _RentDashboardState(); } class _RentDashboardState extends State { late final RentController _controller; @override void initState() { super.initState(); _controller = getIt(); _loadData(); } Future _loadData() async { await Future.wait([ _controller.loadRentStats(), _controller.loadActiveRents(), _controller.loadOverdueRents(), ]); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: ShadcnTheme.background, body: ChangeNotifierProvider.value( value: _controller, child: Consumer( builder: (context, controller, child) { if (controller.isLoading) { return const StandardLoadingState(message: '임대 정보를 불러오는 중...'); } if (controller.hasError) { return StandardErrorState( message: controller.error!, onRetry: _loadData, ); } return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 제목 Text('임대 현황', style: ShadcnTheme.headingH3), const SizedBox(height: 24), // 통계 카드 _buildStatsCards(controller.rentStats ?? {}), const SizedBox(height: 32), // 진행 중인 임대 Text('진행 중인 임대', style: ShadcnTheme.headingH4), const SizedBox(height: 16), _buildActiveRentsList(controller.activeRents), const SizedBox(height: 32), // 연체된 임대 if (controller.overdueRents.isNotEmpty) ...[ Text('연체된 임대', style: ShadcnTheme.headingH4), const SizedBox(height: 16), _buildOverdueRentsList(controller.overdueRents), ], ], ), ); }, ), ), ); } Widget _buildStatsCards(Map stats) { return GridView.count( crossAxisCount: 4, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisSpacing: 16, childAspectRatio: 1.5, children: [ _buildStatCard( title: '전체 임대', value: stats['total_rents']?.toString() ?? '0', icon: Icons.receipt_long, color: Colors.blue, ), _buildStatCard( title: '진행 중', value: stats['active_rents']?.toString() ?? '0', icon: Icons.play_circle_filled, color: Colors.green, ), _buildStatCard( title: '연체', value: stats['overdue_rents']?.toString() ?? '0', icon: Icons.warning, color: Colors.red, ), _buildStatCard( title: '월 수익', value: '₩${_formatCurrency(stats['monthly_revenue'])}', icon: Icons.attach_money, color: Colors.orange, ), ], ); } Widget _buildStatCard({ required String title, required String value, required IconData icon, required Color color, }) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 32, color: color), const SizedBox(height: 8), Text( value, style: ShadcnTheme.headingH4.copyWith(color: color), ), Text( title, style: ShadcnTheme.bodyMedium.copyWith(color: ShadcnTheme.foregroundMuted), textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildActiveRentsList(List rents) { if (rents.isEmpty) { return const Card( child: Padding( padding: EdgeInsets.all(24), child: Center( child: Text('진행 중인 임대가 없습니다'), ), ), ); } return Card( child: ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: rents.length > 5 ? 5 : rents.length, // 최대 5개만 표시 separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { final rent = rents[index]; final startDate = rent.startedAt?.toString().substring(0, 10) ?? 'Unknown'; final endDate = rent.endedAt?.toString().substring(0, 10) ?? 'Unknown'; return ListTile( leading: CircleAvatar( backgroundColor: Colors.blue, child: const Icon(Icons.calendar_today, color: Colors.white), ), title: Text('임대 ID: ${rent.id ?? 'N/A'}'), subtitle: Text('$startDate ~ $endDate'), trailing: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: ShadcnTheme.successLight, borderRadius: BorderRadius.circular(4), ), child: Text( '진행중', style: TextStyle(color: ShadcnTheme.success, fontWeight: FontWeight.bold), ), ), ); }, ), ); } Widget _buildOverdueRentsList(List rents) { return Card( child: ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: rents.length > 5 ? 5 : rents.length, // 최대 5개만 표시 separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { final rent = rents[index]; final endDate = rent.endedAt; final overdueDays = endDate != null ? DateTime.now().difference(endDate).inDays : 0; final endDateStr = endDate?.toString().substring(0, 10) ?? 'Unknown'; return ListTile( leading: CircleAvatar( backgroundColor: Colors.red, child: const Icon(Icons.warning, color: Colors.white), ), title: Text('임대 ID: ${rent.id ?? 'N/A'}'), subtitle: Text('연체 ${overdueDays}일'), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: ShadcnTheme.errorLight, borderRadius: BorderRadius.circular(4), ), child: Text( '연체', style: TextStyle(color: ShadcnTheme.error, fontWeight: FontWeight.bold), ), ), const SizedBox(height: 4), Text( '종료일: $endDateStr', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ); }, ), ); } String _formatCurrency(dynamic amount) { if (amount == null) return '0'; final num = amount is String ? double.tryParse(amount) ?? 0 : amount.toDouble(); return num.toStringAsFixed(0); } }