/// Wrapper BLoC pour la page des membres /// /// Ce fichier enveloppe la MembersPage existante avec le MembresBloc /// pour connecter l'UI riche existante à l'API backend réelle. library members_page_wrapper; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import '../../../../shared/widgets/error_widget.dart'; import '../../../../shared/widgets/loading_widget.dart'; import '../../../../core/utils/logger.dart'; import '../../bloc/membres_bloc.dart'; import '../../bloc/membres_event.dart'; import '../../bloc/membres_state.dart'; import '../../data/models/membre_complete_model.dart'; import '../widgets/add_member_dialog.dart'; import 'members_page_connected.dart'; final _getIt = GetIt.instance; /// Wrapper qui fournit le BLoC à la page des membres class MembersPageWrapper extends StatelessWidget { final String? organisationId; const MembersPageWrapper({super.key, this.organisationId}); @override Widget build(BuildContext context) { AppLogger.info('MembersPageWrapper: Création du BlocProvider'); return BlocProvider( create: (context) { AppLogger.info('MembresPageWrapper: Initialisation du MembresBloc'); final bloc = _getIt(); // Charger les membres au démarrage bloc.add(LoadMembres(organisationId: organisationId)); return bloc; }, child: MembersPageConnected(organisationId: organisationId), ); } } /// Page des membres connectée au BLoC /// /// Cette page gère les états du BLoC et affiche l'UI appropriée class MembersPageConnected extends StatelessWidget { final String? organisationId; const MembersPageConnected({super.key, this.organisationId}); @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { // Après création : afficher le mot de passe temporaire si disponible, puis recharger if (state is MembreCreated) { final motDePasse = state.membre.motDePasseTemporaire; if (motDePasse != null && motDePasse.isNotEmpty) { showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( title: const Text('Compte créé avec succès'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Le membre ${state.membre.nomComplet} a été créé.'), const SizedBox(height: 12), const Text( 'Mot de passe temporaire :', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), SelectableText( motDePasse, style: const TextStyle( fontSize: 18, fontFamily: 'monospace', letterSpacing: 2, ), ), const SizedBox(height: 12), const Text( 'Communiquez ce mot de passe au membre. Il devra le changer à sa première connexion.', style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), actions: [ ElevatedButton( onPressed: () => Navigator.of(_).pop(), child: const Text('OK'), ), ], ), ); } context.read().add(LoadMembres(refresh: true, organisationId: organisationId)); } // Gestion des erreurs avec SnackBar if (state is MembresError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: Colors.red, duration: const Duration(seconds: 4), action: SnackBarAction( label: 'Réessayer', textColor: Colors.white, onPressed: () { context.read().add(LoadMembres(organisationId: organisationId)); }, ), ), ); } }, child: BlocBuilder( builder: (context, state) { AppLogger.blocState('MembresBloc', state.runtimeType.toString()); // État initial if (state is MembresInitial) { return Container( color: const Color(0xFFF8F9FA), child: const Center( child: AppLoadingWidget(message: 'Initialisation...'), ), ); } // État de chargement if (state is MembresLoading) { return Container( color: const Color(0xFFF8F9FA), child: const Center( child: AppLoadingWidget(message: 'Chargement des membres...'), ), ); } // État de rafraîchissement (afficher l'UI avec un indicateur) if (state is MembresRefreshing) { // Affiche un indicateur pendant le rafraîchissement return Container( color: const Color(0xFFF8F9FA), child: const Center( child: AppLoadingWidget(message: 'Actualisation...'), ), ); } // Après création : on recharge la liste (listener a dispatché LoadMembres) if (state is MembreCreated) { return Container( color: const Color(0xFFF8F9FA), child: const Center( child: AppLoadingWidget(message: 'Actualisation...'), ), ); } // État chargé avec succès if (state is MembresLoaded) { final membres = state.membres; AppLogger.info('MembresPageConnected: ${membres.length} membres chargés'); // Convertir les membres en format Map pour l'UI existante final membersData = _convertMembersToMapList(membres); return MembersPageWithDataAndPagination( members: membersData, totalCount: state.totalElements, currentPage: state.currentPage, totalPages: state.totalPages, onPageChanged: (newPage, recherche) { AppLogger.userAction('Load page', data: {'page': newPage}); context.read().add(LoadMembres(page: newPage, recherche: recherche, organisationId: organisationId)); }, onRefresh: () { AppLogger.userAction('Refresh membres'); context.read().add(LoadMembres(refresh: true, organisationId: organisationId)); }, onSearch: (query) { context.read().add(LoadMembres(page: 0, recherche: query, organisationId: organisationId)); }, onAddMember: () async { final bloc = context.read(); await showDialog( context: context, builder: (_) => BlocProvider.value( value: bloc, child: const AddMemberDialog(), ), ); }, ); } // État d'erreur réseau if (state is MembresNetworkError) { AppLogger.error('MembersPageConnected: Erreur réseau', error: state.message); return Container( color: const Color(0xFFF8F9FA), child: NetworkErrorWidget( onRetry: () { AppLogger.userAction('Retry load membres after network error'); context.read().add(LoadMembres(organisationId: organisationId)); }, ), ); } // État d'erreur générale if (state is MembresError) { AppLogger.error('MembersPageConnected: Erreur', error: state.message); return Container( color: const Color(0xFFF8F9FA), child: AppErrorWidget( message: state.message, onRetry: () { AppLogger.userAction('Retry load membres after error'); context.read().add(LoadMembres(organisationId: organisationId)); }, ), ); } // État par défaut (ne devrait jamais arriver) AppLogger.warning('MembersPageConnected: État non géré: ${state.runtimeType}'); return Container( color: const Color(0xFFF8F9FA), child: const Center( child: AppLoadingWidget(message: 'Chargement...'), ), ); }, ), ); } /// Convertit une liste de MembreCompletModel en List> /// pour compatibilité avec l'UI existante List> _convertMembersToMapList(List membres) { return membres.map((membre) => _convertMembreToMap(membre)).toList(); } /// Convertit un MembreCompletModel en Map Map _convertMembreToMap(MembreCompletModel membre) { return { 'id': membre.id ?? '', 'name': membre.nomComplet, 'email': membre.email, 'role': _mapRoleToString(membre.role), 'status': _mapStatutToString(membre.statut), 'joinDate': membre.dateAdhesion, 'lastActivity': membre.derniereActivite ?? DateTime.now(), 'avatar': membre.photo, 'phone': membre.telephone ?? '', 'department': membre.profession ?? '', 'location': '${membre.ville ?? ''}, ${membre.pays ?? ''}', 'permissions': 15, // Valeurs par défaut tant que l'API ne fournit pas permissions 'contributionScore': 0, // Valeurs par défaut tant que l'API ne fournit pas contributionScore 'eventsAttended': membre.nombreEvenementsParticipes, 'projectsInvolved': 0, // Valeurs par défaut tant que l'API ne fournit pas projectsInvolved // Champs supplémentaires du modèle 'prenom': membre.prenom, 'nom': membre.nom, 'dateNaissance': membre.dateNaissance, 'genre': membre.genre?.name, 'adresse': membre.adresse, 'ville': membre.ville, 'codePostal': membre.codePostal, 'region': membre.region, 'pays': membre.pays, 'profession': membre.profession, 'nationalite': membre.nationalite, 'organisationId': membre.organisationId, 'membreBureau': membre.membreBureau, 'responsable': membre.responsable, 'fonctionBureau': membre.fonctionBureau, 'numeroMembre': membre.numeroMembre, 'cotisationAJour': membre.cotisationAJour, // Propriétés calculées 'initiales': membre.initiales, 'age': membre.age, 'estActifEtAJour': membre.estActifEtAJour, }; } /// Mappe le rôle du modèle vers une chaîne lisible String _mapRoleToString(String? role) { if (role == null) return 'Membre Simple'; switch (role.toLowerCase()) { case 'superadmin': return 'Super Administrateur'; case 'orgadmin': return 'Administrateur Org'; case 'moderator': return 'Modérateur'; case 'activemember': return 'Membre Actif'; case 'simplemember': return 'Membre Simple'; case 'visitor': return 'Visiteur'; default: return role; } } /// Mappe le statut du modèle vers une chaîne lisible String _mapStatutToString(StatutMembre? statut) { if (statut == null) return 'Actif'; switch (statut) { case StatutMembre.actif: return 'Actif'; case StatutMembre.inactif: return 'Inactif'; case StatutMembre.suspendu: return 'Suspendu'; case StatutMembre.enAttente: return 'En attente'; } } }