On release/v3.1.4: Sauvegarde temporaire pour changement de branche
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_flutter/hive_flutter.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';
|
||||
@@ -36,6 +37,14 @@ class DotsPainter extends CustomPainter {
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
// 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 AdminHistoryPage extends StatefulWidget {
|
||||
const AdminHistoryPage({super.key});
|
||||
|
||||
@@ -52,6 +61,9 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
String selectedPaymentMethod = 'Tous';
|
||||
String selectedPeriod = 'Tous'; // Période par défaut
|
||||
DateTimeRange? selectedDateRange;
|
||||
|
||||
// État du tri actuel
|
||||
PassageSortType _currentSort = PassageSortType.dateDesc;
|
||||
|
||||
// Contrôleur pour la recherche
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
@@ -215,10 +227,10 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Méthode pour appliquer tous les filtres
|
||||
List<Map<String, dynamic>> _getFilteredPassages() {
|
||||
// Nouvelle méthode pour filtrer une liste de passages déjà formatés
|
||||
List<Map<String, dynamic>> _getFilteredPassagesFromList(List<Map<String, dynamic>> passages) {
|
||||
try {
|
||||
var filtered = _formattedPassages.where((passage) {
|
||||
var filtered = passages.where((passage) {
|
||||
try {
|
||||
// Ne plus exclure automatiquement les passages de type 2
|
||||
// car on propose maintenant un filtre par type dans les "Filtres avancés"
|
||||
@@ -315,26 +327,107 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
}
|
||||
}).toList();
|
||||
|
||||
// Trier par date décroissante (plus récent en premier)
|
||||
filtered.sort((a, b) {
|
||||
try {
|
||||
final DateTime dateA = a['date'] as DateTime;
|
||||
final DateTime dateB = b['date'] as DateTime;
|
||||
return dateB.compareTo(dateA);
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
// Appliquer le tri sélectionné
|
||||
filtered = _sortPassages(filtered);
|
||||
|
||||
debugPrint(
|
||||
'Passages filtrés: ${filtered.length}/${_formattedPassages.length}');
|
||||
'Passages filtrés: ${filtered.length}/${passages.length}');
|
||||
return filtered;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur globale lors du filtrage: $e');
|
||||
return _formattedPassages;
|
||||
return passages;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Méthode pour appliquer tous les filtres (utilisée dans le build)
|
||||
List<Map<String, dynamic>> _getFilteredPassages() {
|
||||
return _getFilteredPassagesFromList(_formattedPassages);
|
||||
}
|
||||
|
||||
// Mettre à jour le filtre par secteur
|
||||
void _updateSectorFilter(String sectorName, int? sectorId) {
|
||||
setState(() {
|
||||
@@ -465,29 +558,135 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Widget de liste des passages avec hauteur fixe
|
||||
// Widget de liste des passages avec hauteur fixe et ValueListenableBuilder
|
||||
SizedBox(
|
||||
height: constraints.maxHeight * 0.7, // 70% de la hauteur disponible
|
||||
child: PassagesListWidget(
|
||||
passages: passages,
|
||||
showFilters:
|
||||
false, // Désactivé car les filtres sont maintenant dans la card "Filtres avancés"
|
||||
showSearch:
|
||||
false, // Désactivé car la recherche est maintenant dans la card "Filtres avancés"
|
||||
showActions: true,
|
||||
// Ne plus passer les filtres individuels car ils sont maintenant appliqués dans _getFilteredPassages()
|
||||
onPassageSelected: (passage) {
|
||||
_openPassageEditDialog(context, passage);
|
||||
},
|
||||
onReceiptView: (passage) {
|
||||
_showReceiptDialog(context, passage);
|
||||
},
|
||||
onDetailsView: (passage) {
|
||||
_showDetailsDialog(context, passage);
|
||||
},
|
||||
onPassageEdit: (passage) {
|
||||
// Action pour modifier le passage
|
||||
// Cette fonctionnalité pourrait être implémentée ultérieurement
|
||||
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();
|
||||
|
||||
// Convertir et formater les passages
|
||||
final formattedPassages = _formatPassagesForWidget(
|
||||
allPassages,
|
||||
_sectorRepository,
|
||||
_membreRepository
|
||||
);
|
||||
|
||||
// Appliquer les filtres
|
||||
final filteredPassages = _getFilteredPassagesFromList(formattedPassages);
|
||||
|
||||
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.of(context).colorScheme.primary
|
||||
: Theme.of(context).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.of(context).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.of(context).colorScheme.primary
|
||||
: Theme.of(context).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.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
passages: filteredPassages,
|
||||
showFilters: false,
|
||||
showSearch: false,
|
||||
showActions: true,
|
||||
// Le widget gère maintenant le flux conditionnel par défaut
|
||||
onPassageSelected: null,
|
||||
onReceiptView: (passage) {
|
||||
_showReceiptDialog(context, passage);
|
||||
},
|
||||
onDetailsView: (passage) {
|
||||
_showDetailsDialog(context, passage);
|
||||
},
|
||||
onPassageEdit: (passage) {
|
||||
// Action pour modifier le passage
|
||||
},
|
||||
onPassageDelete: (passage) {
|
||||
_showDeleteConfirmationDialog(passage);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -584,6 +783,9 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
// Déterminer si le passage a une erreur d'envoi de reçu
|
||||
final bool hasError = passage.emailErreur.isNotEmpty;
|
||||
|
||||
// Récupérer l'ID de l'utilisateur courant pour déterminer la propriété
|
||||
final currentUserId = _userRepository.getCurrentUser()?.id;
|
||||
|
||||
return {
|
||||
'id': passage.id,
|
||||
if (passage.passedAt != null) 'date': passage.passedAt!,
|
||||
@@ -618,6 +820,7 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
'lastSyncedAt': passage.lastSyncedAt,
|
||||
'isActive': passage.isActive,
|
||||
'isSynced': passage.isSynced,
|
||||
'isOwnedByCurrentUser': passage.fkUser == currentUserId, // Ajout du champ pour le widget
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
@@ -653,6 +856,116 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// Afficher les détails avec option de modification
|
||||
void _showDetailsDialogWithEditOption(BuildContext context, Map<String, dynamic> passage, PassageModel passageModel) {
|
||||
final int passageId = passage['id'] as int;
|
||||
final DateTime date = passage['date'] as DateTime;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.info_outline, color: Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
Text('Détails du passage #$passageId'),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 500,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildDetailRow('Date',
|
||||
'${date.day}/${date.month}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}'),
|
||||
_buildDetailRow('Adresse', passage['address'] as String),
|
||||
_buildDetailRow('Secteur', passage['sector'] as String),
|
||||
_buildDetailRow('Collecteur', passage['user'] as String),
|
||||
_buildDetailRow(
|
||||
'Type',
|
||||
AppKeys.typesPassages[passage['type']]?['titre'] ??
|
||||
'Inconnu'),
|
||||
_buildDetailRow('Montant', '${passage['amount']} €'),
|
||||
_buildDetailRow(
|
||||
'Mode de paiement',
|
||||
AppKeys.typesReglements[passage['payment']]?['titre'] ??
|
||||
'Inconnu'),
|
||||
_buildDetailRow('Email', passage['email'] as String),
|
||||
_buildDetailRow(
|
||||
'Reçu envoyé', passage['hasReceipt'] ? 'Oui' : 'Non'),
|
||||
_buildDetailRow(
|
||||
'Erreur d\'envoi', passage['hasError'] ? 'Oui' : 'Non'),
|
||||
_buildDetailRow(
|
||||
'Notes',
|
||||
(passage['notes'] as String).isEmpty
|
||||
? '-'
|
||||
: passage['notes'] as String),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Historique des actions',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHistoryItem(
|
||||
date,
|
||||
passage['user'] as String,
|
||||
'Création du passage',
|
||||
),
|
||||
if (passage['hasReceipt'])
|
||||
_buildHistoryItem(
|
||||
date.add(const Duration(minutes: 5)),
|
||||
'Système',
|
||||
'Envoi du reçu par email',
|
||||
),
|
||||
if (passage['hasError'])
|
||||
_buildHistoryItem(
|
||||
date.add(const Duration(minutes: 6)),
|
||||
'Système',
|
||||
'Erreur lors de l\'envoi du reçu',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
// Fermer le dialog de détails
|
||||
Navigator.pop(dialogContext);
|
||||
// Ouvrir le formulaire de modification
|
||||
_showEditDialog(context, passageModel);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
label: const Text('Modifier'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Méthode pour conserver l'ancienne _showDetailsDialog pour les autres usages
|
||||
void _showDetailsDialog(BuildContext context, Map<String, dynamic> passage) {
|
||||
final int passageId = passage['id'] as int;
|
||||
final DateTime date = passage['date'] as DateTime;
|
||||
@@ -736,13 +1049,6 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Action pour modifier le passage
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('Modifier'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -753,9 +1059,10 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
try {
|
||||
debugPrint('=== DEBUT _openPassageEditDialog ===');
|
||||
|
||||
// Récupérer l'ID du passage
|
||||
// Récupérer l'ID et le type du passage
|
||||
final int passageId = passage['id'] as int;
|
||||
debugPrint('Recherche du passage ID: $passageId');
|
||||
final int passageType = passage['type'] as int? ?? 1;
|
||||
debugPrint('Passage ID: $passageId, Type: $passageType');
|
||||
|
||||
// Trouver le PassageModel original dans la liste
|
||||
final PassageModel? passageModel =
|
||||
@@ -771,23 +1078,17 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('Ouverture du dialog...');
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => PassageFormDialog(
|
||||
passage: passageModel,
|
||||
title: 'Modifier le passage',
|
||||
passageRepository: _passageRepository,
|
||||
userRepository: _userRepository,
|
||||
operationRepository: operationRepository,
|
||||
onSuccess: () {
|
||||
debugPrint('Dialog fermé avec succès');
|
||||
// Recharger les données après modification
|
||||
_loadPassages();
|
||||
},
|
||||
),
|
||||
);
|
||||
// Flux conditionnel selon le type de passage
|
||||
if (passageType == 2) {
|
||||
// Type 2 ("À finaliser") : Ouvrir directement le formulaire de modification
|
||||
debugPrint('Passage type 2 - Ouverture directe du formulaire');
|
||||
_showEditDialog(context, passageModel);
|
||||
} else {
|
||||
// Autres types : Afficher d'abord les détails avec option de modification
|
||||
debugPrint('Passage type $passageType - Affichage des détails d\'abord');
|
||||
_showDetailsDialogWithEditOption(context, passage, passageModel);
|
||||
}
|
||||
|
||||
debugPrint('=== FIN _openPassageEditDialog ===');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('=== ERREUR _openPassageEditDialog ===');
|
||||
@@ -797,13 +1098,35 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur lors de l\'ouverture du formulaire: $e'),
|
||||
content: Text('Erreur lors de l\'ouverture: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode extraite pour ouvrir le dialog de modification
|
||||
void _showEditDialog(BuildContext context, PassageModel passageModel) {
|
||||
debugPrint('Ouverture du formulaire de modification pour le passage ${passageModel.id}');
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => PassageFormDialog(
|
||||
passage: passageModel,
|
||||
title: 'Modifier le passage',
|
||||
passageRepository: _passageRepository,
|
||||
userRepository: _userRepository,
|
||||
operationRepository: operationRepository,
|
||||
onSuccess: () {
|
||||
debugPrint('Dialog fermé avec succès');
|
||||
// Recharger les données après modification
|
||||
_loadPassages();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value) {
|
||||
return Padding(
|
||||
@@ -1334,6 +1657,209 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
// Afficher le dialog de confirmation de suppression
|
||||
void _showDeleteConfirmationDialog(Map<String, dynamic> passage) {
|
||||
final TextEditingController confirmController = TextEditingController();
|
||||
|
||||
// Récupérer l'ID du passage et trouver le PassageModel original
|
||||
final int passageId = passage['id'] as int;
|
||||
final PassageModel? passageModel =
|
||||
_originalPassages.where((p) => p.id == passageId).firstOrNull;
|
||||
|
||||
if (passageModel == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Impossible de trouver le passage'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final String streetNumber = passageModel.numero ?? '';
|
||||
final String fullAddress = '${passageModel.numero ?? ''} ${passageModel.rueBis ?? ''} ${passageModel.rue ?? ''}'.trim();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: const Row(
|
||||
children: [
|
||||
Icon(Icons.warning, color: Colors.red, size: 28),
|
||||
SizedBox(width: 8),
|
||||
Text('Confirmation de suppression'),
|
||||
],
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'ATTENTION : Cette action est irréversible !',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Vous êtes sur le point de supprimer définitivement le passage :',
|
||||
style: TextStyle(color: Colors.grey[800]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
fullAddress.isEmpty ? 'Adresse inconnue' : fullAddress,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (passage['user'] != null)
|
||||
Text(
|
||||
'Collecteur: ${passage['user']}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
if (passage['date'] != null)
|
||||
Text(
|
||||
'Date: ${_formatDate(passage['date'] as DateTime)}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'Pour confirmer la suppression, veuillez saisir le numéro de rue de ce passage :',
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: confirmController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Numéro de rue',
|
||||
hintText: streetNumber.isNotEmpty ? 'Ex: $streetNumber' : 'Saisir le numéro',
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.home),
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
confirmController.dispose();
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
// Vérifier que le numéro saisi correspond
|
||||
final enteredNumber = confirmController.text.trim();
|
||||
if (enteredNumber.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Veuillez saisir le numéro de rue'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (streetNumber.isNotEmpty && enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Le numéro de rue ne correspond pas'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermer le dialog
|
||||
confirmController.dispose();
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
// Effectuer la suppression
|
||||
await _deletePassage(passageModel);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Supprimer définitivement'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Supprimer un passage
|
||||
Future<void> _deletePassage(PassageModel passage) async {
|
||||
try {
|
||||
// Appeler le repository pour supprimer via l'API
|
||||
final success = await _passageRepository.deletePassageViaApi(passage.id);
|
||||
|
||||
if (success && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Passage supprimé avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
// Pas besoin de recharger, le ValueListenableBuilder
|
||||
// se rafraîchira automatiquement après la suppression dans Hive
|
||||
} else if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Erreur lors de la suppression du passage'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur suppression passage: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Formater une date
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
|
||||
}
|
||||
|
||||
// Construction du filtre par mode de règlement
|
||||
Widget _buildPaymentFilter(ThemeData theme) {
|
||||
return Column(
|
||||
|
||||
Reference in New Issue
Block a user