import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/models/cotisation_model.dart'; import '../../../../core/models/membre_model.dart'; import '../../../../shared/theme/app_theme.dart'; import '../../../../shared/widgets/custom_text_field.dart'; import '../../../../shared/widgets/loading_button.dart'; import '../bloc/cotisations_bloc.dart'; import '../bloc/cotisations_event.dart'; import '../bloc/cotisations_state.dart'; /// Page de création d'une nouvelle cotisation class CotisationCreatePage extends StatefulWidget { final MembreModel? membre; // Membre pré-sélectionné (optionnel) const CotisationCreatePage({ super.key, this.membre, }); @override State createState() => _CotisationCreatePageState(); } class _CotisationCreatePageState extends State { final _formKey = GlobalKey(); late CotisationsBloc _cotisationsBloc; // Contrôleurs de champs final _montantController = TextEditingController(); final _descriptionController = TextEditingController(); final _periodeController = TextEditingController(); // Valeurs sélectionnées String _typeCotisation = 'MENSUELLE'; DateTime _dateEcheance = DateTime.now().add(const Duration(days: 30)); MembreModel? _membreSelectionne; // Options disponibles final List _typesCotisation = [ 'MENSUELLE', 'TRIMESTRIELLE', 'SEMESTRIELLE', 'ANNUELLE', 'EXCEPTIONNELLE', ]; @override void initState() { super.initState(); _cotisationsBloc = getIt(); _membreSelectionne = widget.membre; // Pré-remplir la période selon le type _updatePeriodeFromType(); } @override void dispose() { _montantController.dispose(); _descriptionController.dispose(); _periodeController.dispose(); super.dispose(); } void _updatePeriodeFromType() { final now = DateTime.now(); String periode; switch (_typeCotisation) { case 'MENSUELLE': periode = '${_getMonthName(now.month)} ${now.year}'; break; case 'TRIMESTRIELLE': final trimestre = ((now.month - 1) ~/ 3) + 1; periode = 'T$trimestre ${now.year}'; break; case 'SEMESTRIELLE': final semestre = now.month <= 6 ? 1 : 2; periode = 'S$semestre ${now.year}'; break; case 'ANNUELLE': periode = '${now.year}'; break; case 'EXCEPTIONNELLE': periode = 'Exceptionnelle ${now.day}/${now.month}/${now.year}'; break; default: periode = '${now.month}/${now.year}'; } _periodeController.text = periode; } String _getMonthName(int month) { const months = [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ]; return months[month - 1]; } void _onTypeChanged(String? newType) { if (newType != null) { setState(() { _typeCotisation = newType; _updatePeriodeFromType(); }); } } Future _selectDate() async { final picked = await showDatePicker( context: context, initialDate: _dateEcheance, firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), locale: const Locale('fr', 'FR'), ); if (picked != null) { setState(() { _dateEcheance = picked; }); } } Future _selectMembre() async { // TODO: Implémenter la sélection de membre // Pour l'instant, afficher un message ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Fonctionnalité de sélection de membre à implémenter'), backgroundColor: AppTheme.infoColor, ), ); } void _createCotisation() { if (!_formKey.currentState!.validate()) { return; } if (_membreSelectionne == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez sélectionner un membre'), backgroundColor: AppTheme.errorColor, ), ); return; } final montant = double.tryParse(_montantController.text.replaceAll(' ', '')); if (montant == null || montant <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez saisir un montant valide'), backgroundColor: AppTheme.errorColor, ), ); return; } // Créer la cotisation final cotisation = CotisationModel( id: '', // Sera généré par le backend numeroReference: '', // Sera généré par le backend membreId: _membreSelectionne!.id ?? '', nomMembre: _membreSelectionne!.nomComplet, typeCotisation: _typeCotisation, montantDu: montant, montantPaye: 0.0, dateEcheance: _dateEcheance, statut: 'EN_ATTENTE', description: _descriptionController.text.trim(), periode: _periodeController.text.trim(), annee: _dateEcheance.year, mois: _dateEcheance.month, codeDevise: 'XOF', recurrente: _typeCotisation != 'EXCEPTIONNELLE', nombreRappels: 0, dateCreation: DateTime.now(), dateModification: DateTime.now(), ); _cotisationsBloc.add(CreateCotisation(cotisation)); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _cotisationsBloc, child: Scaffold( backgroundColor: AppTheme.backgroundLight, appBar: AppBar( title: const Text('Nouvelle Cotisation'), backgroundColor: AppTheme.accentColor, foregroundColor: Colors.white, elevation: 0, ), body: BlocListener( listener: (context, state) { if (state is CotisationCreated) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Cotisation créée avec succès'), backgroundColor: AppTheme.successColor, ), ); Navigator.of(context).pop(true); } else if (state is CotisationsError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: AppTheme.errorColor, ), ); } }, child: Form( key: _formKey, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Sélection du membre _buildMembreSection(), const SizedBox(height: 24), // Type de cotisation _buildTypeSection(), const SizedBox(height: 24), // Montant _buildMontantSection(), const SizedBox(height: 24), // Période et échéance _buildPeriodeSection(), const SizedBox(height: 24), // Description _buildDescriptionSection(), const SizedBox(height: 32), // Bouton de création _buildCreateButton(), ], ), ), ), ), ), ); } Widget _buildMembreSection() { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Membre', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), if (_membreSelectionne != null) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.accentColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppTheme.accentColor.withOpacity(0.3)), ), child: Row( children: [ CircleAvatar( backgroundColor: AppTheme.accentColor, child: Text( _membreSelectionne!.nomComplet.substring(0, 1).toUpperCase(), style: const TextStyle(color: Colors.white), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _membreSelectionne!.nomComplet, style: const TextStyle( fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), Text( _membreSelectionne!.telephone.isNotEmpty ? _membreSelectionne!.telephone : 'Pas de téléphone', style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary, ), ), ], ), ), IconButton( icon: const Icon(Icons.change_circle), onPressed: _selectMembre, color: AppTheme.accentColor, ), ], ), ) else ElevatedButton.icon( onPressed: _selectMembre, icon: const Icon(Icons.person_add), label: const Text('Sélectionner un membre'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.accentColor, foregroundColor: Colors.white, minimumSize: const Size(double.infinity, 48), ), ), ], ), ), ); } Widget _buildTypeSection() { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Type de cotisation', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), DropdownButtonFormField( value: _typeCotisation, decoration: const InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), items: _typesCotisation.map((type) { return DropdownMenuItem( value: type, child: Text(_getTypeLabel(type)), ); }).toList(), onChanged: _onTypeChanged, ), ], ), ), ); } String _getTypeLabel(String type) { switch (type) { case 'MENSUELLE': return 'Mensuelle'; case 'TRIMESTRIELLE': return 'Trimestrielle'; case 'SEMESTRIELLE': return 'Semestrielle'; case 'ANNUELLE': return 'Annuelle'; case 'EXCEPTIONNELLE': return 'Exceptionnelle'; default: return type; } } Widget _buildMontantSection() { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Montant', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), CustomTextField( controller: _montantController, label: 'Montant (XOF)', keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, TextInputFormatter.withFunction((oldValue, newValue) { // Formater avec des espaces pour les milliers final text = newValue.text.replaceAll(' ', ''); if (text.isEmpty) return newValue; final number = int.tryParse(text); if (number == null) return oldValue; final formatted = number.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]} ', ); return TextEditingValue( text: formatted, selection: TextSelection.collapsed(offset: formatted.length), ); }), ], validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez saisir un montant'; } final montant = double.tryParse(value.replaceAll(' ', '')); if (montant == null || montant <= 0) { return 'Veuillez saisir un montant valide'; } return null; }, suffixIcon: const Icon(Icons.attach_money), ), ], ), ), ); } Widget _buildPeriodeSection() { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Période et échéance', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), CustomTextField( controller: _periodeController, label: 'Période', validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez saisir une période'; } return null; }, ), const SizedBox(height: 16), InkWell( onTap: _selectDate, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.calendar_today, color: AppTheme.accentColor), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Date d\'échéance', style: TextStyle( fontSize: 12, color: AppTheme.textSecondary, ), ), Text( '${_dateEcheance.day}/${_dateEcheance.month}/${_dateEcheance.year}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: AppTheme.textPrimary, ), ), ], ), ), const Icon(Icons.arrow_drop_down), ], ), ), ), ], ), ), ); } Widget _buildDescriptionSection() { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Description (optionnelle)', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), CustomTextField( controller: _descriptionController, label: 'Description de la cotisation', maxLines: 3, maxLength: 500, ), ], ), ), ); } Widget _buildCreateButton() { return BlocBuilder( builder: (context, state) { final isLoading = state is CotisationsLoading; return LoadingButton( onPressed: isLoading ? null : _createCotisation, isLoading: isLoading, text: 'Créer la cotisation', backgroundColor: AppTheme.accentColor, textColor: Colors.white, ); }, ); } }