feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,6 @@ 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';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -190,7 +189,8 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
}
|
||||
|
||||
/// Calcule les données d'activité depuis la Hive box
|
||||
List<ActivityData> _calculateActivityData(Box<PassageModel> passagesBox, int daysToShow) {
|
||||
List<ActivityData> _calculateActivityData(
|
||||
Box<PassageModel> passagesBox, int daysToShow) {
|
||||
try {
|
||||
final passages = passagesBox.values.toList();
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
@@ -200,7 +200,8 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
if (!widget.showAllPassages && currentUser != null) {
|
||||
final userSectors = userRepository.getUserSectors();
|
||||
userSectorIds = userSectors.map((sector) => sector.id).toSet();
|
||||
debugPrint('ActivityChart: Mode USER - Secteurs assignés: $userSectorIds');
|
||||
debugPrint(
|
||||
'ActivityChart: Mode USER - Secteurs assignés: $userSectorIds');
|
||||
} else {
|
||||
debugPrint('ActivityChart: Mode ADMIN - Tous les passages');
|
||||
}
|
||||
@@ -209,7 +210,8 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
final endDate = DateTime.now();
|
||||
final startDate = endDate.subtract(Duration(days: daysToShow - 1));
|
||||
|
||||
debugPrint('ActivityChart: Période du ${DateFormat('yyyy-MM-dd').format(startDate)} au ${DateFormat('yyyy-MM-dd').format(endDate)}');
|
||||
debugPrint(
|
||||
'ActivityChart: Période du ${DateFormat('yyyy-MM-dd').format(startDate)} au ${DateFormat('yyyy-MM-dd').format(endDate)}');
|
||||
debugPrint('ActivityChart: Nombre total de passages: ${passages.length}');
|
||||
|
||||
// Préparer les données par date
|
||||
@@ -232,29 +234,25 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
for (final passage in passages) {
|
||||
// Appliquer les filtres
|
||||
bool shouldInclude = true;
|
||||
String excludeReason = '';
|
||||
|
||||
// Filtrer par secteurs assignés si nécessaire (pour les users)
|
||||
if (userSectorIds != null && !userSectorIds.contains(passage.fkSector)) {
|
||||
if (userSectorIds != null &&
|
||||
!userSectorIds.contains(passage.fkSector)) {
|
||||
shouldInclude = false;
|
||||
excludeReason = 'Secteur ${passage.fkSector} non assigné';
|
||||
}
|
||||
|
||||
// Exclure les passages de type 2 (À finaliser) avec nbPassages = 0
|
||||
if (shouldInclude && passage.fkType == 2 && passage.nbPassages == 0) {
|
||||
shouldInclude = false;
|
||||
excludeReason = 'Type 2 avec nbPassages=0';
|
||||
}
|
||||
|
||||
// Vérifier si le passage est dans la période
|
||||
final passageDate = passage.passedAt;
|
||||
if (shouldInclude && (passageDate == null ||
|
||||
passageDate.isBefore(startDate) ||
|
||||
passageDate.isAfter(endDate))) {
|
||||
if (shouldInclude &&
|
||||
(passageDate == null ||
|
||||
passageDate.isBefore(startDate) ||
|
||||
passageDate.isAfter(endDate))) {
|
||||
shouldInclude = false;
|
||||
excludeReason = passageDate == null
|
||||
? 'Date null'
|
||||
: 'Hors période (${DateFormat('yyyy-MM-dd').format(passageDate)})';
|
||||
}
|
||||
|
||||
if (shouldInclude && passageDate != null) {
|
||||
@@ -264,12 +262,16 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
(dataByDate[dateStr]![passage.fkType] ?? 0) + 1;
|
||||
includedCount++;
|
||||
}
|
||||
} else if (!shouldInclude && userSectorIds != null) {
|
||||
debugPrint('ActivityChart: Passage #${passage.id} exclu - $excludeReason (type=${passage.fkType}, secteur=${passage.fkSector}, date=${passageDate != null ? DateFormat('yyyy-MM-dd').format(passageDate) : 'null'})');
|
||||
}
|
||||
// Debug désactivé pour éviter la pollution de la console avec les passages type 2 sans date
|
||||
// else if (!shouldInclude && userSectorIds != null) {
|
||||
// debugPrint(
|
||||
// 'ActivityChart: Passage #${passage.id} exclu - $excludeReason (type=${passage.fkType}, secteur=${passage.fkSector}, date=${passageDate != null ? DateFormat('yyyy-MM-dd').format(passageDate) : 'null'})');
|
||||
// }
|
||||
}
|
||||
|
||||
debugPrint('ActivityChart: Passages inclus dans le graphique: $includedCount');
|
||||
debugPrint(
|
||||
'ActivityChart: Passages inclus dans le graphique: $includedCount');
|
||||
|
||||
// Convertir en liste d'ActivityData
|
||||
final List<ActivityData> chartData = [];
|
||||
@@ -520,9 +522,11 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
markerSettings: const MarkerSettings(isVisible: false),
|
||||
animationDuration: 1500,
|
||||
// Ajouter le callback de clic uniquement depuis home_page
|
||||
onPointTap: widget.showPeriodButtons ? (ChartPointDetails details) {
|
||||
_handlePointTap(details, typeId);
|
||||
} : null,
|
||||
onPointTap: widget.showPeriodButtons
|
||||
? (ChartPointDetails details) {
|
||||
_handlePointTap(details, typeId);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -537,11 +541,6 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
|
||||
// Récupérer les données du point cliqué
|
||||
final passageBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
final passages = passageBox.values.toList();
|
||||
|
||||
// Calculer la date de début (nombre de jours en arrière)
|
||||
final endDate = DateTime.now();
|
||||
final startDate = endDate.subtract(Duration(days: _selectedDays - 1));
|
||||
|
||||
// Créer les données d'activité
|
||||
final chartData = _calculateActivityData(passageBox, _selectedDays);
|
||||
@@ -562,11 +561,13 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
settingsBox.put('history_selectedTypeId', typeId);
|
||||
|
||||
// Date de début : début de la journée cliquée
|
||||
final startDateTime = DateTime(clickedDate.year, clickedDate.month, clickedDate.day, 0, 0, 0);
|
||||
final startDateTime =
|
||||
DateTime(clickedDate.year, clickedDate.month, clickedDate.day, 0, 0, 0);
|
||||
settingsBox.put('history_startDate', startDateTime.millisecondsSinceEpoch);
|
||||
|
||||
// Date de fin : fin de la journée cliquée
|
||||
final endDateTime = DateTime(clickedDate.year, clickedDate.month, clickedDate.day, 23, 59, 59);
|
||||
final endDateTime = DateTime(
|
||||
clickedDate.year, clickedDate.month, clickedDate.day, 23, 59, 59);
|
||||
settingsBox.put('history_endDate', endDateTime.millisecondsSinceEpoch);
|
||||
|
||||
// Naviguer vers la page historique
|
||||
@@ -592,7 +593,7 @@ class _ActivityChartState extends State<ActivityChart>
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.grey.withValues(alpha: 0.1),
|
||||
: Colors.grey.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/// Bibliothèque de widgets de graphiques pour l'application GeoSector
|
||||
library geosector_charts;
|
||||
|
||||
export 'payment_data.dart';
|
||||
export 'payment_summary_card.dart';
|
||||
export 'passage_data.dart';
|
||||
export 'passage_utils.dart';
|
||||
export 'passage_summary_card.dart';
|
||||
export 'activity_chart.dart';
|
||||
export 'combined_chart.dart';
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:geosector_app/presentation/widgets/charts/passage_data.dart';
|
||||
import 'package:geosector_app/presentation/widgets/charts/passage_utils.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// Widget de graphique combiné pour afficher les passages et règlements
|
||||
class CombinedChart extends StatelessWidget {
|
||||
/// Liste des données de passage par type
|
||||
final List<Map<String, dynamic>> passageData;
|
||||
|
||||
/// Liste des données de règlement par type
|
||||
final List<Map<String, dynamic>> paymentData;
|
||||
|
||||
/// Type de période (Jour, Semaine, Mois, Année)
|
||||
final String periodType;
|
||||
|
||||
/// Hauteur du graphique
|
||||
final double height;
|
||||
|
||||
/// Largeur des barres
|
||||
final double barWidth;
|
||||
|
||||
/// Rayon des points sur les lignes
|
||||
final double dotRadius;
|
||||
|
||||
/// Épaisseur des lignes
|
||||
final double lineWidth;
|
||||
|
||||
/// Montant maximum pour l'axe Y des règlements
|
||||
final double? maxYAmount;
|
||||
|
||||
/// Nombre maximum pour l'axe Y des passages
|
||||
final int? maxYCount;
|
||||
|
||||
const CombinedChart({
|
||||
super.key,
|
||||
required this.passageData,
|
||||
required this.paymentData,
|
||||
this.periodType = 'Jour',
|
||||
this.height = 300,
|
||||
this.barWidth = 16,
|
||||
this.dotRadius = 4,
|
||||
this.lineWidth = 3,
|
||||
this.maxYAmount,
|
||||
this.maxYCount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
// Convertir les données brutes en modèles structurés
|
||||
final passagesByType = PassageUtils.getPassageDataByType(passageData);
|
||||
final paymentsByType = PassageUtils.getPaymentDataByType(paymentData);
|
||||
|
||||
// Extraire les dates uniques pour l'axe X
|
||||
final List<DateTime> allDates = [];
|
||||
for (final data in passageData) {
|
||||
final DateTime date = data['date'] is DateTime
|
||||
? data['date']
|
||||
: DateTime.parse(data['date']);
|
||||
if (!allDates.any((d) =>
|
||||
d.year == date.year && d.month == date.month && d.day == date.day)) {
|
||||
allDates.add(date);
|
||||
}
|
||||
}
|
||||
|
||||
// Trier les dates
|
||||
allDates.sort((a, b) => a.compareTo(b));
|
||||
|
||||
// Calculer le maximum pour les axes Y
|
||||
double maxAmount = 0;
|
||||
for (final typeData in paymentsByType) {
|
||||
for (final data in typeData) {
|
||||
if (data.amount > maxAmount) {
|
||||
maxAmount = data.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int maxCount = 0;
|
||||
for (final typeData in passagesByType) {
|
||||
for (final data in typeData) {
|
||||
if (data.count > maxCount) {
|
||||
maxCount = data.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Utiliser les maximums fournis ou calculés
|
||||
final effectiveMaxYAmount = maxYAmount ?? (maxAmount * 1.2).ceilToDouble();
|
||||
final effectiveMaxYCount = maxYCount ?? (maxCount * 1.2).ceil();
|
||||
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: effectiveMaxYCount.toDouble(),
|
||||
barTouchData: BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
tooltipMargin: 8,
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
final date = allDates[group.x.toInt()];
|
||||
final formattedDate = DateFormat('dd/MM').format(date);
|
||||
|
||||
// Calculer le total des passages pour cette date
|
||||
int totalPassages = 0;
|
||||
for (final typeData in passagesByType) {
|
||||
for (final data in typeData) {
|
||||
if (data.date.year == date.year &&
|
||||
data.date.month == date.month &&
|
||||
data.date.day == date.day) {
|
||||
totalPassages += data.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BarTooltipItem(
|
||||
'$formattedDate: $totalPassages passages',
|
||||
TextStyle(
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: (value, meta) {
|
||||
if (value >= 0 && value < allDates.length) {
|
||||
final date = allDates[value.toInt()];
|
||||
final formattedDate =
|
||||
PassageUtils.formatDateForChart(date, periodType);
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 8,
|
||||
child: Text(
|
||||
formattedDate,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) {
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 8,
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
reservedSize: 30,
|
||||
),
|
||||
),
|
||||
rightTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) {
|
||||
// Convertir la valeur de l'axe Y des passages à l'échelle des montants
|
||||
final amountValue =
|
||||
(value / effectiveMaxYCount) * effectiveMaxYAmount;
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 8,
|
||||
child: Text(
|
||||
'${amountValue.toInt()}€',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
reservedSize: 40,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: theme.dividerColor.withValues(alpha: 0.2),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
drawVerticalLine: false,
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
barGroups: _createBarGroups(allDates, passagesByType),
|
||||
extraLinesData: const ExtraLinesData(
|
||||
horizontalLines: [],
|
||||
verticalLines: [],
|
||||
extraLinesOnTop: true,
|
||||
),
|
||||
),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Créer les groupes de barres pour les passages
|
||||
List<BarChartGroupData> _createBarGroups(
|
||||
List<DateTime> allDates,
|
||||
List<List<PassageData>> passagesByType,
|
||||
) {
|
||||
final List<BarChartGroupData> groups = [];
|
||||
|
||||
for (int i = 0; i < allDates.length; i++) {
|
||||
final date = allDates[i];
|
||||
|
||||
// Calculer le total des passages pour cette date
|
||||
int totalPassages = 0;
|
||||
for (final typeData in passagesByType) {
|
||||
for (final data in typeData) {
|
||||
if (data.date.year == date.year &&
|
||||
data.date.month == date.month &&
|
||||
data.date.day == date.day) {
|
||||
totalPassages += data.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un groupe de barres pour cette date
|
||||
groups.add(
|
||||
BarChartGroupData(
|
||||
x: i,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: totalPassages.toDouble(),
|
||||
color: Colors.blue.shade700,
|
||||
width: barWidth,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(6),
|
||||
topRight: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget de légende pour le graphique combiné
|
||||
class CombinedChartLegend extends StatelessWidget {
|
||||
const CombinedChartLegend({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildLegendItem('Passages', Colors.blue.shade700, isBar: true),
|
||||
_buildLegendItem('Espèces', const Color(0xFF4CAF50)),
|
||||
_buildLegendItem('Chèques', const Color(0xFF2196F3)),
|
||||
_buildLegendItem('CB', const Color(0xFFF44336)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Créer un élément de légende
|
||||
Widget _buildLegendItem(String label, Color color, {bool isBar = false}) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: isBar ? BoxShape.rectangle : BoxShape.circle,
|
||||
borderRadius: isBar ? BorderRadius.circular(3) : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ class _PassageSummaryCardState extends State<PassageSummaryCard>
|
||||
widget.backgroundIcon,
|
||||
size: widget.backgroundIconSize,
|
||||
color: (widget.backgroundIconColor ?? AppTheme.primaryColor)
|
||||
.withValues(alpha: widget.backgroundIconOpacity),
|
||||
.withOpacity(widget.backgroundIconOpacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -161,7 +161,7 @@ class _PaymentSummaryCardState extends State<PaymentSummaryCard>
|
||||
widget.backgroundIcon,
|
||||
size: widget.backgroundIconSize,
|
||||
color: (widget.backgroundIconColor ?? Colors.blue)
|
||||
.withValues(alpha: widget.backgroundIconOpacity),
|
||||
.withOpacity(widget.backgroundIconOpacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -422,10 +422,10 @@ class _PaymentSummaryCardState extends State<PaymentSummaryCard>
|
||||
|
||||
// En mode user, filtrer uniquement les passages créés par l'utilisateur (fkUser)
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final int? filterUserId = widget.showAllPayments ? null : currentUser?.id;
|
||||
final int? filterUserId = widget.showAllPayments ? null : currentUser?.opeUserId;
|
||||
|
||||
for (final passage in passagesBox.values) {
|
||||
// En mode user, ne compter que les passages de l'utilisateur
|
||||
// En mode user, ne compter que les passages de l'utilisateur (comparer avec ope_users.id)
|
||||
if (filterUserId != null && passage.fkUser != filterUserId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user