import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../data/models/rent_dto.dart'; import '../../injection_container.dart'; import '../common/widgets/pagination.dart'; import '../common/layouts/base_list_screen.dart'; import '../common/theme_shadcn.dart'; import 'controllers/rent_controller.dart'; import 'rent_form_dialog.dart'; class RentListScreen extends StatefulWidget { const RentListScreen({super.key}); @override State createState() => _RentListScreenState(); } class _RentListScreenState extends State { late final RentController _controller; final _searchController = TextEditingController(); @override void initState() { super.initState(); _controller = getIt(); _loadData(); } @override void dispose() { _searchController.dispose(); super.dispose(); } Future _loadData() async { await _controller.loadRents(); } Future _refresh() async { await _controller.loadRents(refresh: true); } void _showCreateDialog() { showDialog( context: context, builder: (context) => RentFormDialog( onSubmit: (request) async { final success = await _controller.createRent( equipmentHistoryId: request.equipmentHistoryId, startedAt: request.startedAt, endedAt: request.endedAt, ); if (success && mounted) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('임대 계약이 생성되었습니다')), ); } return success; }, ), ); } void _showEditDialog(RentDto rent) { showDialog( context: context, builder: (context) => RentFormDialog( rent: rent, onSubmit: (request) async { final success = await _controller.updateRent( id: rent.id!, startedAt: request.startedAt, endedAt: request.endedAt, ); if (success && mounted) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('임대 계약이 수정되었습니다')), ); } return success; }, ), ); } Future _deleteRent(RentDto rent) async { final confirmed = await showShadDialog( context: context, builder: (context) => ShadDialog( title: const Text('임대 계약 삭제'), description: Text('ID ${rent.id}번 임대 계약을 삭제하시겠습니까?'), actions: [ ShadButton.outline( onPressed: () => Navigator.of(context).pop(false), child: const Text('취소'), ), ShadButton.destructive( onPressed: () => Navigator.of(context).pop(true), child: const Text('삭제'), ), ], ), ); if (confirmed == true) { final success = await _controller.deleteRent(rent.id!); if (success && mounted) { ShadToaster.of(context).show( const ShadToast( title: Text('삭제 완료'), description: Text('임대 계약이 삭제되었습니다'), ), ); } } } Future _returnRent(RentDto rent) async { final confirmed = await showShadDialog( context: context, builder: (context) => ShadDialog( title: const Text('장비 반납'), description: Text('ID ${rent.id}번 임대 계약의 장비를 반납 처리하시겠습니까?'), actions: [ ShadButton.outline( onPressed: () => Navigator.of(context).pop(false), child: const Text('취소'), ), ShadButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('반납 처리'), ), ], ), ); if (confirmed == true) { final success = await _controller.returnRent(rent.id!); if (success && mounted) { ShadToaster.of(context).show( const ShadToast( title: Text('반납 완료'), description: Text('장비가 반납 처리되었습니다'), ), ); } } } void _onStatusFilter(String? status) { _controller.setStatusFilter(status); _controller.loadRents(); } /// 헤더 셀 빌더 Widget _buildHeaderCell( String text, { required int flex, required bool useExpanded, required double minWidth, }) { final child = Container( alignment: Alignment.centerLeft, child: Text( text, style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500), ), ); if (useExpanded) { return Expanded(flex: flex, child: child); } else { return SizedBox(width: minWidth, child: child); } } /// 데이터 셀 빌더 Widget _buildDataCell( Widget child, { required int flex, required bool useExpanded, required double minWidth, }) { final container = Container( alignment: Alignment.centerLeft, child: child, ); if (useExpanded) { return Expanded(flex: flex, child: container); } else { return SizedBox(width: minWidth, child: container); } } /// 헤더 셀 리스트 List _buildHeaderCells() { return [ _buildHeaderCell('ID', flex: 0, useExpanded: false, minWidth: 60), _buildHeaderCell('장비 이력 ID', flex: 1, useExpanded: true, minWidth: 100), _buildHeaderCell('시작일', flex: 1, useExpanded: true, minWidth: 100), _buildHeaderCell('종료일', flex: 1, useExpanded: true, minWidth: 100), _buildHeaderCell('기간 (일)', flex: 0, useExpanded: false, minWidth: 80), _buildHeaderCell('상태', flex: 0, useExpanded: false, minWidth: 80), _buildHeaderCell('작업', flex: 0, useExpanded: false, minWidth: 120), ]; } /// 테이블 행 빌더 Widget _buildTableRow(RentDto rent, int index) { final days = _controller.calculateRentDays(rent.startedAt, rent.endedAt); final status = _controller.getRentStatus(rent); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: index.isEven ? ShadcnTheme.muted.withValues(alpha: 0.1) : null, border: const Border( bottom: BorderSide(color: Colors.black), ), ), child: Row( children: [ _buildDataCell( Text( rent.id?.toString() ?? '-', style: ShadcnTheme.bodySmall, ), flex: 0, useExpanded: false, minWidth: 60, ), _buildDataCell( Text( rent.equipmentHistoryId.toString(), style: ShadcnTheme.bodyMedium, ), flex: 1, useExpanded: true, minWidth: 100, ), _buildDataCell( Text( DateFormat('yyyy-MM-dd').format(rent.startedAt), style: ShadcnTheme.bodySmall, ), flex: 1, useExpanded: true, minWidth: 100, ), _buildDataCell( Text( DateFormat('yyyy-MM-dd').format(rent.endedAt), style: ShadcnTheme.bodySmall, ), flex: 1, useExpanded: true, minWidth: 100, ), _buildDataCell( Text( '$days일', style: ShadcnTheme.bodySmall, textAlign: TextAlign.center, ), flex: 0, useExpanded: false, minWidth: 80, ), _buildDataCell( _buildStatusChip(status), flex: 0, useExpanded: false, minWidth: 80, ), _buildDataCell( Row( mainAxisSize: MainAxisSize.min, children: [ ShadButton.ghost( size: ShadButtonSize.sm, onPressed: () => _showEditDialog(rent), child: const Icon(Icons.edit, size: 16), ), const SizedBox(width: 4), if (status == '진행중') ShadButton.ghost( size: ShadButtonSize.sm, onPressed: () => _returnRent(rent), child: const Icon(Icons.assignment_return, size: 16), ), if (status == '진행중') const SizedBox(width: 4), ShadButton.ghost( size: ShadButtonSize.sm, onPressed: () => _deleteRent(rent), child: const Icon(Icons.delete, size: 16), ), ], ), flex: 0, useExpanded: false, minWidth: 120, ), ], ), ); } Color _getStatusColor(String? status) { switch (status) { case '진행중': return Colors.blue; case '종료': return Colors.green; case '예약': return Colors.orange; default: return Colors.grey; } } /// 상태 배지 빌더 Widget _buildStatusChip(String? status) { switch (status) { case '진행중': return ShadBadge( child: const Text('진행중'), ); case '종료': return ShadBadge.secondary( child: const Text('종료'), ); case '예약': return ShadBadge.outline( child: const Text('예약'), ); default: return ShadBadge.destructive( child: const Text('알 수 없음'), ); } } /// 데이터 테이블 빌더 Widget _buildDataTable(RentController controller) { final rents = controller.rents; return Container( width: double.infinity, decoration: BoxDecoration( border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: Column( children: [ // 고정 헤더 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: ShadcnTheme.muted.withValues(alpha: 0.3), border: Border(bottom: BorderSide(color: Colors.black)), ), child: Row(children: _buildHeaderCells()), ), // 스크롤 바디 Expanded( child: rents.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.business_center_outlined, size: 64, color: ShadcnTheme.mutedForeground, ), const SizedBox(height: 16), Text( '등록된 임대 계약이 없습니다', style: ShadcnTheme.bodyMedium.copyWith( color: ShadcnTheme.mutedForeground, ), ), ], ), ) : ListView.builder( itemCount: rents.length, itemBuilder: (context, index) => _buildTableRow(rents[index], index), ), ), ], ), ); } /// 검색바 빌더 Widget _buildSearchBar() { return Row( children: [ Expanded( child: ShadInput( controller: _searchController, placeholder: const Text('장비 이력 ID로 검색...'), ), ), const SizedBox(width: 16), ShadButton.outline( onPressed: () { _searchController.clear(); // 검색 초기화 로직 }, child: const Text('초기화'), ), ], ); } /// 액션바 빌더 Widget _buildActionBar() { return Row( children: [ Expanded( child: Row( children: [ Text( '임대 목록', style: ShadcnTheme.headingH6, ), const SizedBox(width: 16), ShadSelect( placeholder: const Text('전체'), options: [ const ShadOption(value: null, child: Text('전체')), const ShadOption(value: 'active', child: Text('진행 중')), const ShadOption(value: 'overdue', child: Text('연체')), const ShadOption(value: 'completed', child: Text('완료')), const ShadOption(value: 'cancelled', child: Text('취소')), ], selectedOptionBuilder: (context, value) { switch (value) { case 'active': return const Text('진행 중'); case 'overdue': return const Text('연체'); case 'completed': return const Text('완료'); case 'cancelled': return const Text('취소'); default: return const Text('전체'); } }, onChanged: _onStatusFilter, ), ], ), ), Row( children: [ ShadButton( onPressed: _showCreateDialog, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add, size: 16), SizedBox(width: 4), Text('새 임대 계약'), ], ), ), ], ), ], ); } /// 페이지네이션 빌더 Widget? _buildPagination() { return Consumer( builder: (context, controller, child) { if (controller.totalPages <= 1) return const SizedBox.shrink(); return Pagination( totalCount: controller.totalRents, currentPage: controller.currentPage, pageSize: 20, onPageChanged: (page) => controller.loadRents(page: page), ); }, ); } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: _controller, child: Consumer( builder: (context, controller, child) { return BaseListScreen( searchBar: _buildSearchBar(), actionBar: _buildActionBar(), dataTable: _buildDataTable(controller), pagination: _buildPagination(), isLoading: controller.isLoading, error: controller.error, onRefresh: () => controller.loadRents(refresh: true), emptyMessage: '등록된 임대 계약이 없습니다', emptyIcon: Icons.business_center_outlined, ); }, ), ); } }