285 lines
9.9 KiB
Dart
285 lines
9.9 KiB
Dart
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geosector_app/core/theme/app_theme.dart';
|
|
import 'package:geosector_app/core/constants/app_keys.dart';
|
|
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
|
|
import 'package:geosector_app/presentation/widgets/charts/charts.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:geosector_app/core/data/models/passage_model.dart';
|
|
|
|
class UserDashboardHomePage extends StatefulWidget {
|
|
const UserDashboardHomePage({super.key});
|
|
|
|
@override
|
|
State<UserDashboardHomePage> createState() => _UserDashboardHomePageState();
|
|
}
|
|
|
|
class _UserDashboardHomePageState extends State<UserDashboardHomePage> {
|
|
// Formater une date au format JJ/MM/YYYY
|
|
String _formatDate(DateTime date) {
|
|
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final size = MediaQuery.of(context).size;
|
|
final isDesktop = size.width > 900;
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.transparent,
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Builder(builder: (context) {
|
|
// Récupérer l'opération actuelle
|
|
final operation = userRepository.getCurrentOperation();
|
|
if (operation != null) {
|
|
return Text(
|
|
'${operation.name} (${_formatDate(operation.dateDebut)}-${_formatDate(operation.dateFin)})',
|
|
style: theme.textTheme.headlineMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
);
|
|
} else {
|
|
return Text(
|
|
'Tableau de bord',
|
|
style: theme.textTheme.headlineMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
);
|
|
}
|
|
}),
|
|
const SizedBox(height: 24),
|
|
|
|
// Synthèse des passages
|
|
_buildSummaryCards(isDesktop),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Graphique des passages
|
|
_buildPassagesChart(context, theme),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Derniers passages
|
|
_buildRecentPassages(context, theme),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Construction des cartes de synthèse
|
|
Widget _buildSummaryCards(bool isDesktop) {
|
|
return Column(
|
|
children: [
|
|
_buildCombinedPassagesCard(context, isDesktop),
|
|
const SizedBox(height: 16),
|
|
_buildCombinedPaymentsCard(isDesktop),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Construction d'une carte combinée pour les règlements (liste + graphique)
|
|
Widget _buildCombinedPaymentsCard(bool isDesktop) {
|
|
return PaymentSummaryCard(
|
|
title: 'Mes règlements',
|
|
titleColor: AppTheme.accentColor,
|
|
titleIcon: Icons.payments,
|
|
height: 300,
|
|
useValueListenable: true,
|
|
userId: userRepository.getCurrentUser()?.id,
|
|
showAllPayments: false,
|
|
isDesktop: isDesktop,
|
|
backgroundIcon: Icons.euro_symbol,
|
|
backgroundIconColor: Colors.blue,
|
|
backgroundIconOpacity: 0.07,
|
|
backgroundIconSize: 180,
|
|
customTotalDisplay: (totalAmount) {
|
|
// Calculer le nombre de passages avec règlement pour le titre personnalisé
|
|
final currentUser = userRepository.getCurrentUser();
|
|
if (currentUser == null) return '${totalAmount.toStringAsFixed(2)} €';
|
|
|
|
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
|
int passagesCount = 0;
|
|
|
|
for (final passage in passagesBox.values) {
|
|
if (passage.fkUser == currentUser.id) {
|
|
double montant = 0.0;
|
|
try {
|
|
String montantStr = passage.montant.replaceAll(',', '.');
|
|
montant = double.tryParse(montantStr) ?? 0.0;
|
|
} catch (e) {
|
|
// Ignorer les erreurs
|
|
}
|
|
if (montant > 0) passagesCount++;
|
|
}
|
|
}
|
|
|
|
return '${totalAmount.toStringAsFixed(2)} € sur $passagesCount passages';
|
|
},
|
|
);
|
|
}
|
|
|
|
|
|
// Construction d'une carte combinée pour les passages (liste + graphique)
|
|
Widget _buildCombinedPassagesCard(BuildContext context, bool isDesktop) {
|
|
return PassageSummaryCard(
|
|
title: 'Mes passages',
|
|
titleColor: AppTheme.primaryColor,
|
|
titleIcon: Icons.route,
|
|
height: 300,
|
|
useValueListenable: true,
|
|
userId: userRepository.getCurrentUser()?.id,
|
|
showAllPassages: false,
|
|
excludePassageTypes: const [2], // Exclure "À finaliser"
|
|
isDesktop: isDesktop,
|
|
);
|
|
}
|
|
|
|
// Construction du graphique des passages
|
|
Widget _buildPassagesChart(BuildContext context, ThemeData theme) {
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
height: 350,
|
|
child: ActivityChart(
|
|
useValueListenable: true, // Utiliser le système réactif
|
|
excludePassageTypes: const [2], // Exclure les passages "À finaliser"
|
|
daysToShow: 15,
|
|
periodType: 'Jour',
|
|
height: 350,
|
|
userId: userRepository.getCurrentUser()?.id,
|
|
title: 'Dernière activité enregistrée sur 15 jours',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Construction de la liste des derniers passages
|
|
Widget _buildRecentPassages(BuildContext context, ThemeData theme) {
|
|
return Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Derniers passages',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Naviguer vers la page d'historique
|
|
},
|
|
child: const Text('Voir tout'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Utilisation du widget commun PassagesListWidget avec ValueListenableBuilder
|
|
ValueListenableBuilder(
|
|
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
|
|
builder: (context, Box<PassageModel> passagesBox, child) {
|
|
final recentPassages = _getRecentPassages(passagesBox);
|
|
|
|
return PassagesListWidget(
|
|
passages: recentPassages,
|
|
showFilters: false,
|
|
showSearch: false,
|
|
showActions: true,
|
|
maxPassages: 10,
|
|
excludePassageTypes: const [2],
|
|
filterByUserId: userRepository.getCurrentUser()?.id,
|
|
periodFilter: 'last15',
|
|
onPassageSelected: (passage) {
|
|
debugPrint('Passage sélectionné: ${passage['id']}');
|
|
},
|
|
onDetailsView: (passage) {
|
|
debugPrint('Affichage des détails: ${passage['id']}');
|
|
},
|
|
onPassageEdit: (passage) {
|
|
debugPrint('Modification du passage: ${passage['id']}');
|
|
},
|
|
onReceiptView: (passage) {
|
|
debugPrint('Affichage du reçu pour le passage: ${passage['id']}');
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Récupère les passages récents pour la liste
|
|
List<Map<String, dynamic>> _getRecentPassages(Box<PassageModel> passagesBox) {
|
|
final allPassages = passagesBox.values.toList();
|
|
allPassages.sort((a, b) => b.passedAt.compareTo(a.passedAt));
|
|
|
|
// Limiter aux 10 passages les plus récents
|
|
final recentPassagesModels = allPassages.take(10).toList();
|
|
|
|
// Convertir les modèles de passage au format attendu par le widget PassagesListWidget
|
|
return recentPassagesModels.map((passage) {
|
|
// Construire l'adresse complète à partir des champs disponibles
|
|
final String address =
|
|
'${passage.numero} ${passage.rue}${passage.rueBis.isNotEmpty ? ' ${passage.rueBis}' : ''}, ${passage.ville}';
|
|
|
|
// Convertir le montant en double
|
|
double amount = 0.0;
|
|
try {
|
|
if (passage.montant.isNotEmpty) {
|
|
// Gérer les formats possibles (virgule ou point)
|
|
String montantStr = passage.montant.replaceAll(',', '.');
|
|
amount = double.tryParse(montantStr) ?? 0.0;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Erreur de conversion du montant: ${passage.montant}');
|
|
amount = 0.0;
|
|
}
|
|
|
|
return {
|
|
'id': passage.id, // Garder l'ID comme int, pas besoin de toString()
|
|
'address': address,
|
|
'amount': amount,
|
|
'date': passage.passedAt,
|
|
'type': passage.fkType,
|
|
'payment': passage.fkTypeReglement,
|
|
'name': passage.name,
|
|
'notes': passage.remarque,
|
|
'hasReceipt': passage.nomRecu.isNotEmpty,
|
|
'hasError': passage.emailErreur.isNotEmpty,
|
|
'fkUser': passage.fkUser,
|
|
};
|
|
}).toList();
|
|
}
|
|
}
|