import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:flutter/material.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:fl_chart/fl_chart.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:geosector_app/presentation/widgets/charts/charts.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; class UserStatisticsPage extends StatefulWidget { const UserStatisticsPage({super.key}); @override State createState() => _UserStatisticsPageState(); } class _UserStatisticsPageState extends State { // Période sélectionnée String _selectedPeriod = 'Semaine'; // Secteur sélectionné (0 = tous les secteurs) int _selectedSectorId = 0; @override Widget build(BuildContext context) { final theme = Theme.of(context); final size = MediaQuery.of(context).size; final isDesktop = size.width > 900; return Scaffold( backgroundColor: Colors.transparent, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Statistiques', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), const SizedBox(height: 16), // Filtres _buildFilters(theme, isDesktop), const SizedBox(height: 24), // Graphiques _buildCharts(theme), const SizedBox(height: 24), // Résumé par type de passage _buildPassageTypeSummary(theme, isDesktop), const SizedBox(height: 24), // Résumé par type de règlement _buildPaymentTypeSummary(theme, isDesktop), ], ), ), ), ); } // Construction des filtres Widget _buildFilters(ThemeData theme, bool isDesktop) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Filtres', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Wrap( spacing: 16, runSpacing: 16, children: [ // Sélection de la période _buildFilterSection( 'Période', ['Jour', 'Semaine', 'Mois', 'Année'], _selectedPeriod, (value) { setState(() { _selectedPeriod = value; }); }, theme, ), // Sélection du secteur (si l'utilisateur a plusieurs secteurs) _buildSectorSelector(context, theme), // Bouton d'application des filtres ElevatedButton.icon( onPressed: () { // Actualiser les statistiques avec les filtres sélectionnés setState(() { // Dans une implémentation réelle, on chargerait ici les données // filtrées par période et secteur }); }, icon: const Icon(Icons.filter_list), label: const Text('Appliquer'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.accentColor, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ], ), ], ), ), ); } // Construction du sélecteur de secteur Widget _buildSectorSelector(BuildContext context, ThemeData theme) { // Utiliser l'instance globale définie dans app.dart // Récupérer les secteurs de l'utilisateur final sectors = userRepository.getUserSectors(); // Si l'utilisateur n'a qu'un seul secteur, ne pas afficher le sélecteur if (sectors.length <= 1) { return const SizedBox.shrink(); } // Créer la liste des options avec "Tous" comme première option final List> items = [ const DropdownMenuItem( value: 0, child: Text('Tous les secteurs'), ), ]; // Ajouter les secteurs de l'utilisateur for (final sector in sectors) { items.add( DropdownMenuItem( value: sector.id, child: Text(sector.libelle), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Secteur', style: theme.textTheme.titleSmall, ), const SizedBox(height: 8), Container( constraints: const BoxConstraints(maxWidth: 250), child: DropdownButton( value: _selectedSectorId, isExpanded: true, items: items, onChanged: (value) { if (value != null) { setState(() { _selectedSectorId = value; }); } }, hint: const Text('Sélectionner un secteur'), ), ), ], ); } // Construction d'une section de filtre Widget _buildFilterSection( String title, List options, String selectedValue, Function(String) onChanged, ThemeData theme, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: theme.textTheme.titleSmall, ), const SizedBox(height: 8), SegmentedButton( segments: options.map((option) { return ButtonSegment( value: option, label: Text(option), ); }).toList(), selected: {selectedValue}, onSelectionChanged: (Set selection) { onChanged(selection.first); }, style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.selected)) { return AppTheme.secondaryColor; } return theme.colorScheme.surface; }, ), foregroundColor: MaterialStateProperty.resolveWith( (Set states) { if (states.contains(MaterialState.selected)) { return Colors.white; } return theme.colorScheme.onSurface; }, ), ), ), ], ); } // Construction des graphiques Widget _buildCharts(ThemeData theme) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Passages et règlements par $_selectedPeriod', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), SizedBox( height: 300, child: _buildActivityChart(theme), ), ], ), ), ); } // Construction du graphique d'activité Widget _buildActivityChart(ThemeData theme) { // Générer des données fictives pour les passages final now = DateTime.now(); final List> passageData = []; // Récupérer le secteur sélectionné (si applicable) final String sectorLabel = _selectedSectorId == 0 ? 'Tous les secteurs' : userRepository.getSectorById(_selectedSectorId)?.libelle ?? 'Secteur inconnu'; // Déterminer la plage de dates en fonction de la période sélectionnée DateTime startDate; int daysToGenerate; switch (_selectedPeriod) { case 'Jour': startDate = DateTime(now.year, now.month, now.day); daysToGenerate = 1; break; case 'Semaine': // Début de la semaine (lundi) final weekday = now.weekday; startDate = now.subtract(Duration(days: weekday - 1)); daysToGenerate = 7; break; case 'Mois': // Début du mois startDate = DateTime(now.year, now.month, 1); // Calculer le nombre de jours dans le mois final lastDayOfMonth = DateTime(now.year, now.month + 1, 0).day; daysToGenerate = lastDayOfMonth; break; case 'Année': // Début de l'année startDate = DateTime(now.year, 1, 1); daysToGenerate = 365; break; default: startDate = DateTime(now.year, now.month, now.day); daysToGenerate = 7; } // Générer des données pour la période sélectionnée for (int i = 0; i < daysToGenerate; i++) { final date = startDate.add(Duration(days: i)); // Générer des données pour chaque type de passage for (int typeId = 1; typeId <= 6; typeId++) { // Générer un nombre de passages basé sur le jour et le type final count = (typeId == 1 || typeId == 2) ? (2 + (date.day % 6)) // Plus de passages pour les types 1 et 2 : (date.day % 4); // Moins pour les autres types if (count > 0) { passageData.add({ 'date': date.toIso8601String(), 'type_passage': typeId, 'nb': count, }); } } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Afficher le secteur sélectionné si ce n'est pas "Tous" if (_selectedSectorId != 0) Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Text( 'Secteur: $sectorLabel', style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), ), ActivityChart( passageData: passageData, periodType: _selectedPeriod, height: 300, ), ], ); } // Construction du résumé par type de passage Widget _buildPassageTypeSummary(ThemeData theme, bool isDesktop) { // Dans une implémentation réelle, ces données seraient filtrées par secteur // en fonction de _selectedSectorId return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Répartition par type de passage', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), Row( children: [ // Graphique circulaire Expanded( flex: isDesktop ? 1 : 2, child: SizedBox( height: 200, child: PassagePieChart( passagesByType: { 1: 60, // Effectués 2: 15, // À finaliser 3: 10, // Refusés 4: 8, // Dons 5: 5, // Lots 6: 2, // Maisons vides }, size: 140, labelSize: 12, showPercentage: true, showIcons: false, // Désactiver les icônes isDonut: true, // Activer le format donut innerRadius: '50%' // Rayon interne du donut ), ), ), // Légende if (isDesktop) Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLegendItem( 'Effectués', '60%', const Color(0xFF4CAF50)), _buildLegendItem( 'À finaliser', '15%', const Color(0xFFFF9800)), _buildLegendItem( 'Refusés', '10%', const Color(0xFFF44336)), _buildLegendItem('Dons', '8%', const Color(0xFF03A9F4)), _buildLegendItem('Lots', '5%', const Color(0xFF0D47A1)), _buildLegendItem( 'Maisons vides', '2%', const Color(0xFF9E9E9E)), ], ), ), ], ), if (!isDesktop) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), _buildLegendItem('Effectués', '60%', const Color(0xFF4CAF50)), _buildLegendItem( 'À finaliser', '15%', const Color(0xFFFF9800)), _buildLegendItem('Refusés', '10%', const Color(0xFFF44336)), _buildLegendItem('Dons', '8%', const Color(0xFF03A9F4)), _buildLegendItem('Lots', '5%', const Color(0xFF0D47A1)), _buildLegendItem( 'Maisons vides', '2%', const Color(0xFF9E9E9E)), ], ), ], ), ), ); } // Construction du résumé par type de règlement Widget _buildPaymentTypeSummary(ThemeData theme, bool isDesktop) { // Dans une implémentation réelle, ces données seraient filtrées par secteur // en fonction de _selectedSectorId return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Répartition par type de règlement', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), Row( children: [ // Graphique circulaire Expanded( flex: isDesktop ? 1 : 2, child: SizedBox( height: 200, child: PieChart( PieChartData( sectionsSpace: 2, centerSpaceRadius: 40, sections: [ _buildPieChartSection( 'Espèces', 30, const Color(0xFF4CAF50), 0), _buildPieChartSection( 'Chèques', 45, const Color(0xFF2196F3), 1), _buildPieChartSection( 'CB', 25, const Color(0xFFF44336), 2), ], ), ), ), ), // Légende if (isDesktop) Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLegendItem( 'Espèces', '30%', const Color(0xFF4CAF50)), _buildLegendItem( 'Chèques', '45%', const Color(0xFF2196F3)), _buildLegendItem('CB', '25%', const Color(0xFFF44336)), ], ), ), ], ), if (!isDesktop) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), _buildLegendItem('Espèces', '30%', const Color(0xFF4CAF50)), _buildLegendItem('Chèques', '45%', const Color(0xFF2196F3)), _buildLegendItem('CB', '25%', const Color(0xFFF44336)), ], ), ], ), ), ); } // Construction d'une section de graphique circulaire PieChartSectionData _buildPieChartSection( String title, double value, Color color, int index) { return PieChartSectionData( color: color, value: value, title: '$value%', radius: 60, titleStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), ); } // Construction d'un élément de légende Widget _buildLegendItem(String title, String value, Color color) { return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( children: [ Container( width: 16, height: 16, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), ), const SizedBox(width: 8), Text( title, style: const TextStyle( fontWeight: FontWeight.w500, ), ), const Spacer(), Text( value, style: TextStyle( fontWeight: FontWeight.bold, color: color, ), ), ], ), ); } }