/// Dialogue de paiement de contribution /// Formulaire pour enregistrer un paiement de contribution library payment_dialog; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../bloc/contributions_bloc.dart'; import '../../bloc/contributions_event.dart'; import '../../data/models/contribution_model.dart'; /// Dialogue de paiement de contribution class PaymentDialog extends StatefulWidget { final ContributionModel cotisation; const PaymentDialog({ super.key, required this.cotisation, }); @override State createState() => _PaymentDialogState(); } class _PaymentDialogState extends State { final _formKey = GlobalKey(); final _montantController = TextEditingController(); final _referenceController = TextEditingController(); final _notesController = TextEditingController(); PaymentMethod _selectedMethode = PaymentMethod.waveMoney; DateTime _datePaiement = DateTime.now(); @override void initState() { super.initState(); // Pré-remplir avec le montant restant _montantController.text = widget.cotisation.montantRestant.toStringAsFixed(0); } @override void dispose() { _montantController.dispose(); _referenceController.dispose(); _notesController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Dialog( child: Container( width: MediaQuery.of(context).size.width * 0.9, constraints: const BoxConstraints(maxHeight: 500), child: Column( mainAxisSize: MainAxisSize.min, children: [ // En-tête Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Color(0xFF10B981), borderRadius: BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), child: Row( children: [ const Icon(Icons.payment, color: Colors.white), const SizedBox(width: 12), const Text( 'Enregistrer un paiement', 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), ), ], ), ), // Informations de la cotisation Container( padding: const EdgeInsets.all(16), color: Colors.grey[100], child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.cotisation.membreNomComplet, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( widget.cotisation.libellePeriode, style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Montant total:', style: TextStyle(color: Colors.grey[600]), ), Text( '${NumberFormat('#,###').format(widget.cotisation.montant)} ${widget.cotisation.devise}', style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Déjà payé:', style: TextStyle(color: Colors.grey[600]), ), Text( '${NumberFormat('#,###').format(widget.cotisation.montantPaye ?? 0)} ${widget.cotisation.devise}', style: const TextStyle(color: Colors.green), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Restant:', style: TextStyle(color: Colors.grey[600]), ), Text( '${NumberFormat('#,###').format(widget.cotisation.montantRestant)} ${widget.cotisation.devise}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.red, ), ), ], ), ], ), ), // Formulaire Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Montant TextFormField( controller: _montantController, decoration: InputDecoration( labelText: 'Montant à payer *', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.attach_money), suffixText: widget.cotisation.devise, ), 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 'Montant invalide'; } if (montant > widget.cotisation.montantRestant) { return 'Montant supérieur au restant dû'; } return null; }, ), const SizedBox(height: 12), // Méthode de paiement DropdownButtonFormField( value: _selectedMethode, decoration: const InputDecoration( labelText: 'Méthode de paiement *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.payment), ), items: PaymentMethod.values.map((methode) { return DropdownMenuItem( value: methode, child: Row( children: [ Icon(_getMethodeIcon(methode), size: 20), const SizedBox(width: 8), Text(_getMethodeLabel(methode)), ], ), ); }).toList(), onChanged: (value) { setState(() { _selectedMethode = value!; }); }, ), const SizedBox(height: 12), // Date de paiement InkWell( onTap: () => _selectDate(context), child: InputDecorator( decoration: const InputDecoration( labelText: 'Date de paiement *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.calendar_today), ), child: Text( DateFormat('dd/MM/yyyy').format(_datePaiement), ), ), ), const SizedBox(height: 12), // Référence TextFormField( controller: _referenceController, decoration: const InputDecoration( labelText: 'Référence de transaction', border: OutlineInputBorder(), prefixIcon: Icon(Icons.receipt), hintText: 'Ex: TRX123456789', ), ), const SizedBox(height: 12), // Notes TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes (optionnel)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.note), ), maxLines: 2, ), ], ), ), ), ), // 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(0xFF10B981), foregroundColor: Colors.white, ), child: const Text('Enregistrer le paiement'), ), ], ), ), ], ), ), ); } IconData _getMethodeIcon(PaymentMethod methode) { switch (methode) { case PaymentMethod.waveMoney: return Icons.phone_android; case PaymentMethod.orangeMoney: return Icons.phone_iphone; case PaymentMethod.freeMoney: return Icons.smartphone; case PaymentMethod.mobileMoney: return Icons.mobile_friendly; case PaymentMethod.especes: return Icons.money; case PaymentMethod.cheque: return Icons.receipt_long; case PaymentMethod.virement: return Icons.account_balance; case PaymentMethod.carteBancaire: return Icons.credit_card; case PaymentMethod.autre: return Icons.more_horiz; } } String _getMethodeLabel(PaymentMethod methode) { switch (methode) { case PaymentMethod.waveMoney: return 'Wave Money'; case PaymentMethod.orangeMoney: return 'Orange Money'; case PaymentMethod.freeMoney: return 'Free Money'; case PaymentMethod.especes: return 'Espèces'; case PaymentMethod.cheque: return 'Chèque'; case PaymentMethod.virement: return 'Virement bancaire'; case PaymentMethod.carteBancaire: return 'Carte bancaire'; case PaymentMethod.mobileMoney: return 'Mobile Money (autre)'; case PaymentMethod.autre: return 'Autre'; } } Future _selectDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: _datePaiement, firstDate: DateTime(2020), lastDate: DateTime.now(), ); if (picked != null && picked != _datePaiement) { setState(() { _datePaiement = picked; }); } } void _submitForm() { if (_formKey.currentState!.validate()) { final montant = double.parse(_montantController.text); // Créer la cotisation mise à jour widget.cotisation.copyWith( montantPaye: (widget.cotisation.montantPaye ?? 0) + montant, datePaiement: _datePaiement, methodePaiement: _selectedMethode, referencePaiement: _referenceController.text.isNotEmpty ? _referenceController.text : null, notes: _notesController.text.isNotEmpty ? _notesController.text : null, statut: (widget.cotisation.montantPaye ?? 0) + montant >= widget.cotisation.montant ? ContributionStatus.payee : ContributionStatus.partielle, ); // Envoyer l'événement au BLoC context.read().add(RecordPayment( contributionId: widget.cotisation.id!, montant: montant, methodePaiement: _selectedMethode, datePaiement: _datePaiement, reference: _referenceController.text.isNotEmpty ? _referenceController.text : null, notes: _notesController.text.isNotEmpty ? _notesController.text : null, )); // Fermer le dialogue Navigator.pop(context); // Afficher un message de succès ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Paiement enregistré avec succès'), backgroundColor: Colors.green, ), ); } } }