/// Dialogue de création de cotisation library create_cotisation_dialog; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../bloc/cotisations_bloc.dart'; import '../../bloc/cotisations_event.dart'; import '../../data/models/cotisation_model.dart'; import '../../../members/bloc/membres_bloc.dart'; import '../../../members/bloc/membres_event.dart'; import '../../../members/bloc/membres_state.dart'; import '../../../members/data/models/membre_complete_model.dart'; class CreateCotisationDialog extends StatefulWidget { const CreateCotisationDialog({super.key}); @override State createState() => _CreateCotisationDialogState(); } class _CreateCotisationDialogState extends State { final _formKey = GlobalKey(); final _montantController = TextEditingController(); final _descriptionController = TextEditingController(); final _searchController = TextEditingController(); MembreCompletModel? _selectedMembre; TypeCotisation _selectedType = TypeCotisation.annuelle; DateTime _dateEcheance = DateTime.now().add(const Duration(days: 30)); int _annee = DateTime.now().year; int? _mois; int? _trimestre; int? _semestre; List _membresDisponibles = []; @override void initState() { super.initState(); context.read().add(const LoadActiveMembres()); } @override void dispose() { _montantController.dispose(); _descriptionController.dispose(); _searchController.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: [ _buildHeader(), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionTitle('Membre'), const SizedBox(height: 12), _buildMembreSelector(), const SizedBox(height: 16), _buildSectionTitle('Type de cotisation'), const SizedBox(height: 12), _buildTypeDropdown(), const SizedBox(height: 12), _buildPeriodeFields(), const SizedBox(height: 16), _buildSectionTitle('Montant'), const SizedBox(height: 12), _buildMontantField(), const SizedBox(height: 16), _buildSectionTitle('Échéance'), const SizedBox(height: 12), _buildDateEcheanceField(), const SizedBox(height: 16), _buildSectionTitle('Description (optionnel)'), const SizedBox(height: 12), _buildDescriptionField(), ], ), ), ), ), _buildActionButtons(), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Color(0xFFEF4444), borderRadius: BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), child: Row( children: [ const Icon(Icons.add_card, color: Colors.white), const SizedBox(width: 12), const Text( 'Créer une cotisation', 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), ), ], ), ); } Widget _buildSectionTitle(String title) { return Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFFEF4444), ), ); } Widget _buildMembreSelector() { return BlocBuilder( builder: (context, state) { if (state is MembresLoaded) { _membresDisponibles = state.membres; } if (_selectedMembre != null) { return _buildSelectedMembre(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSearchField(), const SizedBox(height: 12), if (_membresDisponibles.isNotEmpty) _buildMembresList(), ], ); }, ); } Widget _buildSearchField() { return TextFormField( controller: _searchController, decoration: InputDecoration( labelText: 'Rechercher un membre *', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); context.read().add(const LoadActiveMembres()); }, ) : null, ), onChanged: (value) { if (value.isNotEmpty) { context.read().add(LoadMembres(recherche: value)); } else { context.read().add(const LoadActiveMembres()); } }, ); } Widget _buildMembresList() { return Container( constraints: const BoxConstraints(maxHeight: 200), decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(4), ), child: ListView.builder( shrinkWrap: true, itemCount: _membresDisponibles.length, itemBuilder: (context, index) { final membre = _membresDisponibles[index]; return ListTile( leading: CircleAvatar(child: Text(membre.initiales)), title: Text(membre.nomComplet), subtitle: Text(membre.email), onTap: () => setState(() => _selectedMembre = membre), ); }, ), ); } Widget _buildSelectedMembre() { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.green[50], border: Border.all(color: Colors.green[300]!), borderRadius: BorderRadius.circular(4), ), child: Row( children: [ CircleAvatar(child: Text(_selectedMembre!.initiales)), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _selectedMembre!.nomComplet, style: const TextStyle(fontWeight: FontWeight.bold), ), Text( _selectedMembre!.email, style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), IconButton( icon: const Icon(Icons.close, color: Colors.red), onPressed: () => setState(() => _selectedMembre = null), ), ], ), ); } Widget _buildTypeDropdown() { return DropdownButtonFormField( value: _selectedType, decoration: const InputDecoration( labelText: 'Type *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: TypeCotisation.values.map((type) { return DropdownMenuItem( value: type, child: Text(_getTypeLabel(type)), ); }).toList(), onChanged: (value) { setState(() { _selectedType = value!; _updatePeriodeFields(); }); }, ); } Widget _buildMontantField() { return TextFormField( controller: _montantController, decoration: const InputDecoration( labelText: 'Montant *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.attach_money), suffixText: 'XOF', ), keyboardType: TextInputType.number, validator: (value) { if (value == null || value.isEmpty) { return 'Le montant est obligatoire'; } final montant = double.tryParse(value); if (montant == null || montant <= 0) { return 'Le montant doit être supérieur à 0'; } return null; }, ); } Widget _buildPeriodeFields() { switch (_selectedType) { case TypeCotisation.mensuelle: return Row( children: [ Expanded( child: DropdownButtonFormField( value: _mois, decoration: const InputDecoration( labelText: 'Mois *', border: OutlineInputBorder(), ), items: List.generate(12, (index) { final mois = index + 1; return DropdownMenuItem( value: mois, child: Text(_getNomMois(mois)), ); }).toList(), onChanged: (value) => setState(() => _mois = value), validator: (value) => value == null ? 'Le mois est obligatoire' : null, ), ), const SizedBox(width: 12), Expanded( child: TextFormField( initialValue: _annee.toString(), decoration: const InputDecoration( labelText: 'Année *', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, onChanged: (value) => _annee = int.tryParse(value) ?? DateTime.now().year, ), ), ], ); case TypeCotisation.trimestrielle: return Row( children: [ Expanded( child: DropdownButtonFormField( value: _trimestre, decoration: const InputDecoration( labelText: 'Trimestre *', border: OutlineInputBorder(), ), items: const [ DropdownMenuItem(value: 1, child: Text('T1 (Jan-Mar)')), DropdownMenuItem(value: 2, child: Text('T2 (Avr-Juin)')), DropdownMenuItem(value: 3, child: Text('T3 (Juil-Sep)')), DropdownMenuItem(value: 4, child: Text('T4 (Oct-Déc)')), ], onChanged: (value) => setState(() => _trimestre = value), validator: (value) => value == null ? 'Le trimestre est obligatoire' : null, ), ), const SizedBox(width: 12), Expanded( child: TextFormField( initialValue: _annee.toString(), decoration: const InputDecoration( labelText: 'Année *', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, onChanged: (value) => _annee = int.tryParse(value) ?? DateTime.now().year, ), ), ], ); case TypeCotisation.semestrielle: return Row( children: [ Expanded( child: DropdownButtonFormField( value: _semestre, decoration: const InputDecoration( labelText: 'Semestre *', border: OutlineInputBorder(), ), items: const [ DropdownMenuItem(value: 1, child: Text('S1 (Jan-Juin)')), DropdownMenuItem(value: 2, child: Text('S2 (Juil-Déc)')), ], onChanged: (value) => setState(() => _semestre = value), validator: (value) => value == null ? 'Le semestre est obligatoire' : null, ), ), const SizedBox(width: 12), Expanded( child: TextFormField( initialValue: _annee.toString(), decoration: const InputDecoration( labelText: 'Année *', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, onChanged: (value) => _annee = int.tryParse(value) ?? DateTime.now().year, ), ), ], ); case TypeCotisation.annuelle: case TypeCotisation.exceptionnelle: return TextFormField( initialValue: _annee.toString(), decoration: const InputDecoration( labelText: 'Année *', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, onChanged: (value) => _annee = int.tryParse(value) ?? DateTime.now().year, ); } } Widget _buildDateEcheanceField() { return InkWell( onTap: () => _selectDateEcheance(context), child: InputDecorator( decoration: const InputDecoration( labelText: 'Date d\'échéance *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.calendar_today), ), child: Text(DateFormat('dd/MM/yyyy').format(_dateEcheance)), ), ); } Widget _buildDescriptionField() { return TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description', border: OutlineInputBorder(), prefixIcon: Icon(Icons.notes), ), maxLines: 3, ); } Widget _buildActionButtons() { return 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(0xFFEF4444), foregroundColor: Colors.white, ), child: const Text('Créer la cotisation'), ), ], ), ); } String _getTypeLabel(TypeCotisation type) { switch (type) { case TypeCotisation.annuelle: return 'Annuelle'; case TypeCotisation.mensuelle: return 'Mensuelle'; case TypeCotisation.trimestrielle: return 'Trimestrielle'; case TypeCotisation.semestrielle: return 'Semestrielle'; case TypeCotisation.exceptionnelle: return 'Exceptionnelle'; } } String _getNomMois(int mois) { const moisFr = [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ]; return (mois >= 1 && mois <= 12) ? moisFr[mois - 1] : 'Mois $mois'; } void _updatePeriodeFields() { _mois = null; _trimestre = null; _semestre = null; final now = DateTime.now(); switch (_selectedType) { case TypeCotisation.mensuelle: _mois = now.month; break; case TypeCotisation.trimestrielle: _trimestre = ((now.month - 1) ~/ 3) + 1; break; case TypeCotisation.semestrielle: _semestre = now.month <= 6 ? 1 : 2; break; default: break; } } Future _selectDateEcheance(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: _dateEcheance, firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365 * 2)), ); if (picked != null && picked != _dateEcheance) { setState(() => _dateEcheance = picked); } } void _submitForm() { if (_formKey.currentState!.validate()) { if (_selectedMembre == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez sélectionner un membre'), backgroundColor: Colors.red, ), ); return; } final cotisation = CotisationModel( membreId: _selectedMembre!.id!, membreNom: _selectedMembre!.nom, membrePrenom: _selectedMembre!.prenom, type: _selectedType, montant: double.parse(_montantController.text), dateEcheance: _dateEcheance, annee: _annee, mois: _mois, trimestre: _trimestre, semestre: _semestre, description: _descriptionController.text.isNotEmpty ? _descriptionController.text : null, statut: StatutCotisation.nonPayee, ); context.read().add(CreateCotisation(cotisation: cotisation)); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Cotisation créée avec succès'), backgroundColor: Colors.green, ), ); } } }