/// 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 '../../../../core/widgets/error_widget.dart'; import '../../../../core/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 'members_page_connected.dart'; final _getIt = GetIt.instance; /// Wrapper qui fournit le BLoC à la page des membres class MembersPageWrapper extends StatelessWidget { const MembersPageWrapper({super.key}); @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(const LoadMembres()); return bloc; }, child: const MembersPageConnected(), ); } } /// 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 { const MembersPageConnected({super.key}); @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { // 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(const LoadMembres()); }, ), ), ); } // Message de succès après création if (state is MembresLoaded && state.membres.isNotEmpty) { // Note: On pourrait ajouter un flag dans le state pour savoir si c'est après une création // Pour l'instant, on ne fait rien ici } }, 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) { // TODO: Afficher l'UI avec un indicateur de rafraîchissement 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) { AppLogger.userAction('Load page', data: {'page': newPage}); context.read().add(LoadMembres(page: newPage)); }, onRefresh: () { AppLogger.userAction('Refresh membres'); context.read().add(const LoadMembres(refresh: true)); }, ); } // É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(const LoadMembres()); }, ), ); } // É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(const LoadMembres()); }, ), ); } // É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': DateTime.now(), // TODO: Ajouter ce champ au modèle 'avatar': membre.photo, 'phone': membre.telephone ?? '', 'department': membre.profession ?? '', 'location': '${membre.ville ?? ''}, ${membre.pays ?? ''}', 'permissions': 15, // TODO: Calculer depuis les permissions réelles 'contributionScore': 0, // TODO: Ajouter ce champ au modèle 'eventsAttended': 0, // TODO: Ajouter ce champ au modèle 'projectsInvolved': 0, // TODO: Ajouter ce champ au modèle // 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'; } } }