import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../bloc/reports_bloc.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/widgets/core_card.dart'; /// Page Rapports & Analytics - UnionFlow Mobile /// /// Page complète de génération et consultation des rapports avec /// analytics avancés, graphiques et export de données. class ReportsPage extends StatefulWidget { const ReportsPage({super.key}); @override State createState() => _ReportsPageState(); } class _ReportsPageState extends State with TickerProviderStateMixin { late TabController _tabController; String _selectedPeriod = 'Dernier mois'; String _selectedFormat = 'PDF'; final List _periods = ['Dernière semaine', 'Dernier mois', 'Dernier trimestre', 'Dernière année']; final List _formats = ['PDF', 'Excel', 'CSV', 'JSON']; // Données live du backend Map _statsMembres = {}; Map _statsCotisations = {}; Map _statsEvenements = {}; Map _performance = {}; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(const LoadDashboardReports()); }); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is ReportsDashboardLoaded) { setState(() { _performance = state.performance; _statsMembres = state.statsMembres; _statsCotisations = state.statsCotisations; _statsEvenements = state.statsEvenements; }); } if (state is ReportsError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message), backgroundColor: Colors.orange), ); } if (state is ReportScheduled) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message), backgroundColor: const Color(0xFF00B894), behavior: SnackBarBehavior.floating), ); } if (state is ReportGenerated) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message), backgroundColor: const Color(0xFF00B894), behavior: SnackBarBehavior.floating), ); } }, builder: (context, state) { return Scaffold( backgroundColor: AppColors.darkBackground, body: Column( children: [ _buildHeader(), _buildTabBar(), if (state is ReportsLoading) const LinearProgressIndicator( minHeight: 2, backgroundColor: Colors.transparent, valueColor: AlwaysStoppedAnimation(AppColors.primaryGreen), ), Expanded( child: TabBarView( controller: _tabController, children: [ _buildOverviewTab(), _buildMembersTab(), _buildOrganizationsTab(), _buildEventsTab(), ], ), ), ], ), ); }, ); } Widget _buildHeader() { return Container( width: double.infinity, padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 20, bottom: 30, left: 20, right: 20, ), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.primaryGreen, AppColors.brandGreen, ], ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(32), bottomRight: Radius.circular(32), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'UnionFlow Analytics'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith( color: Colors.white.withOpacity(0.8), fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), const SizedBox(height: 4), const Text( 'Rapports & Insights', style: AppTypography.headerSmall, ), ], ), Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: IconButton( onPressed: () => _showExportDialog(), icon: const Icon(Icons.file_download_outlined, color: Colors.white), ), ), ], ), const SizedBox(height: 24), Row( children: [ Expanded( child: _buildHeaderStat( 'Membres', _statsMembres['total']?.toString() ?? '...', Icons.people_outline, ), ), const SizedBox(width: 12), Expanded( child: _buildHeaderStat( 'Organisations', _statsMembres['totalOrganisations']?.toString() ?? '...', Icons.business_outlined, ), ), const SizedBox(width: 12), Expanded( child: _buildHeaderStat( 'Événements', _statsEvenements['total']?.toString() ?? '...', Icons.event_outlined, ), ), ], ), ], ), ); } Widget _buildHeaderStat(String label, String value, IconData icon) { return Container( padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white.withOpacity(0.2)), ), child: Column( children: [ Icon(icon, color: Colors.white, size: 18), const SizedBox(height: 8), Text( value, style: AppTypography.headerSmall.copyWith(fontSize: 18), ), Text( label.toUpperCase(), style: AppTypography.subtitleSmall.copyWith( color: Colors.white.withOpacity(0.7), fontSize: 8, fontWeight: FontWeight.bold, ), ), ], ), ); } Widget _buildTabBar() { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: AppColors.darkBackground, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)), ), child: TabBar( controller: _tabController, labelColor: AppColors.primaryGreen, unselectedLabelColor: AppColors.textSecondaryLight, indicatorColor: AppColors.primaryGreen, indicatorSize: TabBarIndicatorSize.label, dividerColor: Colors.transparent, labelStyle: AppTypography.badgeText.copyWith(fontWeight: FontWeight.bold), tabs: const [ Tab(text: 'GLOBAL'), Tab(text: 'MEMBRES'), Tab(text: 'ORGS'), Tab(text: 'EVENTS'), ], ), ); } Widget _buildOverviewTab() { return ListView( padding: const EdgeInsets.all(16), children: [ _buildKPICards(), const SizedBox(height: 24), _buildActivityChart(), const SizedBox(height: 24), _buildQuickReports(), const SizedBox(height: 32), ], ); } Widget _buildKPICards() { final totalMembres = _statsMembres['total']?.toString() ?? '--'; final membresActifs = _statsMembres['actifs']?.toString() ?? '--'; final totalCotisations = _statsCotisations['total']?.toString() ?? '--'; final totalEvenements = _statsEvenements['total']?.toString() ?? '--'; return Column( children: [ Row( children: [ Expanded(child: _buildKPICard('Total Membres', totalMembres, Icons.people_outline, AppColors.info)), const SizedBox(width: 16), Expanded(child: _buildKPICard('Membres Actifs', membresActifs, Icons.how_to_reg_outlined, AppColors.success)), ], ), const SizedBox(height: 16), Row( children: [ Expanded(child: _buildKPICard('Cotisations', totalCotisations, Icons.payments_outlined, AppColors.brandGreen)), const SizedBox(width: 16), Expanded(child: _buildKPICard('Événements', totalEvenements, Icons.event_available_outlined, AppColors.warning)), ], ), ], ); } Widget _buildKPICard(String title, String value, IconData icon, Color color) { return CoreCard( padding: const EdgeInsets.all(20), child: Column( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon(icon, color: color, size: 24), ), const SizedBox(height: 12), Text( value, style: AppTypography.headerSmall.copyWith(color: color, fontWeight: FontWeight.bold), ), const SizedBox(height: 4), Text( title.toUpperCase(), style: AppTypography.subtitleSmall.copyWith( fontSize: 9, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), textAlign: TextAlign.center, ), ], ), ); } Widget _buildActivityChart() { return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.analytics_outlined, color: AppColors.primaryGreen, size: 20), const SizedBox(width: 12), Text( 'Évolution de l\'Activité'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 20), Container( height: 180, decoration: BoxDecoration( color: AppColors.lightBorder.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.auto_graph_outlined, color: AppColors.textSecondaryLight, size: 40), SizedBox(height: 12), Text( 'Visualisation graphique en préparation', style: AppTypography.subtitleSmall, ), ], ), ), ), ], ), ); } Widget _buildQuickReports() { return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.flash_on_outlined, color: AppColors.warning, size: 20), const SizedBox(width: 12), Text( 'Rapports Favoris'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 20), _buildQuickReportItem('Bilan Annuel', 'Synthèse financière et activité', Icons.summarize_outlined, () => _generateReport('monthly')), _buildQuickReportItem('Engagement Membres', 'Analyse de participation globale', Icons.query_stats_outlined, () => _generateReport('top_members')), _buildQuickReportItem('Impact Événements', 'Analyse SEO et participation', Icons.insights_outlined, () => _generateReport('events_analysis')), ], ), ); } Widget _buildQuickReportItem(String title, String subtitle, IconData icon, VoidCallback onTap) { return Container( margin: const EdgeInsets.only(bottom: 12), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.lightBorder.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)), ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primaryGreen.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: AppColors.primaryGreen, size: 20), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: AppTypography.actionText), const SizedBox(height: 2), Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), ], ), ), const Icon(Icons.chevron_right_outlined, color: AppColors.textSecondaryLight, size: 20), ], ), ), ), ); } /// Onglet membres Widget _buildMembersTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildMembersStats(), const SizedBox(height: 16), _buildMembersReports(), const SizedBox(height: 80), ], ), ); } Widget _buildMembersStats() { final total = _statsMembres['total']?.toString() ?? '--'; final nouveaux = _statsMembres['nouveaux30j']?.toString() ?? '--'; final actifs = _statsMembres['actifs7j']?.toString() ?? '--'; return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.people_alt_outlined, color: AppColors.info, size: 20), const SizedBox(width: 12), Text( 'Indicateurs Membres'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 20), Row( children: [ Expanded(child: _buildStatItem('Total', total)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('Nouveaux', nouveaux)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('Actifs %', actifs)), ], ), ], ), ); } Widget _buildMembersReports() { return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.assignment_ind_outlined, color: AppColors.info, size: 20), const SizedBox(width: 12), Text( 'Rapports Membres'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 16), _buildReportItem('Liste complète des membres', 'Export avec toutes les informations', Icons.list_alt_outlined), _buildReportItem('Analyse d\'engagement', 'Participation et activité des membres', Icons.trending_up_outlined), _buildReportItem('Segmentation démographique', 'Répartition par âge, région, etc.', Icons.pie_chart_outline), ], ), ); } /// Onglet organisations Widget _buildOrganizationsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildOrganizationsStats(), const SizedBox(height: 16), _buildOrganizationsReports(), const SizedBox(height: 80), ], ), ); } Widget _buildOrganizationsStats() { final total = _statsMembres['totalOrganisations']?.toString() ?? '--'; final actives = _statsMembres['organisationsActives']?.toString() ?? '--'; final moy = _statsMembres['membresParOrganisation']?.toString() ?? '--'; return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.business_center_outlined, color: AppColors.primaryGreen, size: 20), const SizedBox(width: 12), Text( 'Indicateurs Organisations'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 20), Row( children: [ Expanded(child: _buildStatItem('Total', total)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('Actives', actives)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('Membres moy.', moy)), ], ), ], ), ); } Widget _buildOrganizationsReports() { return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.folder_shared_outlined, color: AppColors.primaryGreen, size: 20), const SizedBox(width: 12), Text( 'Rapports Structures'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 16), _buildReportItem('Annuaire des organisations', 'Liste complète avec contacts', Icons.contact_phone_outlined), _buildReportItem('Performance par organisation', 'Activité et engagement', Icons.bar_chart_outlined), _buildReportItem('Analyse de croissance', 'Évolution du nombre de membres', Icons.trending_up_outlined), ], ), ); } /// Onglet événements Widget _buildEventsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( children: [ const SizedBox(height: 16), _buildEventsStats(), const SizedBox(height: 16), _buildEventsReports(), const SizedBox(height: 80), ], ), ); } Widget _buildEventsStats() { final total = _statsEvenements['total']?.toString() ?? '--'; final venir = _statsEvenements['aVenir']?.toString() ?? '--'; final participation = _statsEvenements['participationMoyenne']?.toString() ?? '--'; return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.event_note_outlined, color: AppColors.warning, size: 20), const SizedBox(width: 12), Text( 'Indicateurs Événements'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 20), Row( children: [ Expanded(child: _buildStatItem('Total', total)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('À Venir', venir)), Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)), Expanded(child: _buildStatItem('Part. moyenne', participation)), ], ), ], ), ); } Widget _buildEventsReports() { return CoreCard( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.history_edu_outlined, color: AppColors.warning, size: 20), const SizedBox(width: 12), Text( 'Rapports Logistique'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), ], ), const SizedBox(height: 16), _buildReportItem('Calendrier des événements', 'Planning complet avec détails', Icons.calendar_today_outlined), _buildReportItem('Analyse de participation', 'Taux de participation et feedback', Icons.people_outline), _buildReportItem('ROI des événements', 'Retour sur investissement financier', Icons.analytics_outlined), ], ), ); } // Composants communs Widget _buildStatItem(String label, String value) { return Column( children: [ Text( value, style: AppTypography.headerSmall.copyWith(color: AppColors.primaryGreen, fontWeight: FontWeight.bold), ), const SizedBox(height: 4), Text( label.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ], ); } Widget _buildReportItem(String title, String subtitle, IconData icon) { return Container( margin: const EdgeInsets.only(bottom: 12), child: InkWell( onTap: () => _generateReport(title.toLowerCase().replaceAll(' ', '_')), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.lightBorder.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)), ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primaryGreen.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: AppColors.primaryGreen, size: 20), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: AppTypography.actionText), const SizedBox(height: 2), Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), ], ), ), const Icon(Icons.file_download_outlined, color: AppColors.textSecondaryLight, size: 20), ], ), ), ), ); } // Méthodes d'action void _showExportDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Exporter rapport'), content: Column( mainAxisSize: MainAxisSize.min, children: [ DropdownButtonFormField( value: _selectedPeriod, decoration: const InputDecoration(labelText: 'Période'), items: _periods.map((period) => DropdownMenuItem(value: period, child: Text(period))).toList(), onChanged: (value) => setState(() => _selectedPeriod = value!), ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedFormat, decoration: const InputDecoration(labelText: 'Format'), items: _formats.map((format) => DropdownMenuItem(value: format, child: Text(format))).toList(), onChanged: (value) => setState(() => _selectedFormat = value!), ), ], ), actions: [ TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler')), ElevatedButton( onPressed: () { Navigator.of(context).pop(); context.read().add(GenerateReportRequested('export', format: _selectedFormat)); }, style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF6C5CE7), foregroundColor: Colors.white), child: const Text('Exporter'), ), ], ), ); } void _scheduleReport() { context.read().add(const ScheduleReportRequested()); } void _generateReport(String type) { context.read().add(GenerateReportRequested(type)); } void _showSuccessSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: const Color(0xFF00B894), behavior: SnackBarBehavior.floating), ); } }