/// Page: Project Submissions List /// /// Displays list of user's project submissions with filters. library; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/theme/colors.dart'; import 'package:worker/features/projects/domain/entities/project_submission.dart'; import 'package:worker/features/projects/presentation/providers/submissions_provider.dart'; /// Project Submissions Page class SubmissionsPage extends ConsumerWidget { const SubmissionsPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final submissionsAsync = ref.watch(filteredSubmissionsProvider); final statusListAsync = ref.watch(projectStatusListProvider); final filter = ref.watch(submissionsFilterProvider); final selectedStatus = filter.selectedStatus; return Scaffold( backgroundColor: const Color(0xFFF4F6F8), appBar: AppBar( leading: IconButton( icon: const FaIcon(FontAwesomeIcons.arrowLeft, color: Colors.black, size: 20), onPressed: () => Navigator.of(context).pop(), ), title: const Text( 'Danh sách Dự án', style: TextStyle(color: Colors.black), ), actions: [ IconButton( icon: const FaIcon(FontAwesomeIcons.plus, color: Colors.black, size: 20), onPressed: () async { final result = await context.push(RouteNames.submissionCreate); if (result == true) { // Refresh submissions list after successful creation ref.invalidate(allSubmissionsProvider); } }, ), const SizedBox(width: AppSpacing.sm), ], elevation: AppBarSpecs.elevation, backgroundColor: AppColors.white, centerTitle: false, ), body: Column( children: [ // Search Bar Padding( padding: const EdgeInsets.all(16), child: TextField( decoration: InputDecoration( hintText: 'Mã dự án hoặc tên công trình', prefixIcon: const Icon(Icons.search, color: AppColors.grey500), filled: true, fillColor: AppColors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: AppColors.grey100), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: AppColors.grey100), ), ), onChanged: (value) { ref.read(submissionsFilterProvider.notifier).updateSearchQuery(value); }, ), ), // Status Filter Tabs SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ _buildFilterChip( context, ref, label: 'Tất cả', isSelected: selectedStatus == null, onTap: () => ref.read(submissionsFilterProvider.notifier).clearStatusFilter(), ), const SizedBox(width: 8), // Use projectStatusListProvider to get status options statusListAsync.when( data: (statuses) => Row( children: statuses.map((status) => Padding( padding: const EdgeInsets.only(right: 8), child: _buildFilterChip( context, ref, label: status.label, isSelected: selectedStatus == status.label, onTap: () => ref.read(submissionsFilterProvider.notifier).selectStatus(status.label), ), )).toList(), ), loading: () => const SizedBox.shrink(), error: (_, __) => const SizedBox.shrink(), ), ], ), ), const SizedBox(height: 16), // Submissions List Expanded( child: submissionsAsync.when( data: (submissions) { if (submissions.isEmpty) { return RefreshIndicator( onRefresh: () async { await ref.read(allSubmissionsProvider.notifier).refresh(); }, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 16), children: [ SizedBox( height: MediaQuery.of(context).size.height * 0.5, child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.folderOpen, size: 64, color: AppColors.grey500, ), SizedBox(height: 16), Text( 'Không có dự án nào', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.grey900, ), ), SizedBox(height: 8), Text( 'Không tìm thấy dự án phù hợp', style: TextStyle(color: AppColors.grey500), ), ], ), ), ), ], ), ); } return RefreshIndicator( onRefresh: () async { await ref.read(allSubmissionsProvider.notifier).refresh(); }, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: submissions.length, itemBuilder: (context, index) { final submission = submissions[index]; return _buildSubmissionCard(context, ref, submission); }, ), ); }, loading: () => const Center( child: CircularProgressIndicator(), ), error: (error, stack) => RefreshIndicator( onRefresh: () async { await ref.read(allSubmissionsProvider.notifier).refresh(); }, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 16), children: [ SizedBox( height: MediaQuery.of(context).size.height * 0.5, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: AppColors.danger, ), const SizedBox(height: 16), const Text( 'Có lỗi xảy ra', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.grey900, ), ), const SizedBox(height: 8), Text( error.toString(), style: const TextStyle(color: AppColors.grey500), textAlign: TextAlign.center, ), const SizedBox(height: 16), const Text( 'Kéo xuống để thử lại', style: TextStyle(color: AppColors.grey500), ), ], ), ), ), ], ), ), ), ), ], ), ); } Widget _buildFilterChip( BuildContext context, WidgetRef ref, { required String label, required bool isSelected, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isSelected ? AppColors.primaryBlue : AppColors.white, borderRadius: BorderRadius.circular(20), border: Border.all( color: isSelected ? AppColors.primaryBlue : AppColors.grey100, ), ), child: Text( label, style: TextStyle( color: isSelected ? AppColors.white : AppColors.grey900, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ), ); } Widget _buildSubmissionCard(BuildContext context, WidgetRef ref, ProjectSubmission submission) { return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 1, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: InkWell( onTap: () async { // Navigate to edit submission page final result = await context.push( RouteNames.submissionCreate, extra: submission, ); if (result == true) { // Refresh submissions list after successful update ref.invalidate(allSubmissionsProvider); } }, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( submission.designedArea, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.grey900, ), ), _buildStatusBadge(submission.status, submission.statusColor), ], ), const SizedBox(height: 8), // Text( // 'Tên công trình: ${submission.designedArea}', // style: const TextStyle( // fontSize: 14, // color: AppColors.grey900, // ), // ), // const SizedBox(height: 4), Text( 'Ngày nộp: ${DateFormat('dd/MM/yyyy HH:mm').format(submission.requestDate)}', style: const TextStyle( fontSize: 14, color: AppColors.grey900, ), ), const SizedBox(height: 4), Text( 'Diện tích: ${submission.designArea} m²', style: const TextStyle( fontSize: 13, color: AppColors.grey900, ), ), if (submission.reasonForRejection != null) ...[ const SizedBox(height: 8), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0xFFFEF2F2), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const FaIcon( FontAwesomeIcons.triangleExclamation, size: 14, color: AppColors.danger, ), const SizedBox(width: 8), Expanded( child: Text( submission.reasonForRejection!, style: const TextStyle( fontSize: 12, color: AppColors.danger, ), ), ), ], ), ), ], ], ), ), ), ); } Widget _buildStatusBadge(String status, String statusColor) { final color = _getColorFromStatusColor(statusColor); return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Text( status, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } Color _getColorFromStatusColor(String statusColor) { switch (statusColor) { case 'Warning': return AppColors.warning; case 'Success': return AppColors.success; case 'Danger': return AppColors.danger; case 'Info': return AppColors.info; default: return AppColors.grey500; } } }