import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fl_chart/fl_chart.dart'; import '../../../domain/entities/dashboard_entity.dart'; import '../../bloc/dashboard_bloc.dart'; import '../../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../../shared/widgets/core_card.dart'; /// Widget de graphique pour le dashboard class DashboardChartWidget extends StatelessWidget { final String title; final DashboardChartType chartType; final double height; const DashboardChartWidget({ super.key, required this.title, required this.chartType, this.height = 200, }); @override Widget build(BuildContext context) { return CoreCard( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const SizedBox(height: 16), SizedBox( height: height, child: BlocBuilder( builder: (context, state) { if (state is DashboardLoading) { return _buildLoadingChart(); } else if (state is DashboardLoaded || state is DashboardRefreshing) { final data = state is DashboardLoaded ? state.dashboardData : (state as DashboardRefreshing).dashboardData; return _buildChart(data); } else if (state is DashboardError) { return _buildErrorChart(); } return _buildEmptyChart(); }, ), ), ], ), ); } Widget _buildHeader() { return Row( children: [ Icon( _getChartIcon(), color: AppColors.primaryGreen, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( title.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ), ], ); } Widget _buildChart(DashboardEntity data) { switch (chartType) { case DashboardChartType.memberActivity: return _buildMemberActivityChart(data.stats); case DashboardChartType.contributionTrend: return _buildContributionTrendChart(data.stats); case DashboardChartType.eventParticipation: return _buildEventParticipationChart(data.upcomingEvents); case DashboardChartType.monthlyGrowth: return _buildMonthlyGrowthChart(data.stats); } } Widget _buildMemberActivityChart(DashboardStatsEntity stats) { return PieChart( PieChartData( sectionsSpace: 2, centerSpaceRadius: 40, sections: [ PieChartSectionData( color: AppColors.success, value: stats.activeMembers.toDouble(), title: '${stats.activeMembers}', radius: 50, titleStyle: AppTypography.badgeText.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), PieChartSectionData( color: AppColors.lightBorder, value: (stats.totalMembers - stats.activeMembers).toDouble(), title: '${stats.totalMembers - stats.activeMembers}', radius: 45, titleStyle: AppTypography.badgeText.copyWith( color: AppColors.textSecondaryLight, fontWeight: FontWeight.bold, ), ), ], ), ); } Widget _buildContributionTrendChart(DashboardStatsEntity stats) { return LineChart( LineChartData( gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: stats.totalContributionAmount / 4, getDrawingHorizontalLine: (value) { return const FlLine( color: AppColors.lightBorder, strokeWidth: 1, ); }, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, interval: 1, getTitlesWidget: (double value, TitleMeta meta) { const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun']; if (value.toInt() >= 0 && value.toInt() < months.length) { return Text( months[value.toInt()], style: AppTypography.subtitleSmall.copyWith(fontSize: 8), ); } return const Text(''); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: stats.totalContributionAmount / 4, reservedSize: 60, getTitlesWidget: (double value, TitleMeta meta) { return Text( '${(value / 1000).toStringAsFixed(0)}K', style: AppTypography.subtitleSmall.copyWith(fontSize: 8), ); }, ), ), ), borderData: FlBorderData(show: false), minX: 0, maxX: 5, minY: 0, maxY: stats.totalContributionAmount, lineBarsData: [ LineChartBarData( spots: _generateContributionSpots(stats), isCurved: true, gradient: const LinearGradient( colors: [ AppColors.brandGreen, AppColors.primaryGreen, ], ), barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData(show: true), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ AppColors.brandGreen.withOpacity(0.3), AppColors.primaryGreen.withOpacity(0.1), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ], ), ); } Widget _buildEventParticipationChart(List events) { if (events.isEmpty) { return _buildEmptyChart(); } return BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, maxY: events.map((e) => e.maxParticipants).reduce((a, b) => a > b ? a : b).toDouble(), barTouchData: BarTouchData(enabled: false), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (double value, TitleMeta meta) { if (value.toInt() < events.length) { return Padding( padding: const EdgeInsets.only(top: 8), child: Text( events[value.toInt()].title.length > 8 ? '${events[value.toInt()].title.substring(0, 8)}...' : events[value.toInt()].title, style: AppTypography.subtitleSmall.copyWith(fontSize: 8), textAlign: TextAlign.center, ), ); } return const Text(''); }, reservedSize: 40, ), ), leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), barGroups: events.asMap().entries.map((entry) { final index = entry.key; final event = entry.value; return BarChartGroupData( x: index, barRods: [ BarChartRodData( toY: event.currentParticipants.toDouble(), color: event.isFull ? AppColors.error : event.isAlmostFull ? AppColors.warning : AppColors.success, width: 16, borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), ], ); }).toList(), ), ); } Widget _buildMonthlyGrowthChart(DashboardStatsEntity stats) { return LineChart( LineChartData( gridData: const FlGridData(show: false), titlesData: const FlTitlesData(show: false), borderData: FlBorderData(show: false), minX: 0, maxX: 11, minY: -5, maxY: 20, lineBarsData: [ LineChartBarData( spots: _generateGrowthSpots(stats.monthlyGrowth), isCurved: true, color: stats.hasGrowth ? AppColors.success : AppColors.error, barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData( show: true, color: (stats.hasGrowth ? AppColors.success : AppColors.error) .withOpacity(0.2), ), ), ], ), ); } List _generateContributionSpots(DashboardStatsEntity stats) { final baseAmount = stats.totalContributionAmount / 6; return [ FlSpot(0, baseAmount * 0.8), FlSpot(1, baseAmount * 1.2), FlSpot(2, baseAmount * 0.9), FlSpot(3, baseAmount * 1.5), FlSpot(4, baseAmount * 1.1), FlSpot(5, baseAmount * 1.3), ]; } List _generateGrowthSpots(double currentGrowth) { final baseGrowth = currentGrowth; return List.generate(12, (index) { final variation = (index % 3 - 1) * 2.0; return FlSpot(index.toDouble(), baseGrowth + variation); }); } Widget _buildLoadingChart() { return Container( decoration: BoxDecoration( color: AppColors.lightBorder.withOpacity(0.5), borderRadius: BorderRadius.circular(12), ), child: const Center( child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(AppColors.primaryGreen), ), ), ); } Widget _buildErrorChart() { return Container( decoration: BoxDecoration( color: AppColors.error.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, color: AppColors.error, size: 24, ), const SizedBox(height: 8), Text( 'ERREUR', style: AppTypography.subtitleSmall.copyWith( color: AppColors.error, fontWeight: FontWeight.bold, fontSize: 10, ), ), ], ), ), ); } Widget _buildEmptyChart() { return Container( decoration: BoxDecoration( color: AppColors.lightBorder.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.bar_chart_outlined, color: AppColors.textSecondaryLight, size: 24, ), const SizedBox(height: 8), Text( 'AUCUNE DONNÉE', style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.bold, fontSize: 10, ), ), ], ), ), ); } IconData _getChartIcon() { switch (chartType) { case DashboardChartType.memberActivity: return Icons.pie_chart_outline; case DashboardChartType.contributionTrend: return Icons.trending_up_outlined; case DashboardChartType.eventParticipation: return Icons.bar_chart_outlined; case DashboardChartType.monthlyGrowth: return Icons.show_chart_outlined; } } } enum DashboardChartType { memberActivity, contributionTrend, eventParticipation, monthlyGrowth, }