feat: Gestion des secteurs et migration v3.0.4+304

- Ajout système complet de gestion des secteurs avec contours géographiques
- Import des contours départementaux depuis GeoJSON
- API REST pour la gestion des secteurs (/api/sectors)
- Service de géolocalisation pour déterminer les secteurs
- Migration base de données avec tables x_departements_contours et sectors_adresses
- Interface Flutter pour visualisation et gestion des secteurs
- Ajout thème sombre dans l'application
- Corrections diverses et optimisations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-08-07 11:01:45 +02:00
parent 3bbc599ab4
commit 1018b86537
620 changed files with 120502 additions and 91396 deletions

100
app/lib/presentation/user/user_history_page.dart Normal file → Executable file
View File

@@ -1,10 +1,8 @@
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
// 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 {
@@ -71,46 +69,52 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// Afficher la plage de dates pour le débogage
if (filtered.isNotEmpty) {
// Trier par date pour trouver min et max
final sortedByDate = List<PassageModel>.from(filtered);
sortedByDate.sort((a, b) => a.passedAt.compareTo(b.passedAt));
// Trier par date pour trouver min et max (exclure les passages sans date)
final sortedByDate =
List<PassageModel>.from(filtered.where((p) => p.passedAt != null));
if (sortedByDate.isNotEmpty) {
sortedByDate.sort((a, b) => a.passedAt!.compareTo(b.passedAt!));
final DateTime minDate = sortedByDate.first.passedAt;
final DateTime maxDate = sortedByDate.last.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];
// Log détaillé pour débogage
debugPrint(
'ID: ${p.id}, Type: ${p.fkType}, Date: ${p.passedAt}, Adresse: ${p.rue}');
}
'Plage de dates des passages: ${minDate.toString()} à ${maxDate.toString()}');
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}');
}
// 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}');
}
// Vérifier la distribution des passages par mois
final Map<String, int> 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--- 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}');
}
debugPrint('\n--- DISTRIBUTION PAR MOIS ---');
final sortedMonths = monthCount.keys.toList()..sort();
for (var month in sortedMonths) {
debugPrint('$month: ${monthCount[month]} passages');
// Vérifier la distribution des passages par mois
final Map<String, int> monthCount = {};
for (var passage in filtered) {
// Ignorer les passages sans date
if (passage.passedAt != null) {
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');
}
}
}
@@ -120,9 +124,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
try {
final Map<String, dynamic> passageMap =
_convertPassageModelToMap(passage);
if (passageMap != null) {
passagesMap.add(passageMap);
}
passagesMap.add(passageMap);
} catch (e) {
debugPrint('Erreur lors de la conversion du passage en map: $e');
// Ignorer ce passage et continuer
@@ -195,7 +197,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// Récupérer la date avec gestion d'erreur
DateTime date;
try {
date = passage.passedAt;
date = passage.passedAt ?? DateTime.now();
} catch (e) {
debugPrint('Erreur lors de la récupération de la date: $e');
date = DateTime.now();
@@ -353,7 +355,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Détails du passage'),
title: const Text('Détails du passage'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -379,7 +381,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Fermer'),
child: const Text('Fermer'),
),
if (passage['hasReceipt'] == true)
TextButton(
@@ -387,14 +389,14 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
Navigator.of(context).pop();
_showReceipt(passage);
},
child: Text('Voir le reçu'),
child: const Text('Voir le reçu'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
_editPassage(passage);
},
child: Text('Modifier'),
child: const Text('Modifier'),
),
],
),
@@ -427,11 +429,11 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
SizedBox(
width: 100,
child: Text('$label:',
style: TextStyle(fontWeight: FontWeight.bold))),
style: const TextStyle(fontWeight: FontWeight.bold))),
Expanded(
child: Text(
value,
style: isError ? TextStyle(color: Colors.red) : null,
style: isError ? const TextStyle(color: Colors.red) : null,
),
),
],
@@ -440,7 +442,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
}
// Variable pour gérer la recherche
String _searchQuery = '';
final String _searchQuery = '';
@override
Widget build(BuildContext context) {
@@ -533,7 +535,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'Tous', // Toujours commencer avec 'Tous' pour voir tous les types
initialPaymentFilter: 'Tous',
// Exclure les passages de type 2 (À finaliser)
excludePassageTypes: [2],
excludePassageTypes: const [2],
// Filtrer par utilisateur courant
filterByUserId: userRepository.getCurrentUser()?.id,
// Désactiver les filtres de date implicites