Restructuration majeure du projet: migration de flutt vers app, ajout de l'API et mise à jour du site web

This commit is contained in:
d6soft
2025-05-16 09:19:03 +02:00
parent b5aafc424b
commit 5c2620de30
391 changed files with 19780 additions and 7233 deletions

View File

@@ -0,0 +1,295 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
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 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);
}
} catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte amicale: $e');
throw Exception('Impossible d\'ouvrir la boîte amicale: $e');
}
}
// Récupérer toutes les amicales
List<AmicaleModel> getAllAmicales() {
try {
_ensureBoxIsOpen();
return _amicaleBox.values.toList();
} 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');
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');
if (response.statusCode == 200) {
final amicalesData = response.data;
await processAmicalesData(amicalesData);
return getAllAmicales();
} else {
debugPrint(
'Erreur lors de la récupération des amicales: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des amicales: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// Récupérer une amicale spécifique depuis l'API
Future<AmicaleModel?> fetchAmicaleByIdFromApi(int id) async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/amicales/$id');
if (response.statusCode == 200) {
final amicaleData = response.data;
final amicale = AmicaleModel.fromJson(amicaleData);
await saveAmicale(amicale);
return amicale;
} else {
debugPrint(
'Erreur lors de la récupération de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// 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();
}
}
// Filtrer les amicales par nom
List<AmicaleModel> searchAmicalesByName(String query) {
if (query.isEmpty) {
return getAllAmicales();
}
final lowercaseQuery = query.toLowerCase();
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();
}
// Filtrer les amicales actives
List<AmicaleModel> getActiveAmicales() {
return _amicaleBox.values.where((amicale) => amicale.chkActive).toList();
}
// Filtrer les amicales par code postal
List<AmicaleModel> getAmicalesByPostalCode(String postalCode) {
return _amicaleBox.values
.where((amicale) => amicale.codePostal == postalCode)
.toList();
}
// Filtrer les amicales par ville
List<AmicaleModel> getAmicalesByCity(String city) {
final lowercaseCity = city.toLowerCase();
return _amicaleBox.values
.where((amicale) => amicale.ville.toLowerCase().contains(lowercaseCity))
.toList();
}
}

View File

@@ -0,0 +1,179 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
class ClientRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<ClientModel> get _clientBox =>
Hive.box<ClientModel>(AppKeys.clientsBoxName);
final ApiService _apiService;
bool _isLoading = false;
ClientRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
// 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.clientsBoxName)) {
debugPrint('Ouverture de la boîte clients...');
await Hive.openBox<ClientModel>(AppKeys.clientsBoxName);
}
} catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte clients: $e');
throw Exception('Impossible d\'ouvrir la boîte clients: $e');
}
}
// Récupérer tous les clients
List<ClientModel> getAllClients() {
try {
_ensureBoxIsOpen();
return _clientBox.values.toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des clients: $e');
return [];
}
}
// Récupérer un client par son ID
ClientModel? getClientById(int id) {
try {
_ensureBoxIsOpen();
return _clientBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération du client: $e');
return null;
}
}
// Créer ou mettre à jour un client localement
Future<ClientModel> saveClient(ClientModel client) async {
await _ensureBoxIsOpen();
await _clientBox.put(client.id, client);
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
return client;
}
// Supprimer un client localement
Future<void> deleteClient(int id) async {
await _ensureBoxIsOpen();
await _clientBox.delete(id);
notifyListeners();
}
// Vider la boîte des clients
Future<void> clearClients() async {
await _ensureBoxIsOpen();
await _clientBox.clear();
notifyListeners();
}
// Traiter les données des clients reçues de l'API
Future<void> processClientsData(dynamic clientsData) async {
try {
debugPrint('Traitement des données des clients...');
// Vérifier que les données sont au bon format
if (clientsData == null) {
debugPrint('Aucune donnée de client à traiter');
return;
}
List<dynamic> clientsList;
if (clientsData is List) {
clientsList = clientsData;
} else if (clientsData is Map && clientsData.containsKey('data')) {
clientsList = clientsData['data'] as List<dynamic>;
} else {
debugPrint('Format de données de clients non reconnu');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _clientBox.clear();
// Traiter chaque client
int count = 0;
for (final clientData in clientsList) {
try {
final client = ClientModel.fromJson(clientData);
await _clientBox.put(client.id, client);
count++;
debugPrint('Client traité: ${client.name} (ID: ${client.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'un client: $e');
}
}
debugPrint('$count clients traités et stockés');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des clients: $e');
}
}
// Récupérer les clients depuis l'API
Future<List<ClientModel>> fetchClientsFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/clients');
if (response.statusCode == 200) {
final clientsData = response.data;
await processClientsData(clientsData);
return getAllClients();
} else {
debugPrint(
'Erreur lors de la récupération des clients: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des clients: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// Filtrer les clients par nom
List<ClientModel> searchClientsByName(String query) {
if (query.isEmpty) {
return getAllClients();
}
final lowercaseQuery = query.toLowerCase();
return _clientBox.values
.where((client) => client.name.toLowerCase().contains(lowercaseQuery))
.toList();
}
// Filtrer les clients par type
List<ClientModel> getClientsByType(int type) {
return _clientBox.values.where((client) => client.fkType == type).toList();
}
// Filtrer les clients par région
List<ClientModel> getClientsByRegion(int regionId) {
return _clientBox.values
.where((client) => client.fkRegion == regionId)
.toList();
}
// Filtrer les clients actifs
List<ClientModel> getActiveClients() {
return _clientBox.values
.where((client) => client.chkActive == true)
.toList();
}
}

View File

@@ -0,0 +1,208 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/data/models/membre_model.dart';
class MembreRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<MembreModel> get _membreBox =>
Hive.box<MembreModel>(AppKeys.membresBoxName);
final ApiService _apiService;
bool _isLoading = false;
MembreRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
List<MembreModel> get membres => getAllMembres();
// 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.membresBoxName)) {
debugPrint('Ouverture de la boîte ${AppKeys.membresBoxName}...');
await Hive.openBox<MembreModel>(AppKeys.membresBoxName);
debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès');
}
} catch (e) {
debugPrint(
'Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e');
throw Exception(
'Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e');
}
}
// Récupérer tous les membres
List<MembreModel> getAllMembres() {
try {
return _membreBox.values.toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des membres: $e');
return [];
}
}
// Récupérer un membre par son ID
MembreModel? getMembreById(int id) {
try {
return _membreBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération du membre: $e');
return null;
}
}
// Créer ou mettre à jour un membre
Future<MembreModel> saveMembre(MembreModel membre) async {
await _ensureBoxIsOpen();
await _membreBox.put(membre.id, membre);
notifyListeners();
return membre;
}
// Supprimer un membre
Future<void> deleteMembre(int id) async {
await _ensureBoxIsOpen();
await _membreBox.delete(id);
notifyListeners();
}
// Récupérer les membres depuis l'API (uniquement pour l'interface admin)
Future<List<MembreModel>> fetchMembresFromApi() async {
_isLoading = true;
notifyListeners();
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
debugPrint(
'Pas de connexion Internet, utilisation des données locales');
return getAllMembres();
}
// Endpoint à adapter selon votre API
final response = await _apiService.get('/membres');
final List<dynamic> membresData = response.data['membres'];
// Vider la boîte avant d'ajouter les nouveaux membres
await _ensureBoxIsOpen();
await _membreBox.clear();
final List<MembreModel> membres = [];
for (var membreData in membresData) {
try {
final membre = MembreModel.fromJson(membreData);
await _membreBox.put(membre.id, membre);
membres.add(membre);
} catch (e) {
debugPrint('Erreur lors du traitement d\'un membre: $e');
continue;
}
}
notifyListeners();
return membres;
} catch (e) {
debugPrint(
'Erreur lors de la récupération des membres depuis l\'API: $e');
return getAllMembres();
} finally {
_isLoading = false;
notifyListeners();
}
}
// Créer un membre via l'API
Future<MembreModel?> createMembreViaApi(MembreModel membre) async {
_isLoading = true;
notifyListeners();
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
debugPrint('Pas de connexion Internet, impossible de créer le membre');
return null;
}
// Endpoint à adapter selon votre API
final response =
await _apiService.post('/membres', data: membre.toJson());
final membreData = response.data['membre'];
final newMembre = MembreModel.fromJson(membreData);
await saveMembre(newMembre);
return newMembre;
} catch (e) {
debugPrint('Erreur lors de la création du membre via l\'API: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Mettre à jour un membre via l'API
Future<MembreModel?> updateMembreViaApi(MembreModel membre) async {
_isLoading = true;
notifyListeners();
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
debugPrint(
'Pas de connexion Internet, impossible de mettre à jour le membre');
return null;
}
// Endpoint à adapter selon votre API
final response =
await _apiService.put('/membres/${membre.id}', data: membre.toJson());
final membreData = response.data['membre'];
final updatedMembre = MembreModel.fromJson(membreData);
await saveMembre(updatedMembre);
return updatedMembre;
} catch (e) {
debugPrint('Erreur lors de la mise à jour du membre via l\'API: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer un membre via l'API
Future<bool> deleteMembreViaApi(int id) async {
_isLoading = true;
notifyListeners();
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
debugPrint(
'Pas de connexion Internet, impossible de supprimer le membre');
return false;
}
// Endpoint à adapter selon votre API
await _apiService.delete('/membres/$id');
// Supprimer localement
await deleteMembre(id);
return true;
} catch (e) {
debugPrint('Erreur lors de la suppression du membre via l\'API: $e');
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
}

View File

@@ -0,0 +1,215 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
class OperationRepository extends ChangeNotifier {
// 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<OperationModel> get _operationBox {
_ensureBoxIsOpen();
return Hive.box<OperationModel>(AppKeys.operationsBoxName);
}
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
Future<void> _ensureBoxIsOpen() async {
final boxName = AppKeys.operationsBoxName;
if (!Hive.isBoxOpen(boxName)) {
debugPrint('Ouverture de la boîte $boxName dans OperationRepository...');
await Hive.openBox<OperationModel>(boxName);
}
}
final ApiService _apiService;
bool _isLoading = false;
OperationRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
List<OperationModel> get operations => getAllOperations();
// Récupérer toutes les opérations
List<OperationModel> getAllOperations() {
return _operationBox.values.toList();
}
// Récupérer une opération par son ID
OperationModel? getOperationById(int id) {
return _operationBox.get(id);
}
// Sauvegarder une opération
Future<void> saveOperation(OperationModel operation) async {
await _operationBox.put(operation.id, operation);
notifyListeners();
}
// Supprimer une opération
Future<void> deleteOperation(int id) async {
await _operationBox.delete(id);
notifyListeners();
}
// Créer ou mettre à jour des opérations à partir des données de l'API
Future<void> processOperationsFromApi(List<dynamic> operationsData) async {
_isLoading = true;
notifyListeners();
try {
for (var operationData in operationsData) {
final operationJson = operationData as Map<String, dynamic>;
final operationId = operationJson['id'] is String
? int.parse(operationJson['id'])
: operationJson['id'] as int;
// Vérifier si l'opération existe déjà
OperationModel? existingOperation = getOperationById(operationId);
if (existingOperation == null) {
// Créer une nouvelle opération
final newOperation = OperationModel.fromJson(operationJson);
await saveOperation(newOperation);
} else {
// Mettre à jour l'opération existante
final updatedOperation = existingOperation.copyWith(
name: operationJson['name'],
dateDebut: DateTime.parse(operationJson['date_deb']),
dateFin: DateTime.parse(operationJson['date_fin']),
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveOperation(updatedOperation);
}
}
} catch (e) {
debugPrint('Erreur lors du traitement des opérations: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
// Créer une opération
Future<bool> createOperation(String name, DateTime dateDebut, DateTime dateFin) async {
_isLoading = true;
notifyListeners();
try {
// Préparer les données pour l'API
final Map<String, dynamic> data = {
'name': name,
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
};
// Appeler l'API pour créer l'opération
final response = await _apiService.post('/operations', data: data);
if (response.statusCode == 201 || response.statusCode == 200) {
// Récupérer l'ID de la nouvelle opération
final operationId = response.data['id'] is String
? int.parse(response.data['id'])
: response.data['id'] as int;
// Créer l'opération localement
final newOperation = OperationModel(
id: operationId,
name: name,
dateDebut: dateDebut,
dateFin: dateFin,
lastSyncedAt: DateTime.now(),
isActive: true,
isSynced: true,
);
await saveOperation(newOperation);
return true;
}
return false;
} catch (e) {
debugPrint('Erreur lors de la création de l\'opération: $e');
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Mettre à jour une opération
Future<bool> updateOperation(int id, {String? name, DateTime? dateDebut, DateTime? dateFin, bool? isActive}) async {
_isLoading = true;
notifyListeners();
try {
// Récupérer l'opération existante
final existingOperation = getOperationById(id);
if (existingOperation == null) {
return false;
}
// Préparer les données pour l'API
final Map<String, dynamic> data = {
'id': id,
'name': name ?? existingOperation.name,
'date_deb': (dateDebut ?? existingOperation.dateDebut).toIso8601String().split('T')[0],
'date_fin': (dateFin ?? existingOperation.dateFin).toIso8601String().split('T')[0],
'is_active': isActive ?? existingOperation.isActive,
};
// Appeler l'API pour mettre à jour l'opération
final response = await _apiService.put('/operations/$id', data: data);
if (response.statusCode == 200) {
// Mettre à jour l'opération localement
final updatedOperation = existingOperation.copyWith(
name: name,
dateDebut: dateDebut,
dateFin: dateFin,
isActive: isActive,
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveOperation(updatedOperation);
return true;
}
return false;
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'opération: $e');
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer une opération via l'API
Future<bool> deleteOperationViaApi(int id) async {
_isLoading = true;
notifyListeners();
try {
// Appeler l'API pour supprimer l'opération
final response = await _apiService.delete('/operations/$id');
if (response.statusCode == 200 || response.statusCode == 204) {
// Supprimer l'opération localement
await deleteOperation(id);
return true;
}
return false;
} catch (e) {
debugPrint('Erreur lors de la suppression de l\'opération: $e');
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
}

View File

@@ -0,0 +1,502 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
class PassageRepository extends ChangeNotifier {
// 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<PassageModel>? _box;
Box<PassageModel> get _passageBox {
if (_box != null && _box!.isOpen) {
return _box!;
}
if (!Hive.isBoxOpen(AppKeys.passagesBoxName)) {
throw StateError(
'La boîte ${AppKeys.passagesBoxName} n\'est pas ouverte. Appelez _ensureBoxIsOpen() avant d\'accéder à la boîte.');
}
_box = Hive.box<PassageModel>(AppKeys.passagesBoxName);
return _box!;
}
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
Future<void> _ensureBoxIsOpen() async {
final boxName = AppKeys.passagesBoxName;
// Si nous avons déjà une référence à la boîte et qu'elle est ouverte, retourner
if (_box != null && _box!.isOpen) {
return;
}
// Si la boîte est déjà ouverte, récupérer la référence
if (Hive.isBoxOpen(boxName)) {
_box = Hive.box<PassageModel>(boxName);
debugPrint(
'PassageRepository: Boîte $boxName déjà ouverte, référence récupérée');
return;
}
// Sinon, ouvrir la boîte
try {
debugPrint('PassageRepository: Ouverture de la boîte $boxName...');
_box = await Hive.openBox<PassageModel>(boxName);
debugPrint('PassageRepository: Boîte $boxName ouverte avec succès');
} catch (e) {
debugPrint(
'PassageRepository: ERREUR lors de l\'ouverture de la boîte $boxName: $e');
rethrow; // Propager l'erreur pour permettre une gestion appropriée
}
}
final ApiService _apiService;
bool _isLoading = false;
PassageRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
List<PassageModel> get passages => getAllPassages();
// Récupérer tous les passages
List<PassageModel> getAllPassages() {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getAllPassages');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getAllPassages: $e');
});
return _passageBox.values.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getAllPassages: $e');
return []; // Retourner une liste vide en cas d'erreur
}
}
// Récupérer un passage par son ID
PassageModel? getPassageById(int id) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassageById');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassageById: $e');
});
return _passageBox.get(id);
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassageById: $e');
return null;
}
}
// Récupérer les passages par secteur
List<PassageModel> getPassagesBySector(int sectorId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesBySector');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesBySector: $e');
});
return _passageBox.values
.where((passage) => passage.fkSector == sectorId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesBySector: $e');
return [];
}
}
// Récupérer les passages par opération
List<PassageModel> getPassagesByOperation(int operationId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByOperation');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByOperation: $e');
});
return _passageBox.values
.where((passage) => passage.fkOperation == operationId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByOperation: $e');
return [];
}
}
// Récupérer les passages par type
List<PassageModel> getPassagesByType(int typeId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByType');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByType: $e');
});
return _passageBox.values
.where((passage) => passage.fkType == typeId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByType: $e');
return [];
}
}
// Récupérer les passages par type de règlement
List<PassageModel> getPassagesByPaymentType(int paymentTypeId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByPaymentType');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByPaymentType: $e');
});
return _passageBox.values
.where((passage) => passage.fkTypeReglement == paymentTypeId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByPaymentType: $e');
return [];
}
}
// Sauvegarder un passage
Future<void> savePassage(PassageModel passage) async {
await _passageBox.put(passage.id, passage);
notifyListeners();
}
// Supprimer un passage
Future<void> deletePassage(int id) async {
await _passageBox.delete(id);
notifyListeners();
}
// Traiter les passages reçus de l'API
Future<void> processPassagesFromApi(List<dynamic> passagesData) async {
_isLoading = true;
notifyListeners();
try {
for (var passageData in passagesData) {
final passageJson = passageData as Map<String, dynamic>;
final passageId = passageJson['id'] is String
? int.parse(passageJson['id'])
: passageJson['id'] as int;
// Vérifier si le passage existe déjà
PassageModel? existingPassage = getPassageById(passageId);
if (existingPassage == null) {
// Créer un nouveau passage
final newPassage = PassageModel.fromJson(passageJson);
await savePassage(newPassage);
} else {
// Mettre à jour le passage existant avec les nouvelles données
final updatedPassage = PassageModel.fromJson(passageJson).copyWith(
lastSyncedAt: DateTime.now(),
isActive: existingPassage.isActive,
isSynced: true,
);
await savePassage(updatedPassage);
}
}
} catch (e) {
debugPrint('Erreur lors du traitement des passages: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
// Créer un nouveau passage
Future<bool> createPassage({
required int fkOperation,
required int fkSector,
required int fkUser,
required int fkType,
required String fkAdresse,
required DateTime passedAt,
required String numero,
required String rue,
String rueBis = '',
required String ville,
String residence = '',
required int fkHabitat,
String appt = '',
String niveau = '',
required String gpsLat,
required String gpsLng,
String nomRecu = '',
String remarque = '',
required String montant,
required int fkTypeReglement,
String name = '',
String email = '',
String phone = '',
}) async {
_isLoading = true;
notifyListeners();
try {
// Préparer les données pour l'API
final Map<String, dynamic> data = {
'fk_operation': fkOperation,
'fk_sector': fkSector,
'fk_user': fkUser,
'fk_type': fkType,
'fk_adresse': fkAdresse,
'passed_at': passedAt.toIso8601String(),
'numero': numero,
'rue': rue,
'rue_bis': rueBis,
'ville': ville,
'residence': residence,
'fk_habitat': fkHabitat,
'appt': appt,
'niveau': niveau,
'gps_lat': gpsLat,
'gps_lng': gpsLng,
'nom_recu': nomRecu,
'remarque': remarque,
'montant': montant,
'fk_type_reglement': fkTypeReglement,
'name': name,
'email': email,
'phone': phone,
};
// Appeler l'API pour créer le passage
final response = await _apiService.post('/passages', data: data);
if (response.statusCode == 201 || response.statusCode == 200) {
// Récupérer l'ID du nouveau passage
final passageId = response.data['id'] is String
? int.parse(response.data['id'])
: response.data['id'] as int;
// Créer le modèle local
final newPassage = PassageModel(
id: passageId,
fkOperation: fkOperation,
fkSector: fkSector,
fkUser: fkUser,
fkType: fkType,
fkAdresse: fkAdresse,
passedAt: passedAt,
numero: numero,
rue: rue,
rueBis: rueBis,
ville: ville,
residence: residence,
fkHabitat: fkHabitat,
appt: appt,
niveau: niveau,
gpsLat: gpsLat,
gpsLng: gpsLng,
nomRecu: nomRecu,
remarque: remarque,
montant: montant,
fkTypeReglement: fkTypeReglement,
nbPassages: 1, // Par défaut pour un nouveau passage
name: name,
email: email,
phone: phone,
lastSyncedAt: DateTime.now(),
isActive: true,
isSynced: true,
);
await savePassage(newPassage);
return true;
} else {
debugPrint(
'Erreur lors de la création du passage: ${response.statusMessage}');
return false;
}
} catch (e) {
debugPrint('Erreur lors de la création du passage: $e');
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Mettre à jour un passage existant
Future<bool> updatePassage(PassageModel passage) async {
_isLoading = true;
notifyListeners();
try {
// Préparer les données pour l'API
final Map<String, dynamic> data = passage.toJson();
// Appeler l'API pour mettre à jour le passage
final response =
await _apiService.put('/passages/${passage.id}', data: data);
if (response.statusCode == 200) {
// Mettre à jour le modèle local
final updatedPassage = passage.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await savePassage(updatedPassage);
return true;
} else {
debugPrint(
'Erreur lors de la mise à jour du passage: ${response.statusMessage}');
// Marquer comme non synchronisé mais sauvegarder localement
final updatedPassage = passage.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: false,
);
await savePassage(updatedPassage);
return false;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour du passage: $e');
// Marquer comme non synchronisé mais sauvegarder localement
final updatedPassage = passage.copyWith(
lastSyncedAt: DateTime.now(),
isSynced: false,
);
await savePassage(updatedPassage);
return false;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Synchroniser tous les passages non synchronisés
Future<void> syncUnsyncedPassages() async {
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
return;
}
final unsyncedPassages =
_passageBox.values.where((passage) => !passage.isSynced).toList();
if (unsyncedPassages.isEmpty) {
return;
}
_isLoading = true;
notifyListeners();
for (final passage in unsyncedPassages) {
try {
if (passage.id < 0) {
// Nouveau passage créé localement, à envoyer à l'API
await createPassage(
fkOperation: passage.fkOperation,
fkSector: passage.fkSector,
fkUser: passage.fkUser,
fkType: passage.fkType,
fkAdresse: passage.fkAdresse,
passedAt: passage.passedAt,
numero: passage.numero,
rue: passage.rue,
rueBis: passage.rueBis,
ville: passage.ville,
residence: passage.residence,
fkHabitat: passage.fkHabitat,
appt: passage.appt,
niveau: passage.niveau,
gpsLat: passage.gpsLat,
gpsLng: passage.gpsLng,
nomRecu: passage.nomRecu,
remarque: passage.remarque,
montant: passage.montant,
fkTypeReglement: passage.fkTypeReglement,
name: passage.name,
email: passage.email,
phone: passage.phone,
);
// Supprimer l'ancien passage avec ID temporaire
await deletePassage(passage.id);
} else {
// Passage existant à mettre à jour
await updatePassage(passage);
}
} catch (e) {
debugPrint(
'Erreur lors de la synchronisation du passage ${passage.id}: $e');
}
}
} catch (e) {
debugPrint('Erreur lors de la synchronisation des passages: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
// Récupérer les passages depuis l'API
Future<void> fetchPassages() async {
try {
final hasConnection = await _apiService.hasInternetConnection();
if (!hasConnection) {
return;
}
_isLoading = true;
notifyListeners();
final response = await _apiService.get('/passages');
if (response.statusCode == 200) {
final List<dynamic> passagesData = response.data;
await processPassagesFromApi(passagesData);
}
} catch (e) {
debugPrint('Erreur lors de la récupération des passages: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
// Vider tous les passages
Future<void> clearAllPassages() async {
await _passageBox.clear();
notifyListeners();
}
}

View File

@@ -0,0 +1,85 @@
import 'package:flutter/foundation.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:hive_flutter/hive_flutter.dart';
class RegionRepository extends ChangeNotifier {
late Box<RegionModel> _regionBox;
List<RegionModel> _regions = [];
bool _isLoaded = false;
// Getter pour les régions
List<RegionModel> get regions => _regions;
bool get isLoaded => _isLoaded;
// Initialisation du repository
Future<void> init() async {
if (!Hive.isBoxOpen(AppKeys.regionsBoxName)) {
_regionBox = await Hive.openBox<RegionModel>(AppKeys.regionsBoxName);
} else {
_regionBox = Hive.box<RegionModel>(AppKeys.regionsBoxName);
}
_loadRegions();
}
// Chargement des régions depuis la boîte Hive
void _loadRegions() {
_regions = _regionBox.values.toList();
_isLoaded = true;
notifyListeners();
}
// Mise à jour des régions depuis l'API
Future<void> updateRegionsFromApi(List<dynamic> regionsData) async {
await _regionBox.clear();
for (var regionData in regionsData) {
final region = RegionModel.fromJson(regionData);
await _regionBox.put(region.id, region);
}
_loadRegions();
}
// Récupérer une région par son ID
RegionModel? getRegionById(int id) {
return _regionBox.get(id);
}
// Récupérer une région par son code postal (2 premiers chiffres)
RegionModel? getRegionByPostalCode(String postalCode) {
if (postalCode.length < 2) return null;
final departement = postalCode.substring(0, 2);
for (var region in _regions) {
if (region.departements != null &&
region.departements!.split(',').contains(departement)) {
return region;
}
}
return null;
}
// Récupérer toutes les régions actives
List<RegionModel> getActiveRegions() {
return _regions.where((region) => region.chkActive).toList();
}
// Convertir les régions en format pour le dropdown
List<Map<String, dynamic>> getRegionsForDropdown() {
return _regions
.where((region) => region.chkActive)
.map((region) => {
'id': region.id,
'name': region.libelle,
})
.toList();
}
// Fermeture de la boîte Hive
Future<void> close() async {
await _regionBox.close();
}
}

View File

@@ -0,0 +1,149 @@
import 'package:hive/hive.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
class SectorRepository {
final ApiService _apiService;
SectorRepository(this._apiService);
// 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<SectorModel> get _sectorsBox {
_ensureBoxIsOpen();
return Hive.box<SectorModel>(AppKeys.sectorsBoxName);
}
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
Future<void> _ensureBoxIsOpen() async {
final boxName = AppKeys.sectorsBoxName;
if (!Hive.isBoxOpen(boxName)) {
print('Ouverture de la boîte $boxName dans SectorRepository...');
await Hive.openBox<SectorModel>(boxName);
}
}
// Récupérer tous les secteurs depuis la base de données locale
List<SectorModel> getAllSectors() {
return _sectorsBox.values.toList();
}
// Récupérer un secteur par son ID
SectorModel? getSectorById(int id) {
try {
return _sectorsBox.values.firstWhere(
(sector) => sector.id == id,
);
} catch (e) {
return null;
}
}
// Sauvegarder les secteurs dans la base de données locale
Future<void> saveSectors(List<SectorModel> sectors) async {
// Vider la box avant d'ajouter les nouveaux secteurs
await _sectorsBox.clear();
// Ajouter les nouveaux secteurs
for (final sector in sectors) {
await _sectorsBox.put(sector.id, sector);
}
}
// Ajouter ou mettre à jour un secteur
Future<void> saveSector(SectorModel sector) async {
await _sectorsBox.put(sector.id, sector);
}
// Supprimer un secteur
Future<void> deleteSector(int id) async {
await _sectorsBox.delete(id);
}
// Récupérer les secteurs depuis l'API
Future<List<SectorModel>> fetchSectorsFromApi() async {
try {
final response = await _apiService.get(AppKeys.sectorsEndpoint);
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
if (responseData['status'] == 'success' && responseData['sectors'] != null) {
final List<dynamic> sectorsJson = responseData['sectors'];
final List<SectorModel> sectors = sectorsJson
.map((json) => SectorModel.fromJson(json))
.toList();
// Sauvegarder les secteurs localement
await saveSectors(sectors);
return sectors;
}
return [];
} catch (e) {
// En cas d'erreur, retourner les secteurs locaux
return getAllSectors();
}
}
// Créer un nouveau secteur via l'API
Future<SectorModel?> createSector(SectorModel sector) async {
try {
final response = await _apiService.post(
AppKeys.sectorsEndpoint,
data: sector.toJson(),
);
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
if (responseData['status'] == 'success' && responseData['sector'] != null) {
final SectorModel newSector = SectorModel.fromJson(responseData['sector']);
await saveSector(newSector);
return newSector;
}
return null;
} catch (e) {
return null;
}
}
// Mettre à jour un secteur via l'API
Future<SectorModel?> updateSector(SectorModel sector) async {
try {
final response = await _apiService.put(
'${AppKeys.sectorsEndpoint}/${sector.id}',
data: sector.toJson(),
);
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
if (responseData['status'] == 'success' && responseData['sector'] != null) {
final SectorModel updatedSector = SectorModel.fromJson(responseData['sector']);
await saveSector(updatedSector);
return updatedSector;
}
return null;
} catch (e) {
return null;
}
}
// Supprimer un secteur via l'API
Future<bool> deleteSectorFromApi(int id) async {
try {
final response = await _apiService.delete(
'${AppKeys.sectorsEndpoint}/$id',
);
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
if (responseData['status'] == 'success') {
await deleteSector(id);
return true;
}
return false;
} catch (e) {
return false;
}
}
}

File diff suppressed because it is too large Load Diff