Mise en place suppression membre

This commit is contained in:
d6soft
2025-06-12 16:39:44 +02:00
parent ace38d4025
commit 25c9d5874c
19 changed files with 63219 additions and 61871 deletions

View File

@@ -12,6 +12,9 @@ import 'package:geosector_app/core/repositories/membre_repository.dart';
import 'package:geosector_app/presentation/widgets/amicale_table_widget.dart';
import 'package:geosector_app/presentation/widgets/membre_table_widget.dart';
import 'package:geosector_app/core/utils/api_exception.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/repositories/passage_repository.dart';
import 'package:geosector_app/core/repositories/operation_repository.dart';
/// Class pour dessiner les petits points blancs sur le fond
class DotsPainter extends CustomPainter {
@@ -42,12 +45,16 @@ class AdminAmicalePage extends StatefulWidget {
final UserRepository userRepository;
final AmicaleRepository amicaleRepository;
final MembreRepository membreRepository;
final PassageRepository passageRepository;
final OperationRepository operationRepository;
const AdminAmicalePage({
super.key,
required this.userRepository,
required this.amicaleRepository,
required this.membreRepository,
required this.passageRepository,
required this.operationRepository,
});
@override
@@ -57,11 +64,13 @@ class AdminAmicalePage extends StatefulWidget {
class _AdminAmicalePageState extends State<AdminAmicalePage> {
UserModel? _currentUser;
String? _errorMessage;
int? _currentOperationId;
@override
void initState() {
super.initState();
_loadCurrentUser();
_loadCurrentOperation();
}
void _loadCurrentUser() {
@@ -94,6 +103,19 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
});
}
// Méthode pour charger l'opération courante
void _loadCurrentOperation() {
final currentOperation = widget.operationRepository.getCurrentOperation();
_currentOperationId = currentOperation?.id;
if (currentOperation != null) {
debugPrint('🎯 Opération courante: ${currentOperation.id} - ${currentOperation.name}');
debugPrint('📅 Période: ${currentOperation.dateDebut.toString().substring(0, 10)}${currentOperation.dateFin.toString().substring(0, 10)}');
} else {
debugPrint('⚠️ Aucune opération courante trouvée');
}
}
void _handleEditMembre(MembreModel membre) {
showDialog(
context: context,
@@ -141,12 +163,62 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
);
}
void _handleDeleteMembre(MembreModel membre) {
void _handleDeleteMembre(MembreModel membre) async {
try {
debugPrint('🗑️ Début suppression du membre: ${membre.firstName} ${membre.name} (ID: ${membre.id})');
// Vérifier qu'on a une opération courante
if (_currentOperationId == null) {
debugPrint('❌ Aucune opération courante');
ApiException.showError(context, Exception('Aucune opération active trouvée. Impossible de supprimer le membre.'));
return;
}
debugPrint('🎯 Opération courante: $_currentOperationId');
// Filtrer les passages par opération courante ET par utilisateur
final allUserPassages = widget.passageRepository.getPassagesByUser(membre.id);
debugPrint('📊 Total passages du membre: ${allUserPassages.length}');
final passagesRealises = allUserPassages.where((passage) => passage.fkOperation == _currentOperationId && passage.fkType != 2).toList();
final passagesAFinaliser = allUserPassages.where((passage) => passage.fkOperation == _currentOperationId && passage.fkType == 2).toList();
final totalPassages = passagesRealises.length + passagesAFinaliser.length;
debugPrint('🔍 Passages réalisés (opération $_currentOperationId): ${passagesRealises.length}');
debugPrint('🔍 Passages à finaliser (opération $_currentOperationId): ${passagesAFinaliser.length}');
debugPrint('🔍 Total passages pour l\'opération $_currentOperationId: $totalPassages');
// Récupérer les autres membres de l'amicale (pour le transfert)
final autresmembres = widget.membreRepository.getMembresByAmicale(_currentUser!.fkEntite!).where((m) => m.id != membre.id && m.isActive == true).toList();
debugPrint('👥 Autres membres disponibles: ${autresmembres.length}');
// Afficher le dialog de confirmation approprié
if (totalPassages > 0) {
debugPrint('➡️ Affichage dialog avec passages');
_showDeleteMemberWithPassagesDialog(membre, totalPassages, autresmembres);
} else {
debugPrint('➡️ Affichage dialog simple (pas de passages)');
_showSimpleDeleteConfirmation(membre);
}
} catch (e) {
debugPrint('❌ Erreur lors de la vérification des passages: $e');
if (mounted) {
ApiException.showError(context, e);
}
}
}
void _showSimpleDeleteConfirmation(MembreModel membre) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Confirmer la suppression'),
content: Text('Voulez-vous vraiment supprimer le membre ${membre.firstName} ${membre.name} ?\n\nCette action est irréversible.'),
content: Text('Voulez-vous vraiment supprimer le membre ${membre.firstName} ${membre.name} ?\n\n'
'Ce membre n\'a aucun passage enregistré pour l\'opération courante.\n'
'Cette action est irréversible.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
@@ -155,20 +227,8 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
ElevatedButton(
onPressed: () async {
Navigator.of(context).pop();
try {
// Utiliser la méthode qui passe par l'API
final success = await widget.membreRepository.deleteMembre(membre.id);
if (success && mounted) {
ApiException.showSuccess(context, 'Membre ${membre.firstName} ${membre.name} supprimé avec succès');
} else if (!success && mounted) {
ApiException.showError(context, Exception('Erreur lors de la suppression'));
}
} catch (e) {
if (mounted) {
ApiException.showError(context, e);
}
}
// Suppression simple : pas de passages, donc pas de paramètres
await _deleteMemberAPI(membre.id, 0, hasPassages: false);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
@@ -181,6 +241,245 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
);
}
void _showDeleteMemberWithPassagesDialog(
MembreModel membre,
int totalPassages,
List<MembreModel> autresmembres,
) {
int? selectedMemberForTransfer; // Déclarer la variable à l'extérieur du builder
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
title: const Row(
children: [
Icon(Icons.warning, color: Colors.orange),
SizedBox(width: 8),
Expanded(child: Text('Attention - Passages détectés')),
],
),
content: SizedBox(
width: 500,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Le membre ${membre.firstName} ${membre.name} a $totalPassages passage(s) enregistré(s).',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
// Section transfert
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📋 Transférer les passages',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue.shade700,
),
),
const SizedBox(height: 8),
Text(
'Sélectionnez un membre pour récupérer tous les passages ($totalPassages) :',
),
const SizedBox(height: 8),
DropdownButtonFormField<int>(
value: selectedMemberForTransfer,
decoration: const InputDecoration(
labelText: 'Membre destinataire',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
items: autresmembres
.map((m) => DropdownMenuItem(
value: m.id,
child: Text('${m.firstName} ${m.name}'),
))
.toList(),
onChanged: (value) {
setDialogState(() {
selectedMemberForTransfer = value;
});
debugPrint('✅ Membre destinataire sélectionné: $value');
},
),
// Indicateur visuel de sélection
if (selectedMemberForTransfer != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
const Icon(Icons.check_circle, color: Colors.green, size: 16),
const SizedBox(width: 8),
Text(
'Membre sélectionné',
style: TextStyle(
color: Colors.green.shade700,
fontSize: 12,
),
),
],
),
),
],
],
),
),
const SizedBox(height: 16),
// Option de désactivation
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'💡 Alternative recommandée',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.green.shade700,
),
),
const SizedBox(height: 8),
const Text(
'Vous pouvez désactiver ce membre au lieu de le supprimer. '
'Cela préservera l\'historique des passages tout en empêchant la connexion.',
),
],
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Annuler'),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await _deactivateMember(membre);
},
style: TextButton.styleFrom(
foregroundColor: Colors.green,
),
child: const Text('Désactiver seulement'),
),
ElevatedButton(
onPressed: selectedMemberForTransfer != null
? () async {
debugPrint('🗑️ Suppression avec transfert vers ID: $selectedMemberForTransfer');
Navigator.of(context).pop();
// Suppression avec passages : inclure les paramètres
await _deleteMemberAPI(membre.id, selectedMemberForTransfer!, hasPassages: true);
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: selectedMemberForTransfer != null ? Colors.red : null,
foregroundColor: Colors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (selectedMemberForTransfer != null) const Icon(Icons.delete_forever, size: 16),
if (selectedMemberForTransfer != null) const SizedBox(width: 4),
Text(
selectedMemberForTransfer != null ? 'Supprimer et transférer' : 'Sélectionner un membre',
),
],
),
),
],
);
},
),
);
}
// Méthode unifiée pour appeler l'API de suppression
Future<void> _deleteMemberAPI(int membreId, int transferToUserId, {bool hasPassages = false}) async {
try {
bool success;
if (hasPassages && transferToUserId > 0 && _currentOperationId != null) {
// Suppression avec transfert de passages (inclure operation_id)
debugPrint('🔄 Suppression avec transfert - Opération: $_currentOperationId, Vers: $transferToUserId');
success = await widget.membreRepository.deleteMembre(
membreId,
transferToUserId,
_currentOperationId,
);
} else {
// Suppression simple (pas de passages, donc pas de paramètres)
debugPrint('🗑️ Suppression simple - Aucun passage à transférer');
success = await widget.membreRepository.deleteMembre(membreId);
}
if (success && mounted) {
String message = 'Membre supprimé avec succès';
if (hasPassages && transferToUserId > 0) {
final transferMember = widget.membreRepository.getMembreById(transferToUserId);
final currentOperation = widget.operationRepository.getCurrentOperation();
message += '\nPassages de l\'opération "${currentOperation?.name}" transférés à ${transferMember?.firstName} ${transferMember?.name}';
}
ApiException.showSuccess(context, message);
} else if (mounted) {
ApiException.showError(context, Exception('Erreur lors de la suppression'));
}
} catch (e) {
debugPrint('❌ Erreur suppression membre: $e');
if (mounted) {
ApiException.showError(context, e);
}
}
}
Future<void> _deactivateMember(MembreModel membre) async {
try {
final updatedMember = membre.copyWith(isActive: false);
final success = await widget.membreRepository.updateMembre(updatedMember);
if (success && mounted) {
ApiException.showSuccess(context, 'Membre ${membre.firstName} ${membre.name} désactivé avec succès');
} else if (mounted) {
ApiException.showError(context, Exception('Erreur lors de la désactivation'));
}
} catch (e) {
if (mounted) {
ApiException.showError(context, e);
}
}
}
void _handleAddMembre() {
if (_currentUser?.fkEntite == null) return;
@@ -243,17 +542,23 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
createdAt: DateTime.now(),
);
// Créer le membre via l'API (retourne maintenant le membre créé)
final createdMembre = await widget.membreRepository.createMembre(newMembre);
if (createdMembre != null && mounted) {
// Fermer le dialog
Navigator.of(context).pop();
ApiException.showSuccess(context, 'Membre ${createdMembre.firstName} ${createdMembre.name} ajouté avec succès');
// Afficher le message de succès avec les informations du membre créé
ApiException.showSuccess(context, 'Membre ${createdMembre.firstName} ${createdMembre.name} ajouté avec succès (ID: ${createdMembre.id})');
} else if (mounted) {
// En cas d'échec, ne pas fermer le dialog pour permettre la correction
ApiException.showError(context, Exception('Erreur lors de la création du membre'));
}
} catch (e) {
debugPrint('❌ Erreur création membre: $e');
if (mounted) {
// En cas d'exception, ne pas fermer le dialog
ApiException.showError(context, e);
}
}