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

@@ -8,7 +8,7 @@ part 'anonymous_user_model.g.dart';
/// Ce modèle représente un utilisateur anonyme (pour le cas Resalice)
/// et permet de tracker sa conversion éventuelle en utilisateur authentifié
@HiveType(typeId: 24)
@HiveType(typeId: 23)
class AnonymousUserModel extends HiveObject with EquatableMixin {
@HiveField(0)
final String id;

View File

@@ -8,7 +8,7 @@ part of 'anonymous_user_model.dart';
class AnonymousUserModelAdapter extends TypeAdapter<AnonymousUserModel> {
@override
final int typeId = 24;
final int typeId = 23;
@override
AnonymousUserModel read(BinaryReader reader) {

View File

@@ -8,7 +8,7 @@ part 'audience_target_model.g.dart';
/// Ce modèle représente une cible d'audience pour les annonces et broadcasts
/// Il supporte maintenant le ciblage combiné avec les filtres de rôle et d'entité
@HiveType(typeId: 23)
@HiveType(typeId: 24)
class AudienceTargetModel extends HiveObject with EquatableMixin {
@HiveField(0)
final String id;

View File

@@ -8,7 +8,7 @@ part of 'audience_target_model.dart';
class AudienceTargetModelAdapter extends TypeAdapter<AudienceTargetModel> {
@override
final int typeId = 23;
final int typeId = 24;
@override
AudienceTargetModel read(BinaryReader reader) {

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

View File

@@ -4,22 +4,9 @@ import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:geosector_app/core/services/app_info_service.dart';
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/app.dart';
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/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/hive_reset_state_service.dart';
import 'package:geosector_app/chat/models/chat_adapters.dart';
import 'package:geosector_app/core/services/hive_adapters.dart';
void main() async {
// IMPORTANT: Configurer l'URL strategy pour éviter les # dans les URLs
@@ -31,13 +18,7 @@ void main() async {
await _initializeServices();
// Initialiser Hive avec gestion des erreurs
final hiveInitialized = await _initializeHive();
// TEMPORAIREMENT: Ne pas marquer l'erreur pour éviter la redirection
// if (!hiveInitialized) {
// debugPrint('Incompatibilité détectée dans les données Hive. Marquage pour affichage du dialogue...');
// hiveResetStateService.markAsReset();
// }
await _initializeHive();
// Configurer l'orientation de l'application (mobile uniquement)
if (!kIsWeb) {
@@ -71,185 +52,16 @@ Future<void> _initializeServices() async {
}
}
/// Initialise Hive et les adaptateurs
Future<bool> _initializeHive() async {
Future<void> _initializeHive() async {
try {
// Initialiser Hive
await Hive.initFlutter();
// Enregistrer les adaptateurs Hive pour les modèles principaux
_registerHiveAdapters();
// Enregistrer tous les adapters
HiveAdapters.registerAll();
// Ouvrir uniquement les boîtes essentielles au démarrage
await _openEssentialHiveBoxes();
// Charger les données depuis Hive au démarrage
await CurrentUserService.instance.loadFromHive();
await CurrentAmicaleService.instance.loadFromHive();
debugPrint('✅ Données utilisateur/amicale chargées depuis Hive');
debugPrint('Hive initialisé avec succès');
return true;
debugPrint('✅ Hive et TypeAdapters initialisés');
} catch (e) {
debugPrint('Erreur lors de l\'initialisation de Hive: $e');
return false;
}
}
/// Enregistre tous les adaptateurs Hive
void _registerHiveAdapters() {
// Vérifier si les adaptateurs sont déjà enregistrés pour éviter les doublons
if (!Hive.isAdapterRegistered(0)) {
Hive.registerAdapter(UserModelAdapter());
}
if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(AmicaleModelAdapter());
}
if (!Hive.isAdapterRegistered(2)) {
Hive.registerAdapter(ClientModelAdapter());
}
if (!Hive.isAdapterRegistered(3)) {
Hive.registerAdapter(OperationModelAdapter());
}
if (!Hive.isAdapterRegistered(4)) {
Hive.registerAdapter(SectorModelAdapter());
}
if (!Hive.isAdapterRegistered(5)) {
Hive.registerAdapter(PassageModelAdapter());
}
if (!Hive.isAdapterRegistered(6)) {
Hive.registerAdapter(MembreModelAdapter());
}
if (!Hive.isAdapterRegistered(7)) {
Hive.registerAdapter(UserSectorModelAdapter());
}
if (!Hive.isAdapterRegistered(8)) {
Hive.registerAdapter(RegionModelAdapter());
}
// Modèles de chat
if (!Hive.isAdapterRegistered(9)) {
Hive.registerAdapter(ConversationModelAdapter());
}
if (!Hive.isAdapterRegistered(10)) {
Hive.registerAdapter(MessageModelAdapter());
}
if (!Hive.isAdapterRegistered(11)) {
Hive.registerAdapter(ParticipantModelAdapter());
}
if (!Hive.isAdapterRegistered(12)) {
Hive.registerAdapter(AnonymousUserModelAdapter());
}
if (!Hive.isAdapterRegistered(13)) {
Hive.registerAdapter(AudienceTargetModelAdapter());
}
if (!Hive.isAdapterRegistered(14)) {
Hive.registerAdapter(NotificationSettingsAdapter());
}
}
/// Ouvre les boîtes Hive essentielles avec migration users -> user
Future<void> _openEssentialHiveBoxes() async {
final boxesToOpen = [
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
{'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'},
{'name': AppKeys.clientsBoxName, 'type': 'ClientModel'},
{'name': AppKeys.settingsBoxName, 'type': 'dynamic'},
{'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'},
{'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'},
];
// Logique de migration de l'ancienne box users vers user
try {
// Vérifier si l'ancienne box users existe
if (await _doesBoxExist(AppKeys.usersBoxNameOld)) {
debugPrint('🔄 Migration détectée: box users -> user');
// Ouvrir l'ancienne box
final oldBox = await Hive.openBox<UserModel>(AppKeys.usersBoxNameOld);
// Ouvrir la nouvelle box
final newBox = await Hive.openBox<UserModel>(AppKeys.userBoxName);
// Migrer les données si la nouvelle box est vide
if (oldBox.isNotEmpty && newBox.isEmpty) {
debugPrint('📦 Migration des données users -> user...');
// Chercher l'utilisateur actuel dans l'ancienne box
final userData = oldBox.get('current_user') ?? oldBox.values.firstOrNull;
if (userData != null) {
await newBox.put('current_user', userData);
debugPrint('✅ Migration de users -> user réussie pour ${userData.email}');
}
}
// Fermer et supprimer l'ancienne box
await oldBox.close();
await Hive.deleteBoxFromDisk(AppKeys.usersBoxNameOld);
debugPrint('🗑️ Ancienne box users supprimée');
} else {
// Ouvrir normalement la nouvelle box
await Hive.openBox<UserModel>(AppKeys.userBoxName);
}
} catch (e) {
debugPrint('⚠️ Erreur migration box users: $e');
// En cas d'erreur, ouvrir quand même la nouvelle box
try {
await Hive.openBox<UserModel>(AppKeys.userBoxName);
} catch (e2) {
debugPrint('❌ Impossible d\'ouvrir la box user: $e2');
}
}
// Ouvrir les autres boîtes
for (final box in boxesToOpen) {
try {
final boxName = box['name'] as String;
final boxType = box['type'] as String;
// Skip userBoxName car déjà traité dans la migration
if (boxName == AppKeys.userBoxName) continue;
// Vérifier si la boîte est déjà ouverte
if (Hive.isBoxOpen(boxName)) {
debugPrint('📦 Boîte $boxName déjà ouverte');
continue;
}
switch (boxType) {
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(boxName);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(boxName);
break;
case 'ConversationModel':
await Hive.openBox<ConversationModel>(boxName);
break;
case 'MessageModel':
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
debugPrint('✅ Boîte $boxName ouverte avec succès');
} catch (e) {
debugPrint('❌ Erreur lors de l\'ouverture de la boîte ${box['name']}: $e');
// Ne pas lancer d'erreur, continuer avec les autres boîtes
}
}
}
/// Vérifie si une box Hive existe sur le disque
Future<bool> _doesBoxExist(String boxName) async {
try {
// Tentative d'ouverture pour vérifier l'existence
final box = await Hive.openBox<UserModel>(boxName);
final exists = box.isNotEmpty;
await box.close();
return exists;
} catch (e) {
return false;
debugPrint('Erreur Hive: $e');
rethrow;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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