import 'package:flutter/material.dart'; import 'package:geosector_app/core/services/api_service.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/data/models/amicale_model.dart'; import 'package:geosector_app/core/data/models/membre_model.dart'; import 'package:geosector_app/core/data/models/user_model.dart'; import 'package:geosector_app/presentation/widgets/user_form_dialog.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/core/repositories/amicale_repository.dart'; 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/repositories/passage_repository.dart'; import 'package:geosector_app/core/repositories/operation_repository.dart'; /// Page d'administration de l'amicale et des membres /// Cette page est intégrée dans le tableau de bord administrateur 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 State createState() => _AdminAmicalePageState(); } class _AdminAmicalePageState extends State { UserModel? _currentUser; String? _errorMessage; int? _currentOperationId; @override void initState() { super.initState(); _loadCurrentUser(); _loadCurrentOperation(); } void _loadCurrentUser() { final currentUser = widget.userRepository.getCurrentUser(); debugPrint( '🔍 _loadCurrentUser - Utilisateur: ${currentUser?.username} (ID: ${currentUser?.id})'); debugPrint('🔍 _loadCurrentUser - fkEntite: ${currentUser?.fkEntite}'); if (currentUser == null) { setState(() { _errorMessage = 'Utilisateur non connecté'; }); return; } if (currentUser.fkEntite == null) { setState(() { _errorMessage = 'Utilisateur non associé à une amicale'; }); return; } // Vérifier immédiatement si l'amicale existe final amicale = widget.amicaleRepository.getUserAmicale(currentUser.fkEntite!); debugPrint( '🔍 Amicale trouvée dans le repository: ${amicale?.name ?? 'null'}'); setState(() { _currentUser = currentUser; _errorMessage = null; }); } // 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) { // Récupérer l'amicale actuelle final amicale = widget.amicaleRepository.getUserAmicale(_currentUser!.fkEntite!); showDialog( context: context, builder: (context) => UserFormDialog( title: 'Modifier le membre', user: membre.toUserModel(), showRoleSelector: true, showActiveCheckbox: true, // Activer la checkbox allowUsernameEdit: amicale?.chkUsernameManuel == true, // Conditionnel selon amicale amicale: amicale, // Passer l'amicale isAdmin: true, // Car on est dans la page admin availableRoles: const [ RoleOption( value: 1, label: 'Membre', description: 'Peut consulter et distribuer dans ses secteurs', ), RoleOption( value: 2, label: 'Administrateur', description: 'Peut gérer l\'amicale et ses membres', ), ], onSubmit: (updatedUser, {String? password}) async { try { // Convertir le UserModel mis à jour vers MembreModel final updatedMembre = MembreModel.fromUserModel(updatedUser, membre); // Utiliser directement updateMembre qui passe par l'API /users final success = await widget.membreRepository.updateMembre( updatedMembre, password: password, ); if (success && mounted) { Navigator.of(context).pop(); ApiException.showSuccess(context, 'Membre ${updatedMembre.firstName} ${updatedMembre.name} mis à jour'); } } catch (e) { debugPrint('❌ Erreur mise à jour membre: $e'); if (mounted) { ApiException.showError(context, e); } } }, ), ); } void _handleResetPassword(MembreModel membre) async { // Afficher un dialog de confirmation final bool? confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Row( children: [ Icon(Icons.lock_reset, color: Colors.blue), SizedBox(width: 8), Text('Réinitialiser le mot de passe'), ], ), content: Text( 'Voulez-vous réinitialiser le mot de passe de ${membre.firstName} ${membre.name} ?\n\n' 'Un email sera envoyé à l\'utilisateur avec les instructions de réinitialisation.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, ), child: const Text('Réinitialiser'), ), ], ), ); if (confirm != true) return; try { debugPrint('🔐 Réinitialisation du mot de passe pour: ${membre.firstName} ${membre.name} (ID: ${membre.id})'); final success = await widget.membreRepository.resetMemberPassword(membre.id); if (success && mounted) { ApiException.showSuccess( context, 'Mot de passe réinitialisé avec succès. Un email a été envoyé à ${membre.email}', ); } else if (mounted) { ApiException.showError( context, Exception('Erreur lors de la réinitialisation du mot de passe'), ); } } catch (e) { debugPrint('❌ Erreur réinitialisation mot de passe: $e'); if (mounted) { ApiException.showError(context, e); } } } 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\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(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () async { Navigator.of(context).pop(); // Suppression simple : pas de passages, donc pas de paramètres await _deleteMemberAPI(membre.id, 0, hasPassages: false); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('Supprimer'), ), ], ), ); } void _showDeleteMemberWithPassagesDialog( MembreModel membre, int totalPassages, List 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: 4), const Text( '* Cela peut concerner aussi les anciennes opérations s\'il avait des passages affectés', style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic), ), const SizedBox(height: 8), DropdownButtonFormField( 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 _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 _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'); } } catch (e) { if (mounted) { ApiException.showError(context, e); } } } void _handleAddMembre() { if (_currentUser?.fkEntite == null) return; // Récupérer l'amicale actuelle final amicale = widget.amicaleRepository.getUserAmicale(_currentUser!.fkEntite!); // Créer un UserModel vide avec les valeurs par défaut final newUser = UserModel( id: 0, // ID temporaire pour nouveau membre username: '', firstName: '', name: '', sectName: '', phone: '', mobile: '', email: '', fkTitre: 1, // Par défaut M. fkEntite: _currentUser!.fkEntite!, // Association à l'amicale courante role: 1, // Par défaut membre isActive: true, // Par défaut actif createdAt: DateTime.now(), lastSyncedAt: DateTime.now(), ); showDialog( context: context, builder: (context) => UserFormDialog( title: 'Ajouter un nouveau membre', user: newUser, showRoleSelector: true, showActiveCheckbox: true, allowUsernameEdit: amicale?.chkUsernameManuel == true, // Conditionnel selon amicale amicale: amicale, // Passer l'amicale isAdmin: true, // Car on est dans la page admin availableRoles: const [ RoleOption( value: 1, label: 'Membre', description: 'Peut consulter et distribuer dans ses secteurs', ), RoleOption( value: 2, label: 'Administrateur', description: 'Peut gérer l\'amicale et ses membres', ), ], onSubmit: (newUserData, {String? password}) async { try { // Créer un nouveau MembreModel directement final newMembre = MembreModel( id: 0, // L'API assignera un vrai ID username: newUserData.username, firstName: newUserData.firstName, name: newUserData.name, sectName: newUserData.sectName, phone: newUserData.phone, mobile: newUserData.mobile, email: newUserData.email, fkTitre: newUserData.fkTitre, fkEntite: newUserData.fkEntite!, role: newUserData.role, isActive: newUserData.isActive, dateNaissance: newUserData.dateNaissance, dateEmbauche: newUserData.dateEmbauche, createdAt: DateTime.now(), ); // Créer le membre via l'API (retourne maintenant le membre créé) final createdMembre = await widget.membreRepository.createMembre( newMembre, password: password, ); if (createdMembre != null && mounted) { // Fermer le dialog Navigator.of(context).pop(); // 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); } } }, ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return SafeArea( child: // Contenu principal Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Titre de la page Text( 'Mon amicale et ses membres', style: theme.textTheme.headlineMedium?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), // Message d'erreur si présent if (_errorMessage != null) Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.withOpacity(0.3)), ), child: Row( children: [ const Icon(Icons.error_outline, color: Colors.red), const SizedBox(width: 12), Expanded( child: Text( _errorMessage!, style: const TextStyle(color: Colors.red), ), ), ], ), ), // Contenu principal avec ValueListenableBuilder if (_currentUser != null && _currentUser!.fkEntite != null) Expanded( child: ValueListenableBuilder>( valueListenable: widget.amicaleRepository.getAmicalesBox().listenable(), builder: (context, amicalesBox, child) { debugPrint( '🔍 AmicalesBox - Nombre d\'amicales: ${amicalesBox.length}'); debugPrint( '🔍 AmicalesBox - Clés disponibles: ${amicalesBox.keys.toList()}'); debugPrint( '🔍 Recherche amicale avec fkEntite: ${_currentUser!.fkEntite}'); final amicale = amicalesBox.get(_currentUser!.fkEntite!); debugPrint( '🔍 Amicale récupérée: ${amicale?.name ?? 'AUCUNE'}'); if (amicale == null) { // Ajouter plus d'informations de debug debugPrint('❌ PROBLÈME: Amicale non trouvée'); debugPrint( '❌ fkEntite recherché: ${_currentUser!.fkEntite}'); debugPrint( '❌ Contenu de la box: ${amicalesBox.values.map((a) => '${a.id}: ${a.name}').join(', ')}'); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.business_outlined, size: 64, color: theme.colorScheme.primary.withOpacity(0.7), ), const SizedBox(height: 16), Text( 'Amicale non trouvée', style: theme.textTheme.titleLarge, ), const SizedBox(height: 8), Text( 'L\'amicale associée à votre compte n\'existe plus.\nfkEntite: ${_currentUser!.fkEntite}', textAlign: TextAlign.center, style: theme.textTheme.bodyLarge, ), ], ), ); } return ValueListenableBuilder>( valueListenable: widget.membreRepository.getMembresBox().listenable(), builder: (context, membresBox, child) { // Filtrer les membres par amicale // Note: Il faudra ajouter le champ fkEntite au modèle MembreModel final membres = membresBox.values .where((membre) => membre.fkEntite == _currentUser!.fkEntite) .toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section Amicale Text( 'Informations de l\'amicale', style: theme.textTheme.titleLarge?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), // Tableau Amicale Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: AmicaleTableWidget( amicales: [amicale], onEdit: null, onDelete: null, amicaleRepository: widget.amicaleRepository, userRepository: widget.userRepository, apiService: ApiService.instance, showActionsColumn: false, ), ), const SizedBox(height: 32), // Section Membres Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Membres de l\'amicale (${membres.length})', style: theme.textTheme.titleLarge?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, ), ), ElevatedButton.icon( onPressed: _handleAddMembre, icon: const Icon(Icons.add), label: const Text('Ajouter un membre'), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: Colors.white, ), ), ], ), const SizedBox(height: 16), // Tableau Membres Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: MembreTableWidget( membres: membres, onEdit: _handleEditMembre, onDelete: _handleDeleteMembre, onResetPassword: _handleResetPassword, membreRepository: widget.membreRepository, ), ), ), ], ); }, ); }, ), ), // Message si pas d'utilisateur connecté if (_currentUser == null) const Expanded( child: Center( child: CircularProgressIndicator(), ), ), ], ), ), ); } }