import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/widgets/unified_page_layout.dart'; import '../../../../core/widgets/unified_card.dart'; import '../../../../core/widgets/unified_list_widget.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_text_styles.dart'; import '../../../../core/utils/date_formatter.dart'; import '../../../../core/utils/currency_formatter.dart'; import '../../domain/entities/demande_aide.dart'; import '../bloc/demandes_aide/demandes_aide_bloc.dart'; import '../bloc/demandes_aide/demandes_aide_event.dart'; import '../bloc/demandes_aide/demandes_aide_state.dart'; import '../widgets/demande_aide_card.dart'; import '../widgets/demandes_aide_filter_bottom_sheet.dart'; import '../widgets/demandes_aide_sort_bottom_sheet.dart'; /// Page principale pour afficher la liste des demandes d'aide /// /// Cette page utilise le pattern BLoC pour gérer l'état et affiche /// une liste paginée des demandes d'aide avec des fonctionnalités /// de filtrage, tri, recherche et sélection multiple. class DemandesAidePage extends StatefulWidget { final String? organisationId; final TypeAide? typeAideInitial; final StatutAide? statutInitial; const DemandesAidePage({ super.key, this.organisationId, this.typeAideInitial, this.statutInitial, }); @override State createState() => _DemandesAidePageState(); } class _DemandesAidePageState extends State { final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); bool _isSelectionMode = false; @override void initState() { super.initState(); _scrollController.addListener(_onScroll); // Charger les demandes d'aide au démarrage context.read().add(ChargerDemandesAideEvent( organisationId: widget.organisationId, typeAide: widget.typeAideInitial, statut: widget.statutInitial, )); } @override void dispose() { _scrollController.dispose(); _searchController.dispose(); super.dispose(); } void _onScroll() { if (_isBottom) { context.read().add(const ChargerPlusDemandesAideEvent()); } } bool get _isBottom { if (!_scrollController.hasClients) return false; final maxScroll = _scrollController.position.maxScrollExtent; final currentScroll = _scrollController.offset; return currentScroll >= (maxScroll * 0.9); } @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is DemandesAideError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: AppColors.error, action: state.canRetry ? SnackBarAction( label: 'Réessayer', textColor: Colors.white, onPressed: () => _rafraichir(), ) : null, ), ); } else if (state is DemandesAideOperationSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: AppColors.success, ), ); } else if (state is DemandesAideExported) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Fichier exporté: ${state.filePath}'), backgroundColor: AppColors.success, action: SnackBarAction( label: 'Ouvrir', textColor: Colors.white, onPressed: () => _ouvrirFichier(state.filePath), ), ), ); } }, builder: (context, state) { return UnifiedPageLayout( title: 'Demandes d\'aide', showBackButton: false, actions: _buildActions(state), floatingActionButton: _buildFloatingActionButton(), body: Column( children: [ _buildSearchBar(state), _buildFilterChips(state), Expanded(child: _buildContent(state)), ], ), ); }, ); } List _buildActions(DemandesAideState state) { final actions = []; if (_isSelectionMode && state is DemandesAideLoaded) { // Actions en mode sélection actions.addAll([ IconButton( icon: const Icon(Icons.select_all), onPressed: () => _toggleSelectAll(state), tooltip: state.toutesDemandesSelectionnees ? 'Désélectionner tout' : 'Sélectionner tout', ), IconButton( icon: const Icon(Icons.delete), onPressed: state.nombreDemandesSelectionnees > 0 ? () => _supprimerSelection(state) : null, tooltip: 'Supprimer la sélection', ), IconButton( icon: const Icon(Icons.file_download), onPressed: state.nombreDemandesSelectionnees > 0 ? () => _exporterSelection(state) : null, tooltip: 'Exporter la sélection', ), IconButton( icon: const Icon(Icons.close), onPressed: _quitterModeSelection, tooltip: 'Quitter la sélection', ), ]); } else { // Actions normales actions.addAll([ IconButton( icon: const Icon(Icons.filter_list), onPressed: () => _afficherFiltres(state), tooltip: 'Filtrer', ), IconButton( icon: const Icon(Icons.sort), onPressed: () => _afficherTri(state), tooltip: 'Trier', ), PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) => _onMenuSelected(value, state), itemBuilder: (context) => [ const PopupMenuItem( value: 'refresh', child: ListTile( leading: Icon(Icons.refresh), title: Text('Actualiser'), dense: true, ), ), const PopupMenuItem( value: 'select', child: ListTile( leading: Icon(Icons.checklist), title: Text('Sélection multiple'), dense: true, ), ), const PopupMenuItem( value: 'export_all', child: ListTile( leading: Icon(Icons.file_download), title: Text('Exporter tout'), dense: true, ), ), const PopupMenuItem( value: 'urgentes', child: ListTile( leading: Icon(Icons.priority_high, color: AppColors.error), title: Text('Demandes urgentes'), dense: true, ), ), ], ), ]); } return actions; } Widget _buildFloatingActionButton() { return FloatingActionButton.extended( onPressed: _creerNouvelleDemande, icon: const Icon(Icons.add), label: const Text('Nouvelle demande'), backgroundColor: AppColors.primary, ); } Widget _buildSearchBar(DemandesAideState state) { return Container( padding: const EdgeInsets.all(16.0), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Rechercher des demandes...', prefixIcon: const Icon(Icons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _rechercherDemandes(''); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), onChanged: _rechercherDemandes, onSubmitted: _rechercherDemandes, ), ); } Widget _buildFilterChips(DemandesAideState state) { if (state is! DemandesAideLoaded || !state.hasFiltres) { return const SizedBox.shrink(); } return Container( height: 50, padding: const EdgeInsets.symmetric(horizontal: 16.0), child: ListView( scrollDirection: Axis.horizontal, children: [ if (state.filtres.typeAide != null) _buildFilterChip( 'Type: ${state.filtres.typeAide!.libelle}', () => _supprimerFiltre('typeAide'), ), if (state.filtres.statut != null) _buildFilterChip( 'Statut: ${state.filtres.statut!.libelle}', () => _supprimerFiltre('statut'), ), if (state.filtres.priorite != null) _buildFilterChip( 'Priorité: ${state.filtres.priorite!.libelle}', () => _supprimerFiltre('priorite'), ), if (state.filtres.urgente == true) _buildFilterChip( 'Urgente', () => _supprimerFiltre('urgente'), ), if (state.filtres.motCle != null && state.filtres.motCle!.isNotEmpty) _buildFilterChip( 'Recherche: "${state.filtres.motCle}"', () => _supprimerFiltre('motCle'), ), ActionChip( label: const Text('Effacer tout'), onPressed: _effacerTousFiltres, backgroundColor: AppColors.error.withOpacity(0.1), labelStyle: TextStyle(color: AppColors.error), ), ], ), ); } Widget _buildFilterChip(String label, VoidCallback onDeleted) { return Padding( padding: const EdgeInsets.only(right: 8.0), child: Chip( label: Text(label), onDeleted: onDeleted, backgroundColor: AppColors.primary.withOpacity(0.1), labelStyle: TextStyle(color: AppColors.primary), ), ); } Widget _buildContent(DemandesAideState state) { if (state is DemandesAideInitial) { return const Center( child: Text('Appuyez sur actualiser pour charger les demandes'), ); } if (state is DemandesAideLoading && state.isRefreshing == false) { return const Center(child: CircularProgressIndicator()); } if (state is DemandesAideError && !state.hasCachedData) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( state.isNetworkError ? Icons.wifi_off : Icons.error, size: 64, color: AppColors.error, ), const SizedBox(height: 16), Text( state.message, style: AppTextStyles.bodyLarge, textAlign: TextAlign.center, ), const SizedBox(height: 16), if (state.canRetry) ElevatedButton( onPressed: _rafraichir, child: const Text('Réessayer'), ), ], ), ); } if (state is DemandesAideExporting) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(value: state.progress), const SizedBox(height: 16), Text( state.currentStep ?? 'Export en cours...', style: AppTextStyles.bodyLarge, ), const SizedBox(height: 8), Text( '${(state.progress * 100).toInt()}%', style: AppTextStyles.bodyMedium, ), ], ), ); } if (state is DemandesAideLoaded) { return _buildDemandesList(state); } return const SizedBox.shrink(); } Widget _buildDemandesList(DemandesAideLoaded state) { if (state.demandesFiltrees.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inbox, size: 64, color: AppColors.textSecondary, ), const SizedBox(height: 16), Text( state.hasData ? 'Aucun résultat pour les filtres appliqués' : 'Aucune demande d\'aide', style: AppTextStyles.bodyLarge, textAlign: TextAlign.center, ), if (state.hasFiltres) ...[ const SizedBox(height: 8), TextButton( onPressed: _effacerTousFiltres, child: const Text('Effacer les filtres'), ), ], ], ), ); } return RefreshIndicator( onRefresh: () async => _rafraichir(), child: UnifiedListWidget( items: state.demandesFiltrees, itemBuilder: (context, demande, index) => DemandeAideCard( demande: demande, isSelected: state.demandesSelectionnees[demande.id] == true, isSelectionMode: _isSelectionMode, onTap: () => _onDemandeAideTap(demande), onLongPress: () => _onDemandeAideLongPress(demande), onSelectionChanged: (selected) => _onDemandeAideSelectionChanged(demande.id, selected), ), scrollController: _scrollController, hasReachedMax: state.hasReachedMax, isLoading: state.isLoadingMore, emptyWidget: const SizedBox.shrink(), // Géré plus haut ), ); } // Méthodes d'action void _rafraichir() { context.read().add(const RafraichirDemandesAideEvent()); } void _rechercherDemandes(String query) { context.read().add(FiltrerDemandesAideEvent(motCle: query)); } void _afficherFiltres(DemandesAideState state) { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => DemandesAideFilterBottomSheet( filtresActuels: state is DemandesAideLoaded ? state.filtres : const FiltresDemandesAide(), onFiltresChanged: (filtres) { context.read().add(FiltrerDemandesAideEvent( typeAide: filtres.typeAide, statut: filtres.statut, priorite: filtres.priorite, urgente: filtres.urgente, motCle: filtres.motCle, )); }, ), ); } void _afficherTri(DemandesAideState state) { showModalBottomSheet( context: context, builder: (context) => DemandesAideSortBottomSheet( critereActuel: state is DemandesAideLoaded ? state.criterieTri : null, croissantActuel: state is DemandesAideLoaded ? state.triCroissant : true, onTriChanged: (critere, croissant) { context.read().add(TrierDemandesAideEvent( critere: critere, croissant: croissant, )); }, ), ); } void _onMenuSelected(String value, DemandesAideState state) { switch (value) { case 'refresh': _rafraichir(); break; case 'select': _activerModeSelection(); break; case 'export_all': if (state is DemandesAideLoaded) { _exporterTout(state); } break; case 'urgentes': _afficherDemandesUrgentes(); break; } } void _creerNouvelleDemande() { Navigator.pushNamed(context, '/solidarite/demandes/creer'); } void _onDemandeAideTap(DemandeAide demande) { if (_isSelectionMode) { _onDemandeAideSelectionChanged( demande.id, !(context.read().state as DemandesAideLoaded) .demandesSelectionnees[demande.id] == true, ); } else { Navigator.pushNamed( context, '/solidarite/demandes/details', arguments: demande.id, ); } } void _onDemandeAideLongPress(DemandeAide demande) { if (!_isSelectionMode) { _activerModeSelection(); _onDemandeAideSelectionChanged(demande.id, true); } } void _onDemandeAideSelectionChanged(String demandeId, bool selected) { context.read().add(SelectionnerDemandeAideEvent( demandeId: demandeId, selectionne: selected, )); } void _activerModeSelection() { setState(() { _isSelectionMode = true; }); } void _quitterModeSelection() { setState(() { _isSelectionMode = false; }); context.read().add(const SelectionnerToutesDemandesAideEvent(selectionne: false)); } void _toggleSelectAll(DemandesAideLoaded state) { context.read().add(SelectionnerToutesDemandesAideEvent( selectionne: !state.toutesDemandesSelectionnees, )); } void _supprimerSelection(DemandesAideLoaded state) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: Text( 'Êtes-vous sûr de vouloir supprimer ${state.nombreDemandesSelectionnees} demande(s) d\'aide ?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.pop(context); context.read().add(SupprimerDemandesSelectionnees( demandeIds: state.demandesSelectionneesIds, )); _quitterModeSelection(); }, style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), child: const Text('Supprimer'), ), ], ), ); } void _exporterSelection(DemandesAideLoaded state) { _afficherDialogueExport(state.demandesSelectionneesIds); } void _exporterTout(DemandesAideLoaded state) { _afficherDialogueExport(state.demandesFiltrees.map((d) => d.id).toList()); } void _afficherDialogueExport(List demandeIds) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Exporter les demandes'), content: Column( mainAxisSize: MainAxisSize.min, children: FormatExport.values.map((format) => ListTile( leading: Icon(_getFormatIcon(format)), title: Text(format.libelle), onTap: () { Navigator.pop(context); context.read().add(ExporterDemandesAideEvent( demandeIds: demandeIds, format: format, )); }, )).toList(), ), ), ); } IconData _getFormatIcon(FormatExport format) { switch (format) { case FormatExport.pdf: return Icons.picture_as_pdf; case FormatExport.excel: return Icons.table_chart; case FormatExport.csv: return Icons.grid_on; case FormatExport.json: return Icons.code; } } void _afficherDemandesUrgentes() { context.read().add(ChargerDemandesUrgentesEvent( organisationId: widget.organisationId ?? '', )); } void _supprimerFiltre(String filtre) { final state = context.read().state; if (state is DemandesAideLoaded) { var nouveauxFiltres = state.filtres; switch (filtre) { case 'typeAide': nouveauxFiltres = nouveauxFiltres.copyWith(typeAide: null); break; case 'statut': nouveauxFiltres = nouveauxFiltres.copyWith(statut: null); break; case 'priorite': nouveauxFiltres = nouveauxFiltres.copyWith(priorite: null); break; case 'urgente': nouveauxFiltres = nouveauxFiltres.copyWith(urgente: null); break; case 'motCle': nouveauxFiltres = nouveauxFiltres.copyWith(motCle: ''); _searchController.clear(); break; } context.read().add(FiltrerDemandesAideEvent( typeAide: nouveauxFiltres.typeAide, statut: nouveauxFiltres.statut, priorite: nouveauxFiltres.priorite, urgente: nouveauxFiltres.urgente, motCle: nouveauxFiltres.motCle, )); } } void _effacerTousFiltres() { _searchController.clear(); context.read().add(const FiltrerDemandesAideEvent()); } void _ouvrirFichier(String filePath) { // Implémenter l'ouverture du fichier avec un package comme open_file ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ouverture du fichier: $filePath')), ); } }