Fix: Hive sync et update entité via API REST

- Correction mapping JSON membres (fk_role, chk_active)
- Ajout traitement amicale au login
- Fix callback onSubmit pour sync Hive après update API
This commit is contained in:
d6soft
2025-06-09 18:46:49 +02:00
parent 150016d772
commit 511be5a535
50 changed files with 71865 additions and 71984 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'dart:math' as math;
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
@@ -64,6 +65,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
void _loadCurrentUser() {
final currentUser = widget.userRepository.getCurrentUser();
debugPrint('🔍 _loadCurrentUser - Utilisateur: ${currentUser?.username} (ID: ${currentUser?.id})');
debugPrint('🔍 _loadCurrentUser - fkEntite: ${currentUser?.fkEntite}');
if (currentUser == null) {
setState(() {
_errorMessage = 'Utilisateur non connecté';
@@ -78,43 +82,16 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
return;
}
// Vérifier immédiatement si l'amicale existe
final amicale = widget.amicaleRepository.getUserAmicale(currentUser.fkEntite!);
debugPrint('🔍 Amicale trouvée dans le repository: ${amicale?.name ?? 'null'}');
setState(() {
_currentUser = currentUser;
_errorMessage = null;
});
}
void _handleEditAmicale(AmicaleModel amicale) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Modifier l\'amicale'),
content: Text('Voulez-vous modifier l\'amicale ${amicale.name} ?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
// TODO: Naviguer vers la page de modification
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) => EditAmicalePage(
// amicale: amicale,
// amicaleRepository: widget.amicaleRepository,
// ),
// ),
// );
},
child: const Text('Modifier'),
),
],
),
);
}
void _handleEditMembre(MembreModel membre) {
showDialog(
context: context,
@@ -226,9 +203,19 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
child: ValueListenableBuilder<Box<AmicaleModel>>(
valueListenable: widget.amicaleRepository.getAmicalesBox().listenable(),
builder: (context, amicalesBox, child) {
debugPrint('🔍 AmicalesBox - Nombre d\'amicales: ${amicalesBox.length}');
debugPrint('🔍 AmicalesBox - Clés disponibles: ${amicalesBox.keys.toList()}');
debugPrint('🔍 Recherche amicale avec fkEntite: ${_currentUser!.fkEntite}');
final amicale = amicalesBox.get(_currentUser!.fkEntite!);
debugPrint('🔍 Amicale récupérée: ${amicale?.name ?? 'AUCUNE'}');
if (amicale == null) {
// Ajouter plus d'informations de debug
debugPrint('❌ PROBLÈME: Amicale non trouvée');
debugPrint('❌ fkEntite recherché: ${_currentUser!.fkEntite}');
debugPrint('❌ Contenu de la box: ${amicalesBox.values.map((a) => '${a.id}: ${a.name}').join(', ')}');
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -245,7 +232,7 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
),
const SizedBox(height: 8),
Text(
'L\'amicale associée à votre compte n\'existe plus.',
'L\'amicale associée à votre compte n\'existe plus.\nfkEntite: ${_currentUser!.fkEntite}',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge,
),
@@ -293,7 +280,7 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
onDelete: null,
amicaleRepository: widget.amicaleRepository,
userRepository: widget.userRepository,
apiService: null, // Ou passez l'ApiService si vous l'avez disponible
apiService: ApiService.instance,
showActionsColumn: false,
),
),

View File

@@ -68,10 +68,9 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
}
} catch (e) {
debugPrint('Erreur lors de la récupération de la version: $e');
// Fallback sur la version du AppInfoService si elle existe
if (mounted) {
setState(() {
_appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro
_appVersion = AppInfoService.fullVersion.split(' ').last;
});
}
}
@@ -81,29 +80,24 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
void initState() {
super.initState();
// Animation controller sur 5 secondes (augmenté de 3 à 5 secondes)
// Animation controller
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 5),
);
// Animation de 4x la taille à 1x la taille (augmenté de 3x à 4x)
_scaleAnimation = Tween<double>(
begin: 4.0, // Commencer à 4x la taille
end: 1.0, // Terminer à la taille normale
begin: 4.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeOutBack, // Curve pour un effet de rebond
curve: Curves.easeOutBack,
),
);
// Démarrer l'animation immédiatement
_animationController.forward();
_getAppVersion();
// Simuler le processus d'initialisation
_startInitialization();
}
@@ -114,151 +108,337 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
}
void _startInitialization() async {
// Étape 1: Initialisation des boîtes Hive (0% à 75%)
if (mounted) {
setState(() {
_statusMessage = "Initialisation des données...";
_progress = 0.0;
});
}
// Initialiser toutes les boîtes Hive
await _initializeAllHiveBoxes();
await Future.delayed(const Duration(milliseconds: 500));
// Étape 2: Initialisation des services (75% à 100%)
if (mounted) {
setState(() {
_statusMessage = "Préparation de l'application...";
_progress = 0.75;
});
}
await Future.delayed(const Duration(milliseconds: 500));
// Table rase complète et recréation propre
await _completeReset();
// Finalisation
if (mounted) {
setState(() {
_statusMessage = "Application prête !";
_progress = 1.0;
_isInitializing = false;
_showButtons = true;
_progress = 1.0; // S'assurer que la barre est à 100%
});
}
}
// Méthode pour initialiser toutes les boîtes Hive
Future<void> _initializeAllHiveBoxes() async {
/// RESET COMPLET : Destruction totale et recréation propre
Future<void> _completeReset() async {
try {
debugPrint('Initialisation de toutes les boîtes Hive...');
debugPrint('🧹 RESET COMPLET : Destruction totale des données Hive...');
// Structure pour les boîtes à ouvrir avec leurs noms d'affichage
final boxesToOpen = [
{'name': AppKeys.userBoxName, 'display': 'Préparation utilisateurs'},
{'name': AppKeys.amicaleBoxName, 'display': 'Préparation amicale'},
{'name': AppKeys.clientsBoxName, 'display': 'Préparation clients'},
{'name': AppKeys.regionsBoxName, 'display': 'Préparation régions'},
{'name': AppKeys.operationsBoxName, 'display': 'Préparation opérations'},
{'name': AppKeys.sectorsBoxName, 'display': 'Préparation secteurs'},
{'name': AppKeys.passagesBoxName, 'display': 'Préparation passages'},
{'name': AppKeys.membresBoxName, 'display': 'Préparation membres'},
{'name': AppKeys.userSectorBoxName, 'display': 'Préparation secteurs utilisateurs'},
{'name': AppKeys.settingsBoxName, 'display': 'Préparation paramètres'},
{'name': AppKeys.chatConversationsBoxName, 'display': 'Préparation conversations'},
{'name': AppKeys.chatMessagesBoxName, 'display': 'Préparation messages'},
];
// Étape 1: Sauvegarder les utilisateurs existants (optionnel)
Map<dynamic, UserModel>? existingUsers;
// Calculer l'incrément de progression pour chaque boîte (0.75 / nombre de boîtes)
final progressIncrement = 0.75 / boxesToOpen.length;
double currentProgress = 0.0;
if (mounted) {
setState(() {
_statusMessage = "Sauvegarde des utilisateurs...";
_progress = 0.05;
});
}
// Ouvrir chaque boîte si elle n'est pas déjà ouverte
for (int i = 0; i < boxesToOpen.length; i++) {
final boxName = boxesToOpen[i]['name'] as String;
final displayName = boxesToOpen[i]['display'] as String;
try {
if (Hive.isBoxOpen(AppKeys.userBoxName)) {
final userBox = Hive.box<UserModel>(AppKeys.userBoxName);
existingUsers = Map.from(userBox.toMap());
debugPrint('📦 ${existingUsers.length} utilisateurs sauvegardés');
}
} catch (e) {
debugPrint('⚠️ Erreur sauvegarde utilisateurs: $e');
existingUsers = null;
}
// Mettre à jour la barre de progression et le message
currentProgress = progressIncrement * (i + 1);
// Étape 2: DESTRUCTION RADICALE - Fermer tout ce qui peut être ouvert
if (mounted) {
setState(() {
_statusMessage = "Fermeture de toutes les bases de données...";
_progress = 0.15;
});
}
await _closeAllKnownBoxes();
// Étape 3: DESTRUCTION RADICALE - Supprimer tout Hive du disque
if (mounted) {
setState(() {
_statusMessage = "Suppression complète des anciennes données...";
_progress = 0.25;
});
}
await _nukeHiveCompletely();
// Étape 4: RECRÉATION PROPRE
if (mounted) {
setState(() {
_statusMessage = "Création des nouvelles bases de données...";
_progress = 0.40;
});
}
await _createAllBoxesFresh();
// Étape 5: Restaurer les utilisateurs (optionnel)
if (existingUsers != null && existingUsers.isNotEmpty) {
if (mounted) {
setState(() {
_statusMessage = displayName;
_progress = currentProgress;
_statusMessage = "Restauration des utilisateurs...";
_progress = 0.80;
});
}
if (!Hive.isBoxOpen(boxName)) {
debugPrint('Ouverture de la boîte $boxName ($displayName)...');
// Ouvrir la boîte avec le type approprié
if (boxName == AppKeys.userBoxName) {
await Hive.openBox<UserModel>(boxName);
} else if (boxName == AppKeys.amicaleBoxName) {
await Hive.openBox<AmicaleModel>(boxName);
} else if (boxName == AppKeys.clientsBoxName) {
await Hive.openBox<ClientModel>(boxName);
} else if (boxName == AppKeys.regionsBoxName) {
// Ouvrir la boîte des régions sans type spécifique pour l'instant
// car RegionModelAdapter n'est pas encore enregistré
await Hive.openBox(boxName);
} else if (boxName == AppKeys.operationsBoxName) {
await Hive.openBox<OperationModel>(boxName);
} else if (boxName == AppKeys.sectorsBoxName) {
await Hive.openBox<SectorModel>(boxName);
} else if (boxName == AppKeys.passagesBoxName) {
await Hive.openBox<PassageModel>(boxName);
} else if (boxName == AppKeys.membresBoxName) {
await Hive.openBox<MembreModel>(boxName);
} else if (boxName == AppKeys.userSectorBoxName) {
await Hive.openBox<UserSectorModel>(boxName);
} else if (boxName == AppKeys.chatConversationsBoxName) {
await Hive.openBox<ConversationModel>(boxName);
} else if (boxName == AppKeys.chatMessagesBoxName) {
await Hive.openBox<MessageModel>(boxName);
} else {
await Hive.openBox(boxName);
}
debugPrint('Boîte $boxName ouverte avec succès');
} else {
debugPrint('Boîte $boxName déjà ouverte');
}
// Ajouter une temporisation entre chaque ouverture
await Future.delayed(const Duration(milliseconds: 500));
await _restoreUsers(existingUsers);
}
// Mettre à jour la barre de progression à 0.2 (20%) à la fin
// Étape 6: Vérification finale
if (mounted) {
setState(() {
_statusMessage = 'Toutes les boîtes sont prêtes';
_progress = 0.8;
_statusMessage = "Vérification des bases de données...";
_progress = 0.90;
});
await Future.delayed(const Duration(milliseconds: 500));
}
debugPrint('✅ RESET COMPLET terminé avec succès');
} catch (e) {
debugPrint('❌ Erreur lors du reset complet: $e');
if (mounted) {
setState(() {
_statusMessage = "Préparation de l'application...";
_progress = 0.9;
});
await Future.delayed(const Duration(milliseconds: 500));
}
// Finalisation
if (mounted) {
setState(() {
_statusMessage = "Erreur critique - Redémarrage recommandé";
_progress = 1.0;
_isInitializing = false;
_showButtons = true;
_progress = 1.0;
});
}
debugPrint('Toutes les boîtes Hive sont maintenant ouvertes');
} catch (e) {
debugPrint('Erreur lors de l\'initialisation des boîtes Hive: $e');
}
}
// En cas d'erreur, mettre à jour le message
if (mounted) {
setState(() {
_statusMessage = 'Erreur lors de l\'initialisation des données';
});
/// Ferme toutes les boîtes connues
Future<void> _closeAllKnownBoxes() async {
try {
final allKnownBoxes = [
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
// Boîtes potentiellement problématiques
'auth', 'locations', 'messages', 'temp'
];
debugPrint('🔒 Fermeture de ${allKnownBoxes.length} boîtes connues...');
for (final boxName in allKnownBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).close();
debugPrint('✅ Boîte $boxName fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture $boxName: $e');
// Continuer même en cas d'erreur
}
}
await Future.delayed(const Duration(milliseconds: 1000));
} catch (e) {
debugPrint('❌ Erreur fermeture des boîtes: $e');
}
}
/// Suppression RADICALE de tout Hive
Future<void> _nukeHiveCompletely() async {
try {
debugPrint('💥 DESTRUCTION NUCLÉAIRE de Hive...');
if (kIsWeb) {
// En version web, supprimer toutes les boîtes possibles une par une
final allPossibleBoxes = [
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
// Toutes les boîtes potentiellement corrompues
'auth', 'locations', 'messages', 'temp', 'cache', 'data'
];
for (final boxName in allPossibleBoxes) {
try {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Boîte $boxName DÉTRUITE');
} catch (e) {
debugPrint('⚠️ Erreur destruction $boxName: $e');
}
}
} else {
// Sur mobile/desktop, destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Hive COMPLÈTEMENT DÉTRUIT');
} catch (e) {
debugPrint('⚠️ Erreur destruction totale: $e');
// Fallback : supprimer boîte par boîte
await _deleteBoxesOneByOne();
}
}
// Attendre pour s'assurer que tout est détruit
await Future.delayed(const Duration(seconds: 2));
} catch (e) {
debugPrint('❌ Erreur destruction Hive: $e');
}
}
/// Fallback : supprimer les boîtes une par une
Future<void> _deleteBoxesOneByOne() async {
final allBoxes = [
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
for (final boxName in allBoxes) {
try {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Boîte $boxName supprimée (fallback)');
} catch (e) {
debugPrint('⚠️ Erreur suppression fallback $boxName: $e');
}
}
}
/// Recrée toutes les boîtes VIDES et PROPRES
Future<void> _createAllBoxesFresh() async {
try {
debugPrint('🆕 Création de toutes les boîtes vides...');
final boxesToCreate = [
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
{'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'},
{'name': AppKeys.clientsBoxName, 'type': 'ClientModel'},
{'name': AppKeys.regionsBoxName, 'type': 'dynamic'},
{'name': AppKeys.operationsBoxName, 'type': 'OperationModel'},
{'name': AppKeys.sectorsBoxName, 'type': 'SectorModel'},
{'name': AppKeys.passagesBoxName, 'type': 'PassageModel'},
{'name': AppKeys.membresBoxName, 'type': 'MembreModel'},
{'name': AppKeys.userSectorBoxName, 'type': 'UserSectorModel'},
{'name': AppKeys.settingsBoxName, 'type': 'dynamic'},
{'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'},
{'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'},
];
final progressIncrement = 0.35 / boxesToCreate.length; // De 0.40 à 0.75
for (int i = 0; i < boxesToCreate.length; i++) {
final boxInfo = boxesToCreate[i];
final boxName = boxInfo['name'] as String;
final boxType = boxInfo['type'] as String;
if (mounted) {
setState(() {
_statusMessage = "Création de $boxName...";
_progress = 0.40 + (progressIncrement * i);
});
}
try {
// Créer la boîte avec le bon type
switch (boxType) {
case 'UserModel':
await Hive.openBox<UserModel>(boxName);
break;
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(boxName);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(boxName);
break;
case 'OperationModel':
await Hive.openBox<OperationModel>(boxName);
break;
case 'SectorModel':
await Hive.openBox<SectorModel>(boxName);
break;
case 'PassageModel':
await Hive.openBox<PassageModel>(boxName);
break;
case 'MembreModel':
await Hive.openBox<MembreModel>(boxName);
break;
case 'UserSectorModel':
await Hive.openBox<UserSectorModel>(boxName);
break;
case 'ConversationModel':
await Hive.openBox<ConversationModel>(boxName);
break;
case 'MessageModel':
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
debugPrint('✅ Boîte $boxName créée (type: $boxType)');
} catch (e) {
debugPrint('❌ Erreur création $boxName: $e');
// En cas d'erreur, essayer sans type
try {
await Hive.openBox(boxName);
debugPrint('⚠️ Boîte $boxName créée sans type');
} catch (e2) {
debugPrint('❌ Échec total création $boxName: $e2');
}
}
await Future.delayed(const Duration(milliseconds: 200));
}
} catch (e) {
debugPrint('❌ Erreur création des boîtes: $e');
}
}
/// Restaure les utilisateurs sauvegardés
Future<void> _restoreUsers(Map<dynamic, UserModel> users) async {
try {
if (Hive.isBoxOpen(AppKeys.userBoxName)) {
final userBox = Hive.box<UserModel>(AppKeys.userBoxName);
for (final entry in users.entries) {
try {
await userBox.put(entry.key, entry.value);
} catch (e) {
debugPrint('⚠️ Erreur restauration utilisateur ${entry.key}: $e');
}
}
debugPrint('${users.length} utilisateurs restaurés');
}
} catch (e) {
debugPrint('❌ Erreur restauration utilisateurs: $e');
}
}
@@ -295,7 +475,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
children: [
const Spacer(flex: 2),
// Logo avec animation de réduction
// Logo avec animation
AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
@@ -306,13 +486,13 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
},
child: Image.asset(
'assets/images/logo-geosector-1024.png',
height: 180, // Augmenté de 140 à 180
height: 180,
),
),
const SizedBox(height: 24),
// Titre avec animation fade-in
// Titre
AnimatedOpacity(
opacity: _isInitializing ? 0.9 : 1.0,
duration: const Duration(milliseconds: 500),
@@ -328,7 +508,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
const SizedBox(height: 16),
// Sous-titre avec nouveau slogan
// Sous-titre
AnimatedOpacity(
opacity: _isInitializing ? 0.8 : 1.0,
duration: const Duration(milliseconds: 500),
@@ -356,7 +536,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
valueColor: AlwaysStoppedAnimation<Color>(
theme.colorScheme.primary,
),
minHeight: 10, // Augmenté de 6 à 10
minHeight: 10,
),
),
),
@@ -369,7 +549,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
),
],
// Boutons après l'initialisation
// Boutons (reste identique)
if (_showButtons) ...[
// Bouton Connexion Utilisateur
AnimatedOpacity(
@@ -377,7 +557,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
duration: const Duration(milliseconds: 500),
child: ElevatedButton(
onPressed: () {
context.go('/login/user'); // Utiliser la route spécifique
context.go('/login/user');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
@@ -402,13 +582,13 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
),
const SizedBox(height: 16),
// Bouton Connexion Administrateur
// Bouton Connexion Administrateur
AnimatedOpacity(
opacity: _showButtons ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: ElevatedButton(
onPressed: () {
context.go('/login/admin'); // Utiliser la route spécifique
context.go('/login/admin');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
@@ -432,7 +612,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
),
),
const SizedBox(height: 32), // 2 espaces sous le bouton précédent
const SizedBox(height: 32),
// Bouton d'inscription
AnimatedOpacity(
@@ -472,7 +652,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
duration: const Duration(milliseconds: 500),
child: TextButton.icon(
onPressed: () {
// Déterminer l'URL du site web en fonction de l'environnement
String webUrl = 'https://geosector.fr';
if (kIsWeb) {
@@ -486,7 +665,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
}
}
// Ouvrir l'URL dans une nouvelle fenêtre/onglet
launchUrl(
Uri.parse(webUrl),
mode: LaunchMode.externalApplication,
@@ -514,7 +692,8 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
),
),
),
// Badge de version en bas à droite
// Badge de version
if (_appVersion.isNotEmpty)
Positioned(
bottom: 16,

View File

@@ -98,14 +98,23 @@ class _AmicaleFormState extends State<AmicaleForm> {
// Appeler l'API pour mettre à jour l'entité
Future<void> _updateAmicale(AmicaleModel amicale) async {
if (!mounted) return;
try {
// Afficher un indicateur de chargement
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
return const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Mise à jour en cours...'),
],
),
);
},
);
@@ -121,9 +130,9 @@ class _AmicaleFormState extends State<AmicaleForm> {
'phone': amicale.phone,
'mobile': amicale.mobile,
'email': amicale.email,
'chk_copie_mail_recu': amicale.chkCopieMailRecu,
'chk_accept_sms': amicale.chkAcceptSms,
'chk_stripe': amicale.chkStripe,
'chk_copie_mail_recu': amicale.chkCopieMailRecu ? 1 : 0,
'chk_accept_sms': amicale.chkAcceptSms ? 1 : 0,
'chk_stripe': amicale.chkStripe ? 1 : 0,
};
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
@@ -132,63 +141,81 @@ class _AmicaleFormState extends State<AmicaleForm> {
data['gps_lat'] = amicale.gpsLat;
data['gps_lng'] = amicale.gpsLng;
data['stripe_id'] = amicale.stripeId;
data['chk_demo'] = amicale.chkDemo;
data['chk_active'] = amicale.chkActive;
data['chk_demo'] = amicale.chkDemo ? 1 : 0;
data['chk_active'] = amicale.chkActive ? 1 : 0;
}
// Fermer l'indicateur de chargement
Navigator.of(context).pop();
debugPrint('🔧 Données à envoyer à l\'API: $data');
bool apiSuccess = false;
String? errorMessage;
// Appeler l'API si le service est disponible
if (widget.apiService != null) {
try {
await widget.apiService!.post('/entite/update', data: data);
debugPrint('📡 Appel API pour mise à jour amicale...');
// Afficher un message de succès
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Amicale mise à jour avec succès'),
backgroundColor: Colors.green,
),
);
// Version RESTful correcte avec PUT
final response = await widget.apiService!.put('/entites/${amicale.id}', data: data);
// Alternative avec PATCH si votre API le supporte
// final response = await widget.apiService!.patch('/entites/${amicale.id}', data: data);
debugPrint('📡 Réponse API: ${response.statusCode}');
if (response.statusCode == 200 || response.statusCode == 201) {
apiSuccess = true;
} else {
errorMessage = 'Erreur serveur: ${response.statusCode}';
}
} catch (error) {
// Afficher un message d'erreur
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la mise à jour de l\'amicale: $error'),
backgroundColor: Colors.red,
),
);
}
return; // Sortir de la fonction en cas d'erreur
}
} else {
// Pas d'API service, afficher un message d'information
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Modifications enregistrées localement'),
backgroundColor: Colors.blue,
),
);
debugPrint('❌ Erreur API: $error');
errorMessage = 'Erreur lors de la communication avec le serveur: $error';
}
}
// Appeler la fonction onSubmit si elle existe
if (widget.onSubmit != null) {
widget.onSubmit!(amicale);
}
// Fermer le formulaire
if (mounted) {
// Fermer l'indicateur de chargement
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
if (!mounted) return;
if (apiSuccess) {
// Appeler la fonction onSubmit si elle existe
if (widget.onSubmit != null) {
widget.onSubmit!(amicale);
}
// Afficher un message de succès
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.apiService != null ? 'Amicale mise à jour avec succès' : 'Modifications enregistrées localement'),
backgroundColor: Colors.green,
),
);
// Fermer le formulaire après un délai pour que l'utilisateur voie le message
await Future.delayed(const Duration(milliseconds: 500));
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
} else {
// Afficher un message d'erreur
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage ?? 'Erreur lors de la mise à jour'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
),
);
}
} catch (e) {
debugPrint('❌ Erreur générale dans _updateAmicale: $e');
// Fermer l'indicateur de chargement si encore ouvert
if (Navigator.of(context).canPop()) {
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
@@ -196,8 +223,9 @@ class _AmicaleFormState extends State<AmicaleForm> {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: ${e.toString()}'),
content: Text('Erreur inattendue: ${e.toString()}'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
),
);
}
@@ -205,9 +233,14 @@ class _AmicaleFormState extends State<AmicaleForm> {
}
void _submitForm() {
debugPrint('🔧 _submitForm appelée');
if (_formKey.currentState!.validate()) {
debugPrint('🔧 Formulaire valide');
// Vérifier qu'au moins un numéro de téléphone est renseigné
if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) {
debugPrint('⚠️ Aucun numéro de téléphone renseigné');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez renseigner au moins un numéro de téléphone'),
@@ -217,6 +250,8 @@ class _AmicaleFormState extends State<AmicaleForm> {
return;
}
debugPrint('🔧 Création de l\'objet AmicaleModel...');
final amicale = widget.amicale?.copyWith(
name: _nameController.text,
adresse1: _adresse1Controller.text,
@@ -258,10 +293,13 @@ class _AmicaleFormState extends State<AmicaleForm> {
chkActive: _chkActive,
);
debugPrint('🔧 AmicaleModel créé: ${amicale.name}');
debugPrint('🔧 Appel de _updateAmicale...');
// Appeler l'API pour mettre à jour l'amicale
_updateAmicale(amicale);
// Ne pas appeler widget.onSubmit ici car c'est fait dans _updateAmicale
} else {
debugPrint('❌ Formulaire invalide');
}
}

View File

@@ -22,7 +22,7 @@ class AmicaleTableWidget extends StatelessWidget {
final Function(AmicaleModel)? onDelete;
final AmicaleRepository amicaleRepository;
final UserRepository userRepository; // Nouveau paramètre
final ApiService? apiService; // Nouveau paramètre optionnel
final ApiService? apiService;
final bool isLoading;
final String? emptyMessage;
final bool readOnly;
@@ -35,7 +35,7 @@ class AmicaleTableWidget extends StatelessWidget {
required this.userRepository, // Requis
this.onEdit,
this.onDelete,
this.apiService, // Optionnel
this.apiService,
this.isLoading = false,
this.emptyMessage,
this.readOnly = false,
@@ -83,9 +83,13 @@ class AmicaleTableWidget extends StatelessWidget {
readOnly: false,
userRepository: userRepository,
apiService: apiService,
onSubmit: (updatedAmicale) {
onSubmit: (updatedAmicale) async {
// Sauvegarder l'amicale mise à jour dans le repository
debugPrint('🔄 Sauvegarde de l\'amicale mise à jour: ${updatedAmicale.name}');
await amicaleRepository.saveAmicale(updatedAmicale);
debugPrint('✅ Amicale sauvegardée dans le repository');
Navigator.of(dialogContext).pop();
// La mise à jour sera gérée par les ValueListenableBuilder
},
),
),
@@ -227,7 +231,12 @@ class AmicaleTableWidget extends StatelessWidget {
readOnly: true,
userRepository: userRepository,
apiService: apiService,
onSubmit: (updatedAmicale) {
onSubmit: (updatedAmicale) async {
// Sauvegarder l'amicale mise à jour dans le repository
debugPrint('🔄 Sauvegarde de l\'amicale mise à jour: ${updatedAmicale.name}');
await amicaleRepository.saveAmicale(updatedAmicale);
debugPrint('✅ Amicale sauvegardée dans le repository');
Navigator.of(dialogContext).pop();
},
),

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/app.dart'; // Pour accéder à l'instance globale de ApiService
import 'package:geosector_app/core/services/api_service.dart'; // Import du service singleton
/// Widget de carte réutilisable utilisant Mapbox
///
@@ -105,11 +105,10 @@ class _MapboxMapState extends State<MapboxMap> {
Widget build(BuildContext context) {
// Déterminer l'URL du template de tuiles Mapbox
// Utiliser l'environnement actuel pour obtenir la bonne clé API
final String environment = apiService.getCurrentEnvironment();
final String environment = ApiService.instance.getCurrentEnvironment();
final String mapboxToken = AppKeys.getMapboxApiKey(environment);
final String mapStyle = widget.mapStyle ?? 'mapbox/streets-v11';
final String urlTemplate =
'https://api.mapbox.com/styles/v1/$mapStyle/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
final String urlTemplate = 'https://api.mapbox.com/styles/v1/$mapStyle/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
return Stack(
children: [
@@ -145,12 +144,10 @@ class _MapboxMapState extends State<MapboxMap> {
),
// Polygones
if (widget.polygons != null && widget.polygons!.isNotEmpty)
PolygonLayer(polygons: widget.polygons!),
if (widget.polygons != null && widget.polygons!.isNotEmpty) PolygonLayer(polygons: widget.polygons!),
// Marqueurs
if (widget.markers != null && widget.markers!.isNotEmpty)
MarkerLayer(markers: widget.markers!),
if (widget.markers != null && widget.markers!.isNotEmpty) MarkerLayer(markers: widget.markers!),
],
),

View File

@@ -44,7 +44,7 @@ class MembreRowWidget extends StatelessWidget {
Expanded(
flex: 2,
child: Text(
membre.firstName,
membre.firstName ?? '',
style: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
@@ -54,7 +54,7 @@ class MembreRowWidget extends StatelessWidget {
Expanded(
flex: 2,
child: Text(
membre.name,
membre.name ?? '',
style: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
@@ -70,31 +70,31 @@ class MembreRowWidget extends StatelessWidget {
),
),
// Rôle (fkRole)
// Rôle (role au lieu de fkRole)
Expanded(
flex: 1,
child: Text(
_getRoleName(membre.fkRole),
_getRoleName(membre.role),
style: theme.textTheme.bodyMedium,
),
),
// Statut (actif/inactif)
// Statut (isActive au lieu de chkActive)
Expanded(
flex: 1,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
color: membre.isActive ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3),
color: membre.isActive ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3),
),
),
child: Text(
membre.chkActive == 1 ? 'Actif' : 'Inactif',
membre.isActive ? 'Actif' : 'Inactif',
style: theme.textTheme.bodySmall?.copyWith(
color: membre.chkActive == 1 ? Colors.green[700] : Colors.red[700],
color: membre.isActive ? Colors.green[700] : Colors.red[700],
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
@@ -161,18 +161,20 @@ class MembreRowWidget extends StatelessWidget {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('${membre.firstName} ${membre.name}'),
title: Text('${membre.firstName ?? ''} ${membre.name ?? ''}'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow('ID', membre.id.toString()),
_buildDetailRow('Email', membre.email),
_buildDetailRow('Username', membre.username),
_buildDetailRow('Rôle', _getRoleName(membre.fkRole)),
_buildDetailRow('Titre', membre.fkTitre.toString()),
_buildDetailRow('Username', membre.username ?? 'Non défini'),
_buildDetailRow('Rôle', _getRoleName(membre.role)),
_buildDetailRow('Titre', membre.fkTitre?.toString() ?? 'Non défini'),
_buildDetailRow('Secteur', membre.sectName ?? 'Non défini'),
_buildDetailRow('Statut', membre.chkActive == 1 ? 'Actif' : 'Inactif'),
_buildDetailRow('Statut', membre.isActive ? 'Actif' : 'Inactif'),
_buildDetailRow('Téléphone', membre.phone ?? 'Non défini'),
_buildDetailRow('Mobile', membre.mobile ?? 'Non défini'),
if (membre.dateNaissance != null)
_buildDetailRow('Date de naissance', '${membre.dateNaissance!.day}/${membre.dateNaissance!.month}/${membre.dateNaissance!.year}'),
if (membre.dateEmbauche != null)