feat(#206,#207): cartes rose soin_enfant (backend + UI)
Closes #206 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,7 @@ enum CarteStatut {
|
||||
switch (raw) {
|
||||
case 'refusee':
|
||||
return CarteStatut.refusee;
|
||||
case 'fermee':
|
||||
case 'traitee':
|
||||
return CarteStatut.traitee;
|
||||
case 'ouverte':
|
||||
@@ -110,6 +111,57 @@ class CarteBulle {
|
||||
String? get dateFin => payload['date_fin']?.toString();
|
||||
String? get motif => payload['motif']?.toString();
|
||||
|
||||
/// Heure de relevé (payload soins), format API `HH:mm`.
|
||||
String? get heureReleve => payload['heure_releve']?.toString();
|
||||
|
||||
/// Heure affichable `18h19` (depuis `heure_releve`).
|
||||
String? get heureReleveAffichee {
|
||||
final h = heureReleve?.trim();
|
||||
if (h == null || h.isEmpty) return null;
|
||||
final parts = h.split(':');
|
||||
if (parts.length < 2) return h;
|
||||
return '${parts[0]}h${parts[1]}';
|
||||
}
|
||||
|
||||
/// Ligne détail soin (sans date/heure) — sous la ligne agenda.
|
||||
String? get detailSoin {
|
||||
if (!isSoinEnfant) return null;
|
||||
final st = payload['sous_type']?.toString();
|
||||
if (st == 'fievre') {
|
||||
final t = payload['temperature_c'];
|
||||
final med = payload['medicament_donne']?.toString().trim();
|
||||
String? temp;
|
||||
if (t is num) {
|
||||
temp = t.toStringAsFixed(1).replaceAll('.', ',');
|
||||
} else if (t != null) {
|
||||
temp = t.toString().replaceAll('.', ',');
|
||||
}
|
||||
if (temp == null || temp.isEmpty) return motif;
|
||||
var s = '$temp °C';
|
||||
if (med != null && med.isNotEmpty) {
|
||||
s += ' (Médicament : $med)';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (st == 'bobo') {
|
||||
final nature = payload['bobo_nature']?.toString() ?? 'bobo';
|
||||
final desc = payload['description']?.toString().trim();
|
||||
if (desc != null && desc.isNotEmpty) {
|
||||
return 'Bobo ($nature) : $desc';
|
||||
}
|
||||
return 'Bobo ($nature)';
|
||||
}
|
||||
if (st == 'medicament') {
|
||||
final nom = payload['medicament_nom']?.toString().trim();
|
||||
final ctx = payload['contexte']?.toString().trim();
|
||||
final label =
|
||||
(nom != null && nom.isNotEmpty) ? 'Médicament ($nom)' : 'Médicament';
|
||||
if (ctx != null && ctx.isNotEmpty) return '$label : $ctx';
|
||||
return label;
|
||||
}
|
||||
return motif;
|
||||
}
|
||||
|
||||
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
||||
String? get motifRefus {
|
||||
final fromResponse = lastRefuseComment?.trim();
|
||||
@@ -139,6 +191,21 @@ class CarteBulle {
|
||||
bool get isArretMaladie => typeCode == 'arret_maladie_am';
|
||||
bool get isCongeAm => typeCode == 'conge_am';
|
||||
bool get isSoinEnfant => typeCode == 'soin_enfant';
|
||||
bool get isAbsenceEnfant =>
|
||||
typeCode == 'absence_enfant' || typeCode == 'absence_enfant_modif';
|
||||
|
||||
/// Info seule (`response_mode: none`) : créée déjà `traitee`, mais reste
|
||||
/// pertinente dans la file tant que la période n’est pas finie.
|
||||
bool get restePleineDansFile {
|
||||
if (statut == CarteStatut.ouverte) return true;
|
||||
if (statut != CarteStatut.traitee) return false;
|
||||
if (!periodeEncoreActive) return false;
|
||||
// Arrêt maladie ack : opaque jusqu’à date_fin.
|
||||
if (isArretMaladie) return true;
|
||||
// Absence (et autres infos) : pas d’atténuation tant que période active.
|
||||
if (responseMode == CarteResponseMode.none) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Icône dessin (haut droite) selon le type de bulle.
|
||||
String? get iconAsset {
|
||||
@@ -150,7 +217,12 @@ class CarteBulle {
|
||||
case 'absence_enfant':
|
||||
case 'absence_enfant_modif':
|
||||
return 'assets/images/sac_lapin.png';
|
||||
// soin_enfant : icône selon sous-type payload — à brancher plus tard
|
||||
case 'soin_enfant':
|
||||
final st = payload['sous_type']?.toString();
|
||||
if (st == 'fievre') return 'assets/images/thermo_verre.png';
|
||||
if (st == 'bobo') return 'assets/images/pansement.png';
|
||||
if (st == 'medicament') return 'assets/images/flacon.png';
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/// Configuration UI d'un type de carte — catalogue front (#175).
|
||||
/// Les types émissibles viennent de l'API ; ce fichier affine libellés / hints.
|
||||
enum CartePeriodeMode {
|
||||
/// Plage date_debut → date_fin.
|
||||
range,
|
||||
|
||||
/// Une seule date (début = fin).
|
||||
singleDay,
|
||||
}
|
||||
|
||||
class CarteSousTypeUi {
|
||||
final String code;
|
||||
final String label;
|
||||
|
||||
const CarteSousTypeUi({required this.code, required this.label});
|
||||
}
|
||||
|
||||
class CarteTypeUi {
|
||||
final String typeCode;
|
||||
final String label;
|
||||
final String motifHint;
|
||||
final bool motifRequired;
|
||||
final CartePeriodeMode periodeMode;
|
||||
final List<CarteSousTypeUi> sousTypes;
|
||||
|
||||
const CarteTypeUi({
|
||||
required this.typeCode,
|
||||
required this.label,
|
||||
this.motifHint = 'Motif (optionnel)',
|
||||
this.motifRequired = false,
|
||||
this.periodeMode = CartePeriodeMode.range,
|
||||
this.sousTypes = const [],
|
||||
});
|
||||
|
||||
static const _catalog = <String, CarteTypeUi>{
|
||||
'absence_enfant': CarteTypeUi(
|
||||
typeCode: 'absence_enfant',
|
||||
label: 'Notification d’absence',
|
||||
motifHint: 'Motif de l’absence (optionnel)',
|
||||
),
|
||||
// absence_enfant_modif : hors scope cartes — via agenda uniquement.
|
||||
'conge_am': CarteTypeUi(
|
||||
typeCode: 'conge_am',
|
||||
label: 'Demande de congés',
|
||||
motifHint: 'Précisions (optionnel)',
|
||||
),
|
||||
'arret_maladie_am': CarteTypeUi(
|
||||
typeCode: 'arret_maladie_am',
|
||||
label: 'Arrêt maladie',
|
||||
motifHint: 'Précisions (optionnel)',
|
||||
),
|
||||
'soin_enfant': CarteTypeUi(
|
||||
typeCode: 'soin_enfant',
|
||||
label: 'Soin / bobo',
|
||||
periodeMode: CartePeriodeMode.singleDay,
|
||||
sousTypes: [
|
||||
CarteSousTypeUi(code: 'fievre', label: 'Fièvre'),
|
||||
CarteSousTypeUi(code: 'bobo', label: 'Bobo'),
|
||||
CarteSousTypeUi(code: 'medicament', label: 'Médicament'),
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
/// Fusionne un type API avec le catalogue ; fallback générique sinon.
|
||||
static CarteTypeUi resolve({
|
||||
required String typeCode,
|
||||
String? apiTitre,
|
||||
}) {
|
||||
final known = _catalog[typeCode];
|
||||
if (known != null) return known;
|
||||
final titre = (apiTitre ?? '').trim();
|
||||
return CarteTypeUi(
|
||||
typeCode: typeCode,
|
||||
label: titre.isNotEmpty ? titre : typeCode,
|
||||
motifHint: 'Motif (optionnel)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type SYSTEM émissible renvoyé par GET /cards/types.
|
||||
class CarteTypeEmissible {
|
||||
final String code;
|
||||
final String titre;
|
||||
final String? responseMode;
|
||||
final CarteTypeUi ui;
|
||||
|
||||
const CarteTypeEmissible({
|
||||
required this.code,
|
||||
required this.titre,
|
||||
this.responseMode,
|
||||
required this.ui,
|
||||
});
|
||||
|
||||
factory CarteTypeEmissible.fromJson(Map<String, dynamic> json) {
|
||||
final code = (json['code'] ?? '').toString();
|
||||
final titre = (json['titre'] ?? code).toString();
|
||||
return CarteTypeEmissible(
|
||||
code: code,
|
||||
titre: titre,
|
||||
responseMode: json['response_mode']?.toString(),
|
||||
ui: CarteTypeUi.resolve(typeCode: code, apiTitre: titre),
|
||||
);
|
||||
}
|
||||
|
||||
String get label => ui.label;
|
||||
}
|
||||
Reference in New Issue
Block a user