feat(front): alignement avec la spec backend soins enfant
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':
|
||||
@@ -113,23 +114,7 @@ class CarteBulle {
|
||||
/// Helper pour extraire les informations de soin
|
||||
String? get resumerSoin {
|
||||
if (!isSoinEnfant) return null;
|
||||
|
||||
final st = payload['sous_type']?.toString();
|
||||
final h = payload['heure_releve']?.toString();
|
||||
final hStr = h != null && h.isNotEmpty ? ' à $h' : '';
|
||||
|
||||
if (st == 'fievre') {
|
||||
final t = payload['temperature_c']?.toString() ?? '?';
|
||||
return 'Température : $t°C$hStr';
|
||||
} else if (st == 'bobo') {
|
||||
final nature = payload['bobo_nature']?.toString() ?? 'Bobo';
|
||||
final desc = payload['description']?.toString() ?? '';
|
||||
return desc.isNotEmpty ? '$nature$hStr ($desc)' : '$nature$hStr';
|
||||
} else if (st == 'medicament') {
|
||||
final nom = payload['medicament_nom']?.toString() ?? 'Médicament';
|
||||
return '$nom donné$hStr';
|
||||
}
|
||||
return null;
|
||||
return payload['motif']?.toString();
|
||||
}
|
||||
|
||||
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
||||
|
||||
@@ -49,18 +49,14 @@ class CarteTypeUi {
|
||||
label: 'Arrêt maladie',
|
||||
motifHint: 'Précisions (optionnel)',
|
||||
),
|
||||
// Point d’extension #soins — visible dès que le back l’émet.
|
||||
'soin_enfant': CarteTypeUi(
|
||||
typeCode: 'soin_enfant',
|
||||
label: 'Soin / bobo',
|
||||
motifHint: 'Décrivez le soin ou le bobo',
|
||||
motifRequired: true,
|
||||
periodeMode: CartePeriodeMode.singleDay,
|
||||
sousTypes: [
|
||||
CarteSousTypeUi(code: 'temperature', label: 'Température'),
|
||||
CarteSousTypeUi(code: 'fievre', label: 'Fièvre'),
|
||||
CarteSousTypeUi(code: 'bobo', label: 'Bobo'),
|
||||
CarteSousTypeUi(code: 'medicament', label: 'Médicament'),
|
||||
CarteSousTypeUi(code: 'autre', label: 'Autre'),
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
@@ -91,6 +91,7 @@ class CardsService {
|
||||
required String dateDebut,
|
||||
required String dateFin,
|
||||
String? motif,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}'),
|
||||
@@ -101,6 +102,7 @@ class CardsService {
|
||||
'date_debut': dateDebut,
|
||||
'date_fin': dateFin,
|
||||
if (motif != null && motif.trim().isNotEmpty) 'motif': motif.trim(),
|
||||
if (payload != null) 'payload': payload,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -114,6 +116,49 @@ class CardsService {
|
||||
return CarteBulle.fromJson(Map<String, dynamic>.from(decoded as Map));
|
||||
}
|
||||
|
||||
/// Modifier une carte (corriger un soin, dates en attente...).
|
||||
static Future<CarteBulle> maj({
|
||||
required String cardId,
|
||||
String? dateDebut,
|
||||
String? dateFin,
|
||||
String? motif,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
final response = await http.patch(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}/$cardId'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode({
|
||||
if (dateDebut != null) 'date_debut': dateDebut,
|
||||
if (dateFin != null) 'date_fin': dateFin,
|
||||
if (motif != null && motif.trim().isNotEmpty) 'motif': motif.trim(),
|
||||
if (payload != null) 'payload': payload,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(
|
||||
_extractError(response.body, 'Erreur lors de la mise à jour'),
|
||||
);
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
return CarteBulle.fromJson(Map<String, dynamic>.from(decoded as Map));
|
||||
}
|
||||
|
||||
/// Supprimer une bulle (AM 24h ou avant traitement).
|
||||
static Future<void> supprimer(String cardId) async {
|
||||
final response = await http.delete(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}/$cardId'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
|
||||
if (response.statusCode != 204 && response.statusCode != 200) {
|
||||
throw Exception(
|
||||
_extractError(response.body, 'Erreur lors de la suppression'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Répondre : accept | refuse | ack.
|
||||
static Future<CarteBulle> repondre({
|
||||
required String cardId,
|
||||
|
||||
@@ -10,19 +10,28 @@ import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
|
||||
Future<CarteBulle?> showAjouterCarteModal(
|
||||
BuildContext context, {
|
||||
required String placementId,
|
||||
CarteBulle? carteToEdit,
|
||||
}) {
|
||||
return showDialog<CarteBulle>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => AjouterCarteModal(placementId: placementId),
|
||||
builder: (ctx) => AjouterCarteModal(
|
||||
placementId: placementId,
|
||||
carteToEdit: carteToEdit,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Modale générique : types API + catalogue UI, période (pas de motif V1).
|
||||
/// Modale générique : types API + catalogue UI, période, soins
|
||||
class AjouterCarteModal extends StatefulWidget {
|
||||
final String placementId;
|
||||
final CarteBulle? carteToEdit;
|
||||
|
||||
const AjouterCarteModal({super.key, required this.placementId});
|
||||
const AjouterCarteModal({
|
||||
super.key,
|
||||
required this.placementId,
|
||||
this.carteToEdit,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AjouterCarteModal> createState() => _AjouterCarteModalState();
|
||||
@@ -62,6 +71,57 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
_loadTypes();
|
||||
}
|
||||
|
||||
void _prefillIfEdit(List<CarteTypeEmissible> types) {
|
||||
if (widget.carteToEdit == null) return;
|
||||
final card = widget.carteToEdit!;
|
||||
final type = types.where((t) => t.code == card.typeCode).firstOrNull;
|
||||
if (type != null) {
|
||||
_selected = type;
|
||||
}
|
||||
|
||||
if (card.dateDebut != null) {
|
||||
_dateDebut = DateTime.tryParse(card.dateDebut!) ?? _dateDebut;
|
||||
}
|
||||
if (card.dateFin != null) {
|
||||
_dateFin = DateTime.tryParse(card.dateFin!) ?? _dateFin;
|
||||
}
|
||||
|
||||
_motifController.text = card.motif ?? '';
|
||||
|
||||
if (card.typeCode == 'soin_enfant') {
|
||||
final p = card.payload;
|
||||
_sousTypeCode = p['sous_type']?.toString();
|
||||
final h = p['heure_releve']?.toString();
|
||||
if (h != null && h.contains(':')) {
|
||||
final parts = h.split(':');
|
||||
_heureReleve = TimeOfDay(
|
||||
hour: int.tryParse(parts[0]) ?? 0,
|
||||
minute: int.tryParse(parts[1]) ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
if (_sousTypeCode == 'fievre') {
|
||||
_temperatureController.text = p['temperature_c']?.toString() ?? '';
|
||||
_medicamentDonneController.text = p['medicament_donne']?.toString() ?? '';
|
||||
} else if (_sousTypeCode == 'bobo') {
|
||||
final nature = p['bobo_nature']?.toString() ?? '';
|
||||
final naturesValides = ['Chute', 'Coup', 'Égratignure', 'Piqûre', 'Brûlure', 'Autre'];
|
||||
if (naturesValides.map((n) => n.toLowerCase().replaceAll('é', 'e').replaceAll('û', 'u')).contains(nature.toLowerCase())) {
|
||||
_boboNature = naturesValides.firstWhere(
|
||||
(n) => n.toLowerCase().replaceAll('é', 'e').replaceAll('û', 'u') == nature.toLowerCase().replaceAll('é', 'e').replaceAll('û', 'u'),
|
||||
orElse: () => 'Autre'
|
||||
);
|
||||
} else {
|
||||
_boboNature = 'Autre';
|
||||
}
|
||||
_boboDescriptionController.text = p['description']?.toString() ?? '';
|
||||
} else if (_sousTypeCode == 'medicament') {
|
||||
_medicamentNomController.text = p['medicament_nom']?.toString() ?? '';
|
||||
_medicamentContexteController.text = p['contexte']?.toString() ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_temperatureController.dispose();
|
||||
@@ -87,6 +147,8 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
_loadingTypes = false;
|
||||
if (types.isEmpty) {
|
||||
_error = 'Aucun type de carte disponible pour votre rôle.';
|
||||
} else {
|
||||
_prefillIfEdit(types);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -221,8 +283,6 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
/// (On retire `motifOptionnel` car le backend va dériver du payload).
|
||||
Map<String, dynamic>? _buildPayload() {
|
||||
if (_selected?.code != 'soin_enfant') {
|
||||
final m = _motifController.text.trim();
|
||||
if (m.isNotEmpty) return {'motif': m};
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -239,19 +299,15 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
}
|
||||
base['medicament_donne'] = _medicamentDonneController.text.trim();
|
||||
} else if (_sousTypeCode == 'bobo') {
|
||||
base['bobo_nature'] = _boboNature;
|
||||
// Normaliser l'enregistrement pour le back :
|
||||
final nature = _boboNature?.toLowerCase().replaceAll('é', 'e').replaceAll('û', 'u') ?? 'autre';
|
||||
base['bobo_nature'] = nature;
|
||||
base['description'] = _boboDescriptionController.text.trim();
|
||||
} else if (_sousTypeCode == 'medicament') {
|
||||
base['medicament_nom'] = _medicamentNomController.text.trim();
|
||||
base['contexte'] = _medicamentContexteController.text.trim();
|
||||
}
|
||||
|
||||
// Add motif to base payload for soin_enfant too
|
||||
final m = _motifController.text.trim();
|
||||
if (m.isNotEmpty) {
|
||||
base['motif'] = m;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -290,7 +346,23 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
final created = await CardsService.creer(
|
||||
final payload = _buildPayload();
|
||||
|
||||
final dynamic created;
|
||||
if (widget.carteToEdit != null) {
|
||||
created = await CardsService.maj(
|
||||
cardId: widget.carteToEdit!.id,
|
||||
dateDebut: _fmtIso(_dateDebut),
|
||||
dateFin: _fmtIso(
|
||||
_ui.periodeMode == CartePeriodeMode.singleDay
|
||||
? _dateDebut
|
||||
: _dateFin,
|
||||
),
|
||||
motif: _selected?.code == 'soin_enfant' ? null : _motifController.text.trim(),
|
||||
payload: payload,
|
||||
);
|
||||
} else {
|
||||
created = await CardsService.creer(
|
||||
typeCode: _selected!.code,
|
||||
idPlacement: widget.placementId,
|
||||
dateDebut: _fmtIso(_dateDebut),
|
||||
@@ -299,8 +371,11 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
? _dateDebut
|
||||
: _dateFin,
|
||||
),
|
||||
payload: _buildPayload(),
|
||||
motif: _selected?.code == 'soin_enfant' ? null : _motifController.text.trim(),
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop(created);
|
||||
} catch (e) {
|
||||
@@ -543,9 +618,10 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'Chute', child: Text('Chute')),
|
||||
DropdownMenuItem(value: 'Egratignure', child: Text('Égratignure')),
|
||||
DropdownMenuItem(value: 'Égratignure', child: Text('Égratignure')),
|
||||
DropdownMenuItem(value: 'Coup', child: Text('Coup')),
|
||||
DropdownMenuItem(value: 'Morsure', child: Text('Morsure')),
|
||||
DropdownMenuItem(value: 'Piqûre', child: Text('Piqûre')),
|
||||
DropdownMenuItem(value: 'Brûlure', child: Text('Brûlure')),
|
||||
DropdownMenuItem(value: 'Autre', child: Text('Autre')),
|
||||
],
|
||||
onChanged: _submitting
|
||||
@@ -583,23 +659,12 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
||||
enabled: !_submitting,
|
||||
maxLines: 2,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Contexte / Posologie *',
|
||||
labelText: 'Contexte / Posologie (optionnel)',
|
||||
hintText: 'ex: 2 bouffées suite à une quinte de toux',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _motifController,
|
||||
enabled: !_submitting,
|
||||
maxLines: 2,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Remarques additionnelles (optionnel)',
|
||||
hintText: 'Observations générales...',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1005,11 +1005,10 @@ class _BulleTile extends StatelessWidget {
|
||||
textStyle: GoogleFonts.merriweather(fontSize: 12),
|
||||
),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('La correction de carte sera implémentée ultérieurement.'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
showAjouterCarteModal(
|
||||
context,
|
||||
placementId: card.idPlacement,
|
||||
carteToEdit: card,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user