import 'package:flutter/material.dart'; import '../../theme/app_theme.dart'; import '../cards/unified_card_widget.dart'; /// Section KPI unifiée pour afficher des indicateurs clés /// /// Fournit : /// - Cartes KPI avec animations /// - Layouts adaptatifs (grille ou liste) /// - Indicateurs de tendance /// - Couleurs thématiques class UnifiedKPISection extends StatelessWidget { /// Liste des KPI à afficher final List kpis; /// Titre de la section final String? title; /// Nombre de colonnes dans la grille (par défaut : 2) final int crossAxisCount; /// Espacement entre les cartes final double spacing; /// Callback lors du tap sur un KPI final void Function(UnifiedKPIData kpi)? onKPITap; const UnifiedKPISection({ super.key, required this.kpis, this.title, this.crossAxisCount = 2, this.spacing = 16.0, this.onKPITap, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (title != null) ...[ Text( title!, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 16), ], _buildKPIGrid(), ], ); } Widget _buildKPIGrid() { return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: spacing, mainAxisSpacing: spacing, childAspectRatio: 1.4, ), itemCount: kpis.length, itemBuilder: (context, index) { final kpi = kpis[index]; return UnifiedCard.kpi( onTap: onKPITap != null ? () => onKPITap!(kpi) : null, child: _buildKPIContent(kpi), ); }, ); } Widget _buildKPIContent(UnifiedKPIData kpi) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // En-tête avec icône et titre Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: kpi.color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( kpi.icon, color: kpi.color, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Text( kpi.title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppTheme.textSecondary, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 12), // Valeur principale Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Text( kpi.value, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w700, color: AppTheme.textPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), if (kpi.trend != null) ...[ const SizedBox(width: 8), _buildTrendIndicator(kpi.trend!), ], ], ), // Sous-titre ou description if (kpi.subtitle != null) ...[ const SizedBox(height: 4), Text( kpi.subtitle!, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ], ); } Widget _buildTrendIndicator(UnifiedKPITrend trend) { IconData icon; Color color; switch (trend.direction) { case UnifiedKPITrendDirection.up: icon = Icons.trending_up; color = AppTheme.successColor; break; case UnifiedKPITrendDirection.down: icon = Icons.trending_down; color = AppTheme.errorColor; break; case UnifiedKPITrendDirection.stable: icon = Icons.trending_flat; color = AppTheme.textSecondary; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 12, color: color, ), const SizedBox(width: 2), Text( trend.value, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: color, ), ), ], ), ); } } /// Données pour un KPI unifié class UnifiedKPIData { /// Titre du KPI final String title; /// Valeur principale à afficher final String value; /// Sous-titre ou description optionnelle final String? subtitle; /// Icône représentative final IconData icon; /// Couleur thématique final Color color; /// Indicateur de tendance optionnel final UnifiedKPITrend? trend; /// Données supplémentaires pour les callbacks final Map? metadata; const UnifiedKPIData({ required this.title, required this.value, required this.icon, required this.color, this.subtitle, this.trend, this.metadata, }); } /// Indicateur de tendance pour les KPI class UnifiedKPITrend { /// Direction de la tendance final UnifiedKPITrendDirection direction; /// Valeur de la tendance (ex: "+12%", "-5", "stable") final String value; /// Label descriptif de la tendance (ex: "ce mois", "vs mois dernier") final String? label; const UnifiedKPITrend({ required this.direction, required this.value, this.label, }); } /// Direction de tendance disponibles enum UnifiedKPITrendDirection { up, down, stable, }