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

@@ -61,6 +61,15 @@ class ClientModel extends HiveObject {
@HiveField(18)
final bool? chkActive;
@HiveField(19)
final bool? chkStripe;
@HiveField(20)
final DateTime? createdAt;
@HiveField(21)
final DateTime? updatedAt;
ClientModel({
required this.id,
required this.name,
@@ -81,6 +90,9 @@ class ClientModel extends HiveObject {
this.chkCopieMailRecu,
this.chkAcceptSms,
this.chkActive,
this.chkStripe,
this.createdAt,
this.updatedAt,
});
// Factory pour convertir depuis JSON (API)
@@ -93,8 +105,7 @@ class ClientModel extends HiveObject {
int? fkRegion;
if (json['fk_region'] != null) {
final dynamic rawFkRegion = json['fk_region'];
fkRegion =
rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int;
fkRegion = rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int;
}
// Convertir fk_type en int si présent
@@ -121,11 +132,12 @@ class ClientModel extends HiveObject {
gpsLng: json['gps_lng'],
stripeId: json['stripe_id'],
chkDemo: json['chk_demo'] == 1 || json['chk_demo'] == true,
chkCopieMailRecu: json['chk_copie_mail_recu'] == 1 ||
json['chk_copie_mail_recu'] == true,
chkAcceptSms:
json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true,
chkCopieMailRecu: json['chk_copie_mail_recu'] == 1 || json['chk_copie_mail_recu'] == true,
chkAcceptSms: json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true,
chkActive: json['chk_active'] == 1 || json['chk_active'] == true,
chkStripe: json['chk_stripe'] == 1 || json['chk_stripe'] == true,
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null,
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
);
}
@@ -151,6 +163,9 @@ class ClientModel extends HiveObject {
'chk_copie_mail_recu': chkCopieMailRecu,
'chk_accept_sms': chkAcceptSms,
'chk_active': chkActive,
'chk_stripe': chkStripe,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
};
}
@@ -174,9 +189,12 @@ class ClientModel extends HiveObject {
bool? chkCopieMailRecu,
bool? chkAcceptSms,
bool? chkActive,
bool? chkStripe,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return ClientModel(
id: this.id,
id: id,
name: name ?? this.name,
adresse1: adresse1 ?? this.adresse1,
adresse2: adresse2 ?? this.adresse2,
@@ -195,6 +213,9 @@ class ClientModel extends HiveObject {
chkCopieMailRecu: chkCopieMailRecu ?? this.chkCopieMailRecu,
chkAcceptSms: chkAcceptSms ?? this.chkAcceptSms,
chkActive: chkActive ?? this.chkActive,
chkStripe: chkStripe ?? this.chkStripe,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@@ -36,13 +36,16 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
chkCopieMailRecu: fields[16] as bool?,
chkAcceptSms: fields[17] as bool?,
chkActive: fields[18] as bool?,
chkStripe: fields[19] as bool?,
createdAt: fields[20] as DateTime?,
updatedAt: fields[21] as DateTime?,
);
}
@override
void write(BinaryWriter writer, ClientModel obj) {
writer
..writeByte(19)
..writeByte(22)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -80,7 +83,13 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
..writeByte(17)
..write(obj.chkAcceptSms)
..writeByte(18)
..write(obj.chkActive);
..write(obj.chkActive)
..writeByte(19)
..write(obj.chkStripe)
..writeByte(20)
..write(obj.createdAt)
..writeByte(21)
..write(obj.updatedAt);
}
@override

View File

@@ -8,51 +8,53 @@ class MembreModel extends HiveObject {
final int id;
@HiveField(1)
final int fkRole;
int? fkEntite;
@HiveField(2)
final int fkTitre;
final int role;
@HiveField(3)
final String firstName;
int? fkTitre;
@HiveField(4)
final String? sectName;
String? name;
@HiveField(5)
final DateTime? dateNaissance;
String? firstName;
@HiveField(6)
final DateTime? dateEmbauche;
String? username;
@HiveField(7)
final int chkActive;
String? sectName;
@HiveField(8)
final String name;
@HiveField(9)
final String username;
@HiveField(10)
final String email;
@HiveField(9)
String? phone;
@HiveField(10)
String? mobile;
@HiveField(11)
final int fkEntite;
DateTime? dateNaissance;
@HiveField(12)
DateTime? dateEmbauche;
@HiveField(13)
final DateTime createdAt;
@HiveField(14)
bool isActive;
MembreModel({
required this.id,
required this.fkRole,
required this.fkTitre,
required this.firstName,
this.fkEntite,
required this.role,
this.fkTitre,
this.name,
this.firstName,
this.username,
this.sectName,
required this.email,
this.phone,
this.mobile,
this.dateNaissance,
this.dateEmbauche,
required this.chkActive,
required this.name,
required this.username,
required this.email,
required this.fkEntite,
required this.createdAt,
required this.isActive,
});
// Factory pour convertir depuis JSON (API)
@@ -61,35 +63,53 @@ class MembreModel extends HiveObject {
final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int;
// Convertir le rôle en int, qu'il soit déjà int ou string
final dynamic rawRole = json['fk_role'];
final int fkRole = 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 le titre en int, qu'il soit déjà int ou string
final dynamic rawTitre = json['fk_titre'];
final int fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre 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 le chkActive en int, qu'il soit déjà int ou string
final dynamic rawActive = json['chk_active'];
final int chkActive = rawActive is String ? int.parse(rawActive) : rawActive 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;
}
// Convertir le fkEntite en int, qu'il soit déjà int ou string
final dynamic rawEntite = json['fk_entite'];
final int fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite 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,
fkRole: fkRole,
fkTitre: fkTitre,
firstName: json['first_name'] ?? '',
sectName: json['sect_name'],
dateNaissance: json['date_naissance'] != null ? DateTime.parse(json['date_naissance']) : null,
dateEmbauche: json['date_embauche'] != null ? DateTime.parse(json['date_embauche']) : null,
chkActive: chkActive,
name: json['name'] ?? '',
username: json['username'] ?? '',
email: json['email'] ?? '',
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,
);
}
@@ -97,47 +117,56 @@ class MembreModel extends HiveObject {
Map<String, dynamic> toJson() {
return {
'id': id,
'fk_role': fkRole,
'fk_entite': fkEntite,
'fk_role': role, // Changé pour correspondre à l'API
'fk_titre': fkTitre,
'name': name,
'first_name': firstName,
'username': username,
'sect_name': sectName,
'email': email,
'phone': phone,
'mobile': mobile,
'date_naissance': dateNaissance?.toIso8601String(),
'date_embauche': dateEmbauche?.toIso8601String(),
'chk_active': chkActive,
'name': name,
'username': username,
'email': email,
'fk_entite': fkEntite,
'created_at': createdAt.toIso8601String(),
'chk_active': isActive ? 1 : 0, // Changé pour correspondre à l'API
};
}
// Copier avec de nouvelles valeurs
MembreModel copyWith({
int? fkRole,
int? fkEntite,
int? role,
int? fkTitre,
String? name,
String? firstName,
String? username,
String? sectName,
String? email,
String? phone,
String? mobile,
DateTime? dateNaissance,
DateTime? dateEmbauche,
int? chkActive,
String? name,
String? username,
String? email,
int? fkEntite,
DateTime? createdAt,
bool? isActive,
}) {
return MembreModel(
id: id,
fkRole: fkRole ?? this.fkRole,
fkEntite: fkEntite ?? this.fkEntite,
role: role ?? this.role,
fkTitre: fkTitre ?? this.fkTitre,
name: name ?? this.name,
firstName: firstName ?? this.firstName,
username: username ?? this.username,
sectName: sectName ?? this.sectName,
email: email ?? this.email,
phone: phone ?? this.phone,
mobile: mobile ?? this.mobile,
dateNaissance: dateNaissance ?? this.dateNaissance,
dateEmbauche: dateEmbauche ?? this.dateEmbauche,
chkActive: chkActive ?? this.chkActive,
name: name ?? this.name,
username: username ?? this.username,
email: email ?? this.email,
fkEntite: fkEntite ?? this.fkEntite,
createdAt: createdAt ?? this.createdAt,
isActive: isActive ?? this.isActive,
);
}
}

View File

@@ -18,48 +18,57 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
};
return MembreModel(
id: fields[0] as int,
fkRole: fields[1] as int,
fkTitre: fields[2] as int,
firstName: fields[3] as String,
sectName: fields[4] as String?,
dateNaissance: fields[5] as DateTime?,
dateEmbauche: fields[6] as DateTime?,
chkActive: fields[7] as int,
name: fields[8] as String,
username: fields[9] as String,
email: fields[10] as String,
fkEntite: fields[11] as int,
fkEntite: fields[1] as int?,
role: fields[2] as int,
fkTitre: fields[3] as int?,
name: fields[4] as String?,
firstName: fields[5] as String?,
username: fields[6] as String?,
sectName: fields[7] as String?,
email: fields[8] as String,
phone: fields[9] as String?,
mobile: fields[10] as String?,
dateNaissance: fields[11] as DateTime?,
dateEmbauche: fields[12] as DateTime?,
createdAt: fields[13] as DateTime,
isActive: fields[14] as bool,
);
}
@override
void write(BinaryWriter writer, MembreModel obj) {
writer
..writeByte(12)
..writeByte(15)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.fkRole)
..write(obj.fkEntite)
..writeByte(2)
..write(obj.fkTitre)
..write(obj.role)
..writeByte(3)
..write(obj.firstName)
..write(obj.fkTitre)
..writeByte(4)
..write(obj.sectName)
..writeByte(5)
..write(obj.dateNaissance)
..writeByte(6)
..write(obj.dateEmbauche)
..writeByte(7)
..write(obj.chkActive)
..writeByte(8)
..write(obj.name)
..writeByte(9)
..writeByte(5)
..write(obj.firstName)
..writeByte(6)
..write(obj.username)
..writeByte(10)
..writeByte(7)
..write(obj.sectName)
..writeByte(8)
..write(obj.email)
..writeByte(9)
..write(obj.phone)
..writeByte(10)
..write(obj.mobile)
..writeByte(11)
..write(obj.fkEntite);
..write(obj.dateNaissance)
..writeByte(12)
..write(obj.dateEmbauche)
..writeByte(13)
..write(obj.createdAt)
..writeByte(14)
..write(obj.isActive);
}
@override

