import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../shared/widgets/common/unified_page_layout.dart'; import '../../../../shared/widgets/common/unified_card.dart'; import '../../../../shared/theme/design_system.dart'; import '../../../../core/utils/constants.dart'; import '../bloc/analytics_bloc.dart'; import '../widgets/kpi_card_widget.dart'; import '../widgets/trend_chart_widget.dart'; import '../widgets/period_selector_widget.dart'; import '../widgets/metrics_grid_widget.dart'; import '../widgets/performance_gauge_widget.dart'; import '../widgets/alerts_panel_widget.dart'; import '../../domain/entities/analytics_data.dart'; /// Page principale du tableau de bord analytics class AnalyticsDashboardPage extends StatefulWidget { const AnalyticsDashboardPage({super.key}); @override State createState() => _AnalyticsDashboardPageState(); } class _AnalyticsDashboardPageState extends State with TickerProviderStateMixin { late TabController _tabController; PeriodeAnalyse _periodeSelectionnee = PeriodeAnalyse.ceMois; String? _organisationId; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); _chargerDonneesInitiales(); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _chargerDonneesInitiales() { context.read().add( ChargerTableauBordEvent( periodeAnalyse: _periodeSelectionnee, organisationId: _organisationId, ), ); } void _onPeriodeChanged(PeriodeAnalyse nouvellePeriode) { setState(() { _periodeSelectionnee = nouvellePeriode; }); _chargerDonneesInitiales(); } @override Widget build(BuildContext context) { return UnifiedPageLayout( title: 'Analytics', subtitle: 'Tableau de bord et métriques', showBackButton: false, actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _chargerDonneesInitiales, tooltip: 'Actualiser', ), IconButton( icon: const Icon(Icons.settings), onPressed: () => _ouvrirParametres(context), tooltip: 'Paramètres', ), ], body: Column( children: [ // Sélecteur de période Padding( padding: const EdgeInsets.all(DesignSystem.spacing16), child: PeriodSelectorWidget( periodeSelectionnee: _periodeSelectionnee, onPeriodeChanged: _onPeriodeChanged, ), ), // Onglets TabBar( controller: _tabController, labelColor: DesignSystem.primaryColor, unselectedLabelColor: DesignSystem.textSecondaryColor, indicatorColor: DesignSystem.primaryColor, tabs: const [ Tab( icon: Icon(Icons.dashboard), text: 'Vue d\'ensemble', ), Tab( icon: Icon(Icons.trending_up), text: 'Tendances', ), Tab( icon: Icon(Icons.analytics), text: 'Détails', ), Tab( icon: Icon(Icons.warning), text: 'Alertes', ), ], ), // Contenu des onglets Expanded( child: TabBarView( controller: _tabController, children: [ _buildVueEnsemble(), _buildTendances(), _buildDetails(), _buildAlertes(), ], ), ), ], ), ); } /// Vue d'ensemble avec KPI principaux Widget _buildVueEnsemble() { return BlocBuilder( builder: (context, state) { if (state is AnalyticsLoading) { return const Center( child: CircularProgressIndicator(), ); } if (state is AnalyticsError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64, color: DesignSystem.errorColor, ), const SizedBox(height: DesignSystem.spacing16), Text( 'Erreur lors du chargement', style: DesignSystem.textTheme.headlineSmall, ), const SizedBox(height: DesignSystem.spacing8), Text( state.message, style: DesignSystem.textTheme.bodyMedium, textAlign: TextAlign.center, ), const SizedBox(height: DesignSystem.spacing16), ElevatedButton( onPressed: _chargerDonneesInitiales, child: const Text('Réessayer'), ), ], ), ); } if (state is AnalyticsLoaded) { return SingleChildScrollView( padding: const EdgeInsets.all(DesignSystem.spacing16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Performance globale if (state.performanceGlobale != null) UnifiedCard( variant: UnifiedCardVariant.elevated, child: PerformanceGaugeWidget( score: state.performanceGlobale!, periode: _periodeSelectionnee, ), ), const SizedBox(height: DesignSystem.spacing16), // KPI principaux Text( 'Indicateurs clés', style: DesignSystem.textTheme.headlineSmall, ), const SizedBox(height: DesignSystem.spacing12), MetricsGridWidget( metriques: state.metriques, onMetriquePressed: (metrique) => _ouvrirDetailMetrique( context, metrique, ), ), const SizedBox(height: DesignSystem.spacing24), // Graphiques de tendance rapide Text( 'Évolutions récentes', style: DesignSystem.textTheme.headlineSmall, ), const SizedBox(height: DesignSystem.spacing12), if (state.tendances.isNotEmpty) SizedBox( height: 200, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: state.tendances.length, itemBuilder: (context, index) { final tendance = state.tendances[index]; return Container( width: 300, margin: const EdgeInsets.only( right: DesignSystem.spacing12, ), child: UnifiedCard( variant: UnifiedCardVariant.outlined, child: TrendChartWidget( trend: tendance, compact: true, ), ), ); }, ), ), ], ), ); } return const SizedBox.shrink(); }, ); } /// Onglet des tendances détaillées Widget _buildTendances() { return BlocBuilder( builder: (context, state) { if (state is AnalyticsLoaded && state.tendances.isNotEmpty) { return ListView.builder( padding: const EdgeInsets.all(DesignSystem.spacing16), itemCount: state.tendances.length, itemBuilder: (context, index) { final tendance = state.tendances[index]; return Padding( padding: const EdgeInsets.only( bottom: DesignSystem.spacing16, ), child: UnifiedCard( variant: UnifiedCardVariant.elevated, child: TrendChartWidget( trend: tendance, compact: false, showPredictions: true, showAnomalies: true, ), ), ); }, ); } return const Center( child: Text('Aucune tendance disponible'), ); }, ); } /// Onglet des détails par métrique Widget _buildDetails() { return BlocBuilder( builder: (context, state) { if (state is AnalyticsLoaded) { return ListView.builder( padding: const EdgeInsets.all(DesignSystem.spacing16), itemCount: TypeMetrique.values.length, itemBuilder: (context, index) { final typeMetrique = TypeMetrique.values[index]; final metrique = state.metriques.firstWhere( (m) => m.typeMetrique == typeMetrique, orElse: () => AnalyticsData( id: 'placeholder_$index', typeMetrique: typeMetrique, periodeAnalyse: _periodeSelectionnee, valeur: 0, dateDebut: DateTime.now().subtract(const Duration(days: 30)), dateFin: DateTime.now(), dateCalcul: DateTime.now(), ), ); return Padding( padding: const EdgeInsets.only( bottom: DesignSystem.spacing12, ), child: KPICardWidget( analyticsData: metrique, onTap: () => _ouvrirDetailMetrique(context, metrique), showTrend: true, showDetails: true, ), ); }, ); } return const Center( child: Text('Aucun détail disponible'), ); }, ); } /// Onglet des alertes Widget _buildAlertes() { return BlocBuilder( builder: (context, state) { if (state is AnalyticsLoaded) { final alertes = state.metriques .where((m) => m.isCritique || !m.isDonneesFiables) .toList(); if (alertes.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.check_circle_outline, size: 64, color: DesignSystem.successColor, ), const SizedBox(height: DesignSystem.spacing16), Text( 'Aucune alerte active', style: DesignSystem.textTheme.headlineSmall, ), const SizedBox(height: DesignSystem.spacing8), Text( 'Toutes les métriques sont dans les normes', style: DesignSystem.textTheme.bodyMedium, textAlign: TextAlign.center, ), ], ), ); } return AlertsPanelWidget( alertes: alertes, onAlertePressed: (alerte) => _ouvrirDetailMetrique( context, alerte, ), ); } return const Center( child: Text('Aucune alerte disponible'), ); }, ); } void _ouvrirDetailMetrique(BuildContext context, AnalyticsData metrique) { Navigator.of(context).pushNamed( AppRoutes.analyticsDetail, arguments: { 'metrique': metrique, 'periode': _periodeSelectionnee, 'organisationId': _organisationId, }, ); } void _ouvrirParametres(BuildContext context) { Navigator.of(context).pushNamed(AppRoutes.analyticsSettings); } }