import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../authentication/presentation/bloc/auth_bloc.dart'; import '../../../../shared/design_system/tokens/color_tokens.dart'; /// Page de gestion des événements - Interface sophistiquée et exhaustive /// /// Cette page offre une interface complète pour la gestion des événements /// avec des fonctionnalités avancées de recherche, filtrage, statistiques, /// vue calendrier et actions de gestion basées sur les permissions utilisateur. class EventsPage extends StatefulWidget { const EventsPage({super.key}); @override State createState() => _EventsPageState(); } class _EventsPageState extends State with TickerProviderStateMixin { // Controllers et état final TextEditingController _searchController = TextEditingController(); late TabController _tabController; // État de l'interface String _searchQuery = ''; String _selectedFilter = 'Tous'; // Données de démonstration enrichies final List> _allEvents = [ { 'id': '1', 'title': 'Assemblée Générale Annuelle 2024', 'description': 'Assemblée générale ordinaire avec présentation du bilan annuel, vote du budget et élection du bureau.', 'startDate': DateTime(2024, 10, 15, 14, 0), 'endDate': DateTime(2024, 10, 15, 17, 0), 'location': 'Salle des fêtes municipale', 'address': '12 Place de la Mairie, 75001 Paris', 'type': 'Officiel', 'status': 'Confirmé', 'maxParticipants': 100, 'currentParticipants': 67, 'organizer': 'Bureau Exécutif', 'priority': 'Haute', 'isPublic': true, 'requiresRegistration': true, 'cost': 0.0, 'tags': ['AG', 'Obligatoire', 'Annuel'], 'createdBy': 'Marie Dubois', 'createdAt': DateTime(2024, 8, 1), 'lastModified': DateTime(2024, 9, 15), }, { 'id': '2', 'title': 'Sortie Ski de Fond - Les Rousses', 'description': 'Sortie ski de fond dans le Jura. Matériel fourni, tous niveaux acceptés. Repas chaud inclus.', 'startDate': DateTime(2024, 12, 22, 9, 0), 'endDate': DateTime(2024, 12, 22, 17, 0), 'location': 'Station des Rousses', 'address': 'Les Rousses, 39220 Jura', 'type': 'Loisir', 'status': 'En attente', 'maxParticipants': 25, 'currentParticipants': 18, 'organizer': 'Commission Sports', 'priority': 'Moyenne', 'isPublic': true, 'requiresRegistration': true, 'cost': 35.0, 'tags': ['Sport', 'Hiver', 'Nature'], 'createdBy': 'Pierre Martin', 'createdAt': DateTime(2024, 9, 10), 'lastModified': DateTime(2024, 9, 18), }, { 'id': '3', 'title': 'Formation Premiers Secours PSC1', 'description': 'Formation complète aux gestes de premiers secours. Certification officielle délivrée.', 'startDate': DateTime(2024, 11, 5, 9, 0), 'endDate': DateTime(2024, 11, 5, 17, 0), 'location': 'Centre de Formation', 'address': '45 Avenue des Formations, 75015 Paris', 'type': 'Formation', 'status': 'Confirmé', 'maxParticipants': 12, 'currentParticipants': 10, 'organizer': 'Commission Formation', 'priority': 'Haute', 'isPublic': false, 'requiresRegistration': true, 'cost': 60.0, 'tags': ['Formation', 'Sécurité', 'Certification'], 'createdBy': 'Sophie Laurent', 'createdAt': DateTime(2024, 8, 20), 'lastModified': DateTime(2024, 9, 12), }, { 'id': '4', 'title': 'Réunion Bureau Mensuelle', 'description': 'Réunion mensuelle du bureau pour faire le point sur les activités et prendre les décisions courantes.', 'startDate': DateTime(2024, 10, 28, 19, 30), 'endDate': DateTime(2024, 10, 28, 21, 30), 'location': 'Mairie - Salle du Conseil', 'address': '1 Place de la République, 75001 Paris', 'type': 'Administratif', 'status': 'Confirmé', 'maxParticipants': 15, 'currentParticipants': 12, 'organizer': 'Président', 'priority': 'Moyenne', 'isPublic': false, 'requiresRegistration': false, 'cost': 0.0, 'tags': ['Bureau', 'Mensuel', 'Décisions'], 'createdBy': 'Thomas Durand', 'createdAt': DateTime(2024, 9, 1), 'lastModified': DateTime(2024, 9, 20), }, { 'id': '5', 'title': 'Soirée Galette des Rois', 'description': 'Soirée conviviale avec dégustation de galettes, animations et tirage des rois et reines.', 'startDate': DateTime(2024, 1, 13, 19, 0), 'endDate': DateTime(2024, 1, 13, 23, 0), 'location': 'Salle Communale', 'address': '8 Rue de la Convivialité, 75012 Paris', 'type': 'Social', 'status': 'Terminé', 'maxParticipants': 50, 'currentParticipants': 42, 'organizer': 'Commission Festivités', 'priority': 'Basse', 'isPublic': true, 'requiresRegistration': true, 'cost': 12.0, 'tags': ['Social', 'Tradition', 'Convivialité'], 'createdBy': 'Emma Rousseau', 'createdAt': DateTime(2023, 12, 1), 'lastModified': DateTime(2024, 1, 10), }, { 'id': '6', 'title': 'Conférence Développement Durable', 'description': 'Conférence sur les enjeux du développement durable avec experts et table ronde.', 'startDate': DateTime(2024, 11, 20, 18, 30), 'endDate': DateTime(2024, 11, 20, 21, 0), 'location': 'Amphithéâtre Universitaire', 'address': '123 Boulevard de la Connaissance, 75013 Paris', 'type': 'Culturel', 'status': 'Confirmé', 'maxParticipants': 200, 'currentParticipants': 89, 'organizer': 'Commission Culture', 'priority': 'Moyenne', 'isPublic': true, 'requiresRegistration': true, 'cost': 5.0, 'tags': ['Conférence', 'Environnement', 'Éducation'], 'createdBy': 'Lucas Bernard', 'createdAt': DateTime(2024, 9, 5), 'lastModified': DateTime(2024, 9, 19), }, { 'id': '7', 'title': 'Atelier Cuisine Collaborative', 'description': 'Atelier de cuisine collaborative avec préparation d\'un repas complet et dégustation.', 'startDate': DateTime(2024, 10, 25, 18, 0), 'endDate': DateTime(2024, 10, 25, 22, 0), 'location': 'Cuisine Pédagogique', 'address': '67 Rue des Saveurs, 75011 Paris', 'type': 'Loisir', 'status': 'En cours', 'maxParticipants': 16, 'currentParticipants': 14, 'organizer': 'Commission Loisirs', 'priority': 'Basse', 'isPublic': true, 'requiresRegistration': true, 'cost': 25.0, 'tags': ['Cuisine', 'Créatif', 'Partage'], 'createdBy': 'Camille Moreau', 'createdAt': DateTime(2024, 9, 8), 'lastModified': DateTime(2024, 10, 20), }, { 'id': '8', 'title': 'Randonnée Forêt de Fontainebleau', 'description': 'Randonnée découverte de 12km en forêt de Fontainebleau avec guide naturaliste.', 'startDate': DateTime(2024, 11, 10, 9, 30), 'endDate': DateTime(2024, 11, 10, 16, 0), 'location': 'Forêt de Fontainebleau', 'address': 'Parking Carrefour de la Croix du Grand Maître, 77300 Fontainebleau', 'type': 'Sport', 'status': 'Annulé', 'maxParticipants': 20, 'currentParticipants': 8, 'organizer': 'Commission Nature', 'priority': 'Basse', 'isPublic': true, 'requiresRegistration': true, 'cost': 8.0, 'tags': ['Randonnée', 'Nature', 'Découverte'], 'createdBy': 'Marie Dubois', 'createdAt': DateTime(2024, 8, 25), 'lastModified': DateTime(2024, 10, 15), }, ]; @override void initState() { super.initState(); _tabController = TabController(length: 5, vsync: this); } @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()), ); } return Container( color: const Color(0xFFF8F9FA), child: Column( children: [ // Métriques et statistiques _buildEventMetrics(), // Barre de recherche et filtres _buildSearchAndFilters(), // Navigation par onglets _buildTabBar(), // Contenu des onglets Expanded( child: TabBarView( controller: _tabController, children: [ _buildAllEventsView(), _buildUpcomingEventsView(), _buildOngoingEventsView(), _buildPastEventsView(), _buildCalendarView(), ], ), ), ], ), ); }, ); } /// Métriques et statistiques des événements Widget _buildEventMetrics() { final now = DateTime.now(); final upcomingEvents = _allEvents.where((event) => (event['startDate'] as DateTime).isAfter(now) && event['status'] != 'Annulé' ).length; final ongoingEvents = _allEvents.where((event) => event['status'] == 'En cours' ).length; final totalParticipants = _allEvents.fold(0, (sum, event) => sum + (event['currentParticipants'] as int) ); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: ColorTokens.secondary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.event, color: ColorTokens.secondary), const SizedBox(width: 8), const Text( 'Événements', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const Spacer(), IconButton( icon: const Icon(Icons.add), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Créer événement - Fonctionnalité à venir')), ); }, tooltip: 'Créer un événement', ), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildStatCard('À Venir', upcomingEvents.toString(), ColorTokens.success), _buildStatCard('En Cours', ongoingEvents.toString(), ColorTokens.info), _buildStatCard('Participants', totalParticipants.toString(), ColorTokens.secondary), ], ), ], ), ); } Widget _buildStatCard(String label, String value, Color color) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( children: [ Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( label, style: TextStyle( fontSize: 12, color: color.withOpacity(0.8), ), ), ], ), ); } /// Barre de recherche et filtres Widget _buildSearchAndFilters() { return Container( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Barre de recherche simple 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: TextField( controller: _searchController, onChanged: (value) => setState(() => _searchQuery = value), decoration: InputDecoration( hintText: 'Rechercher un événement...', hintStyle: const TextStyle(color: Color(0xFF9CA3AF), fontSize: 14), prefixIcon: const Icon(Icons.search, color: Color(0xFF6B7280), size: 20), suffixIcon: _searchQuery.isNotEmpty ? IconButton( onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, icon: const Icon(Icons.clear, color: Color(0xFF6B7280), size: 20), ) : null, border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), ), ), ), const SizedBox(height: 8), // Filtres rapides simplifiés SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildSimpleFilter('Tous', _selectedFilter == 'Tous'), _buildSimpleFilter('À venir', _selectedFilter == 'À venir'), _buildSimpleFilter('En cours', _selectedFilter == 'En cours'), _buildSimpleFilter('Terminés', _selectedFilter == 'Terminés'), ], ), ), ], ), ); } /// Filtre simple aligné sur le design system Widget _buildSimpleFilter(String label, bool isSelected) { return Container( margin: const EdgeInsets.only(right: 6), child: InkWell( onTap: () { setState(() { _selectedFilter = isSelected ? 'Tous' : label; }); }, borderRadius: BorderRadius.circular(16), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isSelected ? const Color(0xFF6C5CE7) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? const Color(0xFF6C5CE7) : const Color(0xFFE5E7EB), width: 1, ), ), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : const Color(0xFF6B7280), fontSize: 12, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ), ), ); } /// Navigation par onglets simplifiée Widget _buildTabBar() { return Container( margin: const EdgeInsets.symmetric(horizontal: 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: TabBar( controller: _tabController, isScrollable: true, labelColor: const Color(0xFF6C5CE7), unselectedLabelColor: const Color(0xFF6B7280), indicatorColor: const Color(0xFF6C5CE7), indicatorSize: TabBarIndicatorSize.tab, labelStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), unselectedLabelStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.normal), tabs: const [ Tab(text: 'Tous'), Tab(text: 'À Venir'), Tab(text: 'En Cours'), Tab(text: 'Passés'), Tab(text: 'Calendrier'), ], ), ); } /// Vue de tous les événements Widget _buildAllEventsView() { final filteredEvents = _getFilteredEvents(_allEvents); return _buildEventsListView(filteredEvents, 'all'); } /// Vue des événements à venir Widget _buildUpcomingEventsView() { final now = DateTime.now(); final upcomingEvents = _allEvents.where((event) => (event['startDate'] as DateTime).isAfter(now) && event['status'] != 'Annulé' ).toList(); final filteredEvents = _getFilteredEvents(upcomingEvents); return _buildEventsListView(filteredEvents, 'upcoming'); } /// Vue des événements en cours Widget _buildOngoingEventsView() { final ongoingEvents = _allEvents.where((event) => event['status'] == 'En cours' ).toList(); final filteredEvents = _getFilteredEvents(ongoingEvents); return _buildEventsListView(filteredEvents, 'ongoing'); } /// Vue des événements passés Widget _buildPastEventsView() { final now = DateTime.now(); final pastEvents = _allEvents.where((event) => (event['startDate'] as DateTime).isBefore(now) && event['status'] == 'Terminé' ).toList(); final filteredEvents = _getFilteredEvents(pastEvents); return _buildEventsListView(filteredEvents, 'past'); } /// Vue calendrier Widget _buildCalendarView() { return Container( padding: const EdgeInsets.all(16), child: Column( children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFF6C5CE7).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: const Column( children: [ Icon( Icons.calendar_month, size: 48, color: Color(0xFF6C5CE7), ), SizedBox(height: 16), Text( 'Vue Calendrier', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF374151), ), ), SizedBox(height: 8), Text( 'La vue calendrier interactive sera bientôt disponible', style: TextStyle( color: Color(0xFF6B7280), ), textAlign: TextAlign.center, ), ], ), ), ], ), ); } /// Filtre les événements selon les critères sélectionnés List> _getFilteredEvents(List> events) { var filtered = events.where((event) { // Filtre par recherche textuelle if (_searchQuery.isNotEmpty) { final query = _searchQuery.toLowerCase(); final title = (event['title'] as String).toLowerCase(); final description = (event['description'] as String).toLowerCase(); final location = (event['location'] as String).toLowerCase(); if (!title.contains(query) && !description.contains(query) && !location.contains(query)) { return false; } } // Filtre par catégorie if (_selectedFilter != 'Tous') { switch (_selectedFilter) { case 'À venir': final now = DateTime.now(); if (!(event['startDate'] as DateTime).isAfter(now) || event['status'] == 'Annulé') { return false; } break; case 'En cours': if (event['status'] != 'En cours') return false; break; case 'Terminés': if (event['status'] != 'Terminé') return false; break; case 'Publics': if (!(event['isPublic'] as bool)) return false; break; case 'Privés': if (event['isPublic'] as bool) return false; break; } } return true; }).toList(); // Tri par date par défaut filtered.sort((a, b) => (a['startDate'] as DateTime).compareTo(b['startDate'] as DateTime)); return filtered; } /// Liste des événements avec gestion de l'état vide Widget _buildEventsListView(List> events, String type) { if (events.isEmpty) { return _buildEmptyState(type); } return ListView.builder( padding: const EdgeInsets.all(12), itemCount: events.length, itemBuilder: (context, index) { final event = events[index]; return _buildSimpleEventCard(event); }, ); } /// Carte d'événement simple alignée sur le design system Widget _buildSimpleEventCard(Map event) { final startDate = event['startDate'] as DateTime; final currentParticipants = event['currentParticipants'] as int; final maxParticipants = event['maxParticipants'] as int; return Container( margin: const EdgeInsets.only(bottom: 8), 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: InkWell( onTap: () => _showEventDetails(event), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header avec titre et statut Row( children: [ Expanded( child: Text( event['title'], style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF374151), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: _getStatusColor(event['status']).withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Text( event['status'], style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _getStatusColor(event['status']), ), ), ), ], ), const SizedBox(height: 8), // Informations principales Row( children: [ const Icon( Icons.calendar_today, size: 14, color: Color(0xFF6B7280), ), const SizedBox(width: 4), Text( _formatDate(startDate), style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280), ), ), const SizedBox(width: 12), const Icon( Icons.location_on, size: 14, color: Color(0xFF6B7280), ), const SizedBox(width: 4), Expanded( child: Text( event['location'], style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), // Footer avec type et participants Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: _getTypeColor(event['type']).withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Text( event['type'], style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _getTypeColor(event['type']), ), ), ), const Spacer(), const Icon( Icons.people, size: 14, color: Color(0xFF6B7280), ), const SizedBox(width: 4), Text( '$currentParticipants/$maxParticipants', style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280), fontWeight: FontWeight.w500, ), ), ], ), ], ), ), ), ); } /// État vide selon le type d'événements Widget _buildEmptyState(String type) { String title; String subtitle; IconData icon; switch (type) { case 'upcoming': title = 'Aucun événement à venir'; subtitle = 'Aucun événement n\'est programmé prochainement'; icon = Icons.event_available; break; case 'ongoing': title = 'Aucun événement en cours'; subtitle = 'Aucun événement n\'est actuellement en cours'; icon = Icons.play_circle_filled; break; case 'past': title = 'Aucun événement passé'; subtitle = 'Aucun événement terminé à afficher'; icon = Icons.event_busy; break; default: title = 'Aucun événement trouvé'; subtitle = 'Aucun événement ne correspond aux critères sélectionnés'; icon = Icons.event_note; } return SizedBox( height: 400, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFF6C5CE7).withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( icon, size: 48, color: const Color(0xFF6C5CE7), ), ), const SizedBox(height: 16), Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF374151), ), ), const SizedBox(height: 8), Text( subtitle, style: const TextStyle( color: Color(0xFF6B7280), ), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () { setState(() { _searchController.clear(); _searchQuery = ''; _selectedFilter = 'Tous'; }); }, icon: const Icon(Icons.refresh), label: const Text('Réinitialiser les filtres'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, ), ), ], ), ); } // ═══════════════════════════════════════════════════════════════════════════ // MÉTHODES UTILITAIRES ET HELPERS // ═══════════════════════════════════════════════════════════════════════════ /// Couleur selon le statut de l'événement Color _getStatusColor(String status) { switch (status) { case 'Confirmé': return const Color(0xFF10B981); case 'En attente': return const Color(0xFFF59E0B); case 'En cours': return const Color(0xFF3B82F6); case 'Terminé': return const Color(0xFF6B7280); case 'Annulé': return const Color(0xFFEF4444); default: return const Color(0xFF6B7280); } } /// Couleur selon le type d'événement Color _getTypeColor(String type) { switch (type) { case 'Officiel': return const Color(0xFF3B82F6); case 'Loisir': return const Color(0xFF10B981); case 'Formation': return const Color(0xFFF59E0B); case 'Social': return const Color(0xFF8B5CF6); case 'Administratif': return const Color(0xFFEF4444); case 'Culturel': return const Color(0xFF06B6D4); case 'Sport': return const Color(0xFF84CC16); default: return const Color(0xFF6B7280); } } /// Formatage de la date String _formatDate(DateTime date) { final months = [ 'Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc' ]; return '${date.day} ${months[date.month - 1]} ${date.year}'; } /// Formatage de l'heure String _formatTime(DateTime time) { return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; } // ═══════════════════════════════════════════════════════════════════════════ // ACTIONS ET INTERACTIONS // ═══════════════════════════════════════════════════════════════════════════ /// Afficher les détails d'un événement void _showEventDetails(Map event) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => _buildEventDetailsSheet(event), ); } /// Sheet de détails d'un événement Widget _buildEventDetailsSheet(Map event) { final startDate = event['startDate'] as DateTime; final endDate = event['endDate'] as DateTime; final currentParticipants = event['currentParticipants'] as int; final maxParticipants = event['maxParticipants'] as int; return DraggableScrollableSheet( initialChildSize: 0.8, minChildSize: 0.5, maxChildSize: 0.95, builder: (context, scrollController) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ // Handle Container( margin: const EdgeInsets.symmetric(vertical: 8), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(2), ), ), // Header Padding( padding: const EdgeInsets.all(20), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _getTypeColor(event['type']).withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon( _getEventIcon(event['type']), color: _getTypeColor(event['type']), size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( event['title'], style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF374151), ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getStatusColor(event['status']).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( event['status'], style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _getStatusColor(event['status']), ), ), ), ], ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ), ), // Contenu détaillé Expanded( child: SingleChildScrollView( controller: scrollController, padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Description _buildDetailSection( 'Description', [ Text( event['description'], style: const TextStyle( fontSize: 16, color: Color(0xFF374151), height: 1.5, ), ), ], ), // Informations pratiques _buildDetailSection( 'Informations Pratiques', [ _buildDetailItem(Icons.calendar_today, 'Date et heure', '${_formatDate(startDate)} de ${_formatTime(startDate)} à ${_formatTime(endDate)}'), _buildDetailItem(Icons.location_on, 'Lieu', event['location']), _buildDetailItem(Icons.place, 'Adresse', event['address']), _buildDetailItem(Icons.person, 'Organisateur', event['organizer']), if ((event['cost'] as double) > 0) _buildDetailItem(Icons.euro, 'Coût', '${event['cost']}€'), ], ), // Participation _buildDetailSection( 'Participation', [ _buildDetailItem(Icons.people, 'Participants', '$currentParticipants / $maxParticipants inscrits'), _buildDetailItem(Icons.public, 'Visibilité', (event['isPublic'] as bool) ? 'Événement public' : 'Événement privé'), _buildDetailItem(Icons.app_registration, 'Inscription', (event['requiresRegistration'] as bool) ? 'Inscription requise' : 'Inscription libre'), ], ), // Tags if ((event['tags'] as List).isNotEmpty) ...[ _buildDetailSection( 'Tags', [ Wrap( spacing: 8, runSpacing: 8, children: (event['tags'] as List).map((tag) => Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: const Color(0xFF6C5CE7).withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: Text( tag, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF6C5CE7), ), ), ), ).toList(), ), ], ), ], const SizedBox(height: 20), // Actions Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.of(context).pop(); _showEditEventDialog(event); }, icon: const Icon(Icons.edit), label: const Text('Modifier'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), const SizedBox(width: 12), Expanded( child: OutlinedButton.icon( onPressed: () { Navigator.of(context).pop(); _shareEvent(event); }, icon: const Icon(Icons.share), label: const Text('Partager'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ), ], ), ), ), ], ), ); }, ); } /// Section de détails Widget _buildDetailSection(String title, List items) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF374151), ), ), const SizedBox(height: 12), ...items, const SizedBox(height: 24), ], ); } /// Item de détail Widget _buildDetailItem(IconData icon, String label, String value) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( icon, size: 20, color: const Color(0xFF6B7280), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 14, color: Color(0xFF6B7280), fontWeight: FontWeight.w500, ), ), const SizedBox(height: 2), Text( value, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF374151), ), ), ], ), ), ], ), ); } /// Icône selon le type d'événement IconData _getEventIcon(String type) { switch (type) { case 'Officiel': return Icons.business; case 'Loisir': return Icons.sports_esports; case 'Formation': return Icons.school; case 'Social': return Icons.people; case 'Administratif': return Icons.admin_panel_settings; case 'Culturel': return Icons.theater_comedy; case 'Sport': return Icons.sports; default: return Icons.event; } } @override void dispose() { _searchController.dispose(); _tabController.dispose(); super.dispose(); } /// Modifier un événement void _showEditEventDialog(Map event) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Modification de "${event['title']}" - Fonctionnalité à implémenter'), backgroundColor: const Color(0xFF6C5CE7), ), ); } /// Partager un événement void _shareEvent(Map event) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Partage de "${event['title']}" - Fonctionnalité à implémenter'), backgroundColor: const Color(0xFF10B981), ), ); } }