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:
@@ -0,0 +1,210 @@
|
||||
import 'package:p_tits_pas/models/user.dart';
|
||||
|
||||
/// Réponse unifiée GET /dossiers/:numeroDossier. Ticket #119, #107.
|
||||
class DossierUnifie {
|
||||
final String type; // 'am' | 'family'
|
||||
final dynamic dossier; // DossierAM | DossierFamille
|
||||
|
||||
DossierUnifie({required this.type, required this.dossier});
|
||||
|
||||
bool get isAm => type == 'am';
|
||||
bool get isFamily => type == 'family';
|
||||
|
||||
DossierAM get asAm => dossier as DossierAM;
|
||||
DossierFamille get asFamily => dossier as DossierFamille;
|
||||
|
||||
factory DossierUnifie.fromJson(Map<String, dynamic> json) {
|
||||
final t = json['type'];
|
||||
final raw = t is String ? t : 'family';
|
||||
final typeStr = raw.toLowerCase();
|
||||
final d = json['dossier'];
|
||||
if (d == null || d is! Map<String, dynamic>) {
|
||||
throw FormatException('dossier manquant ou invalide');
|
||||
}
|
||||
final dossierMap = Map<String, dynamic>.from(d as Map);
|
||||
// Seul `am` (casse tolérée) charge le dossier AM ; le reste = famille (API : type "family").
|
||||
final isAm = typeStr == 'am';
|
||||
final dossier = isAm ? DossierAM.fromJson(dossierMap) : DossierFamille.fromJson(dossierMap);
|
||||
return DossierUnifie(type: isAm ? 'am' : 'family', dossier: dossier);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dossier AM (type: 'am'). Champs alignés API.
|
||||
class DossierAM {
|
||||
final String? numeroDossier;
|
||||
final AppUser user;
|
||||
final String? numeroAgrement;
|
||||
final String? nir;
|
||||
final String? presentation;
|
||||
final String? dateAgrement;
|
||||
final int? nbMaxEnfants;
|
||||
final int? placesDisponibles;
|
||||
final String? villeResidence;
|
||||
|
||||
DossierAM({
|
||||
this.numeroDossier,
|
||||
required this.user,
|
||||
this.numeroAgrement,
|
||||
this.nir,
|
||||
this.presentation,
|
||||
this.dateAgrement,
|
||||
this.nbMaxEnfants,
|
||||
this.placesDisponibles,
|
||||
this.villeResidence,
|
||||
});
|
||||
|
||||
factory DossierAM.fromJson(Map<String, dynamic> json) {
|
||||
final userJson = json['user'];
|
||||
final userMap = userJson is Map<String, dynamic>
|
||||
? userJson
|
||||
: <String, dynamic>{};
|
||||
final nbMax = json['nb_max_enfants'];
|
||||
final places = json['place_disponible'];
|
||||
return DossierAM(
|
||||
numeroDossier: json['numero_dossier']?.toString(),
|
||||
user: AppUser.fromJson(Map<String, dynamic>.from(userMap)),
|
||||
numeroAgrement: json['numero_agrement']?.toString(),
|
||||
nir: json['nir']?.toString(),
|
||||
presentation: (json['biographie'] ?? json['presentation'])?.toString(),
|
||||
dateAgrement: json['date_agrement']?.toString(),
|
||||
nbMaxEnfants: nbMax is int ? nbMax : (nbMax is num ? nbMax.toInt() : null),
|
||||
placesDisponibles: places is int ? places : (places is num ? places.toInt() : null),
|
||||
villeResidence: json['ville_residence']?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dossier famille (type: 'family'). Champs alignés API.
|
||||
class DossierFamille {
|
||||
final String? numeroDossier;
|
||||
final List<ParentDossier> parents;
|
||||
final List<EnfantDossier> enfants;
|
||||
final String? presentation;
|
||||
|
||||
DossierFamille({
|
||||
this.numeroDossier,
|
||||
required this.parents,
|
||||
required this.enfants,
|
||||
this.presentation,
|
||||
});
|
||||
|
||||
factory DossierFamille.fromJson(Map<String, dynamic> json) {
|
||||
final parentsRaw = json['parents'];
|
||||
final parentsList = parentsRaw is List
|
||||
? (parentsRaw)
|
||||
.where((e) => e is Map)
|
||||
.map((e) => ParentDossier.fromJson(Map<String, dynamic>.from(e as Map)))
|
||||
.toList()
|
||||
: <ParentDossier>[];
|
||||
final enfantsRaw = json['enfants'];
|
||||
final enfantsList = enfantsRaw is List
|
||||
? (enfantsRaw)
|
||||
.where((e) => e is Map)
|
||||
.map((e) => EnfantDossier.fromJson(Map<String, dynamic>.from(e as Map)))
|
||||
.toList()
|
||||
: <EnfantDossier>[];
|
||||
return DossierFamille(
|
||||
numeroDossier: json['numero_dossier']?.toString(),
|
||||
parents: parentsList,
|
||||
enfants: enfantsList,
|
||||
presentation: (json['texte_motivation'] ?? json['presentation'])?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
bool get isEnAttente =>
|
||||
parents.any((p) => p.statut == 'en_attente');
|
||||
}
|
||||
|
||||
/// Parent dans un dossier famille (champs user exposés).
|
||||
class ParentDossier {
|
||||
final String id;
|
||||
final String email;
|
||||
final String? prenom;
|
||||
final String? nom;
|
||||
final String? telephone;
|
||||
final String? adresse;
|
||||
final String? ville;
|
||||
final String? codePostal;
|
||||
final String? dateNaissance;
|
||||
final String? genre;
|
||||
final String? situationFamiliale;
|
||||
final String? creeLe;
|
||||
final String? statut;
|
||||
|
||||
ParentDossier({
|
||||
required this.id,
|
||||
required this.email,
|
||||
this.prenom,
|
||||
this.nom,
|
||||
this.telephone,
|
||||
this.adresse,
|
||||
this.ville,
|
||||
this.codePostal,
|
||||
this.dateNaissance,
|
||||
this.genre,
|
||||
this.situationFamiliale,
|
||||
this.creeLe,
|
||||
this.statut,
|
||||
});
|
||||
|
||||
String get fullName => '${prenom ?? ''} ${nom ?? ''}'.trim();
|
||||
|
||||
factory ParentDossier.fromJson(Map<String, dynamic> json) {
|
||||
return ParentDossier(
|
||||
id: json['id']?.toString() ?? '',
|
||||
email: json['email']?.toString() ?? '',
|
||||
prenom: json['prenom']?.toString(),
|
||||
nom: json['nom']?.toString(),
|
||||
telephone: json['telephone']?.toString(),
|
||||
adresse: json['adresse']?.toString(),
|
||||
ville: json['ville']?.toString(),
|
||||
codePostal: json['code_postal']?.toString(),
|
||||
dateNaissance: json['date_naissance']?.toString(),
|
||||
genre: json['genre']?.toString(),
|
||||
situationFamiliale: json['situation_familiale']?.toString(),
|
||||
creeLe: json['cree_le']?.toString(),
|
||||
statut: json['statut']?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Enfant dans un dossier famille.
|
||||
class EnfantDossier {
|
||||
final String id;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? birthDate;
|
||||
final String? gender;
|
||||
final String? status;
|
||||
final String? dueDate;
|
||||
final String? photoUrl;
|
||||
final bool consentPhoto;
|
||||
|
||||
EnfantDossier({
|
||||
required this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.birthDate,
|
||||
this.gender,
|
||||
this.status,
|
||||
this.dueDate,
|
||||
this.photoUrl,
|
||||
this.consentPhoto = false,
|
||||
});
|
||||
|
||||
String get fullName => '${firstName ?? ''} ${lastName ?? ''}'.trim();
|
||||
|
||||
factory EnfantDossier.fromJson(Map<String, dynamic> json) {
|
||||
return EnfantDossier(
|
||||
id: json['id']?.toString() ?? '',
|
||||
firstName: (json['first_name'] ?? json['prenom'])?.toString(),
|
||||
lastName: (json['last_name'] ?? json['nom'])?.toString(),
|
||||
birthDate: json['birth_date']?.toString(),
|
||||
gender: (json['gender'] ?? json['genre'])?.toString(),
|
||||
status: json['status']?.toString(),
|
||||
dueDate: json['due_date']?.toString(),
|
||||
photoUrl: json['photo_url']?.toString(),
|
||||
consentPhoto: json['consent_photo'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user