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:
pierre
2025-10-05 20:11:15 +02:00
parent 2786252307
commit 570a1fa1f0
212 changed files with 24275 additions and 11321 deletions

View File

@@ -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;