import 'package:flutter/material.dart'; /// Section des événements à venir du dashboard /// /// Widget réutilisable pour afficher les prochains événements, /// réunions, échéances ou tâches selon le contexte. class UpcomingEventsSection extends StatelessWidget { /// Titre de la section final String title; /// Sous-titre optionnel final String? subtitle; /// Liste des événements à afficher final List events; /// Nombre maximum d'événements à afficher final int maxItems; /// Callback lors du tap sur un événement final Function(UpcomingEvent)? onEventTap; /// Callback pour voir tous les événements final VoidCallback? onViewAll; /// Afficher ou non l'en-tête de section final bool showHeader; /// Afficher ou non le bouton "Voir tout" final bool showViewAll; /// Message à afficher si aucun événement final String? emptyMessage; /// Style de la section final EventsSectionStyle style; const UpcomingEventsSection({ super.key, required this.title, this.subtitle, required this.events, this.maxItems = 3, this.onEventTap, this.onViewAll, this.showHeader = true, this.showViewAll = true, this.emptyMessage, this.style = EventsSectionStyle.card, }); /// Constructeur pour les événements d'organisation const UpcomingEventsSection.organization({ super.key, this.onEventTap, this.onViewAll, }) : title = 'Événements à venir', subtitle = 'Prochaines échéances', events = const [ UpcomingEvent( title: 'Réunion mensuelle', description: 'Point équipe et objectifs', date: '15 Jan 2025', time: '14:00', location: 'Salle de conférence', type: EventType.meeting, ), UpcomingEvent( title: 'Formation sécurité', description: 'Session obligatoire', date: '18 Jan 2025', time: '09:00', location: 'En ligne', type: EventType.training, ), UpcomingEvent( title: 'Assemblée générale', description: 'Vote budget 2025', date: '25 Jan 2025', time: '10:00', location: 'Auditorium', type: EventType.assembly, ), ], maxItems = 3, showHeader = true, showViewAll = true, emptyMessage = null, style = EventsSectionStyle.card; /// Constructeur pour les tâches système const UpcomingEventsSection.systemTasks({ super.key, this.onEventTap, this.onViewAll, }) : title = 'Tâches Programmées', subtitle = 'Maintenance et sauvegardes', events = const [ UpcomingEvent( title: 'Sauvegarde hebdomadaire', description: 'Sauvegarde complète BDD', date: 'Aujourd\'hui', time: '02:00', location: 'Automatique', type: EventType.maintenance, ), UpcomingEvent( title: 'Mise à jour sécurité', description: 'Patches système', date: 'Demain', time: '01:00', location: 'Serveurs', type: EventType.maintenance, ), UpcomingEvent( title: 'Nettoyage logs', description: 'Archivage automatique', date: '20 Jan 2025', time: '03:00', location: 'Système', type: EventType.maintenance, ), ], maxItems = 3, showHeader = true, showViewAll = true, emptyMessage = null, style = EventsSectionStyle.minimal; @override Widget build(BuildContext context) { switch (style) { case EventsSectionStyle.card: return _buildCardStyle(); case EventsSectionStyle.minimal: return _buildMinimalStyle(); case EventsSectionStyle.timeline: return _buildTimelineStyle(); } } /// Style carte avec fond Widget _buildCardStyle() { 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( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (showHeader) _buildHeader(), const SizedBox(height: 12), _buildEventsList(), ], ), ); } /// Style minimal sans fond Widget _buildMinimalStyle() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (showHeader) _buildHeader(), const SizedBox(height: 12), _buildEventsList(), ], ); } /// Style timeline avec ligne temporelle Widget _buildTimelineStyle() { 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( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (showHeader) _buildHeader(), const SizedBox(height: 12), _buildTimelineList(), ], ), ); } /// En-tête de la section Widget _buildHeader() { return Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF6C5CE7), ), ), if (subtitle != null) ...[ const SizedBox(height: 2), Text( subtitle!, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ], ), ), if (showViewAll && onViewAll != null) TextButton( onPressed: onViewAll, child: const Text( 'Voir tout', style: TextStyle( fontSize: 12, color: Color(0xFF6C5CE7), fontWeight: FontWeight.w600, ), ), ), ], ); } /// Liste des événements Widget _buildEventsList() { if (events.isEmpty) { return _buildEmptyState(); } final displayedEvents = events.take(maxItems).toList(); return Column( children: displayedEvents.map((event) => _buildEventItem(event)).toList(), ); } /// Liste timeline Widget _buildTimelineList() { if (events.isEmpty) { return _buildEmptyState(); } final displayedEvents = events.take(maxItems).toList(); return Column( children: displayedEvents.asMap().entries.map((entry) { final index = entry.key; final event = entry.value; final isLast = index == displayedEvents.length - 1; return _buildTimelineItem(event, isLast); }).toList(), ); } /// Élément d'événement Widget _buildEventItem(UpcomingEvent event) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: event.type.color.withOpacity(0.05), borderRadius: BorderRadius.circular(8), border: Border.all( color: event.type.color.withOpacity(0.2), width: 1, ), ), child: InkWell( onTap: onEventTap != null ? () => onEventTap!(event) : null, borderRadius: BorderRadius.circular(8), child: Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: event.type.color.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( event.type.icon, color: event.type.color, size: 16, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( event.title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1F2937), ), ), if (event.description != null) ...[ const SizedBox(height: 2), Text( event.description!, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], const SizedBox(height: 4), Row( children: [ Icon(Icons.access_time, size: 12, color: Colors.grey[500]), const SizedBox(width: 4), Text( '${event.date} à ${event.time}', style: TextStyle( fontSize: 11, color: Colors.grey[500], fontWeight: FontWeight.w500, ), ), if (event.location != null) ...[ const SizedBox(width: 8), Icon(Icons.location_on, size: 12, color: Colors.grey[500]), const SizedBox(width: 4), Text( event.location!, style: TextStyle( fontSize: 11, color: Colors.grey[500], ), ), ], ], ), ], ), ), ], ), ), ); } /// Élément timeline Widget _buildTimelineItem(UpcomingEvent event, bool isLast) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ Container( width: 12, height: 12, decoration: BoxDecoration( color: event.type.color, shape: BoxShape.circle, ), ), if (!isLast) Container( width: 2, height: 40, color: Colors.grey[300], ), ], ), const SizedBox(width: 12), Expanded( child: Padding( padding: EdgeInsets.only(bottom: isLast ? 0 : 16), child: _buildEventItem(event), ), ), ], ); } /// État vide Widget _buildEmptyState() { return Container( padding: const EdgeInsets.all(24), child: Column( children: [ Icon( Icons.event_available, size: 48, color: Colors.grey[400], ), const SizedBox(height: 12), Text( emptyMessage ?? 'Aucun événement à venir', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), ], ), ); } } /// Modèle de données pour un événement à venir class UpcomingEvent { final String title; final String? description; final String date; final String time; final String? location; final EventType type; final Map? metadata; const UpcomingEvent({ required this.title, this.description, required this.date, required this.time, this.location, required this.type, this.metadata, }); } /// Types d'événement enum EventType { meeting(Icons.meeting_room, Color(0xFF6C5CE7)), training(Icons.school, Color(0xFF00B894)), assembly(Icons.groups, Color(0xFF0984E3)), maintenance(Icons.build, Color(0xFFE17055)), deadline(Icons.schedule, Colors.orange), celebration(Icons.celebration, Color(0xFFE84393)); const EventType(this.icon, this.color); final IconData icon; final Color color; } /// Styles de section d'événements enum EventsSectionStyle { card, minimal, timeline, }