Livraison d ela gestion des opérations v0.4.0

This commit is contained in:
d6soft
2025-06-24 13:01:43 +02:00
parent 25c9d5874c
commit 416d648a14
813 changed files with 234012 additions and 73933 deletions

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
@@ -60,58 +61,64 @@ class MembreModel extends HiveObject {
// Factory pour convertir depuis JSON (API)
factory MembreModel.fromJson(Map<String, dynamic> json) {
// Convertir l'ID en int, qu'il soit déjà int ou string
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
try {
// Convertir l'ID en int, qu'il soit déjà int ou string
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
// Convertir le rôle en int (ATTENTION: le champ JSON est 'fk_role' pas 'role')
final dynamic rawRole = json['fk_role']; // Correction ici !
final int role = rawRole is String ? int.parse(rawRole) : rawRole as int;
// Convertir le rôle en int (ATTENTION: le champ JSON est 'fk_role' pas 'role')
final dynamic rawRole = json['fk_role']; // Correction ici !
final int role = rawRole is String ? int.parse(rawRole) : rawRole as int;
// Convertir fkEntite en int si présent
int? fkEntite;
if (json['fk_entite'] != null) {
final dynamic rawEntite = json['fk_entite'];
fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int;
}
// Convertir fkTitre en int si présent
int? fkTitre;
if (json['fk_titre'] != null) {
final dynamic rawTitre = json['fk_titre'];
fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
}
// Gérer les dates nulles ou avec des valeurs invalides comme "0000-00-00"
DateTime? parseDate(String? dateStr) {
if (dateStr == null || dateStr.isEmpty || dateStr == "0000-00-00") {
return null;
// Convertir fkEntite en int si présent
int? fkEntite;
if (json['fk_entite'] != null) {
final dynamic rawEntite = json['fk_entite'];
fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int;
}
try {
return DateTime.parse(dateStr);
} catch (e) {
return null;
}
}
return MembreModel(
id: id,
fkEntite: fkEntite,
role: role,
fkTitre: fkTitre,
name: json['name'],
firstName: json['first_name'],
username: json['username'],
sectName: json['sect_name'],
email: json['email'] ?? '',
phone: json['phone'],
mobile: json['mobile'],
dateNaissance: parseDate(json['date_naissance']),
dateEmbauche: parseDate(json['date_embauche']),
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : DateTime.now(),
// Le champ JSON est 'chk_active' pas 'is_active'
isActive: json['chk_active'] == 1 || json['chk_active'] == true,
);
// Convertir fkTitre en int si présent
int? fkTitre;
if (json['fk_titre'] != null) {
final dynamic rawTitre = json['fk_titre'];
fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
}
// Gérer les dates nulles ou avec des valeurs invalides comme "0000-00-00"
DateTime? parseDate(String? dateStr) {
if (dateStr == null || dateStr.isEmpty || dateStr == "0000-00-00") {
return null;
}
try {
return DateTime.parse(dateStr);
} catch (e) {
return null;
}
}
return MembreModel(
id: id,
fkEntite: fkEntite,
role: role,
fkTitre: fkTitre,
name: json['name'] ?? '', // ← Fallback pour champs manquants
firstName: json['first_name'] ?? '', // ← Fallback pour champs manquants
username: json['username'] ?? '', // ← Fallback pour champs manquants
sectName: json['sect_name'] ?? '',
email: json['email'] ?? '', // ← Déjà OK mais renforcé
phone: json['phone'],
mobile: json['mobile'],
dateNaissance: parseDate(json['date_naissance']),
dateEmbauche: parseDate(json['date_embauche']),
createdAt: DateTime.now(), // ← Simplifié car created_at n'existe pas dans l'API
// Le champ JSON est 'chk_active' pas 'is_active'
isActive: json['chk_active'] == 1 || json['chk_active'] == true,
);
} catch (e) {
debugPrint('❌ Erreur parsing MembreModel: $e');
debugPrint('❌ Données JSON: $json');
rethrow;
}
}
// Convertir en JSON pour l'API

View File

@@ -25,12 +25,16 @@ class OperationModel extends HiveObject {
@HiveField(6)
bool isSynced;
@HiveField(7)
final int fkEntite;
OperationModel({
required this.id,
required this.name,
required this.dateDebut,
required this.dateFin,
required this.lastSyncedAt,
required this.fkEntite,
this.isActive = true,
this.isSynced = false,
});
@@ -40,15 +44,18 @@ class OperationModel extends HiveObject {
// Convertir l'ID en int, qu'il soit déjà int ou string
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
final dynamic rawEntite = json['fk_entite'];
final int fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int;
return OperationModel(
id: id,
name: json['name'],
name: json['libelle'], // ← Correction: utiliser 'libelle' au lieu de 'name'
dateDebut: DateTime.parse(json['date_deb']),
dateFin: DateTime.parse(json['date_fin']),
lastSyncedAt: DateTime.now(),
isActive: true,
isActive: json['chk_active'] == true || json['chk_active'] == 1 || json['chk_active'] == "1",
isSynced: true,
fkEntite: fkEntite,
);
}
@@ -60,6 +67,7 @@ class OperationModel extends HiveObject {
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
'is_active': isActive,
'fk_entite': fkEntite,
};
}
@@ -71,15 +79,17 @@ class OperationModel extends HiveObject {
bool? isActive,
bool? isSynced,
DateTime? lastSyncedAt,
int? fkEntite,
}) {
return OperationModel(
id: this.id,
id: id,
name: name ?? this.name,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
isActive: isActive ?? this.isActive,
isSynced: isSynced ?? this.isSynced,
fkEntite: fkEntite ?? this.fkEntite,
);
}
}

View File

