Files
unionflow-server-api/unionflow-mobile-apps/lib/features/events/presentation/widgets/edit_event_dialog.dart
2025-11-17 16:02:04 +00:00

512 lines
18 KiB
Dart

/// Dialogue de modification d'événement
/// Formulaire pré-rempli pour modifier un événement existant
library edit_event_dialog;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import '../../bloc/evenements_bloc.dart';
import '../../bloc/evenements_event.dart';
import '../../data/models/evenement_model.dart';
/// Dialogue de modification d'événement
class EditEventDialog extends StatefulWidget {
final EvenementModel evenement;
const EditEventDialog({
super.key,
required this.evenement,
});
@override
State<EditEventDialog> createState() => _EditEventDialogState();
}
class _EditEventDialogState extends State<EditEventDialog> {
final _formKey = GlobalKey<FormState>();
// Contrôleurs de texte
late final TextEditingController _titreController;
late final TextEditingController _descriptionController;
late final TextEditingController _lieuController;
late final TextEditingController _adresseController;
late final TextEditingController _capaciteController;
// Valeurs sélectionnées
late DateTime _dateDebut;
late DateTime _dateFin;
late TypeEvenement _selectedType;
late StatutEvenement _selectedStatut;
late bool _inscriptionRequise;
late bool _estPublic;
@override
void initState() {
super.initState();
// Initialiser les contrôleurs avec les valeurs existantes
_titreController = TextEditingController(text: widget.evenement.titre);
_descriptionController = TextEditingController(text: widget.evenement.description ?? '');
_lieuController = TextEditingController(text: widget.evenement.lieu ?? '');
_adresseController = TextEditingController(text: widget.evenement.adresse ?? '');
_capaciteController = TextEditingController(
text: widget.evenement.maxParticipants?.toString() ?? '',
);
// Initialiser les valeurs
_dateDebut = widget.evenement.dateDebut;
_dateFin = widget.evenement.dateFin;
_selectedType = widget.evenement.type;
_selectedStatut = widget.evenement.statut;
_inscriptionRequise = widget.evenement.inscriptionRequise;
_estPublic = widget.evenement.estPublic;
}
@override
void dispose() {
_titreController.dispose();
_descriptionController.dispose();
_lieuController.dispose();
_adresseController.dispose();
_capaciteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return 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: Color(0xFF3B82F6),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
child: Row(
children: [
const Icon(Icons.edit, color: Colors.white),
const SizedBox(width: 12),
const Text(
'Modifier l\'é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 et statut
Row(
children: [
Expanded(
child: DropdownButtonFormField<TypeEvenement>(
value: _selectedType,
decoration: const InputDecoration(
labelText: 'Type *',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.category),
),
items: TypeEvenement.values.map((type) {
return DropdownMenuItem(
value: type,
child: Text(_getTypeLabel(type)),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedType = value!;
});
},
),
),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonFormField<StatutEvenement>(
value: _selectedStatut,
decoration: const InputDecoration(
labelText: 'Statut *',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.flag),
),
items: StatutEvenement.values.map((statut) {
return DropdownMenuItem(
value: statut,
child: Text(_getStatutLabel(statut)),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedStatut = 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 *',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.event_available),
),
child: Text(
DateFormat('dd/MM/yyyy HH:mm').format(_dateFin),
),
),
),
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),
// Capacité
_buildSectionTitle('Paramètres'),
const SizedBox(height: 12),
TextFormField(
controller: _capaciteController,
decoration: InputDecoration(
labelText: 'Capacité maximale',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.people),
suffixText: widget.evenement.participantsActuels > 0
? '${widget.evenement.participantsActuels} inscrits'
: null,
),
keyboardType: TextInputType.number,
validator: (value) {
if (value != null && value.isNotEmpty) {
final capacite = int.tryParse(value);
if (capacite == null || capacite <= 0) {
return 'La capacité doit être un nombre positif';
}
if (capacite < widget.evenement.participantsActuels) {
return 'La capacité ne peut pas être inférieure au nombre d\'inscrits (${widget.evenement.participantsActuels})';
}
}
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('Événement public'),
subtitle: const Text('Visible par tous les membres'),
value: _estPublic,
onChanged: (value) {
setState(() {
_estPublic = value;
});
},
),
],
),
),
),
),
// Boutons d'action
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[100],
border: Border(top: BorderSide(color: Colors.grey[300]!)),
),
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: const Color(0xFF3B82F6),
foregroundColor: Colors.white,
),
child: const Text('Enregistrer'),
),
],
),
),
],
),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF3B82F6),
),
);
}
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';
}
}
String _getStatutLabel(StatutEvenement statut) {
switch (statut) {
case StatutEvenement.planifie:
return 'Planifié';
case StatutEvenement.confirme:
return 'Confirmé';
case StatutEvenement.enCours:
return 'En cours';
case StatutEvenement.termine:
return 'Terminé';
case StatutEvenement.annule:
return 'Annulé';
case StatutEvenement.reporte:
return 'Reporté';
}
}
Future<void> _selectDateDebut(BuildContext context) async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _dateDebut,
firstDate: DateTime.now().subtract(const Duration(days: 365)),
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,
);
// Ajuster la date de fin si elle est avant la date de début
if (_dateFin.isBefore(_dateDebut)) {
_dateFin = _dateDebut.add(const Duration(hours: 2));
}
});
}
}
}
Future<void> _selectDateFin(BuildContext context) async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _dateFin,
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),
);
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 mis à jour
final evenementUpdated = widget.evenement.copyWith(
titre: _titreController.text,
description: _descriptionController.text.isNotEmpty ? _descriptionController.text : null,
dateDebut: _dateDebut,
dateFin: _dateFin,
lieu: _lieuController.text,
adresse: _adresseController.text.isNotEmpty ? _adresseController.text : null,
type: _selectedType,
statut: _selectedStatut,
maxParticipants: _capaciteController.text.isNotEmpty ? int.parse(_capaciteController.text) : null,
inscriptionRequise: _inscriptionRequise,
estPublic: _estPublic,
);
// Envoyer l'événement au BLoC
context.read<EvenementsBloc>().add(UpdateEvenement(widget.evenement.id!.toString(), evenementUpdated));
// Fermer le dialogue
Navigator.pop(context);
// Afficher un message de succès
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Événement modifié avec succès'),
backgroundColor: Colors.green,
),
);
}
}
}