View File

@@ -2,7 +2,7 @@ import 'package:hive/hive.dart';
part 'region_model.g.dart';
@HiveType(typeId: 7) // Assurez-vous que cet ID est unique
@HiveType(typeId: 7)
class RegionModel extends HiveObject {
@HiveField(0)
final int id;

View File

@@ -6,8 +6,7 @@ part 'user_sector_model.g.dart';
///
/// Cette classe représente l'association entre un utilisateur et un secteur,
/// telle que reçue de l'API dans la réponse users_sectors.
@HiveType(
typeId: 7) // Assurez-vous que cet ID est unique parmi vos modèles Hive
@HiveType(typeId: 6)
class UserSectorModel extends HiveObject {
@HiveField(0)
final int id; // ID de l'utilisateur
@@ -38,9 +37,7 @@ class UserSectorModel extends HiveObject {
id: json['id'] is String ? int.parse(json['id']) : json['id'],
firstName: json['first_name'],
sectName: json['sect_name'],
fkSector: json['fk_sector'] is String
? int.parse(json['fk_sector'])
: json['fk_sector'],
fkSector: json['fk_sector'] is String ? int.parse(json['fk_sector']) : json['fk_sector'],
name: json['name'],
);
}

View File

@@ -8,7 +8,7 @@ part of 'user_sector_model.dart';
class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> {
@override
final int typeId = 7;
final int typeId = 6;
@override
UserSectorModel read(BinaryReader reader) {

View File

@@ -1,8 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
@@ -34,6 +31,19 @@ class AmicaleRepository extends ChangeNotifier {
}
}
// Méthode pour exposer la Box Hive (nécessaire pour ValueListenableBuilder)
Box<AmicaleModel> getAmicalesBox() {
try {
if (!Hive.isBoxOpen(AppKeys.amicaleBoxName)) {
throw Exception('La boîte amicales n\'est pas ouverte');
}
return _amicaleBox;
} catch (e) {
debugPrint('Erreur lors de l\'accès à la boîte amicales: $e');
rethrow;
}
}
// Récupérer toutes les amicales
List<AmicaleModel> getAllAmicales() {
return _amicaleBox.values.toList();
@@ -54,6 +64,16 @@ class AmicaleRepository extends ChangeNotifier {
return getAmicalesByType(1);
}
// Récupérer l'amicale de l'utilisateur connecté (basé sur fkEntite)
AmicaleModel? getAmicaleByUserId(int userId, int fkEntite) {
try {
return _amicaleBox.get(fkEntite);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
return null;
}
}
// Sauvegarder une amicale
Future<void> saveAmicale(AmicaleModel amicale) async {
await _amicaleBox.put(amicale.id, amicale);
@@ -66,6 +86,12 @@ class AmicaleRepository extends ChangeNotifier {
notifyListeners();
}
// Vider la boîte des amicales
Future<void> clearAmicales() async {
await _amicaleBox.clear();
notifyListeners();
}
// Créer une amicale via l'API
Future<bool> createAmicale(AmicaleModel amicale) async {
_isLoading = true;
@@ -82,14 +108,33 @@ class AmicaleRepository extends ChangeNotifier {
// Récupérer l'ID de la nouvelle amicale
final amicaleId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
// Créer l'amicale localement avec l'ID retourné par l'API
final newAmicale = amicale.copyWith(
// Créer l'amicale localement avec l'ID retourné par l'API et updatedAt
final amicaleWithNewId = AmicaleModel(
id: amicaleId,
lastSyncedAt: DateTime.now(),
isSynced: true,
name: amicale.name,
adresse1: amicale.adresse1,
adresse2: amicale.adresse2,
codePostal: amicale.codePostal,
ville: amicale.ville,
fkRegion: amicale.fkRegion,
libRegion: amicale.libRegion,
fkType: amicale.fkType,
phone: amicale.phone,
mobile: amicale.mobile,
email: amicale.email,
gpsLat: amicale.gpsLat,
gpsLng: amicale.gpsLng,
stripeId: amicale.stripeId,
chkDemo: amicale.chkDemo,
chkCopieMailRecu: amicale.chkCopieMailRecu,
chkAcceptSms: amicale.chkAcceptSms,
chkActive: amicale.chkActive,
chkStripe: amicale.chkStripe,
createdAt: amicale.createdAt ?? DateTime.now(),
updatedAt: DateTime.now(),
);
await saveAmicale(newAmicale);
await saveAmicale(amicaleWithNewId);
return true;
}
@@ -116,10 +161,9 @@ class AmicaleRepository extends ChangeNotifier {
final response = await ApiService.instance.put('/amicales/${amicale.id}', data: data);
if (response.statusCode == 200) {
// Mettre à jour l'amicale localement
// Mettre à jour l'amicale localement avec updatedAt
final updatedAmicale = amicale.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: true,
updatedAt: DateTime.now(),
);
await saveAmicale(updatedAmicale);
@@ -136,6 +180,35 @@ class AmicaleRepository extends ChangeNotifier {
}
}
// Mettre à jour une amicale via l'API (version alternative avec retour d'objet)
Future<AmicaleModel?> updateAmicaleViaApi(AmicaleModel amicale) async {
_isLoading = true;
notifyListeners();
try {
final response = await ApiService.instance.put(
'/amicales/${amicale.id}',
data: amicale.toJson(),
);
if (response.statusCode == 200) {
final updatedAmicaleData = response.data;
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
await saveAmicale(updatedAmicale);
return updatedAmicale;
} else {
debugPrint('Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer une amicale via l'API
Future<bool> deleteAmicaleViaApi(int id) async {
_isLoading = true;
@@ -161,228 +234,55 @@ class AmicaleRepository extends ChangeNotifier {
}
}
// Traitement des données d'amicales depuis l'API
Future<void> processAmicalesData(dynamic amicalesData) async {
// Traitement des données d'amicale depuis l'API (amicale unique)
Future<void> processAmicalesData(dynamic amicaleData) async {
try {
debugPrint('Traitement des données des amicales...');
debugPrint('Traitement de l\'amicale utilisateur...');
// Vérifier que les données sont au bon format
if (amicalesData == null) {
if (amicaleData == null) {
debugPrint('Aucune donnée d\'amicale à traiter');
return;
}
List<dynamic> amicalesList;
if (amicalesData is List) {
amicalesList = amicalesData;
} else if (amicalesData is Map && amicalesData.containsKey('data')) {
amicalesList = amicalesData['data'] as List<dynamic>;
} else {
debugPrint('Format de données d\'amicales non reconnu');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
// Vider la boîte avant d'ajouter la nouvelle amicale
await _amicaleBox.clear();
// Traiter chaque amicale
int count = 0;
for (final amicaleData in amicalesList) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
debugPrint('$count amicales traitées et stockées');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des amicales: $e');
}
}
}
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
class AmicaleRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
final ApiService _apiService;
bool _isLoading = false;
AmicaleRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
// Méthode pour exposer la Box Hive (nécessaire pour ValueListenableBuilder)
Box<AmicaleModel> getAmicalesBox() {
try {
if (!Hive.isBoxOpen(AppKeys.amicaleBoxName)) {
throw Exception('La boîte amicales n\'est pas ouverte');
}
return _amicaleBox;
} catch (e) {
debugPrint('Erreur lors de l\'accès à la boîte amicales: $e');
rethrow;
}
}
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
Future<void> _ensureBoxIsOpen() async {
try {
if (!Hive.isBoxOpen(AppKeys.amicaleBoxName)) {
debugPrint('Ouverture de la boîte amicale...');
await Hive.openBox<AmicaleModel>(AppKeys.amicaleBoxName);
try {
// Les données sont un objet amicale 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 utilisateur traitée: ${amicale.name} (ID: ${amicale.id})');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement de l\'amicale: $e');
debugPrint('Données reçues: $amicaleData');
}
} catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte amicale: $e');
throw Exception('Impossible d\'ouvrir la boîte amicale: $e');
debugPrint('Erreur lors du traitement de l\'amicale: $e');
}
}
// Récupérer toutes les amicales
List<AmicaleModel> getAllAmicales() {
// Méthode spécifique pour récupérer l'amicale de l'utilisateur connecté
AmicaleModel? getUserAmicale(int fkEntite) {
try {
_ensureBoxIsOpen();
return _amicaleBox.values.toList();
final amicale = _amicaleBox.get(fkEntite);
debugPrint('🔍 Recherche amicale ID $fkEntite: ${amicale?.name ?? 'non trouvée'}');
return amicale;
} catch (e) {
debugPrint('Erreur lors de la récupération des amicales: $e');
return [];
}
}
// Récupérer une amicale par son ID
AmicaleModel? getAmicaleById(int id) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale: $e');
debugPrint('Erreur lors de la récupération de l\'amicale utilisateur: $e');
return null;
}
}
// Récupérer l'amicale de l'utilisateur connecté (basé sur fkEntite)
AmicaleModel? getAmicaleByUserId(int userId, int fkEntite) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(fkEntite);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
return null;
}
}
// Créer ou mettre à jour une amicale localement
Future<AmicaleModel> saveAmicale(AmicaleModel amicale) async {
await _ensureBoxIsOpen();
await _amicaleBox.put(amicale.id, amicale);
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
return amicale;
}
// Supprimer une amicale localement
Future<void> deleteAmicale(int id) async {
await _ensureBoxIsOpen();
await _amicaleBox.delete(id);
notifyListeners();
}
// Vider la boîte des amicales
Future<void> clearAmicales() async {
await _ensureBoxIsOpen();
await _amicaleBox.clear();
notifyListeners();
}
// Traiter les données des amicales reçues de l'API
Future<void> processAmicalesData(dynamic amicalesData) async {
try {
debugPrint('Traitement des données des amicales...');
debugPrint('Détails amicale: $amicalesData');
// Vérifier que les données sont au bon format
if (amicalesData == null) {
debugPrint('Aucune donnée d\'amicale à traiter');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _amicaleBox.clear();
int count = 0;
// Cas 1: Les données sont une liste d'amicales
if (amicalesData is List) {
for (final amicaleData in amicalesData) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 2: Les données sont un objet avec une clé 'data' contenant une liste
else if (amicalesData is Map && amicalesData.containsKey('data')) {
final amicalesList = amicalesData['data'] as List<dynamic>;
for (final amicaleData in amicalesList) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 3: Les données sont un objet amicale unique (pas une liste)
else if (amicalesData is Map) {
try {
// Convertir Map<dynamic, dynamic> en Map<String, dynamic>
final Map<String, dynamic> amicaleMap = {};
amicalesData.forEach((key, value) {
if (key is String) {
amicaleMap[key] = value;
}
});
final amicale = AmicaleModel.fromJson(amicaleMap);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale unique traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement de l\'amicale unique: $e');
debugPrint('Exception détaillée: $e');
}
} else {
debugPrint('Format de données d\'amicale non reconnu');
return;
}
debugPrint('$count amicales traitées et stockées');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des amicales: $e');
}
}
// Récupérer les amicales depuis l'API
Future<List<AmicaleModel>> fetchAmicalesFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/amicales');
final response = await ApiService.instance.get('/amicales');
if (response.statusCode == 200) {
final amicalesData = response.data;
@@ -407,7 +307,7 @@ class AmicaleRepository extends ChangeNotifier {
notifyListeners();
try {
final response = await _apiService.get('/amicales/$id');
final response = await ApiService.instance.get('/amicales/$id');
if (response.statusCode == 200) {
final amicaleData = response.data;
@@ -427,34 +327,7 @@ class AmicaleRepository extends ChangeNotifier {
}
}
// Mettre à jour une amicale via l'API
Future<AmicaleModel?> updateAmicaleViaApi(AmicaleModel amicale) async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.put(
'/amicales/${amicale.id}',
data: amicale.toJson(),
);
if (response.statusCode == 200) {
final updatedAmicaleData = response.data;
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
await saveAmicale(updatedAmicale);
return updatedAmicale;
} else {
debugPrint('Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// === MÉTHODES DE FILTRAGE ET RECHERCHE ===
// Filtrer les amicales par nom
List<AmicaleModel> searchAmicalesByName(String query) {
@@ -466,11 +339,6 @@ class AmicaleRepository extends ChangeNotifier {
return _amicaleBox.values.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery)).toList();
}
// Filtrer les amicales par type
List<AmicaleModel> getAmicalesByType(int type) {
return _amicaleBox.values.where((amicale) => amicale.fkType == type).toList();
}
// Filtrer les amicales par région
List<AmicaleModel> getAmicalesByRegion(int regionId) {
return _amicaleBox.values.where((amicale) => amicale.fkRegion == regionId).toList();

View File

@@ -8,6 +8,7 @@ import 'package:geosector_app/core/constants/app_keys.dart';
class ClientRepository extends ChangeNotifier {
// Constructeur sans paramètres - utilise ApiService.instance
ClientRepository();
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
// et vérifier qu'elle est ouverte avant accès
Box<ClientModel> get _clientBox {
@@ -58,8 +59,11 @@ class ClientRepository extends ChangeNotifier {
notifyListeners();
try {
// Préparer les données pour l'API
// Préparer les données pour l'API - exclure l'id pour la création
final data = client.toJson();
data.remove('id'); // L'API génère l'ID
data.remove('created_at'); // L'API génère created_at
data.remove('updated_at'); // L'API génère updated_at
// Appeler l'API pour créer le client
final response = await ApiService.instance.post('/clients', data: data);
@@ -70,11 +74,13 @@ class ClientRepository extends ChangeNotifier {
// Créer le client localement avec l'ID retourné par l'API
final newClient = client.copyWith(
id: clientId,
lastSyncedAt: DateTime.now(),
isSynced: true,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
await saveClient(newClient);
// Sauvegarder avec le nouvel ID
await _clientBox.put(clientId, newClient);
notifyListeners();
return true;
}
return false;
@@ -95,17 +101,14 @@ class ClientRepository extends ChangeNotifier {
try {
// Préparer les données pour l'API
final data = client.toJson();
// Appeler l'API pour mettre à jour le client
final response = await ApiService.instance.put('/clients/${client.id}', data: data);
if (response.statusCode == 200) {
// Mettre à jour le client localement
// Mettre à jour le client localement avec updatedAt
final updatedClient = client.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: true,
updatedAt: DateTime.now(),
);
await saveClient(updatedClient);
return true;
}
@@ -190,7 +193,6 @@ class ClientRepository extends ChangeNotifier {
// Vider la boîte des clients
Future<void> clearClients() async {
await _ensureBoxIsOpen();
await _clientBox.clear();
notifyListeners();
}
@@ -220,6 +222,7 @@ class ClientRepository extends ChangeNotifier {
}
}
// === MÉTHODES DE FILTRAGE ET RECHERCHE ===
// Filtrer les clients par nom
List<ClientModel> searchClientsByName(String query) {
if (query.isEmpty) {

View File

@@ -8,6 +8,7 @@ import 'package:geosector_app/core/constants/app_keys.dart';
class MembreRepository extends ChangeNotifier {
// Constructeur sans paramètres - utilise ApiService.instance
MembreRepository();
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
// et vérifier qu'elle est ouverte avant accès
Box<MembreModel> get _membreBox {
@@ -43,6 +44,9 @@ class MembreRepository extends ChangeNotifier {
}
}
// === MÉTHODES SPÉCIFIQUES AUX MEMBRES ===
// Récupérer les membres par amicale
List<MembreModel> getMembresByAmicale(int fkEntite) {
try {
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite).toList();
@@ -55,7 +59,7 @@ class MembreRepository extends ChangeNotifier {
// Récupérer les membres actifs par amicale
List<MembreModel> getActiveMembresByAmicale(int fkEntite) {
try {
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite && membre.chkActive == 1).toList();
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite && membre.isActive == true).toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des membres actifs par amicale: $e');
return [];
@@ -72,6 +76,8 @@ class MembreRepository extends ChangeNotifier {
}
}
// === MÉTHODES CRUD DE BASE ===
// Récupérer tous les membres
List<MembreModel> getAllMembres() {
try {
@@ -94,27 +100,28 @@ class MembreRepository extends ChangeNotifier {
// Sauvegarder un membre
Future<void> saveMembre(MembreModel membre) async {
await _ensureBoxIsOpen();
await _membreBox.put(membre.id, membre);
notifyListeners();
}
// Supprimer un membre
Future<void> deleteMembre(int id) async {
await _ensureBoxIsOpen();
await _membreBox.delete(id);
notifyListeners();
}
// === MÉTHODES API ===
// Créer un membre via l'API
Future<bool> createMembre(MembreModel membre) async {
_isLoading = true;
notifyListeners();
try {
// Préparer les données pour l'API
// Préparer les données pour l'API - exclure l'id pour la création
final data = membre.toJson();
data.remove('id'); // L'API génère l'ID
data.remove('created_at'); // L'API génère created_at
// Appeler l'API pour créer le membre
final response = await ApiService.instance.post('/membres', data: data);
@@ -123,11 +130,24 @@ class MembreRepository extends ChangeNotifier {
final membreId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
// Créer le membre localement avec l'ID retourné par l'API
final newMembre = membre.copyWith(
final newMembre = MembreModel(
id: membreId,
lastSyncedAt: DateTime.now(),
isSynced: true,
fkEntite: membre.fkEntite,
role: membre.role,
fkTitre: membre.fkTitre,
name: membre.name,
firstName: membre.firstName,
username: membre.username,
sectName: membre.sectName,
email: membre.email,
phone: membre.phone,
mobile: membre.mobile,
dateNaissance: membre.dateNaissance,
dateEmbauche: membre.dateEmbauche,
createdAt: DateTime.now(),
isActive: membre.isActive,
);
await saveMembre(newMembre);
return true;
}
@@ -154,13 +174,8 @@ class MembreRepository extends ChangeNotifier {
final response = await ApiService.instance.put('/membres/${membre.id}', data: data);
if (response.statusCode == 200) {
// Mettre à jour le membre localement
final updatedMembre = membre.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveMembre(updatedMembre);
// Sauvegarder le membre mis à jour localement
await saveMembre(membre);
return true;
}
@@ -199,6 +214,8 @@ class MembreRepository extends ChangeNotifier {
}
}
// === TRAITEMENT DES DONNÉES ===
// Traitement des données de membres depuis l'API
Future<void> processMembresData(dynamic membresData) async {
try {
@@ -221,9 +238,7 @@ class MembreRepository extends ChangeNotifier {
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _membreBox.clear();
// Traiter chaque membre
int count = 0;
for (final membreData in membresList) {
@@ -242,4 +257,55 @@ class MembreRepository extends ChangeNotifier {
debugPrint('Erreur lors du traitement des membres: $e');
}
}
// Récupérer les membres depuis l'API
Future<List<MembreModel>> fetchMembresFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await ApiService.instance.get('/membres');
if (response.statusCode == 200) {
final membresData = response.data;
await processMembresData(membresData);
return getAllMembres();
} else {
debugPrint('Erreur lors de la récupération des membres: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des membres: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// === MÉTHODES DE FILTRAGE ET RECHERCHE ===
// Filtrer les membres par nom
List<MembreModel> searchMembresByName(String query) {
if (query.isEmpty) {
return getAllMembres();
}
final lowercaseQuery = query.toLowerCase();
return _membreBox.values
.where(
(membre) => (membre.name?.toLowerCase().contains(lowercaseQuery) ?? false) || (membre.firstName?.toLowerCase().contains(lowercaseQuery) ?? false))
.toList();
}
// Filtrer les membres actifs
List<MembreModel> getActiveMembres() {
return _membreBox.values.where((membre) => membre.isActive == true).toList();
}
// Vider la boîte des membres
Future<void> clearMembres() async {
await _membreBox.clear();
notifyListeners();
}
}

View File

@@ -68,6 +68,11 @@ class PassageRepository extends ChangeNotifier {
return _passageBox.values.where((passage) => passage.fkType == type).toList();
}
// Récupérer les passages par opération
List<PassageModel> getPassagesByOperation(int operationId) {
return _passageBox.values.where((passage) => passage.fkOperation == operationId).toList();
}
// Récupérer les passages par date
List<PassageModel> getPassagesByDate(DateTime date) {
return _passageBox.values.where((passage) {

View File

@@ -226,9 +226,6 @@ class UserRepository extends ChangeNotifier {
try {
debugPrint('🔐 Tentative de connexion: $username');
// Étape 1: Nettoyage des données via DataLoadingService
await DataLoadingService.instance.cleanDataBeforeLogin();
// Étape 2: Connexion à l'API (25%)
final apiResult = await loginAPI(username, password, type: type);
@@ -264,7 +261,13 @@ class UserRepository extends ChangeNotifier {
}
// Étape 5: Traitement de toutes les autres données via DataLoadingService
await DataLoadingService.instance.processLoginData(apiResult);
try {
await DataLoadingService.instance.processLoginData(apiResult);
} catch (processingError) {
debugPrint('❌ Erreur lors du traitement des données: $processingError');
// On continue quand même car l'utilisateur est connecté
debugPrint('⚠️ Connexion réussie mais avec des données partielles');
}
debugPrint('✅ Connexion réussie');
return true;

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());
}
}
}