feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
648
app/lib/presentation/widgets/grouped_passages_dialog.dart
Normal file
648
app/lib/presentation/widgets/grouped_passages_dialog.dart
Normal file
@@ -0,0 +1,648 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:hive_flutter/hive_flutter.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';
|
||||
|
||||
/// Dialogue pour afficher les passages groupés d'un immeuble (fkHabitat=2)
|
||||
class GroupedPassagesDialog extends StatelessWidget {
|
||||
final PassageModel referencePassage;
|
||||
final bool isAdmin;
|
||||
|
||||
const GroupedPassagesDialog({
|
||||
super.key,
|
||||
required this.referencePassage,
|
||||
this.isAdmin = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Construire l'adresse complète
|
||||
final String adresse =
|
||||
'${referencePassage.numero} ${referencePassage.rueBis} ${referencePassage.rue}'
|
||||
.trim();
|
||||
final String ville = referencePassage.ville;
|
||||
final String residence = referencePassage.residence;
|
||||
|
||||
// Calculer les dimensions
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final dialogWidth = kIsWeb
|
||||
? 600.0 // Web : largeur fixe plus large
|
||||
: screenWidth * 0.9; // Mobile : 90% largeur
|
||||
final dialogHeight = screenHeight * 0.8; // 80% hauteur max
|
||||
|
||||
// Vérifier si l'utilisateur peut supprimer
|
||||
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 Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Container(
|
||||
width: dialogWidth,
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: dialogHeight,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// En-tête avec adresse, ville, résidence et bouton X
|
||||
_buildHeader(context, adresse, ville, residence),
|
||||
|
||||
const Divider(height: 1),
|
||||
|
||||
// Liste des passages avec ValueListenableBuilder
|
||||
Flexible(
|
||||
child: ValueListenableBuilder<Box<PassageModel>>(
|
||||
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName)
|
||||
.listenable(),
|
||||
builder: (context, box, child) {
|
||||
// Filtrer les passages de la même adresse
|
||||
final passages = _filterPassagesByAddress(box);
|
||||
|
||||
if (passages.isEmpty) {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24.0),
|
||||
child: Text('Aucun passage trouvé'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: passages.length,
|
||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final passage = passages[index];
|
||||
return _buildPassageItem(context, passage, canDelete);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Construire l'en-tête avec adresse, ville, résidence et boutons
|
||||
Widget _buildHeader(
|
||||
BuildContext context, String adresse, String ville, String residence) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Adresse
|
||||
if (adresse.isNotEmpty)
|
||||
Text(
|
||||
adresse,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
// Ville
|
||||
if (ville.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.location_city, size: 16, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
ville,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
// Résidence
|
||||
if (residence.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.apartment, size: 16, color: Colors.grey[600]),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
residence,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// Bouton + pour ajouter un passage
|
||||
IconButton(
|
||||
onPressed: () => _showAddPassageDialog(context),
|
||||
icon: const Icon(Icons.add_circle, size: 28),
|
||||
tooltip: 'Ajouter un passage',
|
||||
color: Colors.green,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Bouton X pour fermer
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: 'Fermer',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Construire une ligne de passage
|
||||
Widget _buildPassageItem(
|
||||
BuildContext context, PassageModel passage, bool canDelete) {
|
||||
final int type = passage.fkType;
|
||||
|
||||
// Récupérer la couleur2 du type
|
||||
final Color typeColor =
|
||||
Color(AppKeys.typesPassages[type]?['couleur2'] ?? 0xFF9E9E9E);
|
||||
|
||||
// Niveau + Appt
|
||||
final String location = [
|
||||
if (passage.niveau.isNotEmpty) 'Niv. ${passage.niveau}',
|
||||
if (passage.appt.isNotEmpty) 'Appt ${passage.appt}',
|
||||
].join(', ');
|
||||
|
||||
// Calculer le montant et vérifier s'il est payé
|
||||
final amount = _parseAmount(passage.montant);
|
||||
final isPaid = amount > 0;
|
||||
final formattedAmount = '${amount.toStringAsFixed(2).replaceAll('.', ',')} €';
|
||||
|
||||
return ListTile(
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
onTap: () => _showEditDialog(context, passage),
|
||||
leading: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: typeColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
// Nom
|
||||
if (passage.name.isNotEmpty)
|
||||
Flexible(
|
||||
child: Text(
|
||||
passage.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'Sans nom',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: location.isNotEmpty || (isPaid && (type == 1 || type == 5))
|
||||
? _buildSubtitle(context, location, passage, isPaid, type, formattedAmount)
|
||||
: null,
|
||||
trailing: _buildTrailing(context, passage, canDelete),
|
||||
);
|
||||
}
|
||||
|
||||
/// Construire la ligne 2 (subtitle) avec Niveau/Appt + Badge montant
|
||||
Widget _buildSubtitle(
|
||||
BuildContext context,
|
||||
String location,
|
||||
PassageModel passage,
|
||||
bool isPaid,
|
||||
int type,
|
||||
String formattedAmount,
|
||||
) {
|
||||
return Row(
|
||||
children: [
|
||||
// Niveau + Appt
|
||||
if (location.isNotEmpty)
|
||||
Text(
|
||||
location,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
const Spacer(),
|
||||
// Badge montant (si > 0 et type 1 ou 5)
|
||||
if (isPaid && (type == 1 || type == 5)) ...[
|
||||
// Récupérer le type de règlement
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final typeReglement = passage.fkTypeReglement;
|
||||
final reglementInfo = AppKeys.typesReglements[typeReglement];
|
||||
final reglementIcon = reglementInfo?['icon_data'] as IconData? ?? Icons.help_outline;
|
||||
final reglementColor = Color(reglementInfo?['couleur'] as int? ?? 0xFF9E9E9E);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: reglementColor.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: reglementColor.withOpacity(0.4),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
reglementIcon,
|
||||
size: 12,
|
||||
color: reglementColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
formattedAmount,
|
||||
style: TextStyle(
|
||||
color: reglementColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Construire le trailing avec icône remarque et bouton delete (ligne 1)
|
||||
Widget? _buildTrailing(
|
||||
BuildContext context,
|
||||
PassageModel passage,
|
||||
bool canDelete,
|
||||
) {
|
||||
final List<Widget> trailingWidgets = [];
|
||||
|
||||
// Icône remarque (si passage.remarque non vide)
|
||||
if (passage.remarque.isNotEmpty) {
|
||||
trailingWidgets.add(
|
||||
Icon(
|
||||
Icons.comment_outlined,
|
||||
size: 16,
|
||||
color: Colors.orange[700],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Bouton delete
|
||||
if (canDelete) {
|
||||
if (trailingWidgets.isNotEmpty) {
|
||||
trailingWidgets.add(const SizedBox(width: 8));
|
||||
}
|
||||
trailingWidgets.add(
|
||||
IconButton(
|
||||
onPressed: () => _showDeleteDialog(context, passage),
|
||||
icon: const Icon(Icons.delete, size: 20),
|
||||
tooltip: 'Supprimer',
|
||||
padding: const EdgeInsets.all(8),
|
||||
constraints: const BoxConstraints(),
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Retourner null si aucun widget, sinon Row
|
||||
if (trailingWidgets.isEmpty) return null;
|
||||
if (trailingWidgets.length == 1) return trailingWidgets.first;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: trailingWidgets,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parser le montant depuis String vers double
|
||||
double _parseAmount(String montantStr) {
|
||||
if (montantStr.isEmpty) return 0.0;
|
||||
try {
|
||||
final cleaned = montantStr.replaceAll(',', '.');
|
||||
return double.tryParse(cleaned) ?? 0.0;
|
||||
} catch (e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Filtrer les passages par adresse et trier par niveau + appt
|
||||
List<PassageModel> _filterPassagesByAddress(Box<PassageModel> box) {
|
||||
// Clé d'adresse du passage de référence
|
||||
final referenceKey =
|
||||
'${referencePassage.numero}|${referencePassage.rueBis}|${referencePassage.rue}|${referencePassage.ville}';
|
||||
|
||||
// Filtrer les passages de la même adresse
|
||||
final passages = box.values.where((p) {
|
||||
final key = '${p.numero}|${p.rueBis}|${p.rue}|${p.ville}';
|
||||
return key == referenceKey && p.fkHabitat == 2;
|
||||
}).toList();
|
||||
|
||||
// Trier par niveau puis appt
|
||||
passages.sort((a, b) {
|
||||
// Convertir niveau en int pour tri numérique
|
||||
final nivA = int.tryParse(a.niveau) ?? 0;
|
||||
final nivB = int.tryParse(b.niveau) ?? 0;
|
||||
|
||||
if (nivA != nivB) {
|
||||
return nivA.compareTo(nivB);
|
||||
}
|
||||
|
||||
// Si même niveau, trier par appt
|
||||
final apptA = a.appt.toLowerCase();
|
||||
final apptB = b.appt.toLowerCase();
|
||||
return apptA.compareTo(apptB);
|
||||
});
|
||||
|
||||
return passages;
|
||||
}
|
||||
|
||||
/// Afficher le dialogue de modification
|
||||
void _showEditDialog(BuildContext context, PassageModel passage) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return PassageFormDialog(
|
||||
passage: passage,
|
||||
title: 'Modifier le passage',
|
||||
passageRepository: passageRepository,
|
||||
userRepository: userRepository,
|
||||
operationRepository: operationRepository,
|
||||
amicaleRepository: amicaleRepository,
|
||||
// Pas de callback onSuccess - ValueListenableBuilder gère la réactivité
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Afficher le dialogue d'ajout d'un passage pré-rempli
|
||||
void _showAddPassageDialog(BuildContext context) {
|
||||
// Créer un passage temporaire pré-rempli avec les infos de l'immeuble
|
||||
final newPassage = PassageModel(
|
||||
id: 0, // Nouveau passage
|
||||
fkOperation: referencePassage.fkOperation,
|
||||
fkSector: referencePassage.fkSector,
|
||||
fkUser: referencePassage.fkUser,
|
||||
fkType: 2, // Type "À finaliser" par défaut
|
||||
fkAdresse: referencePassage.fkAdresse,
|
||||
passedAt: DateTime.now(),
|
||||
numero: referencePassage.numero,
|
||||
rue: referencePassage.rue,
|
||||
rueBis: referencePassage.rueBis,
|
||||
ville: referencePassage.ville,
|
||||
residence: referencePassage.residence,
|
||||
fkHabitat: 2, // Appartement
|
||||
appt: '', // Vide pour saisie
|
||||
niveau: '', // Vide pour saisie
|
||||
gpsLat: referencePassage.gpsLat,
|
||||
gpsLng: referencePassage.gpsLng,
|
||||
nomRecu: '',
|
||||
remarque: '',
|
||||
montant: '0.00',
|
||||
fkTypeReglement: 4,
|
||||
emailErreur: '',
|
||||
nbPassages: 1,
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
stripePaymentId: null,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return PassageFormDialog(
|
||||
passage: newPassage,
|
||||
title: 'Nouveau passage dans l\'immeuble',
|
||||
passageRepository: passageRepository,
|
||||
userRepository: userRepository,
|
||||
operationRepository: operationRepository,
|
||||
amicaleRepository: amicaleRepository,
|
||||
// Pas de callback onSuccess - ValueListenableBuilder gère la réactivité
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Afficher le dialogue de suppression
|
||||
void _showDeleteDialog(BuildContext context, PassageModel passage) {
|
||||
// Réutiliser le même système de confirmation que PassageMapDialog
|
||||
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.niveau.isNotEmpty || passage.appt.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
[
|
||||
if (passage.niveau.isNotEmpty) 'Niveau ${passage.niveau}',
|
||||
if (passage.appt.isNotEmpty) 'Appt ${passage.appt}',
|
||||
].join(', '),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (passage.name.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
passage.name,
|
||||
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, passage);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Supprimer définitivement'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Supprimer un passage
|
||||
Future<void> _deletePassage(BuildContext context, PassageModel passage) 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');
|
||||
// Pas de callback - ValueListenableBuilder rafraîchit automatiquement
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user