feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API
- Mise à jour VERSION vers 3.3.4 - Optimisations et révisions architecture API (deploy-api.sh, scripts de migration) - Ajout documentation Stripe Tap to Pay complète - Migration vers polices Inter Variable pour Flutter - Optimisations build Android et nettoyage fichiers temporaires - Amélioration système de déploiement avec gestion backups - Ajout scripts CRON et migrations base de données 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/user_sector_model.dart';
|
||||
|
||||
/// Widget de graphique d'activité affichant les passages
|
||||
class ActivityChart extends StatefulWidget {
|
||||
@@ -183,9 +184,15 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
final passages = passagesBox.values.toList();
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
|
||||
// Déterminer l'utilisateur cible selon les filtres
|
||||
final int? targetUserId =
|
||||
widget.showAllPassages ? null : (widget.userId ?? currentUser?.id);
|
||||
// Pour les users : récupérer les secteurs assignés
|
||||
Set<int>? userSectorIds;
|
||||
if (!widget.showAllPassages && currentUser != null) {
|
||||
final userSectorBox = Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
|
||||
userSectorIds = userSectorBox.values
|
||||
.where((us) => us.id == currentUser.id)
|
||||
.map((us) => us.fkSector)
|
||||
.toSet();
|
||||
}
|
||||
|
||||
// Calculer la date de début (nombre de jours en arrière)
|
||||
final endDate = DateTime.now();
|
||||
@@ -213,8 +220,8 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
// Appliquer les filtres
|
||||
bool shouldInclude = true;
|
||||
|
||||
// Filtrer par utilisateur si nécessaire
|
||||
if (targetUserId != null && passage.fkUser != targetUserId) {
|
||||
// Filtrer par secteurs assignés si nécessaire (pour les users)
|
||||
if (userSectorIds != null && !userSectorIds.contains(passage.fkSector)) {
|
||||
shouldInclude = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show listEquals;
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
|
||||
@@ -157,7 +158,23 @@ class _PassagePieChartState extends State<PassagePieChart>
|
||||
|
||||
/// Construction du widget avec des données statiques (ancien système)
|
||||
Widget _buildWithStaticData() {
|
||||
final chartData = _prepareChartDataFromMap(widget.passagesByType);
|
||||
// Vérifier si le type Lot doit être affiché
|
||||
bool showLotType = true;
|
||||
final currentUser = CurrentUserService.instance.currentUser;
|
||||
if (currentUser != null && currentUser.fkEntite != null) {
|
||||
final userAmicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
||||
if (userAmicale != null) {
|
||||
showLotType = userAmicale.chkLotActif;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtrer les données pour exclure le type 5 si nécessaire
|
||||
Map<int, int> filteredData = Map.from(widget.passagesByType);
|
||||
if (!showLotType) {
|
||||
filteredData.remove(5);
|
||||
}
|
||||
|
||||
final chartData = _prepareChartDataFromMap(filteredData);
|
||||
return _buildChart(chartData);
|
||||
}
|
||||
|
||||
@@ -167,25 +184,38 @@ class _PassagePieChartState extends State<PassagePieChart>
|
||||
final passages = passagesBox.values.toList();
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
|
||||
// Vérifier si le type Lot doit être affiché
|
||||
bool showLotType = true;
|
||||
if (currentUser != null && currentUser.fkEntite != null) {
|
||||
final userAmicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
||||
if (userAmicale != null) {
|
||||
showLotType = userAmicale.chkLotActif;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer les données selon les filtres
|
||||
final Map<int, int> passagesByType = {};
|
||||
|
||||
// Initialiser tous les types de passage possibles
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (typeId == 5 && !showLotType) {
|
||||
continue;
|
||||
}
|
||||
if (!widget.excludePassageTypes.contains(typeId)) {
|
||||
passagesByType[typeId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// L'API filtre déjà les passages côté serveur
|
||||
// On compte simplement tous les passages de la box
|
||||
for (final passage in passages) {
|
||||
// Appliquer les filtres
|
||||
// Appliquer les filtres locaux uniquement
|
||||
bool shouldInclude = true;
|
||||
|
||||
// Filtrer par utilisateur si nécessaire
|
||||
if (!widget.showAllPassages && widget.userId != null) {
|
||||
// Filtrer par userId si spécifié (cas particulier pour compatibilité)
|
||||
if (widget.userId != null) {
|
||||
shouldInclude = passage.fkUser == widget.userId;
|
||||
} else if (!widget.showAllPassages && currentUser != null) {
|
||||
shouldInclude = passage.fkUser == currentUser.id;
|
||||
}
|
||||
|
||||
// Exclure certains types
|
||||
@@ -193,6 +223,11 @@ class _PassagePieChartState extends State<PassagePieChart>
|
||||
shouldInclude = false;
|
||||
}
|
||||
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (passage.fkType == 5 && !showLotType) {
|
||||
shouldInclude = false;
|
||||
}
|
||||
|
||||
if (shouldInclude) {
|
||||
passagesByType[passage.fkType] =
|
||||
(passagesByType[passage.fkType] ?? 0) + 1;
|
||||
@@ -211,8 +246,23 @@ class _PassagePieChartState extends State<PassagePieChart>
|
||||
Map<int, int> passagesByType) {
|
||||
final List<PassageChartData> chartData = [];
|
||||
|
||||
// Vérifier si le type Lot doit être affiché
|
||||
bool showLotType = true;
|
||||
final currentUser = CurrentUserService.instance.currentUser;
|
||||
if (currentUser != null && currentUser.fkEntite != null) {
|
||||
final userAmicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
||||
if (userAmicale != null) {
|
||||
showLotType = userAmicale.chkLotActif;
|
||||
}
|
||||
}
|
||||
|
||||
// Créer les données du graphique
|
||||
passagesByType.forEach((typeId, count) {
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (typeId == 5 && !showLotType) {
|
||||
return; // Skip ce type
|
||||
}
|
||||
|
||||
// Vérifier que le type existe et que le compteur est positif
|
||||
if (count > 0 && AppKeys.typesPassages.containsKey(typeId)) {
|
||||
final typeInfo = AppKeys.typesPassages[typeId]!;
|
||||
|
||||
@@ -75,6 +75,39 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Si useValueListenable, construire avec ValueListenableBuilder centralisé
|
||||
if (useValueListenable) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> passagesBox, child) {
|
||||
// Calculer les données une seule fois
|
||||
final passagesCounts = _calculatePassagesCounts(passagesBox);
|
||||
final totalUserPassages = passagesCounts.values.fold(0, (sum, count) => sum + count);
|
||||
|
||||
return _buildCardContent(
|
||||
context,
|
||||
totalUserPassages: totalUserPassages,
|
||||
passagesCounts: passagesCounts,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Données statiques
|
||||
final totalPassages = passagesByType?.values.fold(0, (sum, count) => sum + count) ?? 0;
|
||||
return _buildCardContent(
|
||||
context,
|
||||
totalUserPassages: totalPassages,
|
||||
passagesCounts: passagesByType ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Construit le contenu de la card avec les données calculées
|
||||
Widget _buildCardContent(
|
||||
BuildContext context, {
|
||||
required int totalUserPassages,
|
||||
required Map<int, int> passagesCounts,
|
||||
}) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -102,9 +135,7 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Titre avec comptage
|
||||
useValueListenable
|
||||
? _buildTitleWithValueListenable()
|
||||
: _buildTitleWithStaticData(context),
|
||||
_buildTitle(context, totalUserPassages),
|
||||
const Divider(height: 24),
|
||||
// Contenu principal
|
||||
Expanded(
|
||||
@@ -115,9 +146,7 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
// Liste des passages à gauche
|
||||
Expanded(
|
||||
flex: isDesktop ? 1 : 2,
|
||||
child: useValueListenable
|
||||
? _buildPassagesListWithValueListenable()
|
||||
: _buildPassagesListWithStaticData(context),
|
||||
child: _buildPassagesList(context, passagesCounts),
|
||||
),
|
||||
|
||||
// Séparateur vertical
|
||||
@@ -129,9 +158,10 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PassagePieChart(
|
||||
useValueListenable: useValueListenable,
|
||||
passagesByType: passagesByType ?? {},
|
||||
useValueListenable: false, // Utilise les données calculées
|
||||
passagesByType: passagesCounts,
|
||||
excludePassageTypes: excludePassageTypes,
|
||||
showAllPassages: showAllPassages,
|
||||
userId: showAllPassages ? null : userId,
|
||||
size: double.infinity,
|
||||
labelSize: 12,
|
||||
@@ -155,53 +185,8 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction du titre avec ValueListenableBuilder
|
||||
Widget _buildTitleWithValueListenable() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> passagesBox, child) {
|
||||
final totalUserPassages = _calculateUserPassagesCount(passagesBox);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (titleIcon != null) ...[
|
||||
Icon(
|
||||
titleIcon,
|
||||
color: titleColor,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: AppTheme.r(context, 16),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
customTotalDisplay?.call(totalUserPassages) ??
|
||||
totalUserPassages.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: AppTheme.r(context, 20),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction du titre avec données statiques
|
||||
Widget _buildTitleWithStaticData(BuildContext context) {
|
||||
final totalPassages =
|
||||
passagesByType?.values.fold(0, (sum, count) => sum + count) ?? 0;
|
||||
|
||||
/// Construction du titre
|
||||
Widget _buildTitle(BuildContext context, int totalUserPassages) {
|
||||
return Row(
|
||||
children: [
|
||||
if (titleIcon != null) ...[
|
||||
@@ -222,7 +207,8 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
customTotalDisplay?.call(totalPassages) ?? totalPassages.toString(),
|
||||
customTotalDisplay?.call(totalUserPassages) ??
|
||||
totalUserPassages.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: AppTheme.r(context, 20),
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -233,30 +219,28 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction de la liste des passages avec ValueListenableBuilder
|
||||
Widget _buildPassagesListWithValueListenable() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> passagesBox, child) {
|
||||
final passagesCounts = _calculatePassagesCounts(passagesBox);
|
||||
|
||||
return _buildPassagesList(context, passagesCounts);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction de la liste des passages avec données statiques
|
||||
Widget _buildPassagesListWithStaticData(BuildContext context) {
|
||||
return _buildPassagesList(context, passagesByType ?? {});
|
||||
}
|
||||
|
||||
/// Construction de la liste des passages
|
||||
Widget _buildPassagesList(BuildContext context, Map<int, int> passagesCounts) {
|
||||
// Vérifier si le type Lot doit être affiché
|
||||
bool showLotType = true;
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
if (currentUser != null && currentUser.fkEntite != null) {
|
||||
final userAmicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
||||
if (userAmicale != null) {
|
||||
showLotType = userAmicale.chkLotActif;
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...AppKeys.typesPassages.entries.map((entry) {
|
||||
...AppKeys.typesPassages.entries.where((entry) {
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (entry.key == 5 && !showLotType) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).map((entry) {
|
||||
final int typeId = entry.key;
|
||||
final Map<String, dynamic> typeData = entry.value;
|
||||
final int count = passagesCounts[typeId] ?? 0;
|
||||
@@ -303,54 +287,45 @@ class PassageSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Calcule le nombre total de passages pour l'utilisateur
|
||||
int _calculateUserPassagesCount(Box<PassageModel> passagesBox) {
|
||||
if (showAllPassages) {
|
||||
// Pour les administrateurs : tous les passages sauf ceux exclus
|
||||
return passagesBox.values
|
||||
.where((passage) => !excludePassageTypes.contains(passage.fkType))
|
||||
.length;
|
||||
} else {
|
||||
// Pour les utilisateurs : seulement leurs passages
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final targetUserId = userId ?? currentUser?.id;
|
||||
|
||||
if (targetUserId == null) return 0;
|
||||
|
||||
return passagesBox.values
|
||||
.where((passage) =>
|
||||
passage.fkUser == targetUserId &&
|
||||
!excludePassageTypes.contains(passage.fkType))
|
||||
.length;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calcule les compteurs de passages par type
|
||||
Map<int, int> _calculatePassagesCounts(Box<PassageModel> passagesBox) {
|
||||
final Map<int, int> counts = {};
|
||||
|
||||
// Vérifier si le type Lot doit être affiché
|
||||
bool showLotType = true;
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
if (currentUser != null && currentUser.fkEntite != null) {
|
||||
final userAmicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
||||
if (userAmicale != null) {
|
||||
showLotType = userAmicale.chkLotActif;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialiser tous les types
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (typeId == 5 && !showLotType) {
|
||||
continue;
|
||||
}
|
||||
// Exclure les types non désirés
|
||||
if (excludePassageTypes.contains(typeId)) {
|
||||
continue;
|
||||
}
|
||||
counts[typeId] = 0;
|
||||
}
|
||||
|
||||
if (showAllPassages) {
|
||||
// Pour les administrateurs : compter tous les passages
|
||||
for (final passage in passagesBox.values) {
|
||||
counts[passage.fkType] = (counts[passage.fkType] ?? 0) + 1;
|
||||
// L'API filtre déjà les passages côté serveur
|
||||
// On compte simplement tous les passages de la box
|
||||
for (final passage in passagesBox.values) {
|
||||
// Exclure le type Lot (5) si chkLotActif = false
|
||||
if (passage.fkType == 5 && !showLotType) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Pour les utilisateurs : compter seulement leurs passages
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final targetUserId = userId ?? currentUser?.id;
|
||||
|
||||
if (targetUserId != null) {
|
||||
for (final passage in passagesBox.values) {
|
||||
if (passage.fkUser == targetUserId) {
|
||||
counts[passage.fkType] = (counts[passage.fkType] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
// Exclure les types non désirés
|
||||
if (excludePassageTypes.contains(passage.fkType)) {
|
||||
continue;
|
||||
}
|
||||
counts[passage.fkType] = (counts[passage.fkType] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return counts;
|
||||
|
||||
@@ -163,11 +163,6 @@ class _PaymentPieChartState extends State<PaymentPieChart>
|
||||
try {
|
||||
final passages = passagesBox.values.toList();
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
|
||||
// Déterminer l'utilisateur cible selon les filtres
|
||||
final int? targetUserId = widget.showAllPassages
|
||||
? null
|
||||
: (widget.userId ?? currentUser?.id);
|
||||
|
||||
// Initialiser les montants par type de règlement
|
||||
final Map<int, double> paymentAmounts = {
|
||||
@@ -177,37 +172,38 @@ class _PaymentPieChartState extends State<PaymentPieChart>
|
||||
3: 0.0, // CB
|
||||
};
|
||||
|
||||
// Parcourir les passages et calculer les montants par type de règlement
|
||||
// Déterminer le filtre utilisateur : en mode user, on filtre par fkUser
|
||||
final int? filterUserId = widget.showAllPassages
|
||||
? null
|
||||
: (widget.userId ?? currentUser?.id);
|
||||
|
||||
for (final passage in passages) {
|
||||
// Appliquer le filtre utilisateur si nécessaire
|
||||
bool shouldInclude = true;
|
||||
if (targetUserId != null && passage.fkUser != targetUserId) {
|
||||
shouldInclude = false;
|
||||
// En mode user, ne compter que les passages de l'utilisateur
|
||||
if (filterUserId != null && passage.fkUser != filterUserId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldInclude) {
|
||||
final int typeReglement = passage.fkTypeReglement;
|
||||
|
||||
// Convertir la chaîne de montant en double
|
||||
double montant = 0.0;
|
||||
try {
|
||||
// Gérer les formats possibles (virgule ou point)
|
||||
String montantStr = passage.montant.replaceAll(',', '.');
|
||||
montant = double.tryParse(montantStr) ?? 0.0;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur de conversion du montant: ${passage.montant}');
|
||||
}
|
||||
final int typeReglement = passage.fkTypeReglement;
|
||||
|
||||
// Ne compter que les passages avec un montant > 0
|
||||
if (montant > 0) {
|
||||
// Ajouter au montant total par type de règlement
|
||||
if (paymentAmounts.containsKey(typeReglement)) {
|
||||
paymentAmounts[typeReglement] =
|
||||
(paymentAmounts[typeReglement] ?? 0.0) + montant;
|
||||
} else {
|
||||
// Si le type n'est pas dans notre map, l'ajouter à la catégorie par défaut
|
||||
paymentAmounts[0] = (paymentAmounts[0] ?? 0.0) + montant;
|
||||
}
|
||||
// Convertir la chaîne de montant en double
|
||||
double montant = 0.0;
|
||||
try {
|
||||
// Gérer les formats possibles (virgule ou point)
|
||||
String montantStr = passage.montant.replaceAll(',', '.');
|
||||
montant = double.tryParse(montantStr) ?? 0.0;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur de conversion du montant: ${passage.montant}');
|
||||
}
|
||||
|
||||
// Ne compter que les passages avec un montant > 0
|
||||
if (montant > 0) {
|
||||
// Ajouter au montant total par type de règlement
|
||||
if (paymentAmounts.containsKey(typeReglement)) {
|
||||
paymentAmounts[typeReglement] =
|
||||
(paymentAmounts[typeReglement] ?? 0.0) + montant;
|
||||
} else {
|
||||
// Si le type n'est pas dans notre map, l'ajouter à la catégorie par défaut
|
||||
paymentAmounts[0] = (paymentAmounts[0] ?? 0.0) + montant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,39 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Si useValueListenable, construire avec ValueListenableBuilder centralisé
|
||||
if (useValueListenable) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> passagesBox, child) {
|
||||
// Calculer les données une seule fois
|
||||
final paymentAmounts = _calculatePaymentAmounts(passagesBox);
|
||||
final totalAmount = paymentAmounts.values.fold(0.0, (sum, amount) => sum + amount);
|
||||
|
||||
return _buildCardContent(
|
||||
context,
|
||||
totalAmount: totalAmount,
|
||||
paymentAmounts: paymentAmounts,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Données statiques
|
||||
final totalAmount = paymentsByType?.values.fold(0.0, (sum, amount) => sum + amount) ?? 0.0;
|
||||
return _buildCardContent(
|
||||
context,
|
||||
totalAmount: totalAmount,
|
||||
paymentAmounts: paymentsByType ?? {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Construit le contenu de la card avec les données calculées
|
||||
Widget _buildCardContent(
|
||||
BuildContext context, {
|
||||
required double totalAmount,
|
||||
required Map<int, double> paymentAmounts,
|
||||
}) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -99,9 +132,7 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Titre avec comptage
|
||||
useValueListenable
|
||||
? _buildTitleWithValueListenable()
|
||||
: _buildTitleWithStaticData(context),
|
||||
_buildTitle(context, totalAmount),
|
||||
const Divider(height: 24),
|
||||
// Contenu principal
|
||||
Expanded(
|
||||
@@ -112,9 +143,7 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
// Liste des règlements à gauche
|
||||
Expanded(
|
||||
flex: isDesktop ? 1 : 2,
|
||||
child: useValueListenable
|
||||
? _buildPaymentsListWithValueListenable()
|
||||
: _buildPaymentsListWithStaticData(context),
|
||||
child: _buildPaymentsList(context, paymentAmounts),
|
||||
),
|
||||
|
||||
// Séparateur vertical
|
||||
@@ -126,11 +155,9 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PaymentPieChart(
|
||||
useValueListenable: useValueListenable,
|
||||
payments: useValueListenable
|
||||
? []
|
||||
: _convertMapToPaymentData(
|
||||
paymentsByType ?? {}),
|
||||
useValueListenable: false, // Utilise les données calculées
|
||||
payments: _convertMapToPaymentData(paymentAmounts),
|
||||
showAllPassages: showAllPayments,
|
||||
userId: showAllPayments ? null : userId,
|
||||
size: double.infinity,
|
||||
labelSize: 12,
|
||||
@@ -158,53 +185,8 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction du titre avec ValueListenableBuilder
|
||||
Widget _buildTitleWithValueListenable() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> 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: TextStyle(
|
||||
fontSize: AppTheme.r(context, 16),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
customTotalDisplay?.call(paymentStats['totalAmount']) ??
|
||||
'${paymentStats['totalAmount'].toStringAsFixed(2)} €',
|
||||
style: TextStyle(
|
||||
fontSize: AppTheme.r(context, 20),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction du titre avec données statiques
|
||||
Widget _buildTitleWithStaticData(BuildContext context) {
|
||||
final totalAmount =
|
||||
paymentsByType?.values.fold(0.0, (sum, amount) => sum + amount) ?? 0.0;
|
||||
|
||||
/// Construction du titre
|
||||
Widget _buildTitle(BuildContext context, double totalAmount) {
|
||||
return Row(
|
||||
children: [
|
||||
if (titleIcon != null) ...[
|
||||
@@ -237,24 +219,6 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction de la liste des règlements avec ValueListenableBuilder
|
||||
Widget _buildPaymentsListWithValueListenable() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
||||
builder: (context, Box<PassageModel> passagesBox, child) {
|
||||
final paymentAmounts = _calculatePaymentAmounts(passagesBox);
|
||||
|
||||
return _buildPaymentsList(context, paymentAmounts);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction de la liste des règlements avec données statiques
|
||||
Widget _buildPaymentsListWithStaticData(BuildContext context) {
|
||||
return _buildPaymentsList(context, paymentsByType ?? {});
|
||||
}
|
||||
|
||||
/// Construction de la liste des règlements
|
||||
Widget _buildPaymentsList(BuildContext context, Map<int, double> paymentAmounts) {
|
||||
return Column(
|
||||
@@ -307,70 +271,6 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Calcule les statistiques de règlement
|
||||
Map<String, dynamic> _calculatePaymentStats(Box<PassageModel> 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<int, double> _calculatePaymentAmounts(Box<PassageModel> passagesBox) {
|
||||
final Map<int, double> paymentAmounts = {};
|
||||
@@ -380,57 +280,33 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
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;
|
||||
// En mode user, filtrer uniquement les passages créés par l'utilisateur (fkUser)
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final int? filterUserId = showAllPayments ? null : currentUser?.id;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
for (final passage in passagesBox.values) {
|
||||
// En mode user, ne compter que les passages de l'utilisateur
|
||||
if (filterUserId != null && passage.fkUser != filterUserId) {
|
||||
continue;
|
||||
}
|
||||
} 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;
|
||||
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
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (montant > 0) {
|
||||
if (paymentAmounts.containsKey(typeReglement)) {
|
||||
paymentAmounts[typeReglement] =
|
||||
(paymentAmounts[typeReglement] ?? 0.0) + montant;
|
||||
} else {
|
||||
paymentAmounts[0] = (paymentAmounts[0] ?? 0.0) + montant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user