refactor: Remplacement de l'accès direct à Firestore par des Cloud Functions

Migration complète du backend pour utiliser des Cloud Functions comme couche API sécurisée, en remplacement des appels directs à Firestore depuis le client.

**Backend (Cloud Functions):**
- **Centralisation CORS :** Ajout d'un middleware `withCors` et d'une configuration `httpOptions` pour gérer uniformément les en-têtes CORS et les requêtes `OPTIONS` sur toutes les fonctions.
- **Nouvelles Fonctions de Lecture (GET) :**
    - `getEquipments`, `getContainers`, `getEvents`, `getUsers`, `getOptions`, `getEventTypes`, `getRoles`, `getMaintenances`, `getAlerts`.
    - Ces fonctions gèrent les permissions côté serveur, masquant les données sensibles (ex: prix des équipements) pour les utilisateurs non-autorisés.
    - `getEvents` retourne également une map des utilisateurs (`usersMap`) pour optimiser le chargement des données de la main d'œuvre.
- **Nouvelle Fonction de Recherche :**
    - `getContainersByEquipment` : Endpoint dédié pour trouver efficacement tous les containers qui contiennent un équipement spécifique.
- **Nouvelles Fonctions d'Écriture (CRUD) :**
    - Fonctions CRUD complètes pour `eventTypes` (`create`, `update`, `delete`), incluant la validation (unicité du nom, vérification des événements futurs avant suppression).
- **Mise à jour de Fonctions Existantes :**
    - Toutes les fonctions CRUD existantes (`create/update/deleteEquipment`, `create/update/deleteContainer`, etc.) sont wrappées avec le nouveau gestionnaire CORS.

**Frontend (Flutter):**
- **Introduction du `DataService` :** Nouveau service centralisant tous les appels aux Cloud Functions, servant d'intermédiaire entre l'UI/Providers et l'API.
- **Refactorisation des Providers :**
    - `EquipmentProvider`, `ContainerProvider`, `EventProvider`, `UsersProvider`, `MaintenanceProvider` et `AlertProvider` ont été refactorisés pour utiliser le `DataService` au lieu d'accéder directement à Firestore.
    - Les `Stream` Firestore sont remplacés par des chargements de données via des méthodes `Future` (`loadEquipments`, `loadEvents`, etc.).
- **Gestion des Relations Équipement-Container :**
    - Le modèle `EquipmentModel` ne stocke plus `parentBoxIds`.
    - La relation est maintenant gérée par le `ContainerModel` qui contient `equipmentIds`.
    - Le `ContainerEquipmentService` est introduit pour utiliser la nouvelle fonction `getContainersByEquipment`.
    - L'affichage des boîtes parentes (`EquipmentParentContainers`) et le formulaire d'équipement (`EquipmentFormPage`) ont été mis à jour pour refléter ce nouveau modèle de données, synchronisant les ajouts/suppressions d'équipements dans les containers.
- **Amélioration de l'UI :**
    - Nouveau widget `ParentBoxesSelector` pour une sélection améliorée et visuelle des boîtes parentes dans le formulaire d'équipement.
    - Refonte visuelle de `EquipmentParentContainers` pour une meilleure présentation.
This commit is contained in:
ElPoyo
2026-01-12 20:38:46 +01:00
parent 13a890606d
commit f38d75362c
46 changed files with 3367 additions and 1510 deletions

View File

