import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/models/evenement_model.dart'; import '../../../../shared/widgets/unified_components.dart'; import '../../../../shared/theme/app_theme.dart'; import '../bloc/evenement_bloc.dart'; import '../bloc/evenement_event.dart'; import '../bloc/evenement_state.dart'; import '../widgets/evenement_search_bar.dart'; import '../widgets/evenement_filter_chips.dart'; import 'evenement_detail_page.dart'; import 'evenement_create_page.dart'; /// Page des événements refactorisée avec l'architecture unifiée class EvenementsPageUnified extends StatelessWidget { const EvenementsPageUnified({super.key}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt() ..add(const LoadEvenementsAVenir()), child: const _EvenementsPageContent(), ); } } class _EvenementsPageContent extends StatefulWidget { const _EvenementsPageContent(); @override State<_EvenementsPageContent> createState() => _EvenementsPageContentState(); } class _EvenementsPageContentState extends State<_EvenementsPageContent> with TickerProviderStateMixin { late TabController _tabController; String _searchTerm = ''; TypeEvenement? _selectedType; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _tabController.addListener(_onTabChanged); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _onTabChanged() { if (_tabController.indexIsChanging) { _loadEventsForTab(_tabController.index); } } void _loadEventsForTab(int index) { context.read().add(const ResetEvenementState()); switch (index) { case 0: context.read().add(const LoadEvenementsAVenir()); break; case 1: context.read().add(const LoadEvenementsPublics()); break; case 2: context.read().add(const LoadEvenements()); break; } } void _onSearch(String terme) { setState(() { _searchTerm = terme; _selectedType = null; }); if (terme.isNotEmpty) { context.read().add( SearchEvenements(terme: terme, refresh: true), ); } else { context.read().add( const LoadEvenements(refresh: true), ); } } void _onFilterByType(TypeEvenement? type) { setState(() { _selectedType = type; _searchTerm = ''; }); if (type != null) { context.read().add( FilterEvenementsByType(type: type, refresh: true), ); } else { context.read().add( const LoadEvenements(refresh: true), ); } } void _onRefresh() { _loadEventsForTab(_tabController.index); } void _onLoadMore() { final state = context.read().state; if (state is EvenementLoaded && !state.hasReachedMax) { final nextPage = state.currentPage + 1; switch (_tabController.index) { case 0: context.read().add( LoadEvenementsAVenir(page: nextPage), ); break; case 1: context.read().add( LoadEvenementsPublics(page: nextPage), ); break; case 2: if (_searchTerm.isNotEmpty) { context.read().add( SearchEvenements(terme: _searchTerm, page: nextPage), ); } else if (_selectedType != null) { context.read().add( FilterEvenementsByType(type: _selectedType!, page: nextPage), ); } else { context.read().add( LoadEvenements(page: nextPage), ); } break; } } } @override Widget build(BuildContext context) { return UnifiedPageLayout( title: 'Événements', subtitle: 'Gestion des événements de l\'association', icon: Icons.event, iconColor: AppTheme.accentColor, scrollable: false, padding: EdgeInsets.zero, actions: [ IconButton( icon: const Icon(Icons.add), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const EvenementCreatePage(), ), ); }, ), ], body: Column( children: [ // En-tête avec KPI _buildKPISection(), // Onglets _buildTabBar(), // Contenu des onglets Expanded( child: TabBarView( controller: _tabController, children: [ _buildEventsList(showUpcoming: true), _buildEventsList(showPublic: true), _buildEventsListWithFilters(), ], ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const EvenementCreatePage(), ), ); }, backgroundColor: AppTheme.accentColor, child: const Icon(Icons.add), ), ); } Widget _buildKPISection() { return BlocBuilder( builder: (context, state) { final kpis = _buildKPIData(state); return Container( padding: const EdgeInsets.all(AppTheme.spacingMedium), child: UnifiedKPISection( kpis: kpis, crossAxisCount: 3, spacing: AppTheme.spacingSmall, ), ); }, ); } List _buildKPIData(EvenementState state) { int totalEvents = 0; int upcomingEvents = 0; int publicEvents = 0; if (state is EvenementLoaded) { totalEvents = state.evenements.length; upcomingEvents = state.evenements .where((e) => e.dateDebut.isAfter(DateTime.now())) .length; publicEvents = state.evenements .where((e) => e.typeEvenement == TypeEvenement.conference) .length; } return [ UnifiedKPIData( title: 'Total', value: totalEvents.toString(), icon: Icons.event, color: AppTheme.primaryColor, ), UnifiedKPIData( title: 'À venir', value: upcomingEvents.toString(), icon: Icons.schedule, color: AppTheme.accentColor, ), UnifiedKPIData( title: 'Publics', value: publicEvents.toString(), icon: Icons.public, color: AppTheme.successColor, ), ]; } Widget _buildTabBar() { return Container( color: Colors.white, child: TabBar( controller: _tabController, labelColor: AppTheme.primaryColor, unselectedLabelColor: AppTheme.textSecondary, indicatorColor: AppTheme.primaryColor, tabs: const [ Tab(text: 'À venir'), Tab(text: 'Publics'), Tab(text: 'Tous'), ], ), ); } Widget _buildEventsList({bool showUpcoming = false, bool showPublic = false}) { return BlocBuilder( builder: (context, state) { if (state is EvenementError) { return UnifiedPageLayout( title: '', showAppBar: false, errorMessage: state.message, onRefresh: _onRefresh, body: const SizedBox.shrink(), ); } final isLoading = state is EvenementLoading; final events = state is EvenementLoaded ? state.evenements : []; final hasReachedMax = state is EvenementLoaded ? state.hasReachedMax : false; return UnifiedListWidget( items: events, isLoading: isLoading, hasReachedMax: hasReachedMax, onLoadMore: _onLoadMore, onRefresh: () async => _onRefresh(), itemBuilder: (context, evenement, index) { return _buildEventCard(evenement); }, emptyWidget: _buildEmptyState(), ); }, ); } Widget _buildEventsListWithFilters() { return Column( children: [ // Barre de recherche et filtres Container( padding: const EdgeInsets.all(AppTheme.spacingMedium), color: Colors.white, child: Column( children: [ EvenementSearchBar( onSearch: _onSearch, initialValue: _searchTerm, ), const SizedBox(height: AppTheme.spacingSmall), EvenementFilterChips( selectedType: _selectedType, onTypeSelected: _onFilterByType, ), ], ), ), // Liste des événements Expanded( child: _buildEventsList(), ), ], ); } Widget _buildEventCard(EvenementModel evenement) { return UnifiedCard.listItem( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => EvenementDetailPage(evenement: evenement), ), ); }, child: _buildEventCardContent(evenement), ); } Widget _buildEventCardContent(EvenementModel evenement) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppTheme.accentColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.event, color: AppTheme.accentColor, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( evenement.titre, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( evenement.description ?? '', style: const TextStyle( fontSize: 14, color: AppTheme.textSecondary, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), const SizedBox(height: 12), Row( children: [ Icon( Icons.schedule, size: 16, color: AppTheme.textSecondary, ), const SizedBox(width: 4), Text( '${evenement.dateDebut.day}/${evenement.dateDebut.month}/${evenement.dateDebut.year}', style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary, ), ), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getTypeColor(evenement.typeEvenement).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( evenement.typeEvenement.name, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: _getTypeColor(evenement.typeEvenement), ), ), ), ], ), ], ); } Color _getTypeColor(TypeEvenement type) { switch (type) { case TypeEvenement.conference: return AppTheme.successColor; case TypeEvenement.assembleeGenerale: return AppTheme.primaryColor; case TypeEvenement.formation: return AppTheme.warningColor; case TypeEvenement.reunion: return AppTheme.infoColor; default: return AppTheme.textSecondary; } } Widget _buildEmptyState() { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.event_busy, size: 64, color: AppTheme.textSecondary.withOpacity(0.5), ), const SizedBox(height: 16), Text( 'Aucun événement', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: AppTheme.textSecondary.withOpacity(0.7), ), ), const SizedBox(height: 8), Text( 'Créez votre premier événement', style: TextStyle( fontSize: 14, color: AppTheme.textSecondary.withOpacity(0.5), ), ), const SizedBox(height: 24), UnifiedButton.primary( text: 'Créer un événement', icon: Icons.add, onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const EvenementCreatePage(), ), ); }, ), ], ), ), ); } }