import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:flutter/material.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/repositories/passage_repository.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; class UserHistoryPage extends StatefulWidget { const UserHistoryPage({super.key}); @override State createState() => _UserHistoryPageState(); } class _UserHistoryPageState extends State { // Liste qui contiendra les passages convertis List> _convertedPassages = []; // Variables pour indiquer l'état de chargement bool _isLoading = true; String _errorMessage = ''; @override void initState() { super.initState(); // Charger les passages depuis la box Hive au démarrage _loadPassages(); } // 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 // Utiliser la propriété passages qui gère déjà l'ouverture de la box final List allPassages = passageRepository.passages; debugPrint('Nombre total de passages dans la box: ${allPassages.length}'); // Filtrer pour exclure les passages de type 2 List filtered = []; for (var passage in allPassages) { try { if (passage.fkType != 2) { filtered.add(passage); } } catch (e) { debugPrint('Erreur lors du filtrage du passage: $e'); // Si nous ne pouvons pas accéder à fkType, ne pas ajouter ce passage } } debugPrint( 'Nombre de passages après filtrage (fkType != 2): ${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'); }); // Afficher la plage de dates pour le débogage if (filtered.isNotEmpty) { // Trier par date pour trouver min et max final sortedByDate = List.from(filtered); sortedByDate.sort((a, b) => a.passedAt.compareTo(b.passedAt)); final DateTime minDate = sortedByDate.first.passedAt; final DateTime maxDate = sortedByDate.last.passedAt; // Log détaillé pour débogage debugPrint( 'Plage de dates des passages: ${minDate.toString()} à ${maxDate.toString()}'); // Afficher les 5 passages les plus anciens et les 5 plus récents pour débogage debugPrint('\n--- 5 PASSAGES LES PLUS ANCIENS ---'); for (int i = 0; i < sortedByDate.length && i < 5; i++) { final p = sortedByDate[i]; debugPrint( 'ID: ${p.id}, Type: ${p.fkType}, Date: ${p.passedAt}, Adresse: ${p.rue}'); } debugPrint('\n--- 5 PASSAGES LES PLUS RÉCENTS ---'); for (int i = sortedByDate.length - 1; i >= 0 && i >= sortedByDate.length - 5; i--) { final p = sortedByDate[i]; debugPrint( 'ID: ${p.id}, Type: ${p.fkType}, Date: ${p.passedAt}, Adresse: ${p.rue}'); } // Vérifier la distribution des passages par mois final Map monthCount = {}; for (var passage in filtered) { final String monthKey = '${passage.passedAt.year}-${passage.passedAt.month.toString().padLeft(2, '0')}'; monthCount[monthKey] = (monthCount[monthKey] ?? 0) + 1; } debugPrint('\n--- DISTRIBUTION PAR MOIS ---'); final sortedMonths = monthCount.keys.toList()..sort(); for (var month in sortedMonths) { debugPrint('$month: ${monthCount[month]} passages'); } } // Convertir les modèles en Maps pour l'affichage avec gestion d'erreurs List> passagesMap = []; for (var passage in filtered) { try { final Map passageMap = _convertPassageModelToMap(passage); if (passageMap != null) { passagesMap.add(passageMap); } } catch (e) { debugPrint('Erreur lors de la conversion du passage en map: $e'); // Ignorer ce passage et continuer } } debugPrint('Nombre de passages après conversion: ${passagesMap.length}'); // Trier par date (plus récent en premier) avec gestion d'erreurs try { passagesMap.sort((a, b) { try { return (b['date'] as DateTime).compareTo(a['date'] as DateTime); } catch (e) { debugPrint('Erreur lors de la comparaison des dates: $e'); return 0; // Garder l'ordre actuel en cas d'erreur } }); } catch (e) { debugPrint('Erreur lors du tri des passages: $e'); // Continuer sans tri en cas d'erreur } // Debug: vérifier la plage de dates après conversion et tri if (passagesMap.isNotEmpty) { debugPrint('\n--- PLAGE DE DATES APRÈS CONVERSION ET TRI ---'); final firstDate = passagesMap.last['date'] as DateTime; final lastDate = passagesMap.first['date'] as DateTime; debugPrint('Premier passage: ${firstDate.toString()}'); debugPrint('Dernier passage: ${lastDate.toString()}'); } setState(() { _convertedPassages = passagesMap; _isLoading = false; }); } catch (e) { setState(() { _errorMessage = 'Erreur lors du chargement des passages: $e'; _isLoading = false; }); debugPrint(_errorMessage); } } // Convertir un modèle de passage en Map pour l'affichage avec gestion renforcée des erreurs Map _convertPassageModelToMap(PassageModel passage) { try { // Le passage ne peut pas être null en Dart non-nullable, // mais nous gardons cette structure pour faciliter la gestion des erreurs // Construire l'adresse complète avec gestion des erreurs String address = 'Adresse non disponible'; try { address = _buildFullAddress(passage); } catch (e) { debugPrint('Erreur lors de la construction de l\'adresse: $e'); } // Convertir le montant en double avec sécurité double amount = 0.0; try { if (passage.montant.isNotEmpty) { amount = double.parse(passage.montant); } } catch (e) { debugPrint('Erreur de conversion du montant: ${passage.montant}: $e'); } // Récupérer la date avec gestion d'erreur DateTime date; try { date = passage.passedAt; } catch (e) { debugPrint('Erreur lors de la récupération de la date: $e'); date = DateTime.now(); } // Récupérer le type avec gestion d'erreur int type; try { type = passage.fkType; // Si le type n'est pas dans les types connus, utiliser 0 comme valeur par défaut if (!AppKeys.typesPassages.containsKey(type)) { type = 0; // Type inconnu } } catch (e) { debugPrint('Erreur lors de la récupération du type: $e'); type = 0; } // Récupérer le type de règlement avec gestion d'erreur int payment; try { payment = passage.fkTypeReglement; // Si le type de règlement n'est pas dans les types connus, utiliser 0 comme valeur par défaut if (!AppKeys.typesReglements.containsKey(payment)) { payment = 0; // Type de règlement inconnu } } catch (e) { debugPrint('Erreur lors de la récupération du type de règlement: $e'); payment = 0; } // Gérer les champs optionnels String name = ''; try { name = passage.name; } catch (e) { debugPrint('Erreur lors de la récupération du nom: $e'); } String notes = ''; try { notes = passage.remarque; } catch (e) { debugPrint('Erreur lors de la récupération des remarques: $e'); } // Vérifier si un reçu est disponible avec gestion d'erreur bool hasReceipt = false; try { hasReceipt = amount > 0 && type == 1 && passage.nomRecu.isNotEmpty; } catch (e) { debugPrint('Erreur lors de la vérification du reçu: $e'); } // Vérifier s'il y a une erreur avec gestion d'erreur bool hasError = false; try { hasError = passage.emailErreur.isNotEmpty; } catch (e) { debugPrint('Erreur lors de la vérification des erreurs: $e'); } // Log pour débogage debugPrint( 'Conversion passage ID: ${passage.id}, Type: $type, Date: $date'); return { 'id': passage.id.toString(), 'address': address, 'amount': amount, 'date': date, 'type': type, 'payment': payment, 'name': name, 'notes': notes, 'hasReceipt': hasReceipt, 'hasError': hasError, 'fkUser': passage.fkUser, // Ajouter l'ID de l'utilisateur }; } catch (e) { debugPrint('ERREUR CRITIQUE lors de la conversion du passage: $e'); // Retourner un objet valide par défaut pour éviter les erreurs // Récupérer l'ID de l'utilisateur courant pour l'objet par défaut // Utiliser l'instance globale définie dans app.dart final currentUserId = userRepository.getCurrentUser()?.id; return { 'id': 'error', 'address': 'Adresse non disponible', 'amount': 0.0, 'date': DateTime.now(), 'type': 0, 'payment': 0, 'name': 'Nom non disponible', 'notes': '', 'hasReceipt': false, 'hasError': true, 'fkUser': currentUserId, // Ajouter l'ID de l'utilisateur courant }; } } // 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(', '); } @override void dispose() { super.dispose(); } // 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: 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['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: Text('Fermer'), ), if (passage['hasReceipt'] == true) TextButton( onPressed: () { Navigator.of(context).pop(); _showReceipt(passage); }, child: Text('Voir le reçu'), ), TextButton( onPressed: () { Navigator.of(context).pop(); _editPassage(passage); }, child: Text('Modifier'), ), ], ), ); } // Méthode pour éditer un passage void _editPassage(Map passage) { // Implémenter l'ouverture d'un formulaire d'édition // Cette méthode pourrait naviguer vers une page d'édition debugPrint('Édition du passage ${passage['id']}'); // Exemple: Navigator.of(context).push(MaterialPageRoute(builder: (_) => EditPassagePage(passage: passage))); } // Méthode pour afficher un reçu void _showReceipt(Map passage) { // Implémenter l'affichage ou la génération d'un reçu // Cette méthode pourrait générer un PDF et l'afficher debugPrint('Affichage du reçu pour le passage ${passage['id']}'); // Exemple: Navigator.of(context).push(MaterialPageRoute(builder: (_) => ReceiptPage(passage: passage))); } // 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: TextStyle(fontWeight: FontWeight.bold))), Expanded( child: Text( value, style: isError ? TextStyle(color: Colors.red) : null, ), ), ], ), ); } // Variable pour gérer la recherche String _searchQuery = ''; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( backgroundColor: Colors.transparent, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // En-tête avec bouton de rafraîchissement Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Historique des passages', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), IconButton( icon: const Icon(Icons.refresh), onPressed: _loadPassages, tooltip: 'Rafraîchir', ), ], ), ), // 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: theme.textTheme.titleLarge ?.copyWith(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 Column( children: [ // Stat rapide pour l'utilisateur if (_convertedPassages.isNotEmpty) Padding( padding: const EdgeInsets.all(8.0), child: Text( '${_convertedPassages.length} passages au total (${_convertedPassages.where((p) => (p['date'] as DateTime).isAfter(DateTime(2024, 12, 13))).length} de décembre 2024)', style: TextStyle( fontStyle: FontStyle.italic, color: theme.colorScheme.primary), ), ), // Widget de liste des passages Expanded( child: PassagesListWidget( passages: _convertedPassages, showFilters: true, showSearch: true, showActions: true, initialSearchQuery: _searchQuery, initialTypeFilter: 'Tous', // Toujours commencer avec 'Tous' pour voir tous les types initialPaymentFilter: 'Tous', // Exclure les passages de type 2 (À finaliser) excludePassageTypes: [2], // Filtrer par utilisateur courant filterByUserId: userRepository.getCurrentUser()?.id, // Désactiver les filtres de date implicites key: ValueKey( 'passages_list_${DateTime.now().millisecondsSinceEpoch}'), onPassageSelected: (passage) { // Action lors de la sélection d'un passage debugPrint('Passage sélectionné: ${passage['id']}'); _showPassageDetails(passage); }, onDetailsView: (passage) { // Action lors de l'affichage des détails debugPrint('Affichage des détails: ${passage['id']}'); _showPassageDetails(passage); }, onPassageEdit: (passage) { // Action lors de la modification d'un passage debugPrint('Modification du passage: ${passage['id']}'); _editPassage(passage); }, onReceiptView: (passage) { // Action lors de la demande d'affichage du reçu debugPrint( 'Affichage du reçu pour le passage: ${passage['id']}'); _showReceipt(passage); }, ), ), ], ), ], ), ), ); } }