@@ -0,0 +1,52 @@
import 'package:em2rp/models/container_model.dart';
import 'package:em2rp/services/data_service.dart';
import 'package:em2rp/services/api_service.dart';
/// Service pour gérer la relation entre containers et équipements
/// Utilise le principe : seul le container stocke la référence aux équipements
class ContainerEquipmentService {
final DataService _dataService = DataService(apiService);
/// Récupère tous les containers contenant un équipement spécifique
/// Utilise une Cloud Function avec authentification et permissions
Future<List<ContainerModel>> getContainersByEquipment(String equipmentId) async {
try {
final containersData = await _dataService.getContainersByEquipment(equipmentId);
return containersData.map((data) {
// L'ID est dans le champ 'id' retourné par la fonction
final id = data['id'] as String;
return ContainerModel.fromMap(data, id);
}).toList();
} catch (e) {
print('[ContainerEquipmentService] Error getting containers for equipment $equipmentId: $e');
rethrow;
}
}
/// Vérifie si un équipement est dans au moins un container
Future<bool> isEquipmentInAnyContainer(String equipmentId) async {
try {
final containers = await getContainersByEquipment(equipmentId);
return containers.isNotEmpty;
} catch (e) {
print('[ContainerEquipmentService] Error checking if equipment is in container: $e');
return false;
}
}
/// Récupère le nombre de containers contenant un équipement
Future<int> getContainerCountForEquipment(String equipmentId) async {
try {
final containers = await getContainersByEquipment(equipmentId);
return containers.length;
} catch (e) {
print('[ContainerEquipmentService] Error getting container count: $e');
return 0;
}
}
}
/// Instance globale singleton
final containerEquipmentService = ContainerEquipmentService();

View File

