771 lines
29 KiB
Dart
771 lines
29 KiB
Dart
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
import 'dart:math' as math;
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:geosector_app/presentation/widgets/sector_distribution_card.dart';
|
|
import 'package:geosector_app/presentation/widgets/charts/charts.dart';
|
|
import 'package:geosector_app/core/data/models/passage_model.dart';
|
|
import 'package:geosector_app/core/data/models/operation_model.dart';
|
|
import 'package:geosector_app/core/data/models/sector_model.dart';
|
|
import 'package:geosector_app/core/constants/app_keys.dart';
|
|
import 'package:geosector_app/core/theme/app_theme.dart';
|
|
|
|
/// Class pour dessiner les petits points blancs sur le fond
|
|
class DotsPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = Colors.white.withOpacity(0.5)
|
|
..style = PaintingStyle.fill;
|
|
|
|
final random = math.Random(42); // Seed fixe pour consistance
|
|
final numberOfDots = (size.width * size.height) ~/ 1500;
|
|
|
|
for (int i = 0; i < numberOfDots; i++) {
|
|
final x = random.nextDouble() * size.width;
|
|
final y = random.nextDouble() * size.height;
|
|
final radius = 1.0 + random.nextDouble() * 2.0;
|
|
canvas.drawCircle(Offset(x, y), radius, paint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|
|
|
|
class AdminDashboardHomePage extends StatefulWidget {
|
|
const AdminDashboardHomePage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<AdminDashboardHomePage> createState() => _AdminDashboardHomePageState();
|
|
}
|
|
|
|
class _AdminDashboardHomePageState extends State<AdminDashboardHomePage> {
|
|
// Données pour le tableau de bord
|
|
int totalPassages = 0;
|
|
double totalAmounts = 0.0;
|
|
List<Map<String, dynamic>> memberStats = [];
|
|
bool isDataLoaded = false;
|
|
bool isLoading = true;
|
|
bool isFirstLoad = true; // Pour suivre le premier chargement
|
|
|
|
// Données pour les graphiques
|
|
List<PaymentData> paymentData = [];
|
|
Map<int, int> passagesByType = {};
|
|
|
|
// Future pour initialiser les boîtes Hive
|
|
late Future<void> _initFuture;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Initialiser les boîtes Hive avant de charger les données
|
|
_initFuture = _initHiveBoxes().then((_) {
|
|
// Charger les données une fois les boîtes initialisées
|
|
_loadDashboardData();
|
|
|
|
// Après l'affichage des logs "VERIFICATION FINALE DES DONNEES",
|
|
// attendre un court délai puis rafraîchir automatiquement les données
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
if (mounted) {
|
|
setState(() {
|
|
isLoading = true; // Afficher le spinner pendant le rafraîchissement
|
|
});
|
|
_loadDashboardData(); // Rafraîchir les données
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Méthode pour initialiser les boîtes Hive nécessaires
|
|
Future<void> _initHiveBoxes() async {
|
|
try {
|
|
debugPrint('AdminDashboardHomePage: Initialisation des boîtes Hive...');
|
|
|
|
// Liste des boîtes à ouvrir
|
|
final boxesToOpen = [
|
|
{
|
|
'name': AppKeys.operationsBoxName,
|
|
'type': 'OperationModel',
|
|
'opened': false
|
|
},
|
|
{
|
|
'name': AppKeys.passagesBoxName,
|
|
'type': 'PassageModel',
|
|
'opened': false
|
|
},
|
|
{
|
|
'name': AppKeys.sectorsBoxName,
|
|
'type': 'SectorModel',
|
|
'opened': false
|
|
},
|
|
];
|
|
|
|
// Ouvrir chaque boîte
|
|
for (final boxInfo in boxesToOpen) {
|
|
final boxName = boxInfo['name'] as String;
|
|
|
|
if (!Hive.isBoxOpen(boxName)) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Ouverture de la boîte $boxName...');
|
|
try {
|
|
switch (boxInfo['type']) {
|
|
case 'OperationModel':
|
|
await Hive.openBox<OperationModel>(boxName);
|
|
break;
|
|
case 'PassageModel':
|
|
await Hive.openBox<PassageModel>(boxName);
|
|
break;
|
|
case 'SectorModel':
|
|
await Hive.openBox<SectorModel>(boxName);
|
|
break;
|
|
}
|
|
boxInfo['opened'] = true;
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Boîte $boxName ouverte avec succès');
|
|
} catch (boxError) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Erreur lors de l\'ouverture de la boîte $boxName: $boxError');
|
|
// Continuer malgré l'erreur
|
|
}
|
|
} else {
|
|
boxInfo['opened'] = true;
|
|
debugPrint('AdminDashboardHomePage: Boîte $boxName déjà ouverte');
|
|
}
|
|
}
|
|
|
|
// Vérifier si toutes les boîtes ont été ouvertes
|
|
final allBoxesOpened = boxesToOpen.every((box) => box['opened'] == true);
|
|
|
|
if (allBoxesOpened) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Toutes les boîtes Hive ont été ouvertes avec succès');
|
|
} else {
|
|
// Identifier les boîtes qui n'ont pas pu être ouvertes
|
|
final failedBoxes = boxesToOpen
|
|
.where((box) => box['opened'] == false)
|
|
.map((box) => box['name'])
|
|
.join(', ');
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Certaines boîtes n\'ont pas pu être ouvertes: $failedBoxes');
|
|
}
|
|
|
|
// Afficher le nombre d'éléments dans chaque boîte
|
|
debugPrint('VERIFICATION FINALE DES DONNEES');
|
|
if (Hive.isBoxOpen(AppKeys.operationsBoxName)) {
|
|
final operationsBox =
|
|
Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
|
debugPrint('Nombre d\'opérations: ${operationsBox.length}');
|
|
}
|
|
if (Hive.isBoxOpen(AppKeys.passagesBoxName)) {
|
|
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
|
debugPrint('Nombre de passages: ${passagesBox.length}');
|
|
}
|
|
if (Hive.isBoxOpen(AppKeys.sectorsBoxName)) {
|
|
final sectorsBox = Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
|
debugPrint('Nombre de secteurs: ${sectorsBox.length}');
|
|
}
|
|
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Initialisation des boîtes Hive terminée');
|
|
} catch (e) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Erreur lors de l\'initialisation des boîtes Hive: $e');
|
|
// Ne pas propager l'erreur, mais retourner normalement
|
|
// pour éviter que le FutureBuilder ne reste bloqué en état d'erreur
|
|
}
|
|
}
|
|
|
|
/// Prépare les données pour le graphique de paiement
|
|
void _preparePaymentData(List<dynamic> passages) {
|
|
// Réinitialiser les données
|
|
paymentData = [];
|
|
|
|
// Compter les montants par type de règlement
|
|
Map<int, double> paymentAmounts = {};
|
|
|
|
// Initialiser les compteurs pour tous les types de règlement
|
|
for (final typeId in AppKeys.typesReglements.keys) {
|
|
paymentAmounts[typeId] = 0.0;
|
|
}
|
|
|
|
// Calculer les montants par type de règlement
|
|
for (final passage in passages) {
|
|
if (passage.fkTypeReglement != null &&
|
|
passage.montant != null &&
|
|
passage.montant.isNotEmpty) {
|
|
final typeId = passage.fkTypeReglement;
|
|
final amount = double.tryParse(passage.montant) ?? 0.0;
|
|
paymentAmounts[typeId] = (paymentAmounts[typeId] ?? 0.0) + amount;
|
|
}
|
|
}
|
|
|
|
// Créer les objets PaymentData
|
|
paymentAmounts.forEach((typeId, amount) {
|
|
if (amount > 0 && AppKeys.typesReglements.containsKey(typeId)) {
|
|
final typeInfo = AppKeys.typesReglements[typeId]!;
|
|
paymentData.add(PaymentData(
|
|
typeId: typeId,
|
|
amount: amount,
|
|
title: typeInfo['titre'] as String,
|
|
color: Color(typeInfo['couleur'] as int),
|
|
icon: typeInfo['icon_data'] as IconData,
|
|
));
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _loadDashboardData() async {
|
|
if (mounted) {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
}
|
|
|
|
try {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Chargement des données du tableau de bord...');
|
|
// Utiliser les instances globales définies dans app.dart
|
|
// Pas besoin de Provider.of car les instances sont déjà disponibles
|
|
|
|
// S'assurer que la boîte des opérations est ouverte avant d'y accéder
|
|
OperationModel? currentOperation;
|
|
try {
|
|
// Vérifier si la boîte Hive est ouverte
|
|
if (!Hive.isBoxOpen(AppKeys.operationsBoxName)) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Ouverture de la boîte operations dans _loadDashboardData...');
|
|
try {
|
|
await Hive.openBox<OperationModel>(AppKeys.operationsBoxName);
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Boîte operations ouverte avec succès dans _loadDashboardData');
|
|
} catch (boxError) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Erreur lors de l\'ouverture de la boîte operations dans _loadDashboardData: $boxError');
|
|
// Continuer malgré l'erreur
|
|
}
|
|
}
|
|
|
|
// Récupérer l'opération en cours
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Récupération de l\'opération en cours...');
|
|
currentOperation = userRepository.getCurrentOperation();
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Opération récupérée: ${currentOperation?.id ?? "null"}');
|
|
} catch (boxError) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Erreur lors de la récupération de l\'opération: $boxError');
|
|
// Afficher un message d'erreur ou gérer l'erreur de manière appropriée
|
|
}
|
|
|
|
if (currentOperation != null) {
|
|
// Charger les passages pour l'opération en cours
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Chargement des passages pour l\'opération ${currentOperation.id}...');
|
|
final passages =
|
|
passageRepository.getPassagesByOperation(currentOperation.id);
|
|
debugPrint(
|
|
'AdminDashboardHomePage: ${passages.length} passages récupérés');
|
|
|
|
// Calculer le nombre total de passages
|
|
totalPassages = passages.length;
|
|
|
|
// Calculer le montant total collecté
|
|
totalAmounts = passages.fold(
|
|
0.0,
|
|
(sum, passage) =>
|
|
sum +
|
|
(passage.montant.isNotEmpty
|
|
? double.tryParse(passage.montant) ?? 0.0
|
|
: 0.0));
|
|
|
|
// Préparer les données pour le graphique de paiement
|
|
_preparePaymentData(passages);
|
|
|
|
// Compter les passages par type
|
|
passagesByType = {};
|
|
for (final passage in passages) {
|
|
final typeId = passage.fkType;
|
|
passagesByType[typeId] = (passagesByType[typeId] ?? 0) + 1;
|
|
}
|
|
|
|
// Afficher les comptages par type pour le débogage
|
|
debugPrint('AdminDashboardHomePage: Comptage des passages par type:');
|
|
passagesByType.forEach((typeId, count) {
|
|
final typeInfo = AppKeys.typesPassages[typeId];
|
|
final typeName = typeInfo != null ? typeInfo['titre'] : 'Inconnu';
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Type $typeId ($typeName): $count passages');
|
|
});
|
|
|
|
// Charger les statistiques par membre
|
|
memberStats = [];
|
|
final Map<int, int> memberCounts = {};
|
|
|
|
// Compter les passages par membre
|
|
for (final passage in passages) {
|
|
memberCounts[passage.fkUser] =
|
|
(memberCounts[passage.fkUser] ?? 0) + 1;
|
|
}
|
|
|
|
// Récupérer les informations des membres
|
|
for (final entry in memberCounts.entries) {
|
|
final user = userRepository.getUserById(entry.key);
|
|
if (user != null) {
|
|
memberStats.add({
|
|
'name': '${user.firstName ?? ''} ${user.name ?? ''}'.trim(),
|
|
'count': entry.value,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Trier les membres par nombre de passages (décroissant)
|
|
memberStats
|
|
.sort((a, b) => (b['count'] as int).compareTo(a['count'] as int));
|
|
} else {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Aucune opération en cours, impossible de charger les passages');
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
isDataLoaded = true;
|
|
isLoading = false;
|
|
isFirstLoad = false; // Marquer que le premier chargement est terminé
|
|
});
|
|
}
|
|
|
|
// Vérifier si les données sont correctement chargées
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Données chargées: isDataLoaded=$isDataLoaded, totalPassages=$totalPassages, passagesByType=${passagesByType.length} types');
|
|
} catch (e) {
|
|
debugPrint(
|
|
'AdminDashboardHomePage: Erreur lors du chargement des données: $e');
|
|
if (mounted) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
debugPrint('Building AdminDashboardHomePage');
|
|
return FutureBuilder<void>(
|
|
future: _initFuture,
|
|
builder: (context, snapshot) {
|
|
// Afficher un indicateur de chargement pendant l'initialisation des boîtes Hive
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
debugPrint('FutureBuilder: ConnectionState.waiting');
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
|
|
// Même si nous avons une erreur, nous continuons à afficher le contenu
|
|
// car nous avons modifié _initHiveBoxes pour ne pas propager les erreurs
|
|
if (snapshot.hasError) {
|
|
debugPrint('FutureBuilder: hasError - ${snapshot.error}');
|
|
// Nous affichons un message d'erreur mais continuons à afficher le contenu
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content:
|
|
Text('Erreur lors de l\'initialisation: ${snapshot.error}'),
|
|
backgroundColor: Colors.red,
|
|
duration: const Duration(seconds: 5),
|
|
action: SnackBarAction(
|
|
label: 'Réessayer',
|
|
onPressed: () {
|
|
setState(() {
|
|
_initFuture = _initHiveBoxes().then((_) {
|
|
_loadDashboardData();
|
|
});
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
debugPrint('FutureBuilder: Initialisation réussie');
|
|
}
|
|
|
|
// L'initialisation a réussi, afficher le contenu
|
|
final screenWidth = MediaQuery.of(context).size.width;
|
|
final isDesktop = screenWidth > 800;
|
|
// Utiliser l'instance globale définie dans app.dart
|
|
|
|
// Récupérer l'opération en cours (les boîtes sont déjà ouvertes)
|
|
final currentOperation = userRepository.getCurrentOperation();
|
|
|
|
// Titre dynamique avec l'ID et le nom de l'opération
|
|
final String title = currentOperation != null
|
|
? 'Synthèse de l\'opération #${currentOperation.id} ${currentOperation.name}'
|
|
: 'Synthèse de l\'opération';
|
|
|
|
return Stack(children: [
|
|
// Fond dégradé avec petits points blancs
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.white, Colors.red.shade300],
|
|
),
|
|
),
|
|
child: CustomPaint(
|
|
painter: DotsPainter(),
|
|
child: Container(width: double.infinity, height: double.infinity),
|
|
),
|
|
),
|
|
// Contenu de la page
|
|
SingleChildScrollView(
|
|
padding: const EdgeInsets.all(AppTheme.spacingL),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Titre avec bouton de rafraîchissement sur la même ligne
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style:
|
|
Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
// Bouton de rafraîchissement
|
|
if (!isLoading)
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
tooltip: 'Rafraîchir les données',
|
|
onPressed: _loadDashboardData,
|
|
)
|
|
else
|
|
const SizedBox(
|
|
width: 24,
|
|
height: 24,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
// Afficher un indicateur de chargement si les données ne sont pas encore chargées
|
|
if (isLoading && !isDataLoaded)
|
|
const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(32.0),
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
),
|
|
|
|
// Afficher le contenu seulement si les données sont chargées ou en cours de mise à jour
|
|
if (isDataLoaded || isLoading) ...[
|
|
// Cartes de synthèse
|
|
isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: _buildSummaryCard(
|
|
context,
|
|
'Passages totaux',
|
|
totalPassages.toString(),
|
|
Icons.map_outlined,
|
|
AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingM),
|
|
Expanded(
|
|
flex: 2,
|
|
child: _buildSummaryCard(
|
|
context,
|
|
'Montant collecté',
|
|
'${totalAmounts.toStringAsFixed(2)} €',
|
|
Icons.euro_outlined,
|
|
AppTheme.buttonSuccessColor,
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingM),
|
|
Expanded(
|
|
flex: 3,
|
|
child: SectorDistributionCard(
|
|
key: ValueKey(
|
|
'sector_distribution_${isFirstLoad ? 'initial' : 'refreshed'}_$isLoading'),
|
|
height: 200,
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
_buildSummaryCard(
|
|
context,
|
|
'Passages totaux',
|
|
totalPassages.toString(),
|
|
Icons.map_outlined,
|
|
AppTheme.primaryColor,
|
|
),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
_buildSummaryCard(
|
|
context,
|
|
'Montant collecté',
|
|
'${totalAmounts.toStringAsFixed(2)} €',
|
|
Icons.euro_outlined,
|
|
AppTheme.buttonSuccessColor,
|
|
),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
SectorDistributionCard(
|
|
key: ValueKey(
|
|
'sector_distribution_${isFirstLoad ? 'initial' : 'refreshed'}_$isLoading'),
|
|
height: 200,
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: AppTheme.spacingL),
|
|
|
|
// Graphique d'activité
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius:
|
|
BorderRadius.circular(AppTheme.borderRadiusMedium),
|
|
boxShadow: AppTheme.cardShadow,
|
|
),
|
|
child: ActivityChart(
|
|
key: ValueKey(
|
|
'activity_chart_${isFirstLoad ? 'initial' : 'refreshed'}_$isLoading'),
|
|
height: 350,
|
|
showAllPassages:
|
|
true, // Tous les passages, pas seulement ceux de l'utilisateur courant
|
|
title: 'Passages réalisés par jour (15 derniers jours)',
|
|
daysToShow: 15,
|
|
),
|
|
// Si vous avez besoin de passer l'ID de l'opération en cours, décommentez les lignes suivantes
|
|
// child: ActivityChart(
|
|
// height: 350,
|
|
// loadFromHive: true,
|
|
// showAllPassages: true,
|
|
// title: 'Passages réalisés par jour (15 derniers jours)',
|
|
// daysToShow: 15,
|
|
// operationId: userRepository.getCurrentOperation()?.id,
|
|
// ),
|
|
),
|
|
|
|
const SizedBox(height: AppTheme.spacingL),
|
|
|
|
// Graphiques de répartition
|
|
isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: _buildPassageTypeCard(context),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingM),
|
|
Expanded(
|
|
child: _buildPaymentTypeCard(context),
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
_buildPassageTypeCard(context),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
_buildPaymentTypeCard(context),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: AppTheme.spacingL),
|
|
|
|
// Actions rapides - uniquement visible sur le web
|
|
if (kIsWeb) ...[
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius:
|
|
BorderRadius.circular(AppTheme.borderRadiusMedium),
|
|
boxShadow: AppTheme.cardShadow,
|
|
),
|
|
padding: const EdgeInsets.all(AppTheme.spacingM),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Actions sur cette opération',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
Wrap(
|
|
spacing: AppTheme.spacingM,
|
|
runSpacing: AppTheme.spacingM,
|
|
children: [
|
|
_buildActionButton(
|
|
context,
|
|
'Exporter les données',
|
|
Icons.file_download_outlined,
|
|
AppTheme.primaryColor,
|
|
() {},
|
|
),
|
|
_buildActionButton(
|
|
context,
|
|
'Gérer les secteurs',
|
|
Icons.map_outlined,
|
|
AppTheme.accentColor,
|
|
() {},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
],
|
|
),
|
|
)
|
|
]);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildSummaryCard(
|
|
BuildContext context,
|
|
String label,
|
|
String value,
|
|
IconData icon,
|
|
Color color,
|
|
) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(AppTheme.spacingM),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
|
|
boxShadow: AppTheme.cardShadow,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius:
|
|
BorderRadius.circular(AppTheme.borderRadiusSmall),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: color,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingM),
|
|
Text(
|
|
label,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppTheme.spacingM),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 24,
|
|
color: color,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Construit la carte de répartition par type de passage avec liste
|
|
Widget _buildPassageTypeCard(BuildContext context) {
|
|
return PassageSummaryCard(
|
|
title: 'Répartition par type de passage',
|
|
titleColor: AppTheme.primaryColor,
|
|
titleIcon: Icons.route,
|
|
height: 300,
|
|
useValueListenable: false, // Utiliser les données statiques
|
|
showAllPassages: true,
|
|
excludePassageTypes: const [2], // Exclure "À finaliser"
|
|
passagesByType: passagesByType,
|
|
customTotalDisplay: (total) => '$totalPassages passages',
|
|
isDesktop: MediaQuery.of(context).size.width > 800,
|
|
backgroundIcon: Icons.route,
|
|
backgroundIconColor: AppTheme.primaryColor,
|
|
backgroundIconOpacity: 0.07,
|
|
backgroundIconSize: 180,
|
|
);
|
|
}
|
|
|
|
// Construit la carte de répartition par mode de paiement
|
|
Widget _buildPaymentTypeCard(BuildContext context) {
|
|
return PaymentSummaryCard(
|
|
title: 'Répartition par mode de paiement',
|
|
titleColor: AppTheme.buttonSuccessColor,
|
|
titleIcon: Icons.euro,
|
|
height: 300,
|
|
useValueListenable: false, // Utiliser les données statiques
|
|
showAllPayments: true,
|
|
paymentsByType: _convertPaymentDataToMap(paymentData),
|
|
customTotalDisplay: (total) => '${totalAmounts.toStringAsFixed(2)} €',
|
|
isDesktop: MediaQuery.of(context).size.width > 800,
|
|
backgroundIcon: Icons.euro,
|
|
backgroundIconColor: AppTheme.primaryColor,
|
|
backgroundIconOpacity: 0.07,
|
|
backgroundIconSize: 180,
|
|
);
|
|
}
|
|
|
|
// Méthode helper pour convertir les PaymentData en Map
|
|
Map<int, double> _convertPaymentDataToMap(List<PaymentData> paymentDataList) {
|
|
final Map<int, double> result = {};
|
|
for (final payment in paymentDataList) {
|
|
result[payment.typeId] = payment.amount;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Widget _buildActionButton(
|
|
BuildContext context,
|
|
String label,
|
|
IconData icon,
|
|
Color color,
|
|
VoidCallback onPressed,
|
|
) {
|
|
return ElevatedButton.icon(
|
|
onPressed: onPressed,
|
|
icon: Icon(icon),
|
|
label: Text(label),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: color,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppTheme.spacingL,
|
|
vertical: AppTheme.spacingM,
|
|
),
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|