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 15a0f2d2be
commit f3f1a9c5e8
56 changed files with 141860 additions and 142386 deletions

View File

@@ -72,8 +72,6 @@ class ApiService {
return computation();
}
// === TOUTES LES MÉTHODES EXISTANTES RESTENT IDENTIQUES ===
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
@@ -136,7 +134,7 @@ class ApiService {
// Vérifier la connectivité réseau
Future<bool> hasInternetConnection() async {
final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
return connectivityResult.contains(ConnectivityResult.none) == false;
}
// Méthode POST générique
@@ -273,10 +271,6 @@ class ApiService {
}
}
// Espace réservé pour les futures méthodes de gestion des profils
// Espace réservé pour les futures méthodes de gestion des données
// Synchronisation en batch
Future<Map<String, dynamic>> syncData({
List<UserModel>? users,
@@ -297,228 +291,4 @@ class ApiService {
static void reset() {
_instance = null;
}
}
final Dio _dio = Dio();
late final String _baseUrl;
late final String _appIdentifier;
String? _sessionId;
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
// En mode non-web, utiliser l'environnement de développement par défaut
return 'DEV';
}
final currentUrl = html.window.location.href.toLowerCase();
if (currentUrl.contains('dapp.geosector.fr')) {
return 'DEV';
} else if (currentUrl.contains('rapp.geosector.fr')) {
return 'REC';
} else {
return 'PROD';
}
}
// Configure l'URL de base API et l'identifiant d'application selon l'environnement
void _configureEnvironment() {
final env = _determineEnvironment();
switch (env) {
case 'DEV':
_baseUrl = AppKeys.baseApiUrlDev;
_appIdentifier = AppKeys.appIdentifierDev;
break;
case 'REC':
_baseUrl = AppKeys.baseApiUrlRec;
_appIdentifier = AppKeys.appIdentifierRec;
break;
default: // PROD
_baseUrl = AppKeys.baseApiUrlProd;
_appIdentifier = AppKeys.appIdentifierProd;
}
debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl');
}
// Définir l'ID de session
void setSessionId(String? sessionId) {
_sessionId = sessionId;
}
// Obtenir l'environnement actuel (utile pour le débogage)
String getCurrentEnvironment() {
return _determineEnvironment();
}
// Obtenir l'URL API actuelle (utile pour le débogage)
String getCurrentApiUrl() {
return _baseUrl;
}
// Obtenir l'identifiant d'application actuel (utile pour le débogage)
String getCurrentAppIdentifier() {
return _appIdentifier;
}
// Vérifier la connectivité réseau
Future<bool> hasInternetConnection() async {
final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult.contains(ConnectivityResult.none) == false;
}
// Méthode POST générique
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode GET générique
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} catch (e) {
rethrow;
}
}
// Méthode PUT générique
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode DELETE générique
Future<Response> delete(String path) async {
try {
return await _dio.delete(path);
} catch (e) {
rethrow;
}
}
// Authentification avec PHP session
Future<Map<String, dynamic>> login(String username, String password, {required String type}) async {
try {
final response = await _dio.post(AppKeys.loginEndpoint, data: {
'username': username,
'password': password,
'type': type, // Ajouter le type de connexion (user ou admin)
});
// Vérifier la structure de la réponse
final data = response.data as Map<String, dynamic>;
final status = data['status'] as String?;
// Afficher le message en cas d'erreur
if (status != 'success') {
final message = data['message'] as String?;
debugPrint('Erreur d\'authentification: $message');
}
// Si le statut est 'success', récupérer le session_id
if (status == 'success' && data.containsKey('session_id')) {
final sessionId = data['session_id'];
// Définir la session pour les futures requêtes
if (sessionId != null) {
setSessionId(sessionId);
}
}
return data;
} catch (e) {
rethrow;
}
}
// Déconnexion
Future<void> logout() async {
try {
if (_sessionId != null) {
await _dio.post(AppKeys.logoutEndpoint);
_sessionId = null;
}
} catch (e) {
// Même en cas d'erreur, on réinitialise la session
_sessionId = null;
rethrow;
}
}
// Utilisateurs
Future<List<UserModel>> getUsers() async {
try {
final response = await retry(
() => _dio.get('/users'),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return (response.data as List).map((json) => UserModel.fromJson(json)).toList();
} catch (e) {
// Gérer les erreurs
rethrow;
}
}
Future<UserModel> getUserById(int id) async {
try {
final response = await _dio.get('/users/$id');
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> createUser(UserModel user) async {
try {
final response = await _dio.post('/users', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> updateUser(UserModel user) async {
try {
final response = await _dio.put('/users/${user.id}', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<void> deleteUser(String id) async {
try {
await _dio.delete('/users/$id');
} catch (e) {
rethrow;
}
}
// Espace réservé pour les futures méthodes de gestion des profils
// Espace réservé pour les futures méthodes de gestion des données
// Synchronisation en batch
Future<Map<String, dynamic>> syncData({
List<UserModel>? users,
}) async {
try {
final Map<String, dynamic> payload = {
if (users != null) 'users': users.map((u) => u.toJson()).toList(),
};
final response = await _dio.post('/sync', data: payload);
return response.data;
} catch (e) {
rethrow;
}
}
}

View File

@@ -53,135 +53,67 @@ class DataLoadingService extends ChangeNotifier {
Box<MessageModel> get _chatMessageBox => Hive.box<MessageModel>(AppKeys.chatMessagesBoxName);
Box get _settingsBox => Hive.box(AppKeys.settingsBoxName);
// === NETTOYAGE ET PRÉPARATION DES DONNÉES ===
/// Nettoie toutes les données avant le login
Future<void> cleanDataBeforeLogin() async {
try {
_updateLoadingState(LoadingState.initial.copyWith(
progress: 0.05,
message: 'Nettoyage des données...',
stepDescription: 'Suppression des données obsolètes',
));
debugPrint('🧹 Début du nettoyage des données avant login...');
// Étape 1: Nettoyage des boîtes non référencées (5%)
await _cleanNonDefinedBoxes();
// Étape 2: Nettoyage sécurisé des données (10%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.10,
stepDescription: 'Préparation du stockage local',
));
if (kIsWeb) {
await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]);
} else if (Platform.isIOS) {
await _cleanHiveFilesOnIOS();
} else if (Platform.isAndroid) {
await _cleanHiveFilesOnAndroid();
}
// Étape 3: Recréation des boîtes (15%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.15,
stepDescription: 'Initialisation des bases de données',
));
await _clearAndRecreateBoxes();
await _initializeBoxes();
debugPrint('✅ Nettoyage des données terminé');
} catch (e) {
debugPrint('❌ Erreur lors du nettoyage des données: $e');
_updateLoadingState(LoadingState.error('Erreur lors du nettoyage: $e'));
rethrow;
}
}
/// Traite toutes les données reçues de l'API lors du login
/// Les boxes sont déjà propres, on charge juste les données
Future<void> processLoginData(Map<String, dynamic> apiResult) async {
try {
debugPrint('📊 Début du traitement des données de login...');
debugPrint('📊 Début du chargement des données (boxes déjà propres)...');
// Étape 4: Traitement des clients (35%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.35,
stepDescription: 'Chargement des clients',
));
// Vérifier que les boxes sont ouvertes
_verifyBoxesAreOpen();
// Charger les données directement (les boxes sont vides et propres)
if (apiResult['clients'] != null) {
await _processClients(apiResult['clients']);
}
// Étape 5: Traitement des opérations (50%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.50,
stepDescription: 'Chargement des opérations',
));
if (apiResult['operations'] != null) {
await _processOperations(apiResult['operations']);
}
// Étape 6: Traitement des secteurs (65%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.65,
stepDescription: 'Chargement des secteurs',
));
if (apiResult['sectors'] != null) {
await _processSectors(apiResult['sectors']);
if (apiResult['secteurs'] != null) {
await _processSectors(apiResult['secteurs']);
}
// Étape 7: Traitement des passages (75%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.75,
stepDescription: 'Chargement des passages',
));
if (apiResult['passages'] != null) {
await _processPassages(apiResult['passages']);
}
// Étape 8: Traitement des membres (85%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.85,
stepDescription: 'Chargement des membres',
));
final membresData = apiResult['membres'] ?? apiResult['members'];
if (membresData != null) {
await _processMembres(membresData);
if (apiResult['amicale'] != null) {
await _processAmicale(apiResult['amicale']);
}
if (apiResult['membres'] != null) {
await _processMembres(apiResult['membres']);
}
// Étape 9: Traitement des associations utilisateurs-secteurs (95%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.95,
stepDescription: 'Finalisation du chargement',
));
if (apiResult['users_sectors'] != null) {
await _processUserSectors(apiResult['users_sectors']);
if (apiResult['userSecteurs'] != null) {
await _processUserSectors(apiResult['userSecteurs']);
}
// Étape finale: Chargement terminé (100%)
_updateLoadingState(LoadingState.completed.copyWith(
progress: 1.0,
message: 'Chargement terminé avec succès',
stepDescription: 'Données synchronisées',
));
debugPrint('✅ Traitement des données de login terminé');
_logDataSummary();
} catch (e) {
debugPrint('❌ Erreur lors du traitement des données de login: $e');
_updateLoadingState(LoadingState.error('Erreur lors du chargement: $e'));
debugPrint('❌ Erreur lors du chargement: $e');
rethrow;
}
}
void _verifyBoxesAreOpen() {
final requiredBoxes = [
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.amicaleBoxName,
];
for (final boxName in requiredBoxes) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La boîte $boxName n\'est pas ouverte. Redémarrez l\'application.');
}
}
debugPrint('✅ Toutes les boîtes requises sont ouvertes');
}
/// Nettoie complètement toutes les données lors du logout
Future<void> cleanDataAfterLogout() async {
try {
@@ -194,123 +126,12 @@ class DataLoadingService extends ChangeNotifier {
}
}
// === MÉTHODES PRIVÉES DE NETTOYAGE ===
Future<void> _cleanNonDefinedBoxes() async {
final nonDefinedBoxes = ['auth', 'locations', 'messages'];
for (final boxName in nonDefinedBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
debugPrint('Fermeture de la boîte non référencée: $boxName');
await Hive.box(boxName).close();
}
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Box $boxName supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression box $boxName: $e');
}
}
}
Future<void> _clearAndRecreateBoxes() async {
try {
debugPrint('🔄 Recréation des boîtes Hive...');
final boxesToDelete = [
AppKeys.passagesBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.userSectorBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
// Vider chaque boîte sans la fermer
for (final boxName in boxesToDelete) {
try {
if (Hive.isBoxOpen(boxName)) {
final box = Hive.box(boxName);
await box.clear();
debugPrint('✅ Box $boxName vidée');
} else {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Box $boxName supprimée du disque');
}
} catch (e) {
debugPrint('⚠️ Erreur nettoyage box $boxName: $e');
}
}
await Future.delayed(const Duration(milliseconds: 500));
} catch (e) {
debugPrint('❌ Erreur recréation des boîtes: $e');
rethrow;
}
}
Future<void> _initializeBoxes() async {
debugPrint('📦 Initialisation des boîtes Hive...');
final boxesToInit = [
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
for (final boxName in boxesToInit) {
await _ensureBoxIsOpen(boxName);
}
debugPrint('✅ Toutes les boîtes Hive sont ouvertes');
}
Future<void> _ensureBoxIsOpen(String boxName) async {
try {
if (!Hive.isBoxOpen(boxName)) {
debugPrint('Ouverture de la boîte $boxName...');
switch (boxName) {
case AppKeys.passagesBoxName:
await Hive.openBox<PassageModel>(boxName);
break;
case AppKeys.operationsBoxName:
await Hive.openBox<OperationModel>(boxName);
break;
case AppKeys.sectorsBoxName:
await Hive.openBox<SectorModel>(boxName);
break;
case AppKeys.membresBoxName:
await Hive.openBox<MembreModel>(boxName);
break;
case AppKeys.userSectorBoxName:
await Hive.openBox<UserSectorModel>(boxName);
break;
case AppKeys.chatConversationsBoxName:
await Hive.openBox<ConversationModel>(boxName);
break;
case AppKeys.chatMessagesBoxName:
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
}
} catch (e) {
debugPrint('❌ Erreur ouverture box $boxName: $e');
throw Exception('Impossible d\'ouvrir la boîte $boxName: $e');
}
}
// === MÉTHODES DE TRAITEMENT DES DONNÉES ===
Future<void> _processClients(dynamic clientsData) async {
try {
debugPrint('👥 Traitement des clients...');
List<dynamic> clientsList;
if (clientsData is List) {
clientsList = clientsData;
@@ -335,7 +156,7 @@ class DataLoadingService extends ChangeNotifier {
Future<void> _processOperations(dynamic operationsData) async {
try {
debugPrint('⚙️ Traitement des opérations...');
if (operationsData == null) {
debugPrint(' Aucune donnée d\'opération à traiter');
return;
@@ -373,7 +194,7 @@ class DataLoadingService extends ChangeNotifier {
Future<void> _processSectors(dynamic sectorsData) async {
try {
debugPrint('📍 Traitement des secteurs...');
if (sectorsData == null) {
debugPrint(' Aucune donnée de secteur à traiter');
return;
@@ -411,7 +232,7 @@ class DataLoadingService extends ChangeNotifier {
Future<void> _processPassages(dynamic passagesData) async {
try {
debugPrint('🚶 Traitement des passages...');
if (passagesData == null) {
debugPrint(' Aucune donnée de passage à traiter');
return;
@@ -446,10 +267,37 @@ class DataLoadingService extends ChangeNotifier {
}
}
// Ajouter cette nouvelle méthode pour traiter l'amicale unique
Future<void> _processAmicale(dynamic amicaleData) async {
try {
debugPrint('🏢 Traitement de l\'amicale unique...');
if (amicaleData == null) {
debugPrint(' Aucune donnée d\'amicale à traiter');
return;
}
await _amicaleBox.clear();
try {
// Les données d'amicale sont un objet unique
final Map<String, dynamic> amicaleMap = Map<String, dynamic>.from(amicaleData as Map);
final amicale = AmicaleModel.fromJson(amicaleMap);
await _amicaleBox.put(amicale.id, amicale);
debugPrint('✅ Amicale stockée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('⚠️ Erreur traitement amicale: $e');
debugPrint('⚠️ Données reçues: $amicaleData');
}
} catch (e) {
debugPrint('❌ Erreur traitement amicale: $e');
}
}
Future<void> _processMembres(dynamic membresData) async {
try {
debugPrint('👤 Traitement des membres...');
if (membresData == null) {
debugPrint(' Aucune donnée de membre à traiter');
return;
@@ -487,7 +335,7 @@ class DataLoadingService extends ChangeNotifier {
Future<void> _processUserSectors(dynamic userSectorsData) async {
try {
debugPrint('🔗 Traitement des associations utilisateurs-secteurs...');
if (userSectorsData == null) {
debugPrint(' Aucune association utilisateur-secteur à traiter');
return;
@@ -625,43 +473,8 @@ class DataLoadingService extends ChangeNotifier {
}
}
// === MÉTHODES UTILITAIRES ===
/// Affiche un résumé des données chargées
void _logDataSummary() {
try {
debugPrint('📊 === RÉSUMÉ DES DONNÉES CHARGÉES ===');
debugPrint('Opérations: ${_operationBox.length}');
debugPrint('Secteurs: ${_sectorBox.length}');
debugPrint('Passages: ${_passageBox.length}');
debugPrint('Membres: ${_membreBox.length}');
debugPrint('Associations User-Sector: ${_userSectorBox.length}');
debugPrint('Amicales: ${_amicaleBox.length}');
debugPrint('=================================');
} catch (e) {
debugPrint('⚠️ Erreur lors du résumé: $e');
}
}
/// Retourne un résumé des données pour l'UI
Map<String, int> getDataSummary() {
try {
return {
'operations': _operationBox.length,
'sectors': _sectorBox.length,
'passages': _passageBox.length,
'membres': _membreBox.length,
'userSectors': _userSectorBox.length,
'amicales': _amicaleBox.length,
};
} catch (e) {
debugPrint('⚠️ Erreur génération résumé: $e');
return {};
}
}
// === RESET POUR TESTS ===
static void reset() {
_instance = null;
}
}
}

View File

@@ -0,0 +1,65 @@
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/membre_model.dart';
import 'package:geosector_app/core/data/models/user_sector_model.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:geosector_app/chat/models/chat_adapters.dart';
class HiveAdapters {
/// Enregistre tous les TypeAdapters nécessaires
static void registerAll() {
// Modèles principaux
if (!Hive.isAdapterRegistered(0)) {
Hive.registerAdapter(UserModelAdapter());
}
if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(OperationModelAdapter());
}
if (!Hive.isAdapterRegistered(3)) {
Hive.registerAdapter(SectorModelAdapter());
}
if (!Hive.isAdapterRegistered(4)) {
Hive.registerAdapter(PassageModelAdapter());
}
if (!Hive.isAdapterRegistered(5)) {
Hive.registerAdapter(MembreModelAdapter());
}
if (!Hive.isAdapterRegistered(6)) {
Hive.registerAdapter(UserSectorModelAdapter());
}
if (!Hive.isAdapterRegistered(7)) {
Hive.registerAdapter(RegionModelAdapter());
}
if (!Hive.isAdapterRegistered(10)) {
Hive.registerAdapter(ClientModelAdapter());
}
if (!Hive.isAdapterRegistered(11)) {
Hive.registerAdapter(AmicaleModelAdapter());
}
// Chat adapters
if (!Hive.isAdapterRegistered(20)) {
Hive.registerAdapter(ConversationModelAdapter());
}
if (!Hive.isAdapterRegistered(21)) {
Hive.registerAdapter(MessageModelAdapter());
}
if (!Hive.isAdapterRegistered(22)) {
Hive.registerAdapter(ParticipantModelAdapter());
}
if (!Hive.isAdapterRegistered(23)) {
Hive.registerAdapter(AnonymousUserModelAdapter());
}
if (!Hive.isAdapterRegistered(24)) {
Hive.registerAdapter(AudienceTargetModelAdapter());
}
if (!Hive.isAdapterRegistered(25)) {
Hive.registerAdapter(NotificationSettingsAdapter());
}
}
}