import 'package:flutter/material.dart'; import 'package:geosector_app/presentation/widgets/charts/payment_pie_chart.dart'; import 'package:geosector_app/presentation/widgets/charts/payment_data.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/app.dart'; /// Widget commun pour afficher une carte de synthèse des règlements /// avec liste des types à gauche et graphique en camembert à droite class PaymentSummaryCard extends StatelessWidget { /// Titre de la carte final String title; /// Couleur de l'icône et du titre final Color titleColor; /// Icône à afficher dans le titre final IconData? titleIcon; /// Hauteur totale de la carte final double? height; /// Utiliser ValueListenableBuilder pour mise à jour automatique final bool useValueListenable; /// ID de l'utilisateur pour filtrer les passages (si null, tous les utilisateurs) final int? userId; /// Afficher tous les règlements (admin) ou seulement ceux de l'utilisateur final bool showAllPayments; /// Données statiques de règlements par type (utilisé si useValueListenable = false) final Map? paymentsByType; /// Fonction de callback pour afficher la valeur totale personnalisée final String Function(double totalAmount)? customTotalDisplay; /// Afficher le graphique en mode desktop ou mobile final bool isDesktop; /// Icône d'arrière-plan (optionnelle) final IconData? backgroundIcon; /// Couleur de l'icône d'arrière-plan final Color? backgroundIconColor; /// Opacité de l'icône d'arrière-plan final double backgroundIconOpacity; /// Taille de l'icône d'arrière-plan final double backgroundIconSize; const PaymentSummaryCard({ super.key, required this.title, this.titleColor = AppTheme.accentColor, this.titleIcon = Icons.payments, this.height, this.useValueListenable = true, this.userId, this.showAllPayments = false, this.paymentsByType, this.customTotalDisplay, this.isDesktop = true, this.backgroundIcon = Icons.euro_symbol, this.backgroundIconColor, this.backgroundIconOpacity = 0.07, this.backgroundIconSize = 180, }); @override Widget build(BuildContext context) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Stack( children: [ // Icône d'arrière-plan (optionnelle) if (backgroundIcon != null) Positioned.fill( child: Center( child: Icon( backgroundIcon, size: backgroundIconSize, color: (backgroundIconColor ?? Colors.blue) .withOpacity(backgroundIconOpacity), ), ), ), // Contenu principal Container( height: height, padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Titre avec comptage useValueListenable ? _buildTitleWithValueListenable() : _buildTitleWithStaticData(), const Divider(height: 24), // Contenu principal Expanded( child: SizedBox( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Liste des règlements à gauche Expanded( flex: isDesktop ? 1 : 2, child: useValueListenable ? _buildPaymentsListWithValueListenable() : _buildPaymentsListWithStaticData(), ), // Séparateur vertical if (isDesktop) const VerticalDivider(width: 24), // Graphique en camembert à droite Expanded( flex: isDesktop ? 1 : 2, child: Padding( padding: const EdgeInsets.all(8.0), child: PaymentPieChart( useValueListenable: useValueListenable, payments: useValueListenable ? [] : _convertMapToPaymentData( paymentsByType ?? {}), userId: showAllPayments ? null : userId, size: double.infinity, labelSize: 12, showPercentage: true, showIcons: false, showLegend: false, isDonut: true, innerRadius: '50%', enable3DEffect: false, effect3DIntensity: 0.0, enableEnhancedExplode: false, useGradient: false, ), ), ), ], ), ), ), ], ), ), ], ), ); } /// Construction du titre avec ValueListenableBuilder Widget _buildTitleWithValueListenable() { return ValueListenableBuilder( valueListenable: Hive.box(AppKeys.passagesBoxName).listenable(), builder: (context, Box passagesBox, child) { final paymentStats = _calculatePaymentStats(passagesBox); return Row( children: [ if (titleIcon != null) ...[ Icon( titleIcon, color: titleColor, size: 24, ), const SizedBox(width: 8), ], Expanded( child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), Text( customTotalDisplay?.call(paymentStats['totalAmount']) ?? '${paymentStats['totalAmount'].toStringAsFixed(2)} €', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: titleColor, ), ), ], ); }, ); } /// Construction du titre avec données statiques Widget _buildTitleWithStaticData() { final totalAmount = paymentsByType?.values.fold(0.0, (sum, amount) => sum + amount) ?? 0.0; return Row( children: [ if (titleIcon != null) ...[ Icon( titleIcon, color: titleColor, size: 24, ), const SizedBox(width: 8), ], Expanded( child: Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), Text( customTotalDisplay?.call(totalAmount) ?? '${totalAmount.toStringAsFixed(2)} €', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: titleColor, ), ), ], ); } /// Construction de la liste des règlements avec ValueListenableBuilder Widget _buildPaymentsListWithValueListenable() { return ValueListenableBuilder( valueListenable: Hive.box(AppKeys.passagesBoxName).listenable(), builder: (context, Box passagesBox, child) { final paymentAmounts = _calculatePaymentAmounts(passagesBox); return _buildPaymentsList(paymentAmounts); }, ); } /// Construction de la liste des règlements avec données statiques Widget _buildPaymentsListWithStaticData() { return _buildPaymentsList(paymentsByType ?? {}); } /// Construction de la liste des règlements Widget _buildPaymentsList(Map paymentAmounts) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...AppKeys.typesReglements.entries.map((entry) { final int typeId = entry.key; final Map typeData = entry.value; final double amount = paymentAmounts[typeId] ?? 0.0; final Color color = Color(typeData['couleur'] as int); final IconData iconData = typeData['icon_data'] as IconData; return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: color, shape: BoxShape.circle, ), child: Icon( iconData, color: Colors.white, size: 16, ), ), const SizedBox(width: 8), Expanded( child: Text( typeData['titre'] as String, style: const TextStyle(fontSize: 14), ), ), Text( '${amount.toStringAsFixed(2)} €', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color, ), ), ], ), ); }), ], ); } /// Calcule les statistiques de règlement Map _calculatePaymentStats(Box passagesBox) { if (showAllPayments) { // Pour les administrateurs : tous les règlements int passagesWithPaymentCount = 0; double totalAmount = 0.0; for (final passage in passagesBox.values) { // Convertir la chaîne de montant en double double montant = 0.0; try { String montantStr = passage.montant.replaceAll(',', '.'); montant = double.tryParse(montantStr) ?? 0.0; } catch (e) { // Ignorer les erreurs de conversion } if (montant > 0) { passagesWithPaymentCount++; totalAmount += montant; } } return { 'passagesCount': passagesWithPaymentCount, 'totalAmount': totalAmount, }; } else { // Pour les utilisateurs : seulement leurs règlements final currentUser = userRepository.getCurrentUser(); final targetUserId = userId ?? currentUser?.id; if (targetUserId == null) { return {'passagesCount': 0, 'totalAmount': 0.0}; } int passagesWithPaymentCount = 0; double totalAmount = 0.0; for (final passage in passagesBox.values) { if (passage.fkUser == targetUserId) { // Convertir la chaîne de montant en double double montant = 0.0; try { String montantStr = passage.montant.replaceAll(',', '.'); montant = double.tryParse(montantStr) ?? 0.0; } catch (e) { // Ignorer les erreurs de conversion } if (montant > 0) { passagesWithPaymentCount++; totalAmount += montant; } } } return { 'passagesCount': passagesWithPaymentCount, 'totalAmount': totalAmount, }; } } /// Calcule les montants par type de règlement Map _calculatePaymentAmounts(Box passagesBox) { final Map paymentAmounts = {}; // Initialiser tous les types for (final typeId in AppKeys.typesReglements.keys) { paymentAmounts[typeId] = 0.0; } if (showAllPayments) { // Pour les administrateurs : compter tous les règlements for (final passage in passagesBox.values) { final int typeReglement = passage.fkTypeReglement; // Convertir la chaîne de montant en double double montant = 0.0; try { String montantStr = passage.montant.replaceAll(',', '.'); montant = double.tryParse(montantStr) ?? 0.0; } catch (e) { // Ignorer les erreurs de conversion } if (montant > 0) { if (paymentAmounts.containsKey(typeReglement)) { paymentAmounts[typeReglement] = (paymentAmounts[typeReglement] ?? 0.0) + montant; } else { paymentAmounts[0] = (paymentAmounts[0] ?? 0.0) + montant; } } } } else { // Pour les utilisateurs : compter seulement leurs règlements final currentUser = userRepository.getCurrentUser(); final targetUserId = userId ?? currentUser?.id; if (targetUserId != null) { for (final passage in passagesBox.values) { if (passage.fkUser == targetUserId) { final int typeReglement = passage.fkTypeReglement; // Convertir la chaîne de montant en double double montant = 0.0; try { String montantStr = passage.montant.replaceAll(',', '.'); montant = double.tryParse(montantStr) ?? 0.0; } catch (e) { // Ignorer les erreurs de conversion } if (montant > 0) { if (paymentAmounts.containsKey(typeReglement)) { paymentAmounts[typeReglement] = (paymentAmounts[typeReglement] ?? 0.0) + montant; } else { paymentAmounts[0] = (paymentAmounts[0] ?? 0.0) + montant; } } } } } } return paymentAmounts; } /// Convertit une Map en List pour les données statiques List _convertMapToPaymentData(Map paymentsMap) { final List paymentDataList = []; paymentsMap.forEach((typeReglement, montant) { if (montant > 0) { // Ne retourner que les types avec un montant > 0 // Récupérer les informations depuis AppKeys.typesReglements final reglementInfo = AppKeys.typesReglements[typeReglement]; if (reglementInfo != null) { paymentDataList.add(PaymentData( typeId: typeReglement, title: reglementInfo['titre'] as String, amount: montant, color: Color(reglementInfo['couleur'] as int), icon: reglementInfo['icon_data'] as IconData, )); } else { // Fallback pour les types non définis paymentDataList.add(PaymentData( typeId: typeReglement, title: 'Type inconnu', amount: montant, color: Colors.grey, icon: Icons.help_outline, )); } } }); return paymentDataList; } }