petitspas/frontend/lib/services/user_service.dart
Julien Martin 4985726bc6 fix(#131): polish fiche AM — champs pro, places, vigilance et photo.
Fiche pro entièrement éditable, places déclarées en lecture seule avec alerte rouge, icône vigilance sur la liste AM, et cadrage photo aligné sur le wizard validation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:37:21 +02:00

893 lines
29 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/models/enfant_admin_model.dart';
import 'package:p_tits_pas/models/parent_model.dart';
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
import 'package:p_tits_pas/models/pending_family.dart';
import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/services/api/api_config.dart';
import 'package:p_tits_pas/services/api/tokenService.dart';
class DocumentActifInfo {
final String id;
final String type;
final int version;
final String url;
const DocumentActifInfo({
required this.id,
required this.type,
required this.version,
required this.url,
});
factory DocumentActifInfo.fromJson(Map<String, dynamic> json) {
return DocumentActifInfo(
id: (json['id'] ?? '').toString(),
type: (json['type'] ?? '').toString(),
version: int.tryParse((json['version'] ?? '').toString()) ?? 0,
url: (json['url'] ?? '').toString(),
);
}
}
class DocumentsActifsInfo {
final DocumentActifInfo cgu;
final DocumentActifInfo privacy;
const DocumentsActifsInfo({
required this.cgu,
required this.privacy,
});
}
class UserService {
static Future<Map<String, String>> _headers() async {
final token = await TokenService.getToken();
return token != null
? ApiConfig.authHeaders(token)
: Map<String, String>.from(ApiConfig.headers);
}
static String? _toStr(dynamic v) {
if (v == null) return null;
if (v is String) return v;
return v.toString();
}
static String _errMessage(dynamic err) {
if (err == null) return 'Erreur inconnue';
if (err is String) return err;
if (err is Map) {
final m = err['message'];
if (m is String) return m;
if (m is Map && m['message'] is String) return m['message'] as String;
if (m != null) return _toStr(m) ?? 'Erreur inconnue';
}
return _toStr(err) ?? 'Erreur inconnue';
}
/// Documents légaux actifs (CGU + privacy). GET /documents-legaux/actifs.
static Future<DocumentsActifsInfo> getDocumentsLegauxActifs() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}/documents-legaux/actifs'),
headers: ApiConfig.headers,
);
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception(
'Erreur chargement documents légaux (${response.statusCode})',
);
}
}
try {
final decoded = jsonDecode(response.body);
if (decoded is! Map<String, dynamic>) {
throw const FormatException('Réponse invalide');
}
final cgu = decoded['cgu'];
final privacy = decoded['privacy'];
if (cgu is! Map || privacy is! Map) {
throw const FormatException('Documents actifs manquants');
}
return DocumentsActifsInfo(
cgu: DocumentActifInfo.fromJson(Map<String, dynamic>.from(cgu)),
privacy: DocumentActifInfo.fromJson(Map<String, dynamic>.from(privacy)),
);
} catch (e) {
if (e is FormatException) rethrow;
throw Exception(
'Réponse invalide (documents légaux): ${e is Exception ? e.toString() : "format inattendu"}',
);
}
}
/// Utilisateurs en attente de validation (GET /users/pending). Ticket #107.
static Future<List<AppUser>> getPendingUsers({String? role}) async {
final query = role != null ? '?role=$role' : '';
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/pending$query'),
headers: await _headers(),
);
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur chargement comptes en attente (${response.statusCode})');
}
}
try {
final decoded = jsonDecode(response.body);
final data = decoded is List ? decoded as List<dynamic> : <dynamic>[];
return data
.where((e) => e is Map<String, dynamic>)
.map((e) => AppUser.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
} catch (e) {
throw Exception('Réponse invalide (comptes en attente): ${e is Exception ? e.toString() : "format inattendu"}');
}
}
/// Familles en attente (une entrée par famille). GET /parents/pending-families. Ticket #107.
static Future<List<PendingFamily>> getPendingFamilies() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/pending-families'),
headers: await _headers(),
);
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur chargement familles en attente (${response.statusCode})');
}
}
try {
final decoded = jsonDecode(response.body);
final data = decoded is List ? decoded as List<dynamic> : <dynamic>[];
return data
.where((e) => e is Map)
.map((e) => PendingFamily.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
} catch (e) {
throw Exception('Réponse invalide (familles en attente): ${e is Exception ? e.toString() : "format inattendu"}');
}
}
/// Dossier unifié par numéro (AM ou famille). GET /dossiers/:numeroDossier. Ticket #119, #107.
static Future<DossierUnifie> getDossier(String numeroDossier) async {
final encoded = Uri.encodeComponent(numeroDossier);
final uri = Uri.parse('${ApiConfig.baseUrl}${ApiConfig.dossiers}/$encoded');
final response = await http.get(
uri,
headers: await _headers(),
);
if (response.statusCode == 404) {
throw Exception('Aucun dossier avec ce numéro.');
}
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur chargement dossier (${response.statusCode})');
}
}
try {
final decoded = jsonDecode(response.body);
if (decoded is! Map<String, dynamic>) {
throw FormatException('Réponse invalide');
}
final dossier = DossierUnifie.fromJson(Map<String, dynamic>.from(decoded));
return dossier;
} catch (e) {
if (e is FormatException) rethrow;
throw Exception('Réponse invalide (dossier): ${e is Exception ? e.toString() : "format inattendu"}');
}
}
/// Valider un utilisateur (AM). PATCH /users/:id/valider. Ticket #108.
static Future<AppUser> validateUser(String userId, {String? comment}) async {
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId/valider'),
headers: await _headers(),
body: jsonEncode(comment != null ? {'comment': comment} : {}),
);
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur validation (${response.statusCode})');
}
}
final data = jsonDecode(response.body);
return AppUser.fromJson(Map<String, dynamic>.from(data is Map ? data : {}));
}
/// Refuser un compte en attente (AM ou parent). PATCH /users/:id/refuser. Ticket #110.
static Future<AppUser> refuseUser(String userId, {String? comment}) async {
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId/refuser'),
headers: await _headers(),
body: jsonEncode(
comment != null && comment.trim().isNotEmpty
? {'comment': comment.trim()}
: {},
),
);
if (response.statusCode != 200) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur refus (${response.statusCode})');
}
}
final data = jsonDecode(response.body);
return AppUser.fromJson(Map<String, dynamic>.from(data is Map ? data : {}));
}
/// Valider tout le dossier famille. POST /parents/:parentId/valider-dossier. Ticket #108.
static Future<List<AppUser>> validerDossierFamille(String parentId, {String? comment}) async {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$parentId/valider-dossier'),
headers: await _headers(),
body: jsonEncode(comment != null ? {'comment': comment} : {}),
);
// NestJS renvoie souvent 201 Created sur un POST même avec corps JSON — le front ne doit pas traiter ça comme une erreur
// (sinon validation + emails déjà faits côté serveur mais SnackBar rouge côté UI).
final ok = response.statusCode == 200 || response.statusCode == 201;
if (!ok) {
try {
final err = jsonDecode(response.body);
throw Exception(_errMessage(err is Map ? err['message'] : err));
} catch (e) {
if (e is Exception) rethrow;
throw Exception('Erreur validation dossier famille (${response.statusCode})');
}
}
final data = jsonDecode(response.body);
final list = data is List ? data : [];
return list.map((e) => AppUser.fromJson(Map<String, dynamic>.from(e as Map))).toList();
}
// Récupérer la liste des gestionnaires (endpoint dédié)
static Future<List<AppUser>> getGestionnaires() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}'),
headers: await _headers(),
);
if (response.statusCode != 200) {
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(
_toStr(err?['message']) ?? 'Erreur chargement gestionnaires');
}
final List<dynamic> data = jsonDecode(response.body);
return data.map((e) => AppUser.fromJson(e)).toList();
}
static Future<AppUser> createGestionnaire({
required String nom,
required String prenom,
required String email,
required String password,
required String telephone,
String? relaisId,
}) async {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}'),
headers: await _headers(),
body: jsonEncode(<String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
'password': password,
'telephone': telephone,
'cguAccepted': true,
'relaisId': relaisId,
}),
);
if (response.statusCode != 200 && response.statusCode != 201) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur création gestionnaire');
}
throw Exception('Erreur création gestionnaire');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
static Future<AppUser> createAdministrateur({
required String nom,
required String prenom,
required String email,
required String password,
required String telephone,
}) async {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/admin'),
headers: await _headers(),
body: jsonEncode(<String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
'password': password,
'telephone': telephone,
}),
);
if (response.statusCode != 200 && response.statusCode != 201) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur création administrateur');
}
throw Exception('Erreur création administrateur');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
// Récupérer la liste des parents
static Future<List<ParentModel>> getParents() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}'),
headers: await _headers(),
);
if (response.statusCode != 200) {
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parents');
}
final List<dynamic> data = jsonDecode(response.body);
return data
.map((e) => ParentModel.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
}
static Future<ParentModel> getParent(String userId) async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$userId'),
headers: await _headers(),
);
if (response.statusCode != 200) {
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parent');
}
final decoded = jsonDecode(response.body);
return ParentModel.fromJson(
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
);
}
static Future<ParentModel> updateParentFiche({
required String parentUserId,
required Map<String, dynamic> body,
}) async {
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/fiche'),
headers: await _headers(),
body: jsonEncode(body),
);
if (response.statusCode != 200) {
throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour parent'));
}
return _parentModelFromBody(response.body);
}
static Future<ParentModel> attachEnfantToParent({
required String parentUserId,
required String enfantId,
}) async {
final response = await http.post(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
),
headers: await _headers(),
);
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
}
return _parentModelFromBody(response.body);
}
static Future<ParentModel> detachEnfantFromParent({
required String parentUserId,
required String enfantId,
}) async {
final response = await http.delete(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
),
headers: await _headers(),
);
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
}
if (response.body.isEmpty) {
return getParent(parentUserId);
}
return _parentModelFromBody(response.body);
}
static Future<List<EnfantAdminModel>> getEnfants() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
headers: await _headers(),
);
if (response.statusCode != 200) {
throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfants'));
}
final List<dynamic> data = jsonDecode(response.body);
return data
.whereType<Map<String, dynamic>>()
.map(EnfantAdminModel.fromJson)
.toList();
}
static Future<EnfantAdminModel> getEnfant(String enfantId) async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
headers: await _headers(),
);
if (response.statusCode != 200) {
throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfant'));
}
return EnfantAdminModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}
static Future<EnfantAdminModel> updateEnfant({
required String enfantId,
required Map<String, dynamic> body,
}) async {
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
headers: await _headers(),
body: jsonEncode(body),
);
if (response.statusCode != 200) {
throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour enfant'));
}
return EnfantAdminModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}
static ParentModel _parentModelFromBody(String body) {
final decoded = jsonDecode(body);
return ParentModel.fromJson(
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
);
}
static String _extractErrorMessage(String body, String fallback) {
try {
final decoded = jsonDecode(body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
return message.join(' - ');
}
return _toStr(message) ?? fallback;
}
} catch (_) {}
return fallback;
}
// Récupérer la liste des assistantes maternelles
static Future<List<AssistanteMaternelleModel>>
getAssistantesMaternelles() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}'),
headers: await _headers(),
);
if (response.statusCode != 200) {
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM');
}
final List<dynamic> data = jsonDecode(response.body);
return data
.map((e) => AssistanteMaternelleModel.fromJson(
Map<String, dynamic>.from(e as Map),
))
.toList();
}
static Future<AssistanteMaternelleModel> getAssistanteMaternelle(
String userId,
) async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$userId'),
headers: await _headers(),
);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return AssistanteMaternelleModel.fromJson(
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
);
}
if (response.statusCode == 403 || response.statusCode == 404) {
final all = await getAssistantesMaternelles();
return all.firstWhere(
(a) => a.user.id == userId,
orElse: () => throw Exception('Assistante maternelle introuvable'),
);
}
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM');
}
/// Mise à jour fiche AM (identité + champs pro). Ticket #131.
static Future<AssistanteMaternelleModel> updateAmFiche({
required String amUserId,
required Map<String, dynamic> body,
}) async {
final ficheResponse = await http.patch(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/fiche',
),
headers: await _headers(),
body: jsonEncode(body),
);
if (ficheResponse.statusCode == 200) {
return _amModelFromBody(ficheResponse.body);
}
if (ficheResponse.statusCode != 404) {
throw Exception(
_extractErrorMessage(ficheResponse.body, 'Erreur mise à jour AM'),
);
}
final userFields = <String, dynamic>{};
for (final k in [
'nom',
'prenom',
'email',
'telephone',
'adresse',
'ville',
'code_postal',
'statut',
'date_naissance',
'lieu_naissance_ville',
'lieu_naissance_pays',
]) {
if (body.containsKey(k)) userFields[k] = body[k];
}
final proFields = <String, dynamic>{};
for (final entry in body.entries) {
if (!userFields.containsKey(entry.key)) {
proFields[entry.key] = entry.value;
}
}
if (userFields.isNotEmpty) {
final userResponse = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$amUserId'),
headers: await _headers(),
body: jsonEncode(userFields),
);
if (userResponse.statusCode != 200) {
throw Exception(
_extractErrorMessage(userResponse.body, 'Erreur mise à jour identité'),
);
}
}
if (proFields.isNotEmpty) {
final proResponse = await http.patch(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId',
),
headers: await _headers(),
body: jsonEncode(proFields),
);
if (proResponse.statusCode != 200) {
throw Exception(
_extractErrorMessage(proResponse.body, 'Erreur mise à jour fiche pro'),
);
}
}
return getAssistanteMaternelle(amUserId);
}
static Future<AssistanteMaternelleModel> attachEnfantToAm({
required String amUserId,
required String enfantId,
}) async {
final response = await http.post(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
),
headers: await _headers(),
);
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
}
return _amModelFromBody(response.body);
}
static Future<AssistanteMaternelleModel> detachEnfantFromAm({
required String amUserId,
required String enfantId,
}) async {
final response = await http.delete(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
),
headers: await _headers(),
);
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
}
if (response.body.isEmpty) {
return getAssistanteMaternelle(amUserId);
}
return _amModelFromBody(response.body);
}
static AssistanteMaternelleModel _amModelFromBody(String body) {
final decoded = jsonDecode(body);
return AssistanteMaternelleModel.fromJson(
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
);
}
// Récupérer la liste des administrateurs (via /users filtré ou autre)
// Pour l'instant on va utiliser /users et filtrer côté client si on est super admin
static Future<List<AppUser>> getAdministrateurs() async {
// TODO: Endpoint dédié ou filtrage
// En attendant, on retourne une liste vide ou on tente /users
try {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}'),
headers: await _headers(),
);
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
return data
.map((e) => AppUser.fromJson(e))
.where((u) => u.role == 'administrateur' || u.role == 'super_admin')
.toList();
}
} catch (e) {
// On garde un fallback vide pour ne pas bloquer l'UI admin.
}
return [];
}
static Future<AppUser> createAdmin({
required String nom,
required String prenom,
required String email,
required String password,
required String telephone,
}) async {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/admin'),
headers: await _headers(),
body: jsonEncode(<String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
'password': password,
'telephone': telephone,
}),
);
if (response.statusCode != 200 && response.statusCode != 201) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur création administrateur');
}
throw Exception('Erreur création administrateur');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
static Future<AppUser> updateAdmin({
required String adminId,
required String nom,
required String prenom,
required String email,
required String telephone,
String? password,
}) async {
final body = <String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
'telephone': telephone,
};
if (password != null && password.trim().isNotEmpty) {
body['password'] = password.trim();
}
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$adminId'),
headers: await _headers(),
body: jsonEncode(body),
);
if (response.statusCode != 200) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur modification administrateur');
}
throw Exception('Erreur modification administrateur');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
static Future<void> updateGestionnaireRelais({
required String gestionnaireId,
required String? relaisId,
}) async {
final response = await http.patch(
Uri.parse(
'${ApiConfig.baseUrl}${ApiConfig.gestionnaires}/$gestionnaireId'),
headers: await _headers(),
body: jsonEncode(<String, dynamic>{'relaisId': relaisId}),
);
if (response.statusCode != 200 && response.statusCode != 204) {
final err = jsonDecode(response.body) as Map<String, dynamic>?;
throw Exception(
_toStr(err?['message']) ?? 'Erreur rattachement relais au gestionnaire',
);
}
}
static Future<AppUser> updateGestionnaire({
required String gestionnaireId,
required String nom,
required String prenom,
required String email,
String? telephone,
required String? relaisId,
String? password,
}) async {
final body = <String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
'relaisId': relaisId,
};
if (telephone != null && telephone.trim().isNotEmpty) {
body['telephone'] = telephone.trim();
}
if (password != null && password.trim().isNotEmpty) {
body['password'] = password.trim();
}
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.gestionnaires}/$gestionnaireId'),
headers: await _headers(),
body: jsonEncode(body),
);
if (response.statusCode != 200) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur modification gestionnaire');
}
throw Exception('Erreur modification gestionnaire');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
static Future<AppUser> updateAdministrateur({
required String adminId,
required String nom,
required String prenom,
required String email,
String? telephone,
String? password,
}) async {
final body = <String, dynamic>{
'nom': nom,
'prenom': prenom,
'email': email,
};
if (telephone != null && telephone.trim().isNotEmpty) {
body['telephone'] = telephone.trim();
}
if (password != null && password.trim().isNotEmpty) {
body['password'] = password.trim();
}
final response = await http.patch(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$adminId'),
headers: await _headers(),
body: jsonEncode(body),
);
if (response.statusCode != 200) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur modification administrateur');
}
throw Exception('Erreur modification administrateur');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return AppUser.fromJson(data);
}
static Future<void> deleteUser(String userId) async {
final response = await http.delete(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$userId'),
headers: await _headers(),
);
if (response.statusCode != 200 && response.statusCode != 204) {
final decoded = jsonDecode(response.body);
if (decoded is Map<String, dynamic>) {
final message = decoded['message'];
if (message is List && message.isNotEmpty) {
throw Exception(message.join(' - '));
}
throw Exception(_toStr(message) ?? 'Erreur suppression utilisateur');
}
throw Exception('Erreur suppression utilisateur');
}
}
}