/// Page des événements avec données injectées depuis le BLoC /// /// Cette version de EventsPage accepte les données en paramètre /// au lieu d'utiliser des données mock hardcodées. library events_page_connected; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../../../core/utils/logger.dart'; import '../../../authentication/data/models/user_role.dart'; import '../../../authentication/presentation/bloc/auth_bloc.dart'; /// Page de gestion des événements avec données injectées class EventsPageWithData extends StatefulWidget { /// Liste des événements à afficher final List> events; /// Nombre total d'événements final int totalCount; /// Page actuelle. final int currentPage; /// Nombre total de pages final int totalPages; const EventsPageWithData({ super.key, required this.events, required this.totalCount, required this.currentPage, required this.totalPages, }); @override State createState() => _EventsPageWithDataState(); } class _EventsPageWithDataState extends State with TickerProviderStateMixin { // Controllers final TextEditingController _searchController = TextEditingController(); late TabController _tabController; // État String _searchQuery = ''; @override void initState() { super.initState(); _tabController = TabController(length: 5, vsync: this); AppLogger.info('EventsPageWithData initialisée avec ${widget.events.length} événements'); } @override void dispose() { _searchController.dispose(); _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is! AuthAuthenticated) { return Container( color: const Color(0xFFF8F9FA), child: const Center(child: CircularProgressIndicator()), ); } final canManageEvents = _canManageEvents(state.effectiveRole); return Container( color: const Color(0xFFF8F9FA), child: Column( children: [ // Métriques _buildEventMetrics(), // Recherche et filtres _buildSearchAndFilters(canManageEvents), // Onglets _buildTabBar(), // Contenu Expanded( child: TabBarView( controller: _tabController, children: [ _buildAllEventsView(), _buildUpcomingEventsView(), _buildOngoingEventsView(), _buildPastEventsView(), _buildCalendarView(), ], ), ), // Pagination if (widget.totalPages > 1) _buildPagination(), ], ), ); }, ); } /// Métriques des événements Widget _buildEventMetrics() { final upcoming = widget.events.where((e) => e['estAVenir'] == true).length; final ongoing = widget.events.where((e) => e['estEnCours'] == true).length; final past = widget.events.where((e) => e['estPasse'] == true).length; return Container( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: _buildMetricCard( 'À venir', upcoming.toString(), Icons.event_available, const Color(0xFF00B894), ), ), const SizedBox(width: 8), Expanded( child: _buildMetricCard( 'En cours', ongoing.toString(), Icons.event, const Color(0xFF74B9FF), ), ), const SizedBox(width: 8), Expanded( child: _buildMetricCard( 'Passés', past.toString(), Icons.event_busy, const Color(0xFF636E72), ), ), ], ), ); } Widget _buildMetricCard(String label, String value, IconData icon, Color color) { return Container( padding: const EdgeInsets.all(12), 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: Column( children: [ Icon(icon, color: color, size: 24), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: color, ), ), Text( label, style: const TextStyle(fontSize: 10, color: Color(0xFF636E72)), ), ], ), ); } /// Recherche et filtres Widget _buildSearchAndFilters(bool canManageEvents) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ Expanded( child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Rechercher un événement...', prefixIcon: const Icon(Icons.search, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric(vertical: 8), ), onChanged: (value) { setState(() { _searchQuery = value; }); AppLogger.userAction('Search events', data: {'query': value}); }, ), ), if (canManageEvents) ...[ const SizedBox(width: 8), IconButton( icon: const Icon(Icons.add_circle, color: Color(0xFF6C5CE7)), onPressed: () { AppLogger.userAction('Add new event button clicked'); _showAddEventDialog(); }, tooltip: 'Ajouter un événement', ), ], ], ), ); } /// Barre d'onglets Widget _buildTabBar() { return Container( margin: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: TabBar( controller: _tabController, labelColor: const Color(0xFF6C5CE7), unselectedLabelColor: const Color(0xFF636E72), indicatorColor: const Color(0xFF6C5CE7), labelStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), tabs: const [ Tab(text: 'Tous'), Tab(text: 'À venir'), Tab(text: 'En cours'), Tab(text: 'Passés'), Tab(text: 'Calendrier'), ], ), ); } /// Vue tous les événements Widget _buildAllEventsView() { final filtered = _getFilteredEvents(); return _buildEventsList(filtered); } /// Vue événements à venir Widget _buildUpcomingEventsView() { final filtered = _getFilteredEvents() .where((e) => e['estAVenir'] == true) .toList(); return _buildEventsList(filtered); } /// Vue événements en cours Widget _buildOngoingEventsView() { final filtered = _getFilteredEvents() .where((e) => e['estEnCours'] == true) .toList(); return _buildEventsList(filtered); } /// Vue événements passés Widget _buildPastEventsView() { final filtered = _getFilteredEvents() .where((e) => e['estPasse'] == true) .toList(); return _buildEventsList(filtered); } /// Vue calendrier (placeholder) Widget _buildCalendarView() { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.calendar_month, size: 64, color: Color(0xFF636E72)), SizedBox(height: 16), Text( 'Vue calendrier', style: TextStyle(fontSize: 18, color: Color(0xFF636E72)), ), SizedBox(height: 8), Text( 'À implémenter', style: TextStyle(fontSize: 14, color: Color(0xFF636E72)), ), ], ), ); } /// Liste des événements Widget _buildEventsList(List> events) { if (events.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.event_busy, size: 64, color: Color(0xFF636E72)), SizedBox(height: 16), Text( 'Aucun événement trouvé', style: TextStyle(fontSize: 18, color: Color(0xFF636E72)), ), ], ), ); } return RefreshIndicator( onRefresh: () async { // Recharger les événements // Note: Cette page utilise des données passées en paramètre // Le rafraîchissement devrait être géré par le parent await Future.delayed(const Duration(milliseconds: 500)); }, child: ListView.builder( padding: const EdgeInsets.all(12), itemCount: events.length, itemBuilder: (context, index) { final event = events[index]; return _buildEventCard(event); }, ), ); } /// Carte d'événement Widget _buildEventCard(Map event) { final startDate = event['startDate'] as DateTime; final dateFormatter = DateFormat('dd/MM/yyyy HH:mm'); return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: InkWell( onTap: () { AppLogger.userAction('View event details', data: {'eventId': event['id']}); _showEventDetails(event); }, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( event['title'], style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), _buildStatusChip(event['status']), ], ), const SizedBox(height: 8), Row( children: [ const Icon(Icons.calendar_today, size: 14, color: Color(0xFF636E72)), const SizedBox(width: 4), Text( dateFormatter.format(startDate), style: const TextStyle(fontSize: 12, color: Color(0xFF636E72)), ), const SizedBox(width: 12), const Icon(Icons.location_on, size: 14, color: Color(0xFF636E72)), const SizedBox(width: 4), Expanded( child: Text( event['location'], style: const TextStyle(fontSize: 12, color: Color(0xFF636E72)), overflow: TextOverflow.ellipsis, ), ), ], ), if (event['description'] != null && event['description'].toString().isNotEmpty) ...[ const SizedBox(height: 8), Text( event['description'], style: const TextStyle(fontSize: 12, color: Color(0xFF636E72)), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], const SizedBox(height: 8), Row( children: [ _buildTypeChip(event['type']), const SizedBox(width: 8), if (event['cost'] != null && event['cost'] > 0) _buildCostChip(event['cost']), const Spacer(), Text( '${event['currentParticipants']}/${event['maxParticipants']}', style: const TextStyle(fontSize: 12, color: Color(0xFF636E72)), ), const SizedBox(width: 4), const Icon(Icons.people, size: 14, color: Color(0xFF636E72)), ], ), ], ), ), ), ); } Widget _buildStatusChip(String status) { Color color; switch (status) { case 'Confirmé': color = const Color(0xFF00B894); break; case 'Annulé': color = const Color(0xFFFF7675); break; case 'Reporté': color = const Color(0xFFFFBE76); break; case 'Brouillon': color = const Color(0xFF636E72); break; default: color = const Color(0xFF74B9FF); } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( status, style: TextStyle( color: color, fontSize: 10, fontWeight: FontWeight.w600, ), ), ); } Widget _buildTypeChip(String type) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF6C5CE7).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( type, style: const TextStyle( color: Color(0xFF6C5CE7), fontSize: 10, fontWeight: FontWeight.w600, ), ), ); } Widget _buildCostChip(double cost) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFFFBE76).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${cost.toStringAsFixed(2)} €', style: const TextStyle( color: Color(0xFFFFBE76), fontSize: 10, fontWeight: FontWeight.w600, ), ), ); } /// Pagination Widget _buildPagination() { return Container( padding: const EdgeInsets.all(12), decoration: const BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFE0E0E0))), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.chevron_left), onPressed: widget.currentPage > 0 ? () { AppLogger.userAction('Previous page', data: {'page': widget.currentPage - 1}); // TODO: Charger la page précédente } : null, ), Text('Page ${widget.currentPage + 1} / ${widget.totalPages}'), IconButton( icon: const Icon(Icons.chevron_right), onPressed: widget.currentPage < widget.totalPages - 1 ? () { AppLogger.userAction('Next page', data: {'page': widget.currentPage + 1}); // TODO: Charger la page suivante } : null, ), ], ), ); } /// Filtrer les événements List> _getFilteredEvents() { var filtered = widget.events; if (_searchQuery.isNotEmpty) { filtered = filtered.where((e) { final title = e['title'].toString().toLowerCase(); final description = e['description'].toString().toLowerCase(); final query = _searchQuery.toLowerCase(); return title.contains(query) || description.contains(query); }).toList(); } return filtered; } /// Vérifier permissions bool _canManageEvents(UserRole role) { return role.level >= UserRole.moderator.level; } /// Afficher détails événement void _showEventDetails(Map event) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(event['title']), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Description: ${event['description']}'), const SizedBox(height: 8), Text('Lieu: ${event['location']}'), Text('Type: ${event['type']}'), Text('Statut: ${event['status']}'), Text('Participants: ${event['currentParticipants']}/${event['maxParticipants']}'), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Fermer'), ), ], ), ); } /// Dialogue ajout événement void _showAddEventDialog() { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Fonctionnalité à implémenter')), ); } }