Amélioration de la splash_page et du login
This commit is contained in:
353
app/lib/presentation/widgets/charts/passage_summary_card.dart
Normal file
353
app/lib/presentation/widgets/charts/passage_summary_card.dart
Normal file
@@ -0,0 +1,353 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/presentation/widgets/charts/passage_pie_chart.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 passages
|
||||
/// avec liste des types à gauche et graphique en camembert à droite
|
||||
class PassageSummaryCard 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 passages (admin) ou seulement ceux de l'utilisateur
|
||||
final bool showAllPassages;
|
||||
|
||||
/// Types de passages à exclure du graphique
|
||||
final List<int> excludePassageTypes;
|
||||
|
||||
/// Données statiques de passages par type (utilisé si useValueListenable = false)
|
||||
final Map<int, int>? passagesByType;
|
||||
|
||||
/// Fonction de callback pour afficher la valeur totale personnalisée
|
||||
final String Function(int totalPassages)? 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 PassageSummaryCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.titleColor = AppTheme.primaryColor,
|
||||
this.titleIcon = Icons.route,
|
||||
this.height,
|
||||
this.useValueListenable = true,
|
||||
this.userId,
|
||||
this.showAllPassages = false,
|
||||
this.excludePassageTypes = const [2], // Exclure "À finaliser" par défaut
|
||||
this.passagesByType,
|
||||
this.customTotalDisplay,
|
||||
this.isDesktop = true,
|
||||
this.backgroundIcon = Icons.route,
|
||||
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 ?? AppTheme.primaryColor).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 passages à gauche
|
||||
Expanded(
|
||||
flex: isDesktop ? 1 : 2,
|
||||
child: useValueListenable
|
||||
? _buildPassagesListWithValueListenable()
|
||||
: _buildPassagesListWithStaticData(),
|
||||
),
|
||||
|
||||
// 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: PassagePieChart(
|
||||
useValueListenable: useValueListenable,
|
||||
passagesByType: passagesByType ?? {},
|
||||
excludePassageTypes: excludePassageTypes,
|
||||
userId: showAllPassages ? null : userId,
|
||||
size: double.infinity,
|
||||
labelSize: 12,
|
||||
showPercentage: true,
|
||||
showIcons: false,
|
||||
showLegend: false,
|
||||
isDonut: true,
|
||||
innerRadius: '50%',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
customTotalDisplay?.call(totalUserPassages) ?? totalUserPassages.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction du titre avec données statiques
|
||||
Widget _buildTitleWithStaticData() {
|
||||
final totalPassages = passagesByType?.values.fold(0, (sum, count) => sum + count) ?? 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(totalPassages) ?? totalPassages.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 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(passagesCounts);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Construction de la liste des passages avec données statiques
|
||||
Widget _buildPassagesListWithStaticData() {
|
||||
return _buildPassagesList(passagesByType ?? {});
|
||||
}
|
||||
|
||||
/// Construction de la liste des passages
|
||||
Widget _buildPassagesList(Map<int, int> passagesCounts) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...AppKeys.typesPassages.entries.map((entry) {
|
||||
final int typeId = entry.key;
|
||||
final Map<String, dynamic> typeData = entry.value;
|
||||
final int count = passagesCounts[typeId] ?? 0;
|
||||
final Color color = Color(typeData['couleur2'] 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['titres'] as String,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
count.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 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 = {};
|
||||
|
||||
// Initialiser tous les types
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user