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) {
|
switch (raw) {
|
||||||
case 'refusee':
|
case 'refusee':
|
||||||
return CarteStatut.refusee;
|
return CarteStatut.refusee;
|
||||||
|
case 'fermee':
|
||||||
case 'traitee':
|
case 'traitee':
|
||||||
return CarteStatut.traitee;
|
return CarteStatut.traitee;
|
||||||
case 'ouverte':
|
case 'ouverte':
|
||||||
@@ -113,23 +114,7 @@ class CarteBulle {
|
|||||||
/// Helper pour extraire les informations de soin
|
/// Helper pour extraire les informations de soin
|
||||||
String? get resumerSoin {
|
String? get resumerSoin {
|
||||||
if (!isSoinEnfant) return null;
|
if (!isSoinEnfant) return null;
|
||||||
|
return payload['motif']?.toString();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
/// Motif de refus : réponse API, sinon motif payload (co-parent / reload).
|
||||||
|
|||||||
@@ -49,18 +49,14 @@ class CarteTypeUi {
|
|||||||
label: 'Arrêt maladie',
|
label: 'Arrêt maladie',
|
||||||
motifHint: 'Précisions (optionnel)',
|
motifHint: 'Précisions (optionnel)',
|
||||||
),
|
),
|
||||||
// Point d’extension #soins — visible dès que le back l’émet.
|
|
||||||
'soin_enfant': CarteTypeUi(
|
'soin_enfant': CarteTypeUi(
|
||||||
typeCode: 'soin_enfant',
|
typeCode: 'soin_enfant',
|
||||||
label: 'Soin / bobo',
|
label: 'Soin / bobo',
|
||||||
motifHint: 'Décrivez le soin ou le bobo',
|
|
||||||
motifRequired: true,
|
|
||||||
periodeMode: CartePeriodeMode.singleDay,
|
periodeMode: CartePeriodeMode.singleDay,
|
||||||
sousTypes: [
|
sousTypes: [
|
||||||
CarteSousTypeUi(code: 'temperature', label: 'Température'),
|
CarteSousTypeUi(code: 'fievre', label: 'Fièvre'),
|
||||||
CarteSousTypeUi(code: 'bobo', label: 'Bobo'),
|
CarteSousTypeUi(code: 'bobo', label: 'Bobo'),
|
||||||
CarteSousTypeUi(code: 'medicament', label: 'Médicament'),
|
CarteSousTypeUi(code: 'medicament', label: 'Médicament'),
|
||||||
CarteSousTypeUi(code: 'autre', label: 'Autre'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class CardsService {
|
|||||||
required String dateDebut,
|
required String dateDebut,
|
||||||
required String dateFin,
|
required String dateFin,
|
||||||
String? motif,
|
String? motif,
|
||||||
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}'),
|
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}'),
|
||||||
@@ -101,6 +102,7 @@ class CardsService {
|
|||||||
'date_debut': dateDebut,
|
'date_debut': dateDebut,
|
||||||
'date_fin': dateFin,
|
'date_fin': dateFin,
|
||||||
if (motif != null && motif.trim().isNotEmpty) 'motif': motif.trim(),
|
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));
|
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.
|
/// Répondre : accept | refuse | ack.
|
||||||
static Future<CarteBulle> repondre({
|
static Future<CarteBulle> repondre({
|
||||||
required String cardId,
|
required String cardId,
|
||||||
|
|||||||
@@ -10,19 +10,28 @@ import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
|
|||||||
Future<CarteBulle?> showAjouterCarteModal(
|
Future<CarteBulle?> showAjouterCarteModal(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String placementId,
|
required String placementId,
|
||||||
|
CarteBulle? carteToEdit,
|
||||||
}) {
|
}) {
|
||||||
return showDialog<CarteBulle>(
|
return showDialog<CarteBulle>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
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 {
|
class AjouterCarteModal extends StatefulWidget {
|
||||||
final String placementId;
|
final String placementId;
|
||||||
|
final CarteBulle? carteToEdit;
|
||||||
|
|
||||||
const AjouterCarteModal({super.key, required this.placementId});
|
const AjouterCarteModal({
|
||||||
|
super.key,
|
||||||
|
required this.placementId,
|
||||||
|
this.carteToEdit,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AjouterCarteModal> createState() => _AjouterCarteModalState();
|
State<AjouterCarteModal> createState() => _AjouterCarteModalState();
|
||||||
@@ -62,6 +71,57 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
_loadTypes();
|
_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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_temperatureController.dispose();
|
_temperatureController.dispose();
|
||||||
@@ -87,6 +147,8 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
_loadingTypes = false;
|
_loadingTypes = false;
|
||||||
if (types.isEmpty) {
|
if (types.isEmpty) {
|
||||||
_error = 'Aucun type de carte disponible pour votre rôle.';
|
_error = 'Aucun type de carte disponible pour votre rôle.';
|
||||||
|
} else {
|
||||||
|
_prefillIfEdit(types);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -221,8 +283,6 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
/// (On retire `motifOptionnel` car le backend va dériver du payload).
|
/// (On retire `motifOptionnel` car le backend va dériver du payload).
|
||||||
Map<String, dynamic>? _buildPayload() {
|
Map<String, dynamic>? _buildPayload() {
|
||||||
if (_selected?.code != 'soin_enfant') {
|
if (_selected?.code != 'soin_enfant') {
|
||||||
final m = _motifController.text.trim();
|
|
||||||
if (m.isNotEmpty) return {'motif': m};
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,20 +298,16 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
base['temperature_c'] = double.tryParse(t);
|
base['temperature_c'] = double.tryParse(t);
|
||||||
}
|
}
|
||||||
base['medicament_donne'] = _medicamentDonneController.text.trim();
|
base['medicament_donne'] = _medicamentDonneController.text.trim();
|
||||||
} else if (_sousTypeCode == 'bobo') {
|
} 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();
|
base['description'] = _boboDescriptionController.text.trim();
|
||||||
} else if (_sousTypeCode == 'medicament') {
|
} else if (_sousTypeCode == 'medicament') {
|
||||||
base['medicament_nom'] = _medicamentNomController.text.trim();
|
base['medicament_nom'] = _medicamentNomController.text.trim();
|
||||||
base['contexte'] = _medicamentContexteController.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;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,17 +346,36 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
_error = null;
|
_error = null;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final created = await CardsService.creer(
|
final payload = _buildPayload();
|
||||||
typeCode: _selected!.code,
|
|
||||||
idPlacement: widget.placementId,
|
final dynamic created;
|
||||||
dateDebut: _fmtIso(_dateDebut),
|
if (widget.carteToEdit != null) {
|
||||||
dateFin: _fmtIso(
|
created = await CardsService.maj(
|
||||||
_ui.periodeMode == CartePeriodeMode.singleDay
|
cardId: widget.carteToEdit!.id,
|
||||||
? _dateDebut
|
dateDebut: _fmtIso(_dateDebut),
|
||||||
: _dateFin,
|
dateFin: _fmtIso(
|
||||||
),
|
_ui.periodeMode == CartePeriodeMode.singleDay
|
||||||
payload: _buildPayload(),
|
? _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),
|
||||||
|
dateFin: _fmtIso(
|
||||||
|
_ui.periodeMode == CartePeriodeMode.singleDay
|
||||||
|
? _dateDebut
|
||||||
|
: _dateFin,
|
||||||
|
),
|
||||||
|
motif: _selected?.code == 'soin_enfant' ? null : _motifController.text.trim(),
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.of(context).pop(created);
|
Navigator.of(context).pop(created);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -543,9 +618,10 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
),
|
),
|
||||||
items: const [
|
items: const [
|
||||||
DropdownMenuItem(value: 'Chute', child: Text('Chute')),
|
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: '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')),
|
DropdownMenuItem(value: 'Autre', child: Text('Autre')),
|
||||||
],
|
],
|
||||||
onChanged: _submitting
|
onChanged: _submitting
|
||||||
@@ -583,23 +659,12 @@ class _AjouterCarteModalState extends State<AjouterCarteModal> {
|
|||||||
enabled: !_submitting,
|
enabled: !_submitting,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Contexte / Posologie *',
|
labelText: 'Contexte / Posologie (optionnel)',
|
||||||
hintText: 'ex: 2 bouffées suite à une quinte de toux',
|
hintText: 'ex: 2 bouffées suite à une quinte de toux',
|
||||||
border: OutlineInputBorder(),
|
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),
|
textStyle: GoogleFonts.merriweather(fontSize: 12),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAjouterCarteModal(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text('La correction de carte sera implémentée ultérieurement.'),
|
placementId: card.idPlacement,
|
||||||
duration: Duration(seconds: 2),
|
carteToEdit: card,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user