import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:flutter/material.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart'; import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/core/data/models/sector_model.dart'; import 'package:geosector_app/core/repositories/sector_repository.dart'; class UserHistoryPage extends StatefulWidget { const UserHistoryPage({super.key}); @override State createState() => _UserHistoryPageState(); } // Enum pour gérer les types de tri enum PassageSortType { dateDesc, // Plus récent en premier (défaut) dateAsc, // Plus ancien en premier addressAsc, // Adresse A-Z addressDesc, // Adresse Z-A } class _UserHistoryPageState extends State { // Liste qui contiendra les passages convertis List> _convertedPassages = []; // Variables pour indiquer l'état de chargement bool _isLoading = true; String _errorMessage = ''; // Statistiques pour l'affichage int _totalSectors = 0; int _sharedMembersCount = 0; // État du tri actuel PassageSortType _currentSort = PassageSortType.dateDesc; // État des filtres (uniquement pour synchronisation) int? selectedSectorId; String selectedPeriod = 'Toutes'; DateTimeRange? selectedDateRange; // Repository pour les secteurs late SectorRepository _sectorRepository; // Liste des secteurs disponibles pour l'utilisateur List _userSectors = []; // Box des settings pour sauvegarder les préférences late Box _settingsBox; @override void initState() { super.initState(); // Initialiser le repository _sectorRepository = sectorRepository; // Initialiser les settings et charger les données _initSettingsAndLoad(); } // Initialiser les settings et charger les préférences Future _initSettingsAndLoad() async { try { // Ouvrir la box des settings if (!Hive.isBoxOpen(AppKeys.settingsBoxName)) { _settingsBox = await Hive.openBox(AppKeys.settingsBoxName); } else { _settingsBox = Hive.box(AppKeys.settingsBoxName); } // Charger les préférences présélectionnées _loadPreselectedFilters(); // Charger les secteurs de l'utilisateur _loadUserSectors(); // Charger les passages await _loadPassages(); } catch (e) { debugPrint('Erreur lors de l\'initialisation: $e'); setState(() { _isLoading = false; _errorMessage = 'Erreur lors de l\'initialisation: $e'; }); } } // Charger les secteurs de l'utilisateur void _loadUserSectors() { try { // Récupérer l'ID de l'utilisateur courant final currentUserId = userRepository.getCurrentUser()?.id; if (currentUserId != null) { // Récupérer tous les secteurs final allSectors = _sectorRepository.getAllSectors(); // Filtrer les secteurs où l'utilisateur a des passages final userSectorIds = {}; final allPassages = passageRepository.passages; for (var passage in allPassages) { if (passage.fkUser == currentUserId && passage.fkSector != null) { userSectorIds.add(passage.fkSector!); } } // Récupérer les secteurs correspondants _userSectors = allSectors.where((sector) => userSectorIds.contains(sector.id)).toList(); debugPrint('Nombre de secteurs pour l\'utilisateur: ${_userSectors.length}'); } } catch (e) { debugPrint('Erreur lors du chargement des secteurs utilisateur: $e'); } } // Charger les filtres présélectionnés depuis Hive void _loadPreselectedFilters() { try { // Charger le secteur présélectionné final int? preselectedSectorId = _settingsBox.get('history_selectedSectorId'); final String? preselectedPeriod = _settingsBox.get('history_selectedPeriod'); if (preselectedSectorId != null) { selectedSectorId = preselectedSectorId; debugPrint('Secteur présélectionné: ID $preselectedSectorId'); } if (preselectedPeriod != null) { selectedPeriod = preselectedPeriod; _updatePeriodFilter(preselectedPeriod); debugPrint('Période présélectionnée: $preselectedPeriod'); } // Nettoyer les valeurs après utilisation _settingsBox.delete('history_selectedSectorId'); _settingsBox.delete('history_selectedSectorName'); _settingsBox.delete('history_selectedTypeId'); _settingsBox.delete('history_selectedPeriod'); _settingsBox.delete('history_selectedPaymentId'); } catch (e) { debugPrint('Erreur lors du chargement des filtres présélectionnés: $e'); } } // Sauvegarder les préférences de filtres void _saveFilterPreferences() { try { if (selectedSectorId != null) { _settingsBox.put('history_selectedSectorId', selectedSectorId); } if (selectedPeriod != 'Toutes') { _settingsBox.put('history_selectedPeriod', selectedPeriod); } } catch (e) { debugPrint('Erreur lors de la sauvegarde des préférences: $e'); } } // Mettre à jour le filtre par secteur void _updateSectorFilter(String sectorName, int? sectorId) { setState(() { selectedSectorId = sectorId; }); _saveFilterPreferences(); } // Mettre à jour le filtre par période void _updatePeriodFilter(String period) { setState(() { selectedPeriod = period; // Mettre à jour la plage de dates en fonction de la période final DateTime now = DateTime.now(); switch (period) { case 'Derniers 15 jours': selectedDateRange = DateTimeRange( start: now.subtract(const Duration(days: 15)), end: now, ); break; case 'Dernière semaine': selectedDateRange = DateTimeRange( start: now.subtract(const Duration(days: 7)), end: now, ); break; case 'Dernier mois': selectedDateRange = DateTimeRange( start: DateTime(now.year, now.month - 1, now.day), end: now, ); break; case 'Tous': selectedDateRange = null; break; } }); _saveFilterPreferences(); } // Méthode pour charger les passages depuis le repository Future _loadPassages() async { setState(() { _isLoading = true; _errorMessage = ''; }); try { // Utiliser l'instance globale définie dans app.dart final List allPassages = passageRepository.passages; debugPrint('Nombre total de passages dans la box: ${allPassages.length}'); // Filtrer les passages de l'utilisateur courant final currentUserId = userRepository.getCurrentUser()?.id; List filtered = allPassages.where((p) => p.fkUser == currentUserId).toList(); debugPrint('Nombre de passages de l\'utilisateur: ${filtered.length}'); // Afficher la distribution des types de passages pour le débogage final Map typeCount = {}; for (var passage in filtered) { typeCount[passage.fkType] = (typeCount[passage.fkType] ?? 0) + 1; } typeCount.forEach((type, count) { debugPrint('Type de passage $type: $count passages'); }); // Calculer le nombre de secteurs uniques final Set uniqueSectors = {}; for (var passage in filtered) { if (passage.fkSector != null && passage.fkSector! > 0) { uniqueSectors.add(passage.fkSector!); } } // Compter les membres partagés (autres membres dans la même amicale) int sharedMembers = 0; try { final allMembers = membreRepository.membres; // Compter les membres autres que l'utilisateur courant sharedMembers = allMembers.where((membre) => membre.id != currentUserId).length; debugPrint('Nombre de membres partagés: $sharedMembers'); } catch (e) { debugPrint('Erreur lors du comptage des membres: $e'); } // Convertir les modèles en Maps pour l'affichage List> passagesMap = []; for (var passage in filtered) { try { final Map passageMap = _convertPassageModelToMap(passage); passagesMap.add(passageMap); } catch (e) { debugPrint('Erreur lors de la conversion du passage en map: $e'); } } debugPrint('Nombre de passages après conversion: ${passagesMap.length}'); // Trier par date (plus récent en premier) passagesMap = _sortPassages(passagesMap); setState(() { _convertedPassages = passagesMap; _totalSectors = uniqueSectors.length; _sharedMembersCount = sharedMembers; _isLoading = false; }); } catch (e) { setState(() { _errorMessage = 'Erreur lors du chargement des passages: $e'; _isLoading = false; }); debugPrint(_errorMessage); } } // Filtrer les passages selon les critères sélectionnés List> _getFilteredPassages(List> passages) { return passages.where((passage) { // Filtrer par secteur if (selectedSectorId != null && passage['fkSector'] != selectedSectorId) { return false; } // Filtrer par période/date if (selectedDateRange != null && passage['date'] is DateTime) { final DateTime passageDate = passage['date'] as DateTime; if (passageDate.isBefore(selectedDateRange!.start) || passageDate.isAfter(selectedDateRange!.end)) { return false; } } return true; }).toList(); } // Convertir un modèle de passage en Map pour l'affichage Map _convertPassageModelToMap(PassageModel passage) { try { // Construire l'adresse complète String address = _buildFullAddress(passage); // Convertir le montant en double double amount = 0.0; if (passage.montant.isNotEmpty) { amount = double.tryParse(passage.montant) ?? 0.0; } // Récupérer la date DateTime date = passage.passedAt ?? DateTime.now(); // Récupérer le type int type = passage.fkType; if (!AppKeys.typesPassages.containsKey(type)) { type = 1; // Type 1 par défaut (Effectué) } // Récupérer le type de règlement int payment = passage.fkTypeReglement; if (!AppKeys.typesReglements.containsKey(payment)) { payment = 0; // Type de règlement inconnu } // Vérifier si un reçu est disponible bool hasReceipt = amount > 0 && type == 1 && passage.nomRecu.isNotEmpty; // Vérifier s'il y a une erreur bool hasError = passage.emailErreur.isNotEmpty; // Récupérer le secteur SectorModel? sector; if (passage.fkSector != null) { sector = _sectorRepository.getSectorById(passage.fkSector!); } return { 'id': passage.id, 'address': address, 'amount': amount, 'date': date, 'type': type, 'payment': payment, 'name': passage.name, 'notes': passage.remarque, 'hasReceipt': hasReceipt, 'hasError': hasError, 'fkUser': passage.fkUser, 'fkSector': passage.fkSector, 'sector': sector?.libelle ?? 'Secteur inconnu', 'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Composants de l'adresse pour le tri 'rue': passage.rue, 'numero': passage.numero, 'rueBis': passage.rueBis, }; } catch (e) { debugPrint('Erreur lors de la conversion du passage: $e'); // Retourner un objet valide par défaut final currentUserId = userRepository.getCurrentUser()?.id; return { 'id': 0, 'address': 'Adresse non disponible', 'amount': 0.0, 'date': DateTime.now(), 'type': 1, 'payment': 1, 'name': 'Nom non disponible', 'notes': '', 'hasReceipt': false, 'hasError': true, 'fkUser': currentUserId, 'fkSector': null, 'sector': 'Secteur inconnu', 'rue': '', 'numero': '', 'rueBis': '', }; } } // Méthode pour trier les passages selon le type de tri sélectionné List> _sortPassages(List> passages) { final sortedPassages = List>.from(passages); switch (_currentSort) { case PassageSortType.dateDesc: sortedPassages.sort((a, b) { try { return (b['date'] as DateTime).compareTo(a['date'] as DateTime); } catch (e) { return 0; } }); break; case PassageSortType.dateAsc: sortedPassages.sort((a, b) { try { return (a['date'] as DateTime).compareTo(b['date'] as DateTime); } catch (e) { return 0; } }); break; case PassageSortType.addressAsc: sortedPassages.sort((a, b) { try { // Tri intelligent par rue, numéro, rueBis final String rueA = a['rue'] ?? ''; final String rueB = b['rue'] ?? ''; final String numeroA = a['numero'] ?? ''; final String numeroB = b['numero'] ?? ''; final String rueBisA = a['rueBis'] ?? ''; final String rueBisB = b['rueBis'] ?? ''; // D'abord comparer les rues int rueCompare = rueA.toLowerCase().compareTo(rueB.toLowerCase()); if (rueCompare != 0) return rueCompare; // Si les rues sont identiques, comparer les numéros (numériquement) int numA = int.tryParse(numeroA) ?? 0; int numB = int.tryParse(numeroB) ?? 0; int numCompare = numA.compareTo(numB); if (numCompare != 0) return numCompare; // Si les numéros sont identiques, comparer les rueBis return rueBisA.toLowerCase().compareTo(rueBisB.toLowerCase()); } catch (e) { return 0; } }); break; case PassageSortType.addressDesc: sortedPassages.sort((a, b) { try { // Tri intelligent inversé final String rueA = a['rue'] ?? ''; final String rueB = b['rue'] ?? ''; final String numeroA = a['numero'] ?? ''; final String numeroB = b['numero'] ?? ''; final String rueBisA = a['rueBis'] ?? ''; final String rueBisB = b['rueBis'] ?? ''; // D'abord comparer les rues (inversé) int rueCompare = rueB.toLowerCase().compareTo(rueA.toLowerCase()); if (rueCompare != 0) return rueCompare; // Si les rues sont identiques, comparer les numéros (inversé) int numA = int.tryParse(numeroA) ?? 0; int numB = int.tryParse(numeroB) ?? 0; int numCompare = numB.compareTo(numA); if (numCompare != 0) return numCompare; // Si les numéros sont identiques, comparer les rueBis (inversé) return rueBisB.toLowerCase().compareTo(rueBisA.toLowerCase()); } catch (e) { return 0; } }); break; } return sortedPassages; } // Construire l'adresse complète à partir des composants String _buildFullAddress(PassageModel passage) { final List addressParts = []; // Numéro et rue if (passage.numero.isNotEmpty) { addressParts.add('${passage.numero} ${passage.rue}'); } else { addressParts.add(passage.rue); } // Complément rue bis if (passage.rueBis.isNotEmpty) { addressParts.add(passage.rueBis); } // Résidence/Bâtiment if (passage.residence.isNotEmpty) { addressParts.add(passage.residence); } // Appartement if (passage.appt.isNotEmpty) { addressParts.add('Appt ${passage.appt}'); } // Niveau if (passage.niveau.isNotEmpty) { addressParts.add('Niveau ${passage.niveau}'); } // Ville if (passage.ville.isNotEmpty) { addressParts.add(passage.ville); } return addressParts.join(', '); } // Méthode pour afficher les détails d'un passage void _showPassageDetails(Map passage) { // Récupérer les informations du type de passage et du type de règlement final typePassage = AppKeys.typesPassages[passage['type']] as Map; final typeReglement = AppKeys.typesReglements[passage['payment']] as Map; showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Détails du passage'), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ _buildDetailRow('Adresse', passage['address']), _buildDetailRow('Nom', passage['name']), _buildDetailRow('Date', '${passage['date'].day}/${passage['date'].month}/${passage['date'].year}'), _buildDetailRow('Type', typePassage['titre']), _buildDetailRow('Règlement', typeReglement['titre']), _buildDetailRow('Montant', '${passage['amount']}€'), if (passage['sector'] != null) _buildDetailRow('Secteur', passage['sector']), if (passage['notes'] != null && passage['notes'].toString().isNotEmpty) _buildDetailRow('Notes', passage['notes']), if (passage['hasReceipt'] == true) _buildDetailRow('Reçu', 'Disponible'), if (passage['hasError'] == true) _buildDetailRow('Erreur', 'Détectée', isError: true), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), if (passage['hasReceipt'] == true) TextButton( onPressed: () { Navigator.of(context).pop(); _showReceipt(passage); }, child: const Text('Voir le reçu'), ), TextButton( onPressed: () { Navigator.of(context).pop(); _editPassage(passage); }, child: const Text('Modifier'), ), ], ), ); } // Méthode pour éditer un passage void _editPassage(Map passage) { debugPrint('Édition du passage ${passage['id']}'); } // Méthode pour afficher un reçu void _showReceipt(Map passage) { debugPrint('Affichage du reçu pour le passage ${passage['id']}'); } // Helper pour construire une ligne de détails Widget _buildDetailRow(String label, String value, {bool isError = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 100, child: Text('$label:', style: const TextStyle(fontWeight: FontWeight.bold))), Expanded( child: Text( value, style: isError ? const TextStyle(color: Colors.red) : null, ), ), ], ), ); } // Les filtres sont maintenant gérés directement dans le PassagesListWidget // Méthodes de filtre retirées car maintenant gérées dans le widget @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( backgroundColor: Colors.transparent, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Les filtres sont maintenant intégrés dans le PassagesListWidget // Affichage du chargement ou des erreurs if (_isLoading) const Expanded( child: Center( child: CircularProgressIndicator(), ), ) else if (_errorMessage.isNotEmpty) Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.red), const SizedBox(height: 16), Text( 'Erreur de chargement', style: TextStyle( fontSize: AppTheme.r(context, 22), color: Colors.red), ), const SizedBox(height: 8), Text(_errorMessage), const SizedBox(height: 16), ElevatedButton( onPressed: _loadPassages, child: const Text('Réessayer'), ), ], ), ), ) // Utilisation du widget PassagesListWidget pour afficher la liste des passages else Expanded( child: Container( color: Colors.transparent, child: Column( children: [ // Widget de liste des passages avec ValueListenableBuilder Expanded( child: ValueListenableBuilder( valueListenable: Hive.box(AppKeys.passagesBoxName).listenable(), builder: (context, Box passagesBox, child) { // Reconvertir les passages à chaque changement final currentUserId = userRepository.getCurrentUser()?.id; final List allPassages = passagesBox.values .where((p) => p.fkUser == currentUserId) .toList(); // Appliquer le même filtrage et conversion List> passagesMap = []; for (var passage in allPassages) { try { final Map passageMap = _convertPassageModelToMap(passage); passagesMap.add(passageMap); } catch (e) { debugPrint('Erreur lors de la conversion du passage en map: $e'); } } // Appliquer le tri sélectionné passagesMap = _sortPassages(passagesMap); return PassagesListWidget( // Données passages: passagesMap, // Activation des filtres showFilters: true, showSearch: true, showTypeFilter: true, showPaymentFilter: true, showSectorFilter: true, showUserFilter: false, // Pas de filtre membre pour la page user showPeriodFilter: true, // Données pour les filtres sectors: _userSectors, members: null, // Pas de filtre membre pour la page user // Valeurs initiales initialSectorId: selectedSectorId, initialPeriod: selectedPeriod, dateRange: selectedDateRange, // Filtre par utilisateur courant filterByUserId: currentUserId, // Bouton d'ajout showAddButton: true, onAddPassage: () async { // Ouvrir le dialogue de création de passage await showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return PassageFormDialog( title: 'Nouveau passage', passageRepository: passageRepository, userRepository: userRepository, operationRepository: operationRepository, onSuccess: () { // Le widget se rafraîchira automatiquement via ValueListenableBuilder }, ); }, ); }, sortingButtons: Row( children: [ // Bouton tri par date avec icône calendrier IconButton( icon: Icon( Icons.calendar_today, size: 20, color: _currentSort == PassageSortType.dateDesc || _currentSort == PassageSortType.dateAsc ? theme.colorScheme.primary : theme.colorScheme.onSurface.withValues(alpha: 0.6), ), tooltip: _currentSort == PassageSortType.dateAsc ? 'Tri par date (ancien en premier)' : 'Tri par date (récent en premier)', onPressed: () { setState(() { if (_currentSort == PassageSortType.dateDesc) { _currentSort = PassageSortType.dateAsc; } else { _currentSort = PassageSortType.dateDesc; } }); }, ), // Indicateur de direction pour la date if (_currentSort == PassageSortType.dateDesc || _currentSort == PassageSortType.dateAsc) Icon( _currentSort == PassageSortType.dateAsc ? Icons.arrow_upward : Icons.arrow_downward, size: 14, color: theme.colorScheme.primary, ), const SizedBox(width: 4), // Bouton tri par adresse avec icône maison IconButton( icon: Icon( Icons.home, size: 20, color: _currentSort == PassageSortType.addressDesc || _currentSort == PassageSortType.addressAsc ? theme.colorScheme.primary : theme.colorScheme.onSurface.withValues(alpha: 0.6), ), tooltip: _currentSort == PassageSortType.addressAsc ? 'Tri par adresse (A-Z)' : 'Tri par adresse (Z-A)', onPressed: () { setState(() { if (_currentSort == PassageSortType.addressAsc) { _currentSort = PassageSortType.addressDesc; } else { _currentSort = PassageSortType.addressAsc; } }); }, ), // Indicateur de direction pour l'adresse if (_currentSort == PassageSortType.addressDesc || _currentSort == PassageSortType.addressAsc) Icon( _currentSort == PassageSortType.addressAsc ? Icons.arrow_upward : Icons.arrow_downward, size: 14, color: theme.colorScheme.primary, ), ], ), // Actions showActions: true, key: const ValueKey('user_passages_list'), // Callback pour synchroniser les filtres onFiltersChanged: (filters) { setState(() { selectedSectorId = filters['sectorId']; selectedPeriod = filters['period'] ?? 'Toutes'; selectedDateRange = filters['dateRange']; }); }, onDetailsView: (passage) { debugPrint('Affichage des détails: ${passage['id']}'); _showPassageDetails(passage); }, onPassageEdit: (passage) { debugPrint('Modification du passage: ${passage['id']}'); _editPassage(passage); }, onReceiptView: (passage) { debugPrint('Affichage du reçu pour le passage: ${passage['id']}'); _showReceipt(passage); }, onPassageDelete: (passage) { // Pas besoin de recharger, le ValueListenableBuilder // se rafraîchira automatiquement après la suppression }, ); }, ), ), ], ), ), ), ], ), ), ); } @override void dispose() { super.dispose(); } }