/// 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:intl/intl.dart'; import 'package:worker/core/constants/ui_constants.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 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: () { // TODO: Navigate to create submission page ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Tạo dự án mới - Đang phát triển')), ); }, ), 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 dự án', 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), ...SubmissionStatus.values.map((status) => Padding( padding: const EdgeInsets.only(right: 8), child: _buildFilterChip( context, ref, label: status.displayName, isSelected: selectedStatus == status, onTap: () => ref.read(submissionsFilterProvider.notifier).selectStatus(status), ), )), ], ), ), 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: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const FaIcon( FontAwesomeIcons.folderOpen, size: 64, color: AppColors.grey500, ), const SizedBox(height: 16), const Text( 'Không có dự án nào', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.grey900, ), ), const SizedBox(height: 8), const 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, 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, ProjectSubmission submission) { return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 1, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: InkWell( onTap: () { // TODO: Navigate to submission detail ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Chi tiết dự án ${submission.submissionId}')), ); }, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border( left: BorderSide( color: _getStatusColor(submission.status), width: 4, ), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '#${submission.submissionId}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.grey900, ), ), _buildStatusBadge(submission.status), ], ), const SizedBox(height: 8), Text( 'Tên công trình: ${submission.projectName}', style: const TextStyle( fontSize: 14, color: AppColors.grey900, ), ), const SizedBox(height: 4), Text( 'Ngày nộp: ${DateFormat('dd/MM/yyyy').format(submission.submittedAt)}', style: const TextStyle( fontSize: 13, color: AppColors.grey500, ), ), const SizedBox(height: 4), Text( 'Diện tích: ${submission.projectAddress ?? "N/A"}', style: const TextStyle( fontSize: 13, color: AppColors.grey500, ), ), if (submission.rejectionReason != 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.rejectionReason!, style: const TextStyle( fontSize: 12, color: AppColors.danger, ), ), ), ], ), ), ], ], ), ), ), ); } Widget _buildStatusBadge(SubmissionStatus status) { final color = _getStatusColor(status); 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.displayName, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } Color _getStatusColor(SubmissionStatus status) { switch (status) { case SubmissionStatus.pending: return AppColors.warning; case SubmissionStatus.reviewing: return AppColors.info; case SubmissionStatus.approved: return AppColors.success; case SubmissionStatus.rejected: return AppColors.danger; } } }