@@ -0,0 +1,339 @@
import 'package:em2rp/services/api_service.dart';
/// Service générique pour les opérations de lecture de données via Cloud Functions
class DataService {
final ApiService _apiService;
DataService(this._apiService);
/// Récupère toutes les options
Future<List<Map<String, dynamic>>> getOptions() async {
try {
final result = await _apiService.call('getOptions', {});
final options = result['options'] as List<dynamic>?;
if (options == null) return [];
return options.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des options: $e');
}
}
/// Récupère tous les types d'événements
Future<List<Map<String, dynamic>>> getEventTypes() async {
try {
final result = await _apiService.call('getEventTypes', {});
final eventTypes = result['eventTypes'] as List<dynamic>?;
if (eventTypes == null) return [];
return eventTypes.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des types d\'événements: $e');
}
}
/// Récupère tous les rôles
Future<List<Map<String, dynamic>>> getRoles() async {
try {
final result = await _apiService.call('getRoles', {});
final roles = result['roles'] as List<dynamic>?;
if (roles == null) return [];
return roles.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des rôles: $e');
}
}
/// Met à jour les équipements d'un événement
Future<void> updateEventEquipment({
required String eventId,
List<Map<String, dynamic>>? assignedEquipment,
String? preparationStatus,
String? loadingStatus,
String? unloadingStatus,
String? returnStatus,
}) async {
try {
final data = <String, dynamic>{'eventId': eventId};
if (assignedEquipment != null) data['assignedEquipment'] = assignedEquipment;
if (preparationStatus != null) data['preparationStatus'] = preparationStatus;
if (loadingStatus != null) data['loadingStatus'] = loadingStatus;
if (unloadingStatus != null) data['unloadingStatus'] = unloadingStatus;
if (returnStatus != null) data['returnStatus'] = returnStatus;
await _apiService.call('updateEventEquipment', data);
} catch (e) {
throw Exception('Erreur lors de la mise à jour des équipements de l\'événement: $e');
}
}
/// Met à jour uniquement le statut d'un équipement
Future<void> updateEquipmentStatusOnly({
required String equipmentId,
String? status,
int? availableQuantity,
}) async {
try {
final data = <String, dynamic>{'equipmentId': equipmentId};
if (status != null) data['status'] = status;
if (availableQuantity != null) data['availableQuantity'] = availableQuantity;
await _apiService.call('updateEquipmentStatusOnly', data);
} catch (e) {
throw Exception('Erreur lors de la mise à jour du statut de l\'équipement: $e');
}
}
/// Met à jour un utilisateur
Future<void> updateUser(String userId, Map<String, dynamic> data) async {
try {
final requestData = {'userId': userId, ...data};
await _apiService.call('updateUser', requestData);
} catch (e) {
throw Exception('Erreur lors de la mise à jour de l\'utilisateur: $e');
}
}
/// Met à jour un événement
Future<void> updateEvent(String eventId, Map<String, dynamic> data) async {
try {
final requestData = {'eventId': eventId, ...data};
await _apiService.call('updateEvent', requestData);
} catch (e) {
throw Exception('Erreur lors de la mise à jour de l\'événement: $e');
}
}
/// Supprime un événement
Future<void> deleteEvent(String eventId) async {
try {
await _apiService.call('deleteEvent', {'eventId': eventId});
} catch (e) {
throw Exception('Erreur lors de la suppression de l\'événement: $e');
}
}
/// Crée un nouvel équipement
Future<void> createEquipment(String equipmentId, Map<String, dynamic> data) async {
try {
final requestData = {'equipmentId': equipmentId, ...data};
await _apiService.call('createEquipment', requestData);
} catch (e) {
throw Exception('Erreur lors de la création de l\'équipement: $e');
}
}
/// Met à jour un équipement
Future<void> updateEquipment(String equipmentId, Map<String, dynamic> data) async {
try {
final requestData = {'equipmentId': equipmentId, ...data};
await _apiService.call('updateEquipment', requestData);
} catch (e) {
throw Exception('Erreur lors de la mise à jour de l\'équipement: $e');
}
}
/// Supprime un équipement
Future<void> deleteEquipment(String equipmentId) async {
try {
await _apiService.call('deleteEquipment', {'equipmentId': equipmentId});
} catch (e) {
throw Exception('Erreur lors de la suppression de l\'équipement: $e');
}
}
/// Récupère les événements utilisant un type d'événement donné
Future<List<Map<String, dynamic>>> getEventsByEventType(String eventTypeId) async {
try {
final result = await _apiService.call('getEventsByEventType', {'eventTypeId': eventTypeId});
final events = result['events'] as List<dynamic>?;
if (events == null) return [];
return events.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des événements: $e');
}
}
/// Crée un type d'événement
Future<String> createEventType({
required String name,
required double defaultPrice,
}) async {
try {
final result = await _apiService.call('createEventType', {
'name': name,
'defaultPrice': defaultPrice,
});
return result['id'] as String;
} catch (e) {
throw Exception('Erreur lors de la création du type d\'événement: $e');
}
}
/// Met à jour un type d'événement
Future<void> updateEventType({
required String eventTypeId,
String? name,
double? defaultPrice,
}) async {
try {
final data = <String, dynamic>{'eventTypeId': eventTypeId};
if (name != null) data['name'] = name;
if (defaultPrice != null) data['defaultPrice'] = defaultPrice;
await _apiService.call('updateEventType', data);
} catch (e) {
throw Exception('Erreur lors de la mise à jour du type d\'événement: $e');
}
}
/// Supprime un type d'événement
Future<void> deleteEventType(String eventTypeId) async {
try {
await _apiService.call('deleteEventType', {'eventTypeId': eventTypeId});
} catch (e) {
throw Exception('Erreur lors de la suppression du type d\'événement: $e');
}
}
/// Crée une option
Future<String> createOption(String code, Map<String, dynamic> data) async {
try {
final requestData = {'code': code, ...data};
final result = await _apiService.call('createOption', requestData);
return result['id'] as String? ?? code;
} catch (e) {
throw Exception('Erreur lors de la création de l\'option: $e');
}
}
/// Met à jour une option
Future<void> updateOption(String optionId, Map<String, dynamic> data) async {
try {
final requestData = {'optionId': optionId, ...data};
await _apiService.call('updateOption', requestData);
} catch (e) {
throw Exception('Erreur lors de la mise à jour de l\'option: $e');
}
}
/// Supprime une option
Future<void> deleteOption(String optionId) async {
try {
await _apiService.call('deleteOption', {'optionId': optionId});
} catch (e) {
throw Exception('Erreur lors de la suppression de l\'option: $e');
}
}
// ============================================================================
// LECTURE DES DONNÉES (avec permissions côté serveur)
// ============================================================================
/// Récupère tous les événements (filtrés selon permissions)
/// Retourne { events: List<Map>, users: Map<String, Map> }
Future<Map<String, dynamic>> getEvents({String? userId}) async {
try {
final data = <String, dynamic>{};
if (userId != null) data['userId'] = userId;
final result = await _apiService.call('getEvents', data);
// Extraire events et users
final events = result['events'] as List<dynamic>? ?? [];
final users = result['users'] as Map<String, dynamic>? ?? {};
return {
'events': events.map((e) => e as Map<String, dynamic>).toList(),
'users': users,
};
} catch (e) {
throw Exception('Erreur lors de la récupération des événements: $e');
}
}
/// Récupère tous les équipements (avec masquage des prix selon permissions)
Future<List<Map<String, dynamic>>> getEquipments() async {
try {
print('[DataService] Calling getEquipments API...');
final result = await _apiService.call('getEquipments', {});
print('[DataService] API call successful, parsing result...');
final equipments = result['equipments'] as List<dynamic>?;
if (equipments == null) {
print('[DataService] No equipments in result');
return [];
}
print('[DataService] Found ${equipments.length} equipments');
return equipments.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
print('[DataService] Error getting equipments: $e');
throw Exception('Erreur lors de la récupération des équipements: $e');
}
}
/// Récupère tous les conteneurs
Future<List<Map<String, dynamic>>> getContainers() async {
try {
final result = await _apiService.call('getContainers', {});
final containers = result['containers'] as List<dynamic>?;
if (containers == null) return [];
return containers.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des conteneurs: $e');
}
}
/// Récupère les maintenances (optionnellement filtrées par équipement)
Future<List<Map<String, dynamic>>> getMaintenances({String? equipmentId}) async {
try {
final data = <String, dynamic>{};
if (equipmentId != null) data['equipmentId'] = equipmentId;
final result = await _apiService.call('getMaintenances', data);
final maintenances = result['maintenances'] as List<dynamic>?;
if (maintenances == null) return [];
return maintenances.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des maintenances: $e');
}
}
/// Récupère les alertes
Future<List<Map<String, dynamic>>> getAlerts() async {
try {
final result = await _apiService.call('getAlerts', {});
final alerts = result['alerts'] as List<dynamic>?;
if (alerts == null) return [];
return alerts.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des alertes: $e');
}
}
/// Récupère les utilisateurs (filtrés selon permissions)
Future<List<Map<String, dynamic>>> getUsers() async {
try {
final result = await _apiService.call('getUsers', {});
final users = result['users'] as List<dynamic>?;
if (users == null) return [];
return users.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des utilisateurs: $e');
}
}
/// Récupère les containers contenant un équipement spécifique
Future<List<Map<String, dynamic>>> getContainersByEquipment(String equipmentId) async {
try {
final result = await _apiService.call('getContainersByEquipment', {
'equipmentId': equipmentId,
});
final containers = result['containers'] as List<dynamic>?;
if (containers == null) return [];
return containers.map((e) => e as Map<String, dynamic>).toList();
} catch (e) {
throw Exception('Erreur lors de la récupération des containers pour l\'équipement: $e');
}
}
}

View File

@@ -1,12 +1,15 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:em2rp/models/equipment_model.dart';
import 'package:em2rp/models/container_model.dart';
import 'package:em2rp/models/alert_model.dart';
import 'package:em2rp/models/maintenance_model.dart';
import 'package:em2rp/services/api_service.dart';
import 'package:em2rp/services/data_service.dart';
class EquipmentService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final ApiService _apiService = apiService;
final DataService _dataService = DataService(apiService);
// Collection references (utilisées seulement pour les lectures)
CollectionReference get _equipmentCollection => _firestore.collection('equipments');
@@ -134,7 +137,7 @@ class EquipmentService {
.get();
for (var eventDoc in eventsQuery.docs) {
final eventData = eventDoc.data() as Map<String, dynamic>;
final eventData = eventDoc.data();
final assignedEquipmentRaw = eventData['assignedEquipment'] ?? [];
if (assignedEquipmentRaw is List) {
@@ -170,7 +173,7 @@ class EquipmentService {
for (var doc in equipmentQuery.docs) {
final equipment = EquipmentModel.fromMap(
doc.data() as Map<String, dynamic>,
doc.data(),
doc.id,
);
@@ -230,7 +233,7 @@ class EquipmentService {
for (var doc in equipmentQuery.docs) {
final equipment = EquipmentModel.fromMap(
doc.data() as Map<String, dynamic>,
doc.data(),
doc.id,
);
@@ -285,7 +288,7 @@ class EquipmentService {
final models = <String>{};
for (var doc in equipmentQuery.docs) {
final data = doc.data() as Map<String, dynamic>;
final data = doc.data();
final model = data['model'] as String?;
if (model != null && model.isNotEmpty) {
models.add(model);
@@ -306,7 +309,7 @@ class EquipmentService {
final brands = <String>{};
for (var doc in equipmentQuery.docs) {
final data = doc.data() as Map<String, dynamic>;
final data = doc.data();
final brand = data['brand'] as String?;
if (brand != null && brand.isNotEmpty) {
brands.add(brand);
@@ -329,7 +332,7 @@ class EquipmentService {
final models = <String>{};
for (var doc in equipmentQuery.docs) {
final data = doc.data() as Map<String, dynamic>;
final data = doc.data();
final model = data['model'] as String?;
if (model != null && model.isNotEmpty) {
models.add(model);
@@ -354,26 +357,16 @@ class EquipmentService {
}
}
/// Récupérer toutes les boîtes (équipements qui peuvent contenir d'autres équipements)
Future<List<EquipmentModel>> getBoxes() async {
/// Récupérer toutes les boîtes/containers disponibles
Future<List<ContainerModel>> getBoxes() async {
try {
// Les boîtes sont généralement des équipements de catégorie "structure" ou "other"
// On pourrait aussi ajouter un champ spécifique "isBox" dans le modèle
final equipmentQuery = await _firestore.collection('equipments')
.where('category', whereIn: [
equipmentCategoryToString(EquipmentCategory.structure),
equipmentCategoryToString(EquipmentCategory.other),
])
.get();
final containersData = await _dataService.getContainers();
final boxes = <EquipmentModel>[];
for (var doc in equipmentQuery.docs) {
final equipment = EquipmentModel.fromMap(
doc.data() as Map<String, dynamic>,
doc.id,
);
// On pourrait ajouter un filtre supplémentaire ici si besoin
boxes.add(equipment);
final boxes = <ContainerModel>[];
for (var data in containersData) {
final id = data['id'] as String;
final container = ContainerModel.fromMap(data, id);
boxes.add(container);
}
return boxes;
@@ -401,7 +394,7 @@ class EquipmentService {
for (var doc in query.docs) {
equipments.add(
EquipmentModel.fromMap(
doc.data() as Map<String, dynamic>,
doc.data(),
doc.id,
),
);

View File

@@ -1,4 +1,3 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:file_picker/file_picker.dart';
@@ -8,32 +7,34 @@ import 'package:em2rp/models/event_model.dart';
import 'package:em2rp/models/event_type_model.dart';
import 'package:em2rp/models/user_model.dart';
import 'package:em2rp/services/api_service.dart';
import 'package:em2rp/services/data_service.dart';
import 'dart:developer' as developer;
class EventFormService {
static final ApiService _apiService = apiService;
static final DataService _dataService = DataService(FirebaseFunctionsApiService());
// ============================================================================
// READ Operations - Utilise Firestore (peut rester en lecture directe)
// READ Operations - Utilise l'API (sécurisé avec permissions côté serveur)
// ============================================================================
static Future<List<EventTypeModel>> fetchEventTypes() async {
developer.log('Fetching event types from Firestore...', name: 'EventFormService');
developer.log('Fetching event types via API...', name: 'EventFormService');
try {
final snapshot = await FirebaseFirestore.instance.collection('eventTypes').get();
final eventTypes = snapshot.docs.map((doc) => EventTypeModel.fromMap(doc.data(), doc.id)).toList();
final eventTypesData = await _dataService.getEventTypes();
final eventTypes = eventTypesData.map((data) => EventTypeModel.fromMap(data, data['id'] as String)).toList();
developer.log('${eventTypes.length} event types loaded.', name: 'EventFormService');
return eventTypes;
} catch (e, s) {
developer.log('Error fetching event types', name: 'EventFormService', error: e, stackTrace: s);
throw Exception("Could not load event types. Please check Firestore permissions.");
throw Exception("Could not load event types. Please check permissions.");
}
}
static Future<List<UserModel>> fetchUsers() async {
try {
final snapshot = await FirebaseFirestore.instance.collection('users').get();
return snapshot.docs.map((doc) => UserModel.fromMap(doc.data(), doc.id)).toList();
final usersData = await _dataService.getUsers();
return usersData.map((data) => UserModel.fromMap(data, data['id'] as String)).toList();
} catch (e) {
developer.log('Error fetching users', name: 'EventFormService', error: e);
throw Exception("Could not load users.");
@@ -171,9 +172,15 @@ class EventFormService {
}
static Future<void> updateEventDocuments(String eventId, List<Map<String, String>> documents) async {
await FirebaseFirestore.instance
.collection('events')
.doc(eventId)
.update({'documents': documents});
// Utiliser l'API pour mettre à jour les documents
try {
await _apiService.call('updateEvent', {
'eventId': eventId,
'documents': documents,
});
} catch (e) {
developer.log('Error updating event documents', name: 'EventFormService', error: e);
throw Exception("Could not update event documents.");
}
}
}

View File

@@ -66,19 +66,30 @@ END:VCALENDAR''';
}
/// Récupère les détails de la main d'œuvre
static Future<List<String>> _getWorkforceDetails(List<DocumentReference> workforce) async {
static Future<List<String>> _getWorkforceDetails(List<dynamic> workforce) async {
final List<String> workforceNames = [];
for (final ref in workforce) {
try {
final doc = await ref.get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>?;
if (data != null) {
final firstName = data['firstName'] ?? '';
final lastName = data['lastName'] ?? '';
if (firstName.isNotEmpty || lastName.isNotEmpty) {
workforceNames.add('$firstName $lastName'.trim());
DocumentReference? docRef;
// Gérer String (UID) ou DocumentReference
if (ref is String) {
docRef = FirebaseFirestore.instance.collection('users').doc(ref);
} else if (ref is DocumentReference) {
docRef = ref;
}
if (docRef != null) {
final doc = await docRef.get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>?;
if (data != null) {
final firstName = data['firstName'] ?? '';
final lastName = data['lastName'] ?? '';
if (firstName.isNotEmpty || lastName.isNotEmpty) {
workforceNames.add('$firstName $lastName'.trim());
}
}
}
}

View File

@@ -1,40 +1,48 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../models/user_model.dart';
import 'package:em2rp/services/data_service.dart';
import 'package:em2rp/services/api_service.dart';
/// @deprecated Ce service est obsolète. Utilisez UsersProvider avec DataService à la place.
/// Ce service reste pour compatibilité mais toutes les opérations passent par l'API.
class UserService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final DataService _dataService = DataService(FirebaseFunctionsApiService());
/// @deprecated Utilisez UsersProvider.fetchUsers() à la place
Future<List<UserModel>> fetchUsers() async {
try {
final snapshot = await _firestore.collection('users').get();
return snapshot.docs
.map((doc) => UserModel.fromMap(doc.data(), doc.id))
.toList();
final usersData = await _dataService.getUsers();
return usersData.map((data) => UserModel.fromMap(data, data['id'] as String)).toList();
} catch (e) {
print("Erreur: $e");
return [];
}
}
/// @deprecated Utilisez DataService.updateUser() à la place
Future<void> updateUser(UserModel user) async {
try {
await _firestore.collection('users').doc(user.uid).update(user.toMap());
await _dataService.updateUser(user.uid, user.toMap());
} catch (e) {
print("Erreur mise à jour: $e");
}
}
/// @deprecated Utilisez API deleteUser à la place
Future<void> deleteUser(String uid) async {
try {
await _firestore.collection('users').doc(uid).delete();
// TODO: Créer une Cloud Function deleteUser
print("Suppression d'utilisateur non implémentée via API");
} catch (e) {
print("Erreur suppression: $e");
}
}
/// Firebase Auth reste OK (pas Firestore)
Future<void> resetPassword(String email) async {
try {
// Firebase Auth est OK, ce n'est pas Firestore
await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
print("Email de réinitialisation envoyé à $email");
} catch (e) {