release: squash merge develop → master
Quotidien / Epic C : feed bulles parent (#173), module Cartes (#194/#195), evenements_agenda (#205), couples garde AM (#169–#171), API absences (#172). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
class AbsenceGarde {
|
||||
final String id;
|
||||
final String idPlacement;
|
||||
final String type;
|
||||
final String dateDebut;
|
||||
final String dateFin;
|
||||
final String statut;
|
||||
final String expireAt;
|
||||
final String? creePar;
|
||||
final String? motif;
|
||||
final String? idEnfant;
|
||||
final String? prenomEnfant;
|
||||
final String? idAm;
|
||||
final String? prenomAm;
|
||||
final String? nomAm;
|
||||
final String creeLe;
|
||||
final String modifieLe;
|
||||
|
||||
AbsenceGarde({
|
||||
required this.id,
|
||||
required this.idPlacement,
|
||||
required this.type,
|
||||
required this.dateDebut,
|
||||
required this.dateFin,
|
||||
required this.statut,
|
||||
required this.expireAt,
|
||||
this.creePar,
|
||||
this.motif,
|
||||
this.idEnfant,
|
||||
this.prenomEnfant,
|
||||
this.idAm,
|
||||
this.prenomAm,
|
||||
this.nomAm,
|
||||
required this.creeLe,
|
||||
required this.modifieLe,
|
||||
});
|
||||
|
||||
factory AbsenceGarde.fromJson(Map<String, dynamic> json) {
|
||||
return AbsenceGarde(
|
||||
id: json['id'] ?? '',
|
||||
idPlacement: json['id_placement'] ?? '',
|
||||
type: json['type'] ?? '',
|
||||
dateDebut: json['date_debut'] ?? '',
|
||||
dateFin: json['date_fin'] ?? '',
|
||||
statut: json['statut'] ?? '',
|
||||
expireAt: json['expire_at'] ?? '',
|
||||
creePar: json['cree_par'],
|
||||
motif: json['motif'],
|
||||
idEnfant: json['id_enfant'],
|
||||
prenomEnfant: json['prenom_enfant'],
|
||||
idAm: json['id_am'],
|
||||
prenomAm: json['prenom_am'],
|
||||
nomAm: json['nom_am'],
|
||||
creeLe: json['cree_le'] ?? '',
|
||||
modifieLe: json['modifie_le'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ enum CardColorVertical {
|
||||
lime('assets/cards/card_lime.png'),
|
||||
lavender('assets/cards/card_lavender.png'),
|
||||
green('assets/cards/card_green.png'),
|
||||
blue('assets/cards/card_blue.png');
|
||||
blue('assets/cards/card_blue.png'),
|
||||
aqua('assets/cards/card_aqua.png');
|
||||
|
||||
final String path;
|
||||
const CardColorVertical(this.path);
|
||||
@@ -18,8 +19,9 @@ enum CardColorHorizontal {
|
||||
lime('assets/cards/card_lime_h.png'),
|
||||
lavender('assets/cards/card_lavender_h.png'),
|
||||
green('assets/cards/card_green_h.png'),
|
||||
blue('assets/cards/card_blue_h.png');
|
||||
blue('assets/cards/card_blue_h.png'),
|
||||
aqua('assets/cards/card_aqua_h.png');
|
||||
|
||||
final String path;
|
||||
const CardColorHorizontal(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import 'package:p_tits_pas/models/card_assets.dart';
|
||||
|
||||
/// Statut d'une instance de carte (file d'attention) — contrat #194 / #173.
|
||||
enum CarteStatut {
|
||||
ouverte('ouverte'),
|
||||
refusee('refusee'),
|
||||
traitee('traitee');
|
||||
|
||||
final String apiValue;
|
||||
const CarteStatut(this.apiValue);
|
||||
|
||||
static CarteStatut fromApi(String? raw) {
|
||||
switch (raw) {
|
||||
case 'refusee':
|
||||
return CarteStatut.refusee;
|
||||
case 'traitee':
|
||||
return CarteStatut.traitee;
|
||||
case 'ouverte':
|
||||
default:
|
||||
return CarteStatut.ouverte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mode de réponse attendu selon le type SYSTEM.
|
||||
enum CarteResponseMode {
|
||||
none('none'),
|
||||
ack('ack'),
|
||||
acceptRefuse('accept_refuse');
|
||||
|
||||
final String apiValue;
|
||||
const CarteResponseMode(this.apiValue);
|
||||
|
||||
static CarteResponseMode fromApi(String? raw) {
|
||||
switch (raw) {
|
||||
case 'ack':
|
||||
return CarteResponseMode.ack;
|
||||
case 'accept_refuse':
|
||||
return CarteResponseMode.acceptRefuse;
|
||||
case 'none':
|
||||
default:
|
||||
return CarteResponseMode.none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bulle / carte de la file d'attention.
|
||||
class CarteBulle {
|
||||
final String id;
|
||||
final String typeCode;
|
||||
final String titre;
|
||||
final String idPlacement;
|
||||
final String? idAbsence;
|
||||
final String operation;
|
||||
final CarteStatut statut;
|
||||
final Map<String, dynamic> payload;
|
||||
final DateTime? purgeAt;
|
||||
final String? creePar;
|
||||
final bool isCreator;
|
||||
final CarteResponseMode responseMode;
|
||||
final String? lastRefuseComment;
|
||||
final DateTime? creeLe;
|
||||
final DateTime? modifieLe;
|
||||
|
||||
const CarteBulle({
|
||||
required this.id,
|
||||
required this.typeCode,
|
||||
required this.titre,
|
||||
required this.idPlacement,
|
||||
this.idAbsence,
|
||||
this.operation = 'create',
|
||||
required this.statut,
|
||||
this.payload = const {},
|
||||
this.purgeAt,
|
||||
this.creePar,
|
||||
this.isCreator = false,
|
||||
this.responseMode = CarteResponseMode.none,
|
||||
this.lastRefuseComment,
|
||||
this.creeLe,
|
||||
this.modifieLe,
|
||||
});
|
||||
|
||||
String? get dateDebut => payload['date_debut']?.toString();
|
||||
String? get dateFin => payload['date_fin']?.toString();
|
||||
String? get motif => payload['motif']?.toString();
|
||||
|
||||
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
||||
String? get motifRefus {
|
||||
final fromResponse = lastRefuseComment?.trim();
|
||||
if (fromResponse != null && fromResponse.isNotEmpty) return fromResponse;
|
||||
final fromPayload = motif?.trim();
|
||||
if (fromPayload != null && fromPayload.isNotEmpty) return fromPayload;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Libellé UI (sémantique produit) — pas le titre brut API.
|
||||
String get titreAffiche {
|
||||
switch (typeCode) {
|
||||
case 'conge_am':
|
||||
return 'Demande de congés';
|
||||
case 'arret_maladie_am':
|
||||
return 'Arrêt maladie';
|
||||
case 'absence_enfant':
|
||||
case 'absence_enfant_modif':
|
||||
return 'Notification d’absence';
|
||||
case 'soin_enfant':
|
||||
return 'Soin / bobo';
|
||||
default:
|
||||
return titre;
|
||||
}
|
||||
}
|
||||
|
||||
bool get isArretMaladie => typeCode == 'arret_maladie_am';
|
||||
bool get isCongeAm => typeCode == 'conge_am';
|
||||
bool get isSoinEnfant => typeCode == 'soin_enfant';
|
||||
|
||||
/// Icône dessin (haut droite) selon le type de bulle.
|
||||
String? get iconAsset {
|
||||
switch (typeCode) {
|
||||
case 'conge_am':
|
||||
return 'assets/images/valise.png';
|
||||
case 'arret_maladie_am':
|
||||
return 'assets/images/thermo_croix.png';
|
||||
case 'absence_enfant':
|
||||
case 'absence_enfant_modif':
|
||||
return 'assets/images/sac_lapin.png';
|
||||
// soin_enfant : icône selon sous-type payload — à brancher plus tard
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Fin de période (jour inclus). Null si dates absentes / invalides.
|
||||
DateTime? get dateFinJour {
|
||||
final raw = dateFin;
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
final dt = DateTime.tryParse(raw);
|
||||
if (dt == null) return null;
|
||||
return DateTime(dt.year, dt.month, dt.day);
|
||||
}
|
||||
|
||||
/// Vrai tant que la date de fin n’est pas dépassée (jour courant inclus).
|
||||
bool get periodeEncoreActive {
|
||||
final fin = dateFinJour;
|
||||
if (fin == null) return true;
|
||||
final today = DateTime.now();
|
||||
final todayDay = DateTime(today.year, today.month, today.day);
|
||||
return !todayDay.isAfter(fin);
|
||||
}
|
||||
|
||||
/// Teinte pastel UI dérivée du `type_code` (pas de `couleur` API).
|
||||
/// Voir `docs/32_MINI-SPEC-BULLES-CARTES.md` §4.
|
||||
CardColorHorizontal get colorFamily {
|
||||
switch (typeCode) {
|
||||
case 'conge_am':
|
||||
return CardColorHorizontal.aqua;
|
||||
case 'arret_maladie_am':
|
||||
return CardColorHorizontal.blue;
|
||||
case 'absence_enfant':
|
||||
case 'absence_enfant_modif':
|
||||
return CardColorHorizontal.peach;
|
||||
case 'soin_enfant':
|
||||
return CardColorHorizontal.pink;
|
||||
default:
|
||||
return CardColorHorizontal.lavender;
|
||||
}
|
||||
}
|
||||
|
||||
/// Asset horizontal pastel (palette `assets/cards/` uniquement).
|
||||
String get backgroundAsset => colorFamily.path;
|
||||
|
||||
/// Le destinataire peut répondre (pas le créateur, carte ouverte).
|
||||
bool get peutRepondre =>
|
||||
!isCreator &&
|
||||
statut == CarteStatut.ouverte &&
|
||||
responseMode != CarteResponseMode.none;
|
||||
|
||||
factory CarteBulle.fromJson(Map<String, dynamic> json) {
|
||||
final rawPayload = json['payload'];
|
||||
return CarteBulle(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
typeCode: (json['type_code'] ?? '').toString(),
|
||||
titre: (json['titre'] ?? json['type_code'] ?? 'Carte').toString(),
|
||||
idPlacement: (json['id_placement'] ?? '').toString(),
|
||||
idAbsence: json['id_absence']?.toString(),
|
||||
operation: (json['operation'] ?? 'create').toString(),
|
||||
statut: CarteStatut.fromApi(json['statut']?.toString()),
|
||||
payload: rawPayload is Map
|
||||
? Map<String, dynamic>.from(rawPayload)
|
||||
: const {},
|
||||
purgeAt: _parseDate(json['purge_at']),
|
||||
creePar: json['cree_par']?.toString(),
|
||||
isCreator: json['is_creator'] == true,
|
||||
responseMode: CarteResponseMode.fromApi(json['response_mode']?.toString()),
|
||||
lastRefuseComment: json['last_refuse_comment']?.toString(),
|
||||
creeLe: _parseDate(json['cree_le']),
|
||||
modifieLe: _parseDate(json['modifie_le']),
|
||||
);
|
||||
}
|
||||
|
||||
static DateTime? _parseDate(dynamic v) {
|
||||
if (v == null) return null;
|
||||
return DateTime.tryParse(v.toString());
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,15 @@ class CoupleMembre {
|
||||
class CoupleGarde {
|
||||
final String id;
|
||||
final CoupleMembre enfant;
|
||||
final CoupleMembre am;
|
||||
final CoupleMembre? am;
|
||||
final List<CoupleMembre> parents;
|
||||
final bool courant;
|
||||
|
||||
const CoupleGarde({
|
||||
required this.id,
|
||||
required this.enfant,
|
||||
required this.am,
|
||||
this.am,
|
||||
this.parents = const [],
|
||||
this.courant = false,
|
||||
});
|
||||
|
||||
@@ -55,9 +57,15 @@ class CoupleGarde {
|
||||
enfant: CoupleMembre.fromJson(
|
||||
Map<String, dynamic>.from(json['enfant'] ?? const {}),
|
||||
),
|
||||
am: CoupleMembre.fromJson(
|
||||
Map<String, dynamic>.from(json['am'] ?? const {}),
|
||||
),
|
||||
am: json['am'] != null
|
||||
? CoupleMembre.fromJson(Map<String, dynamic>.from(json['am']))
|
||||
: null,
|
||||
parents: json['parents'] != null
|
||||
? (json['parents'] as List)
|
||||
.whereType<Map>()
|
||||
.map((e) => CoupleMembre.fromJson(Map<String, dynamic>.from(e)))
|
||||
.toList()
|
||||
: const [],
|
||||
courant: json['courant'] == true,
|
||||
);
|
||||
}
|
||||
@@ -67,6 +75,7 @@ class CoupleGarde {
|
||||
id: id,
|
||||
enfant: enfant,
|
||||
am: am,
|
||||
parents: parents,
|
||||
courant: courant ?? this.courant,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user