Refactoring
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../shared/widgets/common/unified_card.dart';
|
||||
import '../../../../shared/theme/design_system.dart';
|
||||
import '../../../../core/utils/formatters.dart';
|
||||
import '../../domain/entities/analytics_data.dart';
|
||||
|
||||
/// Widget de carte KPI utilisant le design system unifié
|
||||
class KPICardWidget extends StatelessWidget {
|
||||
const KPICardWidget({
|
||||
super.key,
|
||||
required this.analyticsData,
|
||||
this.onTap,
|
||||
this.showTrend = true,
|
||||
this.showDetails = false,
|
||||
this.compact = false,
|
||||
});
|
||||
|
||||
final AnalyticsData analyticsData;
|
||||
final VoidCallback? onTap;
|
||||
final bool showTrend;
|
||||
final bool showDetails;
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UnifiedCard(
|
||||
variant: UnifiedCardVariant.elevated,
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
compact ? DesignSystem.spacing12 : DesignSystem.spacing16,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// En-tête avec icône et titre
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(DesignSystem.spacing8),
|
||||
decoration: BoxDecoration(
|
||||
color: _getCouleurMetrique().withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(DesignSystem.radius8),
|
||||
),
|
||||
child: Icon(
|
||||
_getIconeMetrique(),
|
||||
color: _getCouleurMetrique(),
|
||||
size: compact ? 20 : 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: DesignSystem.spacing12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
analyticsData.libelleAffichage,
|
||||
style: compact
|
||||
? DesignSystem.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
)
|
||||
: DesignSystem.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (!compact && analyticsData.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: DesignSystem.spacing4,
|
||||
),
|
||||
child: Text(
|
||||
analyticsData.description!,
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Indicateur de fiabilité
|
||||
if (showDetails)
|
||||
_buildIndicateurFiabilite(),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: compact ? DesignSystem.spacing8 : DesignSystem.spacing16),
|
||||
|
||||
// Valeur principale
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
analyticsData.valeurFormatee,
|
||||
style: compact
|
||||
? DesignSystem.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getCouleurMetrique(),
|
||||
)
|
||||
: DesignSystem.textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getCouleurMetrique(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Évolution
|
||||
if (showTrend && analyticsData.pourcentageEvolution != null)
|
||||
_buildEvolution(),
|
||||
],
|
||||
),
|
||||
|
||||
// Détails supplémentaires
|
||||
if (showDetails) ...[
|
||||
const SizedBox(height: DesignSystem.spacing12),
|
||||
_buildDetails(),
|
||||
],
|
||||
|
||||
// Période et dernière mise à jour
|
||||
if (!compact) ...[
|
||||
const SizedBox(height: DesignSystem.spacing12),
|
||||
_buildInfosPeriode(),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget d'évolution avec icône et pourcentage
|
||||
Widget _buildEvolution() {
|
||||
final evolution = analyticsData.pourcentageEvolution!;
|
||||
final isPositive = evolution > 0;
|
||||
final isNegative = evolution < 0;
|
||||
|
||||
Color couleur;
|
||||
IconData icone;
|
||||
|
||||
if (isPositive) {
|
||||
couleur = DesignSystem.successColor;
|
||||
icone = Icons.trending_up;
|
||||
} else if (isNegative) {
|
||||
couleur = DesignSystem.errorColor;
|
||||
icone = Icons.trending_down;
|
||||
} else {
|
||||
couleur = DesignSystem.warningColor;
|
||||
icone = Icons.trending_flat;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: DesignSystem.spacing8,
|
||||
vertical: DesignSystem.spacing4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: couleur.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(DesignSystem.radius12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icone,
|
||||
size: 16,
|
||||
color: couleur,
|
||||
),
|
||||
const SizedBox(width: DesignSystem.spacing4),
|
||||
Text(
|
||||
analyticsData.evolutionFormatee,
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: couleur,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget d'indicateur de fiabilité
|
||||
Widget _buildIndicateurFiabilite() {
|
||||
final fiabilite = analyticsData.indicateurFiabilite;
|
||||
Color couleur;
|
||||
|
||||
if (fiabilite >= 90) {
|
||||
couleur = DesignSystem.successColor;
|
||||
} else if (fiabilite >= 70) {
|
||||
couleur = DesignSystem.warningColor;
|
||||
} else {
|
||||
couleur = DesignSystem.errorColor;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: DesignSystem.spacing6,
|
||||
vertical: DesignSystem.spacing2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: couleur.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(DesignSystem.radius8),
|
||||
border: Border.all(
|
||||
color: couleur.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'${fiabilite.toStringAsFixed(0)}%',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: couleur,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget des détails supplémentaires
|
||||
Widget _buildDetails() {
|
||||
return Column(
|
||||
children: [
|
||||
// Valeur précédente
|
||||
if (analyticsData.valeurPrecedente != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Période précédente',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formaterValeur(analyticsData.valeurPrecedente!),
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: DesignSystem.spacing4),
|
||||
|
||||
// Éléments analysés
|
||||
if (analyticsData.nombreElementsAnalyses != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Éléments analysés',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
analyticsData.nombreElementsAnalyses.toString(),
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: DesignSystem.spacing4),
|
||||
|
||||
// Temps de calcul
|
||||
if (analyticsData.tempsCalculMs != null)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Temps de calcul',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${analyticsData.tempsCalculMs}ms',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget des informations de période
|
||||
Widget _buildInfosPeriode() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
analyticsData.periodeAnalyse.libelle,
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Mis à jour ${AppFormatters.formatDateRelative(analyticsData.dateCalcul)}',
|
||||
style: DesignSystem.textTheme.bodySmall?.copyWith(
|
||||
color: DesignSystem.textSecondaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Obtient la couleur de la métrique
|
||||
Color _getCouleurMetrique() {
|
||||
return Color(int.parse(
|
||||
analyticsData.couleur.replaceFirst('#', '0xFF'),
|
||||
));
|
||||
}
|
||||
|
||||
/// Obtient l'icône de la métrique
|
||||
IconData _getIconeMetrique() {
|
||||
switch (analyticsData.icone) {
|
||||
case 'people':
|
||||
return Icons.people;
|
||||
case 'attach_money':
|
||||
return Icons.attach_money;
|
||||
case 'event':
|
||||
return Icons.event;
|
||||
case 'favorite':
|
||||
return Icons.favorite;
|
||||
case 'trending_up':
|
||||
return Icons.trending_up;
|
||||
case 'business':
|
||||
return Icons.business;
|
||||
case 'settings':
|
||||
return Icons.settings;
|
||||
default:
|
||||
return Icons.analytics;
|
||||
}
|
||||
}
|
||||
|
||||
/// Formate une valeur selon le type de métrique
|
||||
String _formaterValeur(double valeur) {
|
||||
switch (analyticsData.typeMetrique.typeValeur) {
|
||||
case 'amount':
|
||||
return '${valeur.toStringAsFixed(0)} ${analyticsData.unite}';
|
||||
case 'percentage':
|
||||
return '${valeur.toStringAsFixed(1)}${analyticsData.unite}';
|
||||
case 'average':
|
||||
return valeur.toStringAsFixed(1);
|
||||
default:
|
||||
return valeur.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user