Files
geo/app/lib/presentation/widgets/passage_map_dialog.dart
pierre 570a1fa1f0 feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API
- Mise à jour VERSION vers 3.3.4
- Optimisations et révisions architecture API (deploy-api.sh, scripts de migration)
- Ajout documentation Stripe Tap to Pay complète
- Migration vers polices Inter Variable pour Flutter
- Optimisations build Android et nettoyage fichiers temporaires
- Amélioration système de déploiement avec gestion backups
- Ajout scripts CRON et migrations base de données

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-05 20:11:15 +02:00

331 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/utils/api_exception.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
import 'package:geosector_app/app.dart';
class PassageMapDialog extends StatelessWidget {
final PassageModel passage;
final bool isAdmin;
final VoidCallback? onDeleted;
const PassageMapDialog({
super.key,
required this.passage,
this.isAdmin = false,
this.onDeleted,
});
@override
Widget build(BuildContext context) {
final int type = passage.fkType;
// Récupérer le type de passage
final String typePassage =
AppKeys.typesPassages[type]?['titre'] ?? 'Inconnu';
// Utiliser couleur2 pour le badge (couleur1 peut être blanche pour type 2)
final Color typeColor =
Color(AppKeys.typesPassages[type]?['couleur2'] ?? 0xFF9E9E9E);
// Construire l'adresse complète
final String adresse =
'${passage.numero} ${passage.rueBis} ${passage.rue}'.trim();
// Vérifier si l'utilisateur peut supprimer (admin ou user avec permission)
bool canDelete = isAdmin;
if (!isAdmin) {
try {
final amicale = CurrentAmicaleService.instance.currentAmicale;
if (amicale != null) {
canDelete = amicale.chkUserDeletePass == true;
}
} catch (e) {
debugPrint('Erreur lors de la vérification des permissions: $e');
}
}
return AlertDialog(
title: Row(
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: typeColor,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
typePassage,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Adresse
_buildInfoRow(Icons.location_on, 'Adresse',
adresse.isEmpty ? 'Non renseignée' : adresse),
// Ville
if (passage.ville.isNotEmpty)
_buildInfoRow(Icons.location_city, 'Ville', passage.ville),
],
),
actions: [
// Bouton de modification
TextButton.icon(
onPressed: () {
Navigator.of(context).pop();
_showEditDialog(context);
},
icon: const Icon(Icons.edit, size: 20),
label: const Text('Modifier'),
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
// Bouton de suppression si autorisé
if (canDelete)
TextButton.icon(
onPressed: () {
Navigator.of(context).pop();
_showDeleteConfirmationDialog(context);
},
icon: const Icon(Icons.delete, size: 20),
label: const Text('Supprimer'),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
),
// Bouton de fermeture
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'),
),
],
);
}
// Helper pour construire une ligne d'information
Widget _buildInfoRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 16, color: Colors.grey[600]),
const SizedBox(width: 8),
Expanded(
child: RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black87),
children: [
TextSpan(
text: '$label: ',
style: const TextStyle(fontWeight: FontWeight.w600),
),
TextSpan(text: value),
],
),
),
),
],
),
);
}
// Afficher le dialog de modification
void _showEditDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return PassageFormDialog(
passage: passage,
title: 'Modifier le passage',
passageRepository: passageRepository,
userRepository: userRepository,
operationRepository: operationRepository,
amicaleRepository: amicaleRepository,
onSuccess: () {
// Appeler le callback si fourni pour rafraîchir l'affichage
onDeleted?.call();
},
);
},
);
}
// Afficher le dialog de confirmation de suppression
void _showDeleteConfirmationDialog(BuildContext context) {
final TextEditingController confirmController = TextEditingController();
final String streetNumber = passage.numero;
final String fullAddress =
'${passage.numero} ${passage.rueBis} ${passage.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,
),
),
if (passage.ville.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
passage.ville,
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(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Supprimer définitivement'),
),
],
);
},
);
}
// Supprimer un passage
Future<void> _deletePassage(BuildContext context) async {
try {
// Appeler le repository pour supprimer via l'API
final success = await passageRepository.deletePassageViaApi(passage.id);
if (success && context.mounted) {
ApiException.showSuccess(context, 'Passage supprimé avec succès');
// Appeler le callback si fourni
onDeleted?.call();
} else if (context.mounted) {
ApiException.showError(
context, Exception('Erreur lors de la suppression'));
}
} catch (e) {
debugPrint('Erreur suppression passage: $e');
if (context.mounted) {
ApiException.showError(context, e);
}
}
}
}