Version propre - Dashboard enhanced

This commit is contained in:
DahoudG
2025-09-13 19:05:06 +00:00
parent 3df010add7
commit 73459b3092
70 changed files with 15317 additions and 1498 deletions

View File

@@ -0,0 +1,316 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../core/models/cotisation_model.dart';
import '../../../../shared/theme/app_theme.dart';
/// Widget card pour afficher une cotisation
class CotisationCard extends StatelessWidget {
final CotisationModel cotisation;
final VoidCallback? onTap;
final VoidCallback? onPay;
final VoidCallback? onEdit;
final VoidCallback? onDelete;
const CotisationCard({
super.key,
required this.cotisation,
this.onTap,
this.onPay,
this.onEdit,
this.onDelete,
});
@override
Widget build(BuildContext context) {
final currencyFormat = NumberFormat.currency(
locale: 'fr_FR',
symbol: 'FCFA',
decimalDigits: 0,
);
final dateFormat = DateFormat('dd/MM/yyyy', 'fr_FR');
return Card(
elevation: 2,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: _getStatusColor().withOpacity(0.3),
width: 1,
),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header avec statut et actions
Row(
children: [
// Statut badge
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _getStatusColor().withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
cotisation.libelleStatut,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _getStatusColor(),
),
),
),
const Spacer(),
// Actions
if (cotisation.statut == 'EN_ATTENTE' || cotisation.statut == 'EN_RETARD')
IconButton(
onPressed: onPay,
icon: const Icon(Icons.payment, size: 20),
color: AppTheme.successColor,
tooltip: 'Payer',
),
if (onEdit != null)
IconButton(
onPressed: onEdit,
icon: const Icon(Icons.edit, size: 20),
color: AppTheme.primaryColor,
tooltip: 'Modifier',
),
if (onDelete != null)
IconButton(
onPressed: onDelete,
icon: const Icon(Icons.delete, size: 20),
color: AppTheme.errorColor,
tooltip: 'Supprimer',
),
],
),
const SizedBox(height: 12),
// Informations principales
Row(
children: [
// Icône du type
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Text(
cotisation.iconeTypeCotisation,
style: const TextStyle(fontSize: 20),
),
),
),
const SizedBox(width: 12),
// Détails
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
cotisation.libelleTypeCotisation,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
if (cotisation.nomMembre != null) ...[
const SizedBox(height: 2),
Text(
cotisation.nomMembre!,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
],
if (cotisation.periode != null) ...[
const SizedBox(height: 2),
Text(
cotisation.periode!,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textHint,
),
),
],
],
),
),
// Montant
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
currencyFormat.format(cotisation.montantDu),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
if (cotisation.montantPaye > 0) ...[
const SizedBox(height: 2),
Text(
'Payé: ${currencyFormat.format(cotisation.montantPaye)}',
style: const TextStyle(
fontSize: 12,
color: AppTheme.successColor,
),
),
],
],
),
],
),
const SizedBox(height: 12),
// Barre de progression du paiement
if (cotisation.montantPaye > 0 && !cotisation.isEntierementPayee) ...[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Progression',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
Text(
'${cotisation.pourcentagePaiement.toStringAsFixed(0)}%',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppTheme.textSecondary,
),
),
],
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: cotisation.pourcentagePaiement / 100,
backgroundColor: AppTheme.borderColor,
valueColor: AlwaysStoppedAnimation<Color>(
cotisation.pourcentagePaiement >= 100
? AppTheme.successColor
: AppTheme.primaryColor,
),
),
],
),
const SizedBox(height: 12),
],
// Informations d'échéance
Row(
children: [
Icon(
Icons.schedule,
size: 16,
color: cotisation.isEnRetard
? AppTheme.errorColor
: cotisation.echeanceProche
? AppTheme.warningColor
: AppTheme.textHint,
),
const SizedBox(width: 4),
Text(
'Échéance: ${dateFormat.format(cotisation.dateEcheance)}',
style: TextStyle(
fontSize: 12,
color: cotisation.isEnRetard
? AppTheme.errorColor
: cotisation.echeanceProche
? AppTheme.warningColor
: AppTheme.textSecondary,
),
),
if (cotisation.messageUrgence.isNotEmpty) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: cotisation.isEnRetard
? AppTheme.errorColor.withOpacity(0.1)
: AppTheme.warningColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
cotisation.messageUrgence,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: cotisation.isEnRetard
? AppTheme.errorColor
: AppTheme.warningColor,
),
),
),
],
],
),
// Référence
const SizedBox(height: 8),
Row(
children: [
const Icon(
Icons.tag,
size: 16,
color: AppTheme.textHint,
),
const SizedBox(width: 4),
Text(
'Réf: ${cotisation.numeroReference}',
style: const TextStyle(
fontSize: 12,
color: AppTheme.textHint,
),
),
],
),
],
),
),
),
);
}
Color _getStatusColor() {
switch (cotisation.statut) {
case 'PAYEE':
return AppTheme.successColor;
case 'EN_ATTENTE':
return AppTheme.warningColor;
case 'EN_RETARD':
return AppTheme.errorColor;
case 'PARTIELLEMENT_PAYEE':
return AppTheme.infoColor;
case 'ANNULEE':
return AppTheme.textHint;
default:
return AppTheme.textSecondary;
}
}
}

View File

@@ -0,0 +1,283 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../shared/theme/app_theme.dart';
/// Widget pour afficher les statistiques des cotisations
class CotisationsStatsCard extends StatelessWidget {
final Map<String, dynamic> statistics;
const CotisationsStatsCard({
super.key,
required this.statistics,
});
@override
Widget build(BuildContext context) {
final currencyFormat = NumberFormat.currency(
locale: 'fr_FR',
symbol: 'FCFA',
decimalDigits: 0,
);
final totalCotisations = statistics['totalCotisations'] as int? ?? 0;
final cotisationsPayees = statistics['cotisationsPayees'] as int? ?? 0;
final cotisationsEnRetard = statistics['cotisationsEnRetard'] as int? ?? 0;
final tauxPaiement = statistics['tauxPaiement'] as double? ?? 0.0;
return Card(
elevation: 2,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Titre
Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: AppTheme.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
Icons.analytics,
size: 18,
color: AppTheme.accentColor,
),
),
const SizedBox(width: 12),
const Text(
'Statistiques des cotisations',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 16),
// Grille des statistiques
Row(
children: [
// Total des cotisations
Expanded(
child: _buildStatItem(
icon: Icons.receipt_long,
label: 'Total',
value: totalCotisations.toString(),
color: AppTheme.primaryColor,
),
),
const SizedBox(width: 12),
// Cotisations payées
Expanded(
child: _buildStatItem(
icon: Icons.check_circle,
label: 'Payées',
value: cotisationsPayees.toString(),
color: AppTheme.successColor,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
// Cotisations en retard
Expanded(
child: _buildStatItem(
icon: Icons.warning,
label: 'En retard',
value: cotisationsEnRetard.toString(),
color: AppTheme.errorColor,
),
),
const SizedBox(width: 12),
// Taux de paiement
Expanded(
child: _buildStatItem(
icon: Icons.trending_up,
label: 'Taux paiement',
value: '${tauxPaiement.toStringAsFixed(1)}%',
color: tauxPaiement >= 80
? AppTheme.successColor
: tauxPaiement >= 60
? AppTheme.warningColor
: AppTheme.errorColor,
),
),
],
),
const SizedBox(height: 16),
// Barre de progression globale
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Progression globale',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppTheme.textSecondary,
),
),
Text(
'${tauxPaiement.toStringAsFixed(1)}%',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: tauxPaiement / 100,
backgroundColor: AppTheme.borderColor,
valueColor: AlwaysStoppedAnimation<Color>(
tauxPaiement >= 80
? AppTheme.successColor
: tauxPaiement >= 60
? AppTheme.warningColor
: AppTheme.errorColor,
),
),
],
),
// Montants si disponibles
if (statistics.containsKey('montantTotal') ||
statistics.containsKey('montantPaye')) ...[
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
Row(
children: [
if (statistics.containsKey('montantTotal')) ...[
Expanded(
child: _buildMoneyStatItem(
label: 'Montant total',
value: currencyFormat.format(
(statistics['montantTotal'] as num?)?.toDouble() ?? 0.0
),
color: AppTheme.textPrimary,
),
),
],
if (statistics.containsKey('montantTotal') &&
statistics.containsKey('montantPaye'))
const SizedBox(width: 12),
if (statistics.containsKey('montantPaye')) ...[
Expanded(
child: _buildMoneyStatItem(
label: 'Montant payé',
value: currencyFormat.format(
(statistics['montantPaye'] as num?)?.toDouble() ?? 0.0
),
color: AppTheme.successColor,
),
),
],
],
),
],
],
),
),
);
}
Widget _buildStatItem({
required IconData icon,
required String label,
required String value,
required Color color,
}) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Icon(
icon,
size: 24,
color: color,
),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildMoneyStatItem({
required String label,
required String value,
required Color color,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
],
);
}
}