On release/v3.1.4: Sauvegarde temporaire pour changement de branche

This commit is contained in:
2025-08-21 17:51:22 +02:00
parent 6c8853e553
commit 41a4505b4b
1697 changed files with 167987 additions and 231472 deletions

View File

@@ -1,7 +1,9 @@
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
// Pour accéder aux instances globales
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';
@@ -12,6 +14,14 @@ class UserHistoryPage extends StatefulWidget {
State<UserHistoryPage> 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<UserHistoryPage> {
// Liste qui contiendra les passages convertis
List<Map<String, dynamic>> _convertedPassages = [];
@@ -19,6 +29,13 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// 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;
@override
void initState() {
@@ -42,21 +59,10 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
debugPrint('Nombre total de passages dans la box: ${allPassages.length}');
// Filtrer pour exclure les passages de type 2
List<PassageModel> 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
}
}
// Ne plus filtrer les passages de type 2 - laisser le widget gérer le filtrage
List<PassageModel> filtered = allPassages;
debugPrint(
'Nombre de passages après filtrage (fkType != 2): ${filtered.length}');
debugPrint('Nombre total de passages disponibles: ${filtered.length}');
// Afficher la distribution des types de passages pour le débogage
final Map<int, int> typeCount = {};
@@ -156,9 +162,34 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
debugPrint('Premier passage: ${firstDate.toString()}');
debugPrint('Dernier passage: ${lastDate.toString()}');
}
// Calculer le nombre de secteurs uniques
final Set<int> 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 {
// Utiliser l'instance globale définie dans app.dart
final currentUserId = userRepository.getCurrentUser()?.id;
final allMembers = membreRepository.membres; // Utiliser la propriété 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');
}
setState(() {
_convertedPassages = passagesMap;
_totalSectors = uniqueSectors.length;
_sharedMembersCount = sharedMembers;
_isLoading = false;
});
} catch (e) {
@@ -207,13 +238,13 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
int type;
try {
type = passage.fkType;
// Si le type n'est pas dans les types connus, utiliser 0 comme valeur par défaut
// Si le type n'est pas dans les types connus, utiliser 1 comme valeur par défaut
if (!AppKeys.typesPassages.containsKey(type)) {
type = 0; // Type inconnu
type = 1; // Type 1 par défaut (Effectué)
}
} catch (e) {
debugPrint('Erreur lors de la récupération du type: $e');
type = 0;
type = 1; // Type 1 par défaut
}
// Récupérer le type de règlement avec gestion d'erreur
@@ -265,7 +296,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'Conversion passage ID: ${passage.id}, Type: $type, Date: $date');
return {
'id': passage.id.toString(),
'id': passage.id, // Garder l'ID comme int, pas besoin de toString()
'address': address,
'amount': amount,
'date': date,
@@ -276,6 +307,11 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'hasReceipt': hasReceipt,
'hasError': hasError,
'fkUser': passage.fkUser, // Ajouter l'ID de l'utilisateur
'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Ajout du champ pour le widget
// Ajouter les composants de l'adresse pour le tri
'rue': passage.rue,
'numero': passage.numero,
'rueBis': passage.rueBis,
};
} catch (e) {
debugPrint('ERREUR CRITIQUE lors de la conversion du passage: $e');
@@ -289,17 +325,105 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'address': 'Adresse non disponible',
'amount': 0.0,
'date': DateTime.now(),
'type': 0,
'payment': 0,
'type': 1, // Type 1 par défaut au lieu de 0
'payment': 1, // Payment 1 par défaut au lieu de 0
'name': 'Nom non disponible',
'notes': '',
'hasReceipt': false,
'hasError': true,
'fkUser': currentUserId, // Ajouter l'ID de l'utilisateur courant
// Composants de l'adresse pour le tri
'rue': '',
'numero': '',
'rueBis': '',
};
}
}
// Méthode pour trier les passages selon le type de tri sélectionné
List<Map<String, dynamic>> _sortPassages(List<Map<String, dynamic>> passages) {
final sortedPassages = List<Map<String, dynamic>>.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 (numérique), 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é par rue, numéro (numérique), 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 (inversé)
int rueCompare = rueB.toLowerCase().compareTo(rueA.toLowerCase());
if (rueCompare != 0) return rueCompare;
// Si les rues sont identiques, comparer les numéros (inversé numériquement)
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<String> addressParts = [];
@@ -457,20 +581,45 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// En-tête avec bouton de rafraîchissement
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
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',
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isLoading
? 'Historique des passages'
: 'Historique des ${_convertedPassages.length} passages${_totalSectors > 0 ? ' ($_totalSectors secteur${_totalSectors > 1 ? 's' : ''})' : ''}',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
if (!_isLoading && _sharedMembersCount > 0)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
'Partagés avec $_sharedMembersCount membre${_sharedMembersCount > 1 ? 's' : ''}',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
fontStyle: FontStyle.italic,
),
),
),
],
),
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadPassages,
tooltip: 'Rafraîchir',
),
],
),
],
),
@@ -510,61 +659,159 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
)
// 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),
Expanded(
child: Container(
color: Colors.transparent,
child: Column(
children: [
// Widget de liste des passages avec ValueListenableBuilder
Expanded(
child: ValueListenableBuilder(
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
builder: (context, Box<PassageModel> passagesBox, child) {
// Reconvertir les passages à chaque changement
final List<PassageModel> allPassages = passagesBox.values.toList();
// Appliquer le même filtrage et conversion
List<Map<String, dynamic>> passagesMap = [];
for (var passage in allPassages) {
try {
final Map<String, dynamic> 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(
showAddButton: true, // Activer le bouton de création
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.withOpacity(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.withOpacity(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,
),
],
),
passages: passagesMap,
showFilters: true,
showSearch: true,
showActions: true,
initialSearchQuery: _searchQuery,
initialTypeFilter: 'Tous',
initialPaymentFilter: 'Tous',
excludePassageTypes: const [],
filterByUserId: userRepository.getCurrentUser()?.id,
key: const ValueKey('user_passages_list'),
// Le widget gère maintenant le flux conditionnel par défaut
onPassageSelected: null,
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
},
);
},
),
),
),
// 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: const [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);
},
),
],
),
],
),
),
],
),