@@ -22,6 +22,7 @@ class OperationModelAdapter extends TypeAdapter<OperationModel> {
dateDebut: fields[2] as DateTime,
dateFin: fields[3] as DateTime,
lastSyncedAt: fields[4] as DateTime,
fkEntite: fields[7] as int,
isActive: fields[5] as bool,
isSynced: fields[6] as bool,
);
@@ -30,7 +31,7 @@ class OperationModelAdapter extends TypeAdapter<OperationModel> {
@override
void write(BinaryWriter writer, OperationModel obj) {
writer
..writeByte(7)
..writeByte(8)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -44,7 +45,9 @@ class OperationModelAdapter extends TypeAdapter<OperationModel> {
..writeByte(5)
..write(obj.isActive)
..writeByte(6)
..write(obj.isSynced);
..write(obj.isSynced)
..writeByte(7)
..write(obj.fkEntite);
}
@override

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
part 'passage_model.g.dart';
@@ -125,66 +126,72 @@ class PassageModel extends HiveObject {
// Factory pour convertir depuis JSON (API)
factory PassageModel.fromJson(Map<String, dynamic> json) {
// Convertir l'ID en int, qu'il soit déjà int ou string
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
// Convertir les autres champs numériques
final dynamic rawFkOperation = json['fk_operation'];
final int fkOperation = rawFkOperation is String ? int.parse(rawFkOperation) : rawFkOperation as int;
final dynamic rawFkSector = json['fk_sector'];
final int fkSector = rawFkSector is String ? int.parse(rawFkSector) : rawFkSector as int;
final dynamic rawFkUser = json['fk_user'];
final int fkUser = rawFkUser is String ? int.parse(rawFkUser) : rawFkUser as int;
final dynamic rawFkType = json['fk_type'];
final int fkType = rawFkType is String ? int.parse(rawFkType) : rawFkType as int;
final dynamic rawFkHabitat = json['fk_habitat'];
final int fkHabitat = rawFkHabitat is String ? int.parse(rawFkHabitat) : rawFkHabitat as int;
final dynamic rawFkTypeReglement = json['fk_type_reglement'];
final int fkTypeReglement = rawFkTypeReglement is String ? int.parse(rawFkTypeReglement) : rawFkTypeReglement as int;
final dynamic rawNbPassages = json['nb_passages'];
final int nbPassages = rawNbPassages is String ? int.parse(rawNbPassages) : rawNbPassages as int;
// Convertir la date
final DateTime passedAt = DateTime.parse(json['passed_at']);
return PassageModel(
id: id,
fkOperation: fkOperation,
fkSector: fkSector,
fkUser: fkUser,
fkType: fkType,
fkAdresse: json['fk_adresse'] as String,
passedAt: passedAt,
numero: json['numero'] as String,
rue: json['rue'] as String,
rueBis: json['rue_bis'] as String? ?? '',
ville: json['ville'] as String,
residence: json['residence'] as String? ?? '',
fkHabitat: fkHabitat,
appt: json['appt'] as String? ?? '',
niveau: json['niveau'] as String? ?? '',
gpsLat: json['gps_lat'] as String,
gpsLng: json['gps_lng'] as String,
nomRecu: json['nom_recu'] as String? ?? '',
remarque: json['remarque'] as String? ?? '',
montant: json['montant'] as String,
fkTypeReglement: fkTypeReglement,
emailErreur: json['email_erreur'] as String? ?? '',
nbPassages: nbPassages,
name: json['name'] as String,
email: json['email'] as String? ?? '',
phone: json['phone'] as String? ?? '',
lastSyncedAt: DateTime.now(),
isActive: true,
isSynced: true,
);
try {
// Convertir l'ID en int, qu'il soit déjà int ou string
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
// Convertir les autres champs numériques
final dynamic rawFkOperation = json['fk_operation'];
final int fkOperation = rawFkOperation is String ? int.parse(rawFkOperation) : rawFkOperation as int;
final dynamic rawFkSector = json['fk_sector'];
final int fkSector = rawFkSector is String ? int.parse(rawFkSector) : rawFkSector as int;
final dynamic rawFkUser = json['fk_user'];
final int fkUser = rawFkUser is String ? int.parse(rawFkUser) : rawFkUser as int;
final dynamic rawFkType = json['fk_type'];
final int fkType = rawFkType is String ? int.parse(rawFkType) : rawFkType as int;
final dynamic rawFkHabitat = json['fk_habitat'];
final int fkHabitat = rawFkHabitat is String ? int.parse(rawFkHabitat) : rawFkHabitat as int;
final dynamic rawFkTypeReglement = json['fk_type_reglement'];
final int fkTypeReglement = rawFkTypeReglement is String ? int.parse(rawFkTypeReglement) : rawFkTypeReglement as int;
final dynamic rawNbPassages = json['nb_passages'];
final int nbPassages = rawNbPassages is String ? int.parse(rawNbPassages) : rawNbPassages as int;
// Convertir la date
final DateTime passedAt = DateTime.parse(json['passed_at']);
return PassageModel(
id: id,
fkOperation: fkOperation,
fkSector: fkSector,
fkUser: fkUser,
fkType: fkType,
fkAdresse: json['fk_adresse']?.toString() ?? '', // ← Gestion null
passedAt: passedAt,
numero: json['numero']?.toString() ?? '', // ← Gestion null
rue: json['rue']?.toString() ?? '', // ← Gestion null
rueBis: json['rue_bis']?.toString() ?? '',
ville: json['ville']?.toString() ?? '', // ← Gestion null
residence: json['residence']?.toString() ?? '',
fkHabitat: fkHabitat,
appt: json['appt']?.toString() ?? '',
niveau: json['niveau']?.toString() ?? '',
gpsLat: json['gps_lat']?.toString() ?? '', // ← Gestion null
gpsLng: json['gps_lng']?.toString() ?? '', // ← Gestion null
nomRecu: json['nom_recu']?.toString() ?? '', // ← Gestion null explicite
remarque: json['remarque']?.toString() ?? '',
montant: json['montant']?.toString() ?? '0.00', // ← Gestion null avec fallback
fkTypeReglement: fkTypeReglement,
emailErreur: json['email_erreur']?.toString() ?? '',
nbPassages: nbPassages,
name: json['name']?.toString() ?? '', // ← Gestion null
email: json['email']?.toString() ?? '',
phone: json['phone']?.toString() ?? '',
lastSyncedAt: DateTime.now(),
isActive: true,
isSynced: true,
);
} catch (e) {
debugPrint('❌ Erreur parsing PassageModel: $e');
debugPrint('❌ Données JSON: $json');
rethrow;
}
}
// Convertir en JSON pour l'API

View File

@@ -2,7 +2,11 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/data/models/user_sector_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/services/data_loading_service.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
class OperationRepository extends ChangeNotifier {
@@ -13,6 +17,9 @@ class OperationRepository extends ChangeNotifier {
return Hive.box<OperationModel>(AppKeys.operationsBoxName);
}
// Getter exposant publiquement la Hive Box
Box<OperationModel> get operationBox => _operationBox;
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
Future<void> _ensureBoxIsOpen() async {
const boxName = AppKeys.operationsBoxName;
@@ -97,10 +104,14 @@ class OperationRepository extends ChangeNotifier {
notifyListeners();
try {
debugPrint('🔄 Traitement de ${operationsData.length} opérations depuis l\'API');
for (var operationData in operationsData) {
final operationJson = operationData as Map<String, dynamic>;
final operationId = operationJson['id'] is String ? int.parse(operationJson['id']) : operationJson['id'] as int;
debugPrint('📝 Traitement opération ID: $operationId, libelle: ${operationJson['libelle']}');
// Vérifier si l'opération existe déjà
OperationModel? existingOperation = getOperationById(operationId);
@@ -108,20 +119,27 @@ class OperationRepository extends ChangeNotifier {
// Créer une nouvelle opération
final newOperation = OperationModel.fromJson(operationJson);
await saveOperation(newOperation);
debugPrint('✅ Nouvelle opération créée: ${newOperation.name}');
} else {
// Mettre à jour l'opération existante
final updatedOperation = existingOperation.copyWith(
name: operationJson['name'],
name: operationJson['libelle'], // ← Correction: utiliser 'libelle' au lieu de 'name'
fkEntite: operationJson['fk_entite'],
dateDebut: DateTime.parse(operationJson['date_deb']),
dateFin: DateTime.parse(operationJson['date_fin']),
isActive: operationJson['chk_active'] == true || operationJson['chk_active'] == 1 || operationJson['chk_active'] == "1",
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveOperation(updatedOperation);
debugPrint('✅ Opération mise à jour: ${updatedOperation.name}');
}
}
debugPrint('🎉 Traitement terminé - ${_operationBox.length} opérations dans la box');
} catch (e) {
debugPrint('Erreur lors du traitement des opérations: $e');
debugPrint('Erreur lors du traitement des opérations: $e');
debugPrint('❌ Stack trace: ${StackTrace.current}');
} finally {
_isLoading = false;
notifyListeners();
@@ -141,40 +159,116 @@ class OperationRepository extends ChangeNotifier {
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
};
debugPrint('🚀 Création d\'une nouvelle opération: $data');
// Appeler l'API pour créer l'opération
final response = await ApiService.instance.post('/operations', data: data);
if (response.statusCode == 201 || response.statusCode == 200) {
// Récupérer l'ID de la nouvelle opération
final operationId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
debugPrint('✅ Opération créée avec succès');
// Créer l'opération localement
final newOperation = OperationModel(
id: operationId,
name: name,
dateDebut: dateDebut,
dateFin: dateFin,
lastSyncedAt: DateTime.now(),
isActive: true,
isSynced: true,
);
await saveOperation(newOperation);
// Traiter la réponse complète de l'API
await _processCreationResponse(response.data);
return true;
}
debugPrint('❌ Échec de la création - Code: ${response.statusCode}');
return false;
} catch (e) {
debugPrint('Erreur lors de la création de l\'opération: $e');
return false;
debugPrint('Erreur lors de la création de l\'opération: $e');
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Traiter la réponse complète après création d'opération
Future<void> _processCreationResponse(Map<String, dynamic> responseData) async {
try {
debugPrint('🔄 Traitement de la réponse de création d\'opération');
// Traiter les opérations (groupe operations)
if (responseData['operations'] != null) {
await processOperationsFromApi(responseData['operations']);
debugPrint('✅ Opérations traitées');
}
// Traiter les secteurs (groupe secteurs) via DataLoadingService
if (responseData['secteurs'] != null) {
await DataLoadingService.instance.processSectorsFromApi(responseData['secteurs']);
debugPrint('✅ Secteurs traités');
}
// Traiter les passages (groupe passages) via DataLoadingService
if (responseData['passages'] != null) {
await DataLoadingService.instance.processPassagesFromApi(responseData['passages']);
debugPrint('✅ Passages traités');
}
// Traiter les users_sectors (groupe users_sectors) via DataLoadingService
if (responseData['users_sectors'] != null) {
await DataLoadingService.instance.processUserSectorsFromApi(responseData['users_sectors']);
debugPrint('✅ Users_sectors traités');
}
debugPrint('🎉 Tous les groupes de données ont été traités avec succès');
} catch (e) {
debugPrint('❌ Erreur lors du traitement de la réponse: $e');
// Ne pas faire échouer la création si le traitement des données supplémentaires échoue
}
}
// Méthode universelle pour sauvegarder une opération (création ou mise à jour)
Future<bool> saveOperationFromModel(OperationModel operation) async {
debugPrint('=== saveOperationFromModel APPELÉ ===');
debugPrint('operation.id: ${operation.id}');
debugPrint('operation.name: ${operation.name}');
try {
if (operation.id == 0) {
// Nouvelle opération - créer
debugPrint('=== CRÉATION (POST) ===');
return await createOperation(
operation.name,
operation.dateDebut,
operation.dateFin,
);
} else {
// Opération existante - mettre à jour
debugPrint('=== MISE À JOUR (PUT) ===');
final result = await updateOperation(
operation.id,
name: operation.name,
dateDebut: operation.dateDebut,
dateFin: operation.dateFin,
isActive: operation.isActive,
fkEntite: operation.fkEntite, // ← Inclure fkEntite
);
debugPrint('=== RÉSULTAT UPDATE: $result ===');
return result;
}
} catch (e) {
debugPrint('=== ERREUR dans saveOperationFromModel: $e ===');
// Propager l'exception pour que la page parente puisse la gérer
rethrow;
}
}
// Méthode pour mettre à jour une opération avec un objet OperationModel
Future<bool> updateOperationFromModel(OperationModel operation) async {
return await updateOperation(
operation.id,
name: operation.name,
dateDebut: operation.dateDebut,
dateFin: operation.dateFin,
isActive: operation.isActive,
fkEntite: operation.fkEntite, // ← Inclure fkEntite
);
}
// Mettre à jour une opération
Future<bool> updateOperation(int id, {String? name, DateTime? dateDebut, DateTime? dateFin, bool? isActive}) async {
Future<bool> updateOperation(int id, {String? name, DateTime? dateDebut, DateTime? dateFin, bool? isActive, int? fkEntite}) async {
_isLoading = true;
notifyListeners();
@@ -182,7 +276,8 @@ class OperationRepository extends ChangeNotifier {
// Récupérer l'opération existante
final existingOperation = getOperationById(id);
if (existingOperation == null) {
return false;
debugPrint('❌ Opération avec l\'ID $id non trouvée');
throw Exception('Opération non trouvée');
}
// Préparer les données pour l'API
@@ -191,59 +286,203 @@ class OperationRepository extends ChangeNotifier {
'name': name ?? existingOperation.name,
'date_deb': (dateDebut ?? existingOperation.dateDebut).toIso8601String().split('T')[0],
'date_fin': (dateFin ?? existingOperation.dateFin).toIso8601String().split('T')[0],
'is_active': isActive ?? existingOperation.isActive,
'chk_active': isActive ?? existingOperation.isActive, // Utiliser chk_active comme dans l'API
'fk_entite': fkEntite ?? existingOperation.fkEntite, // ← Inclure fkEntite
};
debugPrint('🔄 Mise à jour de l\'opération $id avec les données: $data');
// Appeler l'API pour mettre à jour l'opération
final response = await ApiService.instance.put('/operations/$id', data: data);
if (response.statusCode == 200) {
debugPrint('✅ Opération $id mise à jour avec succès');
// Mettre à jour l'opération localement
final updatedOperation = existingOperation.copyWith(
name: name,
dateDebut: dateDebut,
dateFin: dateFin,
isActive: isActive,
fkEntite: fkEntite,
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveOperation(updatedOperation);
return true;
} else {
debugPrint('❌ Échec de la mise à jour - Code: ${response.statusCode}');
throw Exception('Échec de la mise à jour de l\'opération');
}
return false;
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'opération: $e');
return false;
debugPrint('Erreur lors de la mise à jour de l\'opération: $e');
// Propager l'exception pour qu'elle soit gérée par l'interface
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer une opération via l'API
// Supprimer une opération inactive via l'API
Future<bool> deleteOperationViaApi(int id) async {
_isLoading = true;
notifyListeners();
try {
// Appeler l'API pour supprimer l'opération
debugPrint('🗑️ Suppression opération inactive $id');
// Appeler l'API pour supprimer l'opération inactive
final response = await ApiService.instance.delete('/operations/$id');
if (response.statusCode == 200 || response.statusCode == 204) {
// Supprimer l'opération localement
await deleteOperation(id);
debugPrint('✅ Suppression réussie - Traitement de la réponse');
// Traiter la réponse qui contient les 3 dernières opérations
if (response.data != null && response.data['operations'] != null) {
// Vider la box des opérations
await _operationBox.clear();
// Recharger les opérations depuis la réponse API
await processOperationsFromApi(response.data['operations']);
debugPrint('✅ Opérations rechargées après suppression');
} else {
// Fallback : supprimer localement seulement
await deleteOperation(id);
}
return true;
}
debugPrint('❌ Échec suppression - Code: ${response.statusCode}');
return false;
} catch (e) {
debugPrint('Erreur lors de la suppression de l\'opération: $e');
return false;
debugPrint('Erreur lors de la suppression de l\'opération: $e');
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer une opération active via l'API (avec réactivation automatique)
Future<bool> deleteActiveOperationViaApi(int id) async {
_isLoading = true;
notifyListeners();
try {
debugPrint('🗑️ Suppression opération active $id');
// Appeler l'API pour supprimer l'opération active
final response = await ApiService.instance.delete('/operations/$id');
if (response.statusCode == 200 || response.statusCode == 204) {
debugPrint('✅ Suppression opération active réussie - Traitement complet');
// Traiter la réponse complète qui contient tous les groupes de données
if (response.data != null) {
await _processActiveDeleteResponse(response.data);
debugPrint('✅ Données rechargées après suppression opération active');
} else {
// Fallback : supprimer localement seulement
await deleteOperation(id);
}
return true;
}
debugPrint('❌ Échec suppression opération active - Code: ${response.statusCode}');
return false;
} catch (e) {
debugPrint('❌ Erreur lors de la suppression de l\'opération active: $e');
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Traiter la réponse complète après suppression d'opération active
Future<void> _processActiveDeleteResponse(Map<String, dynamic> responseData) async {
try {
debugPrint('🔄 Traitement de la réponse de suppression d\'opération active');
// Vider toutes les Box concernées
await _clearAllRelatedBoxes();
// Traiter les opérations (groupe operations)
if (responseData['operations'] != null) {
await processOperationsFromApi(responseData['operations']);
debugPrint('✅ Opérations traitées');
}
// Traiter les secteurs (groupe secteurs) via DataLoadingService
if (responseData['secteurs'] != null) {
await DataLoadingService.instance.processSectorsFromApi(responseData['secteurs']);
debugPrint('✅ Secteurs traités');
}
// Traiter les passages (groupe passages) via DataLoadingService
if (responseData['passages'] != null) {
await DataLoadingService.instance.processPassagesFromApi(responseData['passages']);
debugPrint('✅ Passages traités');
}
// Traiter les users_sectors (groupe users_sectors) via DataLoadingService
if (responseData['users_sectors'] != null) {
await DataLoadingService.instance.processUserSectorsFromApi(responseData['users_sectors']);
debugPrint('✅ Users_sectors traités');
}
debugPrint('🎉 Tous les groupes de données ont été traités après suppression opération active');
} catch (e) {
debugPrint('❌ Erreur lors du traitement de la réponse de suppression: $e');
// Ne pas faire échouer la suppression si le traitement des données supplémentaires échoue
}
}
// Vider toutes les Box liées lors de suppression d'opération active
Future<void> _clearAllRelatedBoxes() async {
try {
// Vider les Box respectives avant rechargement complet
await _operationBox.clear();
if (Hive.isBoxOpen(AppKeys.sectorsBoxName)) {
final sectorsBox = Hive.box<SectorModel>(AppKeys.sectorsBoxName);
await sectorsBox.clear();
}
if (Hive.isBoxOpen(AppKeys.passagesBoxName)) {
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
await passagesBox.clear();
}
if (Hive.isBoxOpen(AppKeys.userSectorBoxName)) {
final userSectorsBox = Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
await userSectorsBox.clear();
}
debugPrint('✅ Toutes les Box ont été vidées');
} catch (e) {
debugPrint('❌ Erreur lors du vidage des Box: $e');
}
}
// Export Excel d'une opération
Future<void> exportOperationToExcel(int operationId, String operationName) async {
try {
debugPrint('📊 Export Excel opération $operationId: $operationName');
// Générer le nom de fichier avec la date actuelle
final now = DateTime.now();
final dateStr = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
final fileName = 'operation_${operationName.replaceAll(' ', '_')}_$dateStr.xlsx';
// Appeler l'API pour télécharger le fichier Excel
await ApiService.instance.downloadOperationExcel(operationId, fileName);
debugPrint('✅ Export Excel terminé pour opération $operationId');
} catch (e) {
debugPrint('❌ Erreur lors de l\'export Excel: $e');
rethrow;
}
}
}

View File

@@ -8,6 +8,7 @@ import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/core/services/data_loading_service.dart';
import 'package:geosector_app/core/services/hive_service.dart';
import 'package:geosector_app/core/services/hive_reset_state_service.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
@@ -280,7 +281,7 @@ class UserRepository extends ChangeNotifier {
}
}
/// Déconnexion simplifiée avec DataLoadingService
/// Déconnexion simplifiée avec HiveService
Future<bool> logout(BuildContext context) async {
_isLoading = true;
notifyListeners();
@@ -301,8 +302,8 @@ class UserRepository extends ChangeNotifier {
await CurrentUserService.instance.clearUser();
await CurrentAmicaleService.instance.clearAmicale();
// Nettoyage complet via DataLoadingService
await DataLoadingService.instance.cleanDataAfterLogout();
// Nettoyage des données via HiveService (préserve les utilisateurs)
await HiveService.instance.cleanDataOnLogout();
// Réinitialiser l'état de HiveResetStateService
hiveResetStateService.reset();

View File

@@ -307,6 +307,69 @@ class ApiService {
}
}
// Export Excel d'une opération
Future<void> downloadOperationExcel(int operationId, String fileName) async {
try {
debugPrint('📊 Téléchargement Excel pour opération $operationId');
final response = await _dio.get(
'/operations/$operationId/export/excel',
options: Options(
responseType: ResponseType.bytes, // Important pour les fichiers binaires
headers: {
'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
},
),
);
if (response.statusCode == 200) {
debugPrint('✅ Fichier Excel reçu (${response.data.length} bytes)');
if (kIsWeb) {
// Pour le web : déclencher le téléchargement via le navigateur
_downloadFileWeb(response.data, fileName);
} else {
// Pour mobile : sauvegarder dans le dossier de téléchargements
await _downloadFileMobile(response.data, fileName);
}
debugPrint('✅ Export Excel terminé: $fileName');
} else {
throw ApiException('Erreur lors du téléchargement: ${response.statusCode}');
}
} on DioException catch (e) {
throw ApiException.fromDioException(e);
} catch (e) {
if (e is ApiException) rethrow;
throw ApiException('Erreur inattendue lors de l\'export Excel', originalError: e);
}
}
// Téléchargement pour le web
void _downloadFileWeb(List<int> bytes, String fileName) {
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.AnchorElement(href: url)
..setAttribute('download', fileName)
..click();
html.Url.revokeObjectUrl(url);
debugPrint('🌐 Téléchargement web déclenché: $fileName');
}
// Téléchargement pour mobile
Future<void> _downloadFileMobile(List<int> bytes, String fileName) async {
try {
// Pour mobile, on pourrait utiliser path_provider pour obtenir le dossier de téléchargements
// et file_picker ou similar pour sauvegarder le fichier
// Pour l'instant, on lance juste une exception informative
throw const ApiException('Téléchargement mobile non implémenté. Utilisez la version web.');
} catch (e) {
rethrow;
}
}
// Méthode de nettoyage pour les tests
static void reset() {
_instance = null;

View File

@@ -126,6 +126,28 @@ class DataLoadingService extends ChangeNotifier {
}
}
// === MÉTHODES PUBLIQUES POUR TRAITEMENT EXTERNE ===
/// Méthode publique pour traiter les secteurs depuis l'extérieur
Future<void> processSectorsFromApi(dynamic sectorsData) async {
await _processSectors(sectorsData);
}
/// Méthode publique pour traiter les passages depuis l'extérieur
Future<void> processPassagesFromApi(dynamic passagesData) async {
await _processPassages(passagesData);
}
/// Méthode publique pour traiter les associations user-sectors depuis l'extérieur
Future<void> processUserSectorsFromApi(dynamic userSectorsData) async {
await _processUserSectors(userSectorsData);
}
/// Méthode publique pour traiter les opérations depuis l'extérieur
Future<void> processOperationsFromApi(dynamic operationsData) async {
await _processOperations(operationsData);
}
// === MÉTHODES DE TRAITEMENT DES DONNÉES ===
Future<void> _processClients(dynamic clientsData) async {
@@ -251,17 +273,20 @@ class DataLoadingService extends ChangeNotifier {
await _passageBox.clear();
int count = 0;
int errorCount = 0;
for (final passageData in passagesList) {
try {
final passage = PassageModel.fromJson(passageData);
await _passageBox.put(passage.id, passage);
count++;
} catch (e) {
debugPrint('⚠️ Erreur traitement passage: $e');
errorCount++;
debugPrint('⚠️ Erreur traitement passage ${passageData['id']}: $e');
// Continue avec le passage suivant au lieu de s'arrêter
}
}
debugPrint('$count passages stockés');
debugPrint('$count passages stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
} catch (e) {
debugPrint('❌ Erreur traitement passages: $e');
}
@@ -278,7 +303,6 @@ class DataLoadingService extends ChangeNotifier {
}
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);
@@ -316,17 +340,20 @@ class DataLoadingService extends ChangeNotifier {
await _membreBox.clear();
int count = 0;
int errorCount = 0;
for (final membreData in membresList) {
try {
final membre = MembreModel.fromJson(membreData);
await _membreBox.put(membre.id, membre);
count++;
} catch (e) {
debugPrint('⚠️ Erreur traitement membre: $e');
errorCount++;
debugPrint('⚠️ Erreur traitement membre ${membreData['id']}: $e');
// Continue avec le membre suivant au lieu de s'arrêter
}
}
debugPrint('$count membres stockés');
debugPrint('$count membres stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
} catch (e) {
debugPrint('❌ Erreur traitement membres: $e');
}

View File

@@ -0,0 +1,499 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/hive_web_fix.dart';
import 'package:geosector_app/core/services/hive_adapters.dart';
// Import de tous les modèles typés
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/chat/models/conversation_model.dart';
import 'package:geosector_app/chat/models/message_model.dart';
/// Service singleton centralisé pour la gestion complète des Box Hive
/// Utilisé par main.dart pour l'initialisation et par logout pour le nettoyage
class HiveService {
static HiveService? _instance;
static HiveService get instance => _instance ??= HiveService._internal();
HiveService._internal();
/// Configuration des Box typées de l'application
static const List<HiveBoxConfig> _boxConfigs = [
HiveBoxConfig<UserModel>(AppKeys.userBoxName, 'UserModel'),
HiveBoxConfig<AmicaleModel>(AppKeys.amicaleBoxName, 'AmicaleModel'),
HiveBoxConfig<ClientModel>(AppKeys.clientsBoxName, 'ClientModel'),
HiveBoxConfig<OperationModel>(AppKeys.operationsBoxName, 'OperationModel'),
HiveBoxConfig<SectorModel>(AppKeys.sectorsBoxName, 'SectorModel'),
HiveBoxConfig<PassageModel>(AppKeys.passagesBoxName, 'PassageModel'),
HiveBoxConfig<MembreModel>(AppKeys.membresBoxName, 'MembreModel'),
HiveBoxConfig<UserSectorModel>(AppKeys.userSectorBoxName, 'UserSectorModel'),
HiveBoxConfig<ConversationModel>(AppKeys.chatConversationsBoxName, 'ConversationModel'),
HiveBoxConfig<MessageModel>(AppKeys.chatMessagesBoxName, 'MessageModel'),
HiveBoxConfig<dynamic>(AppKeys.settingsBoxName, 'Settings'),
HiveBoxConfig<dynamic>(AppKeys.regionsBoxName, 'Regions'),
];
bool _isInitialized = false;
bool get isInitialized => _isInitialized;
// === INITIALISATION COMPLÈTE (appelée par main.dart) ===
/// Initialisation complète de Hive avec réinitialisation totale
Future<void> initializeAndResetHive() async {
if (_isInitialized) {
debugPrint(' HiveService déjà initialisé');
return;
}
try {
debugPrint('🔧 Initialisation complète de Hive avec reset...');
// 1. Initialisation de base de Hive
await Hive.initFlutter();
debugPrint('✅ Hive.initFlutter() terminé');
// 2. Enregistrement des adaptateurs
_registerAdapters();
// 3. Destruction complète des anciennes données
await _destroyAllData();
// 4. Création des Box vides et propres
await _createAllBoxes();
_isInitialized = true;
debugPrint('✅ HiveService initialisé avec succès');
} catch (e) {
debugPrint('❌ Erreur lors de l\'initialisation complète: $e');
_isInitialized = false;
rethrow;
}
}
// === INITIALISATION SIMPLE (appelée par splash_page si besoin) ===
/// Initialisation simple sans reset (utilisée par splash_page si déjà initialisé)
Future<void> ensureBoxesAreOpen() async {
try {
debugPrint('🔍 Vérification et ouverture des Box...');
// Vérifier si toutes les Box sont ouvertes
bool allOpen = true;
for (final config in _boxConfigs) {
if (!Hive.isBoxOpen(config.name)) {
allOpen = false;
break;
}
}
if (allOpen) {
debugPrint('✅ Toutes les Box sont déjà ouvertes');
return;
}
// Ouvrir les Box manquantes
await _createAllBoxes();
debugPrint('✅ Box manquantes ouvertes');
} catch (e) {
debugPrint('❌ Erreur lors de la vérification des Box: $e');
rethrow;
}
}
// === NETTOYAGE LOGOUT (appelé lors du logout) ===
/// Nettoyage sélectif lors du logout (préserve les utilisateurs)
Future<void> cleanDataOnLogout() async {
try {
debugPrint('🧹 Nettoyage des données au logout...');
// Nettoyer toutes les Box sauf les utilisateurs
for (final config in _boxConfigs) {
if (config.name != AppKeys.userBoxName) {
await _clearSingleBox(config.name);
}
}
debugPrint('✅ Nettoyage logout terminé (utilisateurs préservés)');
} catch (e) {
debugPrint('❌ Erreur lors du nettoyage logout: $e');
rethrow;
}
}
// === MÉTHODES PRIVÉES D'INITIALISATION ===
/// Enregistrement de tous les adaptateurs Hive
void _registerAdapters() {
try {
if (!Hive.isAdapterRegistered(0)) {
// Utiliser HiveAdapters existant pour enregistrer tous les adaptateurs
HiveAdapters.registerAll();
debugPrint('🔌 Adaptateurs Hive enregistrés via HiveAdapters');
} else {
debugPrint(' Adaptateurs déjà enregistrés');
}
} catch (e) {
debugPrint('❌ Erreur enregistrement adaptateurs: $e');
// Ne pas faire échouer l'initialisation
}
}
/// Destruction complète de toutes les données selon la plateforme
Future<void> _destroyAllData() async {
try {
debugPrint('💥 Destruction complète des données Hive...');
// 1. Fermer toutes les Box ouvertes
await _closeAllOpenBoxes();
// 2. Suppression selon la plateforme
if (kIsWeb) {
await _destroyDataWeb();
} else if (Platform.isIOS) {
await _destroyDataIOS();
} else if (Platform.isAndroid) {
await _destroyDataAndroid();
} else {
await _destroyDataDesktop();
}
// 3. Attendre pour s'assurer que tout est détruit
await Future.delayed(const Duration(seconds: 1));
debugPrint('✅ Destruction complète terminée');
} catch (e) {
debugPrint('❌ Erreur destruction: $e');
// Continuer malgré l'erreur
}
}
/// Fermeture de toutes les Box ouvertes
Future<void> _closeAllOpenBoxes() async {
try {
debugPrint('🔒 Fermeture de toutes les Box ouvertes...');
// Fermer les Box configurées
for (final config in _boxConfigs) {
try {
if (Hive.isBoxOpen(config.name)) {
await Hive.box(config.name).close();
debugPrint('🔒 Box ${config.name} fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture ${config.name}: $e');
}
}
// Fermer aussi les Box potentiellement orphelines
final orphanBoxes = ['auth', 'temp', 'cache', 'locations', 'messages'];
for (final boxName in orphanBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).close();
debugPrint('🔒 Box orpheline $boxName fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture orpheline $boxName: $e');
}
}
} catch (e) {
debugPrint('❌ Erreur fermeture générale: $e');
}
}
/// Destruction des données sur Web
Future<void> _destroyDataWeb() async {
try {
debugPrint('🌐 Destruction Web...');
// Sur Web, utiliser le HiveWebFix si disponible
try {
await HiveWebFix.resetHiveCompletely();
debugPrint('✅ Destruction Web via HiveWebFix');
return;
} catch (e) {
debugPrint('⚠️ HiveWebFix échoué, fallback...');
}
// Fallback : supprimer Box par Box
for (final config in _boxConfigs) {
try {
await Hive.deleteBoxFromDisk(config.name);
debugPrint('🗑️ Box Web ${config.name} supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression Web ${config.name}: $e');
}
}
} catch (e) {
debugPrint('❌ Erreur destruction Web: $e');
}
}
/// Destruction des données sur iOS
Future<void> _destroyDataIOS() async {
try {
debugPrint('🍎 Destruction iOS...');
// Méthode 1: Destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Destruction iOS complète');
return;
} catch (e) {
debugPrint('⚠️ Destruction iOS totale échouée, méthode alternative...');
}
// Méthode 2: Suppression des fichiers manuellement
try {
final appDir = await getApplicationDocumentsDirectory();
final hiveDir = Directory('${appDir.path}/hive');
if (await hiveDir.exists()) {
await hiveDir.delete(recursive: true);
debugPrint('✅ Dossier Hive iOS supprimé');
}
} catch (e) {
debugPrint('⚠️ Suppression dossier iOS échouée: $e');
}
// Méthode 3: Fallback Box par Box
await _fallbackDeleteBoxes();
} catch (e) {
debugPrint('❌ Erreur destruction iOS: $e');
}
}
/// Destruction des données sur Android
Future<void> _destroyDataAndroid() async {
try {
debugPrint('🤖 Destruction Android...');
// Méthode 1: Destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Destruction Android complète');
return;
} catch (e) {
debugPrint('⚠️ Destruction Android totale échouée, méthode alternative...');
}
// Méthode 2: Suppression des fichiers .hive et .lock
try {
final appDir = await getApplicationDocumentsDirectory();
final files = await appDir.list().toList();
int deletedCount = 0;
for (final file in files) {
final fileName = file.path.split('/').last;
if (fileName.endsWith('.hive') || fileName.endsWith('.lock')) {
try {
await file.delete();
deletedCount++;
} catch (e) {
debugPrint('⚠️ Erreur suppression fichier $fileName: $e');
}
}
}
debugPrint('$deletedCount fichiers Android supprimés');
} catch (e) {
debugPrint('⚠️ Suppression fichiers Android échouée: $e');
}
// Méthode 3: Fallback Box par Box
await _fallbackDeleteBoxes();
} catch (e) {
debugPrint('❌ Erreur destruction Android: $e');
}
}
/// Destruction des données sur Desktop
Future<void> _destroyDataDesktop() async {
try {
debugPrint('🖥️ Destruction Desktop...');
// Destruction totale
await Hive.deleteFromDisk();
debugPrint('✅ Destruction Desktop complète');
} catch (e) {
debugPrint('⚠️ Destruction Desktop échouée, fallback...');
await _fallbackDeleteBoxes();
}
}
/// Fallback : suppression Box par Box
Future<void> _fallbackDeleteBoxes() async {
debugPrint('🔄 Fallback: suppression Box par Box...');
for (final config in _boxConfigs) {
try {
await Hive.deleteBoxFromDisk(config.name);
debugPrint('🗑️ Box fallback ${config.name} supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression fallback ${config.name}: $e');
}
}
}
/// Création de toutes les Box vides et propres
Future<void> _createAllBoxes() async {
try {
debugPrint('🆕 Création de toutes les Box...');
for (int i = 0; i < _boxConfigs.length; i++) {
final config = _boxConfigs[i];
try {
await _createSingleBox(config);
debugPrint('✅ Box ${config.name} créée (${i + 1}/${_boxConfigs.length})');
} catch (e) {
debugPrint('❌ Erreur création ${config.name}: $e');
// Continuer même en cas d'erreur
}
// Petite pause entre les créations
await Future.delayed(const Duration(milliseconds: 100));
}
debugPrint('✅ Toutes les Box ont été créées');
} catch (e) {
debugPrint('❌ Erreur création des Box: $e');
rethrow;
}
}
/// Création d'une Box individuelle avec le bon type
Future<void> _createSingleBox(HiveBoxConfig config) async {
try {
if (Hive.isBoxOpen(config.name)) {
debugPrint(' Box ${config.name} déjà ouverte');
return;
}
switch (config.type) {
case 'UserModel':
await Hive.openBox<UserModel>(config.name);
break;
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(config.name);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(config.name);
break;
case 'OperationModel':
await Hive.openBox<OperationModel>(config.name);
break;
case 'SectorModel':
await Hive.openBox<SectorModel>(config.name);
break;
case 'PassageModel':
await Hive.openBox<PassageModel>(config.name);
break;
case 'MembreModel':
await Hive.openBox<MembreModel>(config.name);
break;
case 'UserSectorModel':
await Hive.openBox<UserSectorModel>(config.name);
break;
case 'ConversationModel':
await Hive.openBox<ConversationModel>(config.name);
break;
case 'MessageModel':
await Hive.openBox<MessageModel>(config.name);
break;
default:
// Pour Settings, Regions, etc.
await Hive.openBox(config.name);
}
} catch (e) {
debugPrint('❌ Erreur création spécifique ${config.name}: $e');
// Fallback : essayer sans type
try {
await Hive.openBox(config.name);
debugPrint('⚠️ Box ${config.name} créée sans type');
} catch (e2) {
debugPrint('❌ Échec total ${config.name}: $e2');
rethrow;
}
}
}
// === MÉTHODES UTILITAIRES ===
/// Vider une Box individuelle
Future<void> _clearSingleBox(String boxName) async {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).clear();
debugPrint('🧹 Box $boxName vidée');
} else {
debugPrint(' Box $boxName n\'est pas ouverte, impossible de la vider');
}
} catch (e) {
debugPrint('⚠️ Erreur vidage $boxName: $e');
}
}
/// Vérification que toutes les Box sont ouvertes
bool areAllBoxesOpen() {
for (final config in _boxConfigs) {
if (!Hive.isBoxOpen(config.name)) {
debugPrint('❌ Box ${config.name} n\'est pas ouverte');
return false;
}
}
debugPrint('✅ Toutes les Box sont ouvertes');
return true;
}
/// Récupération sécurisée d'une Box typée
Box<T> getTypedBox<T>(String boxName) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La Box $boxName n\'est pas ouverte');
}
return Hive.box<T>(boxName);
}
/// Récupération sécurisée d'une Box non-typée
Box getBox(String boxName) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La Box $boxName n\'est pas ouverte');
}
return Hive.box(boxName);
}
/// Liste des noms de toutes les Box configurées
List<String> getAllBoxNames() {
return _boxConfigs.map((config) => config.name).toList();
}
/// Diagnostic complet de l'état des Box
Map<String, bool> getDiagnostic() {
final diagnostic = <String, bool>{};
for (final config in _boxConfigs) {
diagnostic[config.name] = Hive.isBoxOpen(config.name);
}
return diagnostic;
}
/// Reset complet du service (pour tests)
static void reset() {
_instance = null;
}
}
/// Configuration d'une Box Hive avec type
class HiveBoxConfig<T> {
final String name;
final String type;
const HiveBoxConfig(this.name, this.type);
}