/// Dialogue de création d'événement /// Formulaire complet pour créer un nouvel événement library create_event_dialog; import 'package:flutter/material.dart'; import '../../../../shared/design_system/tokens/module_colors.dart'; import '../../../../shared/design_system/tokens/color_tokens.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../bloc/evenements_bloc.dart'; import '../../bloc/evenements_event.dart'; import '../../bloc/evenements_state.dart'; import '../../data/models/evenement_model.dart'; /// Dialogue de création d'événement class CreateEventDialog extends StatefulWidget { const CreateEventDialog({super.key}); @override State createState() => _CreateEventDialogState(); } class _CreateEventDialogState extends State { final _formKey = GlobalKey(); bool _createSent = false; // Contrôleurs de texte final _titreController = TextEditingController(); final _descriptionController = TextEditingController(); final _lieuController = TextEditingController(); final _adresseController = TextEditingController(); final _capaciteController = TextEditingController(); // Valeurs sélectionnées DateTime _dateDebut = DateTime.now().add(const Duration(days: 7)); DateTime? _dateFin; TypeEvenement _selectedType = TypeEvenement.autre; bool _inscriptionRequise = true; bool _visiblePublic = true; @override void dispose() { _titreController.dispose(); _descriptionController.dispose(); _lieuController.dispose(); _adresseController.dispose(); _capaciteController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listenWhen: (_, state) => _createSent && (state is EvenementCreated || state is EvenementsError), listener: (context, state) { if (!_createSent || !mounted) return; if (state is EvenementsError) { setState(() => _createSent = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message), backgroundColor: ColorTokens.error), ); return; } if (state is EvenementCreated) { setState(() => _createSent = false); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Événement créé avec succès'), backgroundColor: ColorTokens.success, ), ); } }, child: Dialog( child: Container( width: MediaQuery.of(context).size.width * 0.9, constraints: const BoxConstraints(maxHeight: 600), child: Column( mainAxisSize: MainAxisSize.min, children: [ // En-tête Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: ModuleColors.evenements, borderRadius: BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), child: Row( children: [ const Icon(Icons.event, color: Colors.white), const SizedBox(width: 12), const Text( 'Créer un événement', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const Spacer(), IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: () => Navigator.pop(context), ), ], ), ), // Formulaire Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Informations de base _buildSectionTitle('Informations de base'), const SizedBox(height: 12), TextFormField( controller: _titreController, decoration: const InputDecoration( labelText: 'Titre *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.title), ), validator: (value) { if (value == null || value.isEmpty) { return 'Le titre est obligatoire'; } if (value.length < 3) { return 'Le titre doit contenir au moins 3 caractères'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description', border: OutlineInputBorder(), prefixIcon: Icon(Icons.description), ), maxLines: 3, ), const SizedBox(height: 12), // Type d'événement Builder(builder: (context) { final scheme = Theme.of(context).colorScheme; return DropdownButtonFormField( value: _selectedType, isExpanded: true, menuMaxHeight: 220, dropdownColor: scheme.surface, style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: scheme.onSurface), decoration: const InputDecoration( labelText: 'Type d\'événement *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: TypeEvenement.values.map((type) { return DropdownMenuItem( value: type, child: Text(_getTypeLabel(type), overflow: TextOverflow.ellipsis, style: TextStyle(color: scheme.onSurface)), ); }).toList(), onChanged: (value) { setState(() { _selectedType = value!; }); }, ); }), const SizedBox(height: 16), // Dates _buildSectionTitle('Dates et horaires'), const SizedBox(height: 12), InkWell( onTap: () => _selectDateDebut(context), child: InputDecorator( decoration: const InputDecoration( labelText: 'Date de début *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.calendar_today), ), child: Text( DateFormat('dd/MM/yyyy HH:mm').format(_dateDebut), ), ), ), const SizedBox(height: 12), InkWell( onTap: () => _selectDateFin(context), child: InputDecorator( decoration: const InputDecoration( labelText: 'Date de fin (optionnel)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.event_available), ), child: Text( _dateFin != null ? DateFormat('dd/MM/yyyy HH:mm').format(_dateFin!) : 'Sélectionner une date', ), ), ), const SizedBox(height: 16), // Lieu _buildSectionTitle('Lieu'), const SizedBox(height: 12), TextFormField( controller: _lieuController, decoration: const InputDecoration( labelText: 'Lieu *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.place), ), validator: (value) { if (value == null || value.isEmpty) { return 'Le lieu est obligatoire'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _adresseController, decoration: const InputDecoration( labelText: 'Adresse complète', border: OutlineInputBorder(), prefixIcon: Icon(Icons.location_on), ), maxLines: 2, ), const SizedBox(height: 16), // Paramètres _buildSectionTitle('Paramètres'), const SizedBox(height: 12), TextFormField( controller: _capaciteController, decoration: const InputDecoration( labelText: 'Capacité maximale', border: OutlineInputBorder(), prefixIcon: Icon(Icons.people), hintText: 'Nombre de places disponibles', ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { final capacite = int.tryParse(value); if (capacite == null || capacite <= 0) { return 'Capacité invalide'; } } return null; }, ), const SizedBox(height: 12), SwitchListTile( title: const Text('Inscription requise'), subtitle: const Text('Les participants doivent s\'inscrire'), value: _inscriptionRequise, onChanged: (value) { setState(() { _inscriptionRequise = value; }); }, ), SwitchListTile( title: const Text('Visible publiquement'), subtitle: const Text('L\'événement est visible par tous'), value: _visiblePublic, onChanged: (value) { setState(() { _visiblePublic = value; }); }, ), ], ), ), ), ), // Boutons d'action Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, border: Border(top: BorderSide(color: Theme.of(context).colorScheme.outline)), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), const SizedBox(width: 12), ElevatedButton( onPressed: _submitForm, style: ElevatedButton.styleFrom( backgroundColor: ModuleColors.evenements, foregroundColor: Colors.white, ), child: const Text('Créer l\'événement'), ), ], ), ), ], ), ), ), ); } Widget _buildSectionTitle(String title) { return Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: ModuleColors.evenements, ), ); } String _getTypeLabel(TypeEvenement type) { switch (type) { case TypeEvenement.assembleeGenerale: return 'Assemblée Générale'; case TypeEvenement.reunion: return 'Réunion'; case TypeEvenement.formation: return 'Formation'; case TypeEvenement.conference: return 'Conférence'; case TypeEvenement.atelier: return 'Atelier'; case TypeEvenement.seminaire: return 'Séminaire'; case TypeEvenement.evenementSocial: return 'Événement Social'; case TypeEvenement.manifestation: return 'Manifestation'; case TypeEvenement.celebration: return 'Célébration'; case TypeEvenement.autre: return 'Autre'; } } Future _selectDateDebut(BuildContext context) async { final DateTime? pickedDate = await showDatePicker( context: context, initialDate: _dateDebut, firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365 * 2)), ); if (pickedDate != null) { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_dateDebut), ); if (pickedTime != null) { setState(() { _dateDebut = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, pickedTime.hour, pickedTime.minute, ); }); } } } Future _selectDateFin(BuildContext context) async { final DateTime? pickedDate = await showDatePicker( context: context, initialDate: _dateFin ?? _dateDebut.add(const Duration(hours: 2)), firstDate: _dateDebut, lastDate: DateTime.now().add(const Duration(days: 365 * 2)), ); if (pickedDate != null) { final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_dateFin ?? _dateDebut.add(const Duration(hours: 2))), ); if (pickedTime != null) { setState(() { _dateFin = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, pickedTime.hour, pickedTime.minute, ); }); } } } void _submitForm() { if (_formKey.currentState!.validate()) { // Créer le modèle d'événement final evenement = EvenementModel( titre: _titreController.text, description: _descriptionController.text.isNotEmpty ? _descriptionController.text : null, dateDebut: _dateDebut, dateFin: _dateFin ?? _dateDebut.add(const Duration(hours: 2)), lieu: _lieuController.text, adresse: _adresseController.text.isNotEmpty ? _adresseController.text : null, type: _selectedType, maxParticipants: _capaciteController.text.isNotEmpty ? int.parse(_capaciteController.text) : null, inscriptionRequise: _inscriptionRequise, estPublic: _visiblePublic, statut: StatutEvenement.planifie, ); setState(() => _createSent = true); context.read().add(CreateEvenement(evenement)); } } }