Files
superport/lib/screens/rent/rent_dashboard.dart

253 lines
8.0 KiB
Dart

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<RentDashboard> createState() => _RentDashboardState();
}
class _RentDashboardState extends State<RentDashboard> {
late final RentController _controller;
@override
void initState() {
super.initState();
_controller = getIt<RentController>();
_loadData();
}
Future<void> _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<RentController>(
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<String, dynamic> 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);
}
}