feat(admin): onglet À valider, dossier unifié et modales de validation
- Onglet « À valider » (AM + familles), pending-families et détail dossier par numéro. - Wizards validation AM et famille, modale commune, chargement via GET /dossiers/:num. - UI : cartes enfants, photo AM (cadre uniforme, ratio), NIR affiché formaté (espaces autour du tiret). - Backend : DTO / routes parents pending ; scripts de test et mise à jour issue Gitea. Tickets #107, #119. Made-with: Cursor
This commit is contained in:
@@ -3,6 +3,8 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:p_tits_pas/models/user.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';
|
||||
|
||||
@@ -20,6 +22,145 @@ class UserService {
|
||||
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';
|
||||
}
|
||||
|
||||
/// 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 response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.dossiers}/$encoded'),
|
||||
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');
|
||||
}
|
||||
return DossierUnifie.fromJson(Map<String, dynamic>.from(decoded));
|
||||
} 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 : {}));
|
||||
}
|
||||
|
||||
/// 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} : {}),
|
||||
);
|
||||
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 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(
|
||||
|
||||
Reference in New Issue
Block a user