Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,676 @@
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<DemandesAidePage> createState() => _DemandesAidePageState();
}
class _DemandesAidePageState extends State<DemandesAidePage> {
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<DemandesAideBloc>().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<DemandesAideBloc>().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<DemandesAideBloc, DemandesAideState>(
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<Widget> _buildActions(DemandesAideState state) {
final actions = <Widget>[];
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<String>(
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<DemandeAide>(
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<DemandesAideBloc>().add(const RafraichirDemandesAideEvent());
}
void _rechercherDemandes(String query) {
context.read<DemandesAideBloc>().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<DemandesAideBloc>().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<DemandesAideBloc>().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<DemandesAideBloc>().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<DemandesAideBloc>().add(SelectionnerDemandeAideEvent(
demandeId: demandeId,
selectionne: selected,
));
}
void _activerModeSelection() {
setState(() {
_isSelectionMode = true;
});
}
void _quitterModeSelection() {
setState(() {
_isSelectionMode = false;
});
context.read<DemandesAideBloc>().add(const SelectionnerToutesDemandesAideEvent(selectionne: false));
}
void _toggleSelectAll(DemandesAideLoaded state) {
context.read<DemandesAideBloc>().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<DemandesAideBloc>().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<String> 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<DemandesAideBloc>().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<DemandesAideBloc>().add(ChargerDemandesUrgentesEvent(
organisationId: widget.organisationId ?? '',
));
}
void _supprimerFiltre(String filtre) {
final state = context.read<DemandesAideBloc>().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<DemandesAideBloc>().add(FiltrerDemandesAideEvent(
typeAide: nouveauxFiltres.typeAide,
statut: nouveauxFiltres.statut,
priorite: nouveauxFiltres.priorite,
urgente: nouveauxFiltres.urgente,
motCle: nouveauxFiltres.motCle,
));
}
}
void _effacerTousFiltres() {
_searchController.clear();
context.read<DemandesAideBloc>().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')),
);
}
}