Clean project: remove test files, debug logs, and add documentation

This commit is contained in:
dahoud
2025-10-05 13:41:33 +00:00
parent 96a17eadbd
commit 291847924c
438 changed files with 65754 additions and 32713 deletions

View File

@@ -0,0 +1,572 @@
/// 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<CreateCotisationDialog> createState() => _CreateCotisationDialogState();
}
class _CreateCotisationDialogState extends State<CreateCotisationDialog> {
final _formKey = GlobalKey<FormState>();
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<MembreCompletModel> _membresDisponibles = [];
@override
void initState() {
super.initState();
context.read<MembresBloc>().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<MembresBloc, MembresState>(
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<MembresBloc>().add(const LoadActiveMembres());
},
)
: null,
),
onChanged: (value) {
if (value.isNotEmpty) {
context.read<MembresBloc>().add(LoadMembres(recherche: value));
} else {
context.read<MembresBloc>().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<TypeCotisation>(
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<int>(
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<int>(
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<int>(
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<void> _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<CotisationsBloc>().add(CreateCotisation(cotisation: cotisation));
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cotisation créée avec succès'),
backgroundColor: Colors.green,
),
);
}
}
}

View File

@@ -0,0 +1,396 @@
/// Dialogue de paiement de cotisation
/// Formulaire pour enregistrer un paiement de cotisation
library payment_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';
/// Dialogue de paiement de cotisation
class PaymentDialog extends StatefulWidget {
final CotisationModel cotisation;
const PaymentDialog({
super.key,
required this.cotisation,
});
@override
State<PaymentDialog> createState() => _PaymentDialogState();
}
class _PaymentDialogState extends State<PaymentDialog> {
final _formKey = GlobalKey<FormState>();
final _montantController = TextEditingController();
final _referenceController = TextEditingController();
final _notesController = TextEditingController();
MethodePaiement _selectedMethode = MethodePaiement.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<MethodePaiement>(
value: _selectedMethode,
decoration: const InputDecoration(
labelText: 'Méthode de paiement *',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.payment),
),
items: MethodePaiement.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(MethodePaiement methode) {
switch (methode) {
case MethodePaiement.waveMoney:
return Icons.phone_android;
case MethodePaiement.orangeMoney:
return Icons.phone_iphone;
case MethodePaiement.freeMoney:
return Icons.smartphone;
case MethodePaiement.mobileMoney:
return Icons.mobile_friendly;
case MethodePaiement.especes:
return Icons.money;
case MethodePaiement.cheque:
return Icons.receipt_long;
case MethodePaiement.virement:
return Icons.account_balance;
case MethodePaiement.carteBancaire:
return Icons.credit_card;
case MethodePaiement.autre:
return Icons.more_horiz;
}
}
String _getMethodeLabel(MethodePaiement methode) {
switch (methode) {
case MethodePaiement.waveMoney:
return 'Wave Money';
case MethodePaiement.orangeMoney:
return 'Orange Money';
case MethodePaiement.freeMoney:
return 'Free Money';
case MethodePaiement.especes:
return 'Espèces';
case MethodePaiement.cheque:
return 'Chèque';
case MethodePaiement.virement:
return 'Virement bancaire';
case MethodePaiement.carteBancaire:
return 'Carte bancaire';
case MethodePaiement.mobileMoney:
return 'Mobile Money (autre)';
case MethodePaiement.autre:
return 'Autre';
}
}
Future<void> _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
final cotisationUpdated = 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
? StatutCotisation.payee
: StatutCotisation.partielle,
);
// Envoyer l'événement au BLoC
context.read<CotisationsBloc>().add(EnregistrerPaiement(
cotisationId: 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,
),
);
}
}
}