From 267fe63aec2c9432d7a46fdc386b58ced143e57e Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Fri, 17 Jul 2026 13:12:46 +0200 Subject: [PATCH] =?UTF-8?q?fix(#138):=20diff=C3=A9rer=20le=20rattachement?= =?UTF-8?q?=20AM=20de=20la=20fiche=20enfant=20au=20Sauvegarder.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attach/détach AM restent locaux comme sur la fiche AM ; la sync API n'a lieu qu'à l'enregistrement, avec rechargement du statut au retour de la fiche AM. Co-authored-by: Cursor --- .../common/admin_child_detail_modal.dart | 175 ++++++++---------- 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/frontend/lib/widgets/admin/common/admin_child_detail_modal.dart b/frontend/lib/widgets/admin/common/admin_child_detail_modal.dart index a08948e..b1dac62 100644 --- a/frontend/lib/widgets/admin/common/admin_child_detail_modal.dart +++ b/frontend/lib/widgets/admin/common/admin_child_detail_modal.dart @@ -42,10 +42,11 @@ class _AdminChildDetailModalState extends State { bool _dirty = false; bool _saving = false; bool _deleting = false; - bool _placementBusy = false; bool _loadingAm = true; String? _currentUserRole; AssistanteMaternelleModel? _linkedAm; + /// AM rattachée au chargement (pour sync différé au Sauvegarder). + String? _baselineAmUserId; static const double _modalWidth = 930; static const double _mainRowHeight = 380; @@ -74,7 +75,7 @@ class _AdminChildDetailModalState extends State { bool get _canDelete => (_currentUserRole ?? '').toLowerCase() == 'super_admin'; - bool get _busy => _saving || _deleting || _placementBusy; + bool get _busy => _saving || _deleting; @override void initState() { @@ -113,11 +114,15 @@ class _AdminChildDetailModalState extends State { if (!mounted) return; setState(() { _linkedAm = am; + _baselineAmUserId = am?.user.id; _loadingAm = false; }); } catch (_) { if (!mounted) return; - setState(() => _loadingAm = false); + setState(() { + _baselineAmUserId = null; + _loadingAm = false; + }); } } @@ -248,6 +253,8 @@ class _AdminChildDetailModalState extends State { if (!_dirty) return; setState(() => _saving = true); try { + await _syncAmPlacement(); + await UserService.updateEnfant( enfantId: widget.enfant.id, body: { @@ -268,6 +275,7 @@ class _AdminChildDetailModalState extends State { setState(() { _dirty = false; _saving = false; + _baselineAmUserId = _linkedAm?.user.id; }); widget.onSaved?.call(); ScaffoldMessenger.of(context).showSnackBar( @@ -282,6 +290,25 @@ class _AdminChildDetailModalState extends State { } } + /// Applique rattachement / détachement AM (différé jusqu'au Sauvegarder). + Future _syncAmPlacement() async { + final baselineId = _baselineAmUserId; + final currentId = _linkedAm?.user.id; + + if (baselineId != null && baselineId != currentId) { + await UserService.detachEnfantFromAm( + amUserId: baselineId, + enfantId: widget.enfant.id, + ); + } + if (currentId != null && currentId != baselineId) { + await UserService.attachEnfantToAm( + amUserId: currentId, + enfantId: widget.enfant.id, + ); + } + } + Future _delete() async { if (!_canDelete || _deleting) return; @@ -338,14 +365,39 @@ class _AdminChildDetailModalState extends State { context: context, builder: (ctx) => AdminAmEditModal( assistante: am, - onSaved: () { - _loadLinkedAm(); + onSaved: () async { + await _reloadPlacementFromServer(); widget.onSaved?.call(); }, ), ); } + /// Recharge AM + statut après une modale externe (ex. fiche AM). + Future _reloadPlacementFromServer() async { + try { + final results = await Future.wait([ + UserService.findAmForEnfant(widget.enfant.id), + UserService.getEnfant(widget.enfant.id), + ]); + if (!mounted) return; + final am = results[0] as AssistanteMaternelleModel?; + final enfant = results[1] as EnfantAdminModel; + setState(() { + _linkedAm = am; + _baselineAmUserId = am?.user.id; + _status = normalizeEnfantStatus(enfant.status); + if (!enfantStatusValues.contains(_status)) { + _status = 'sans_garde'; + } + _coerceGenderForStatus(); + }); + } catch (_) { + if (!mounted) return; + await _loadLinkedAm(); + } + } + Future _attachAm() async { List all; try { @@ -368,8 +420,7 @@ class _AdminChildDetailModalState extends State { return; } - final candidates = all; - if (candidates.isEmpty) { + if (all.isEmpty) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Aucune assistante maternelle disponible')), @@ -382,7 +433,7 @@ class _AdminChildDetailModalState extends State { context: context, builder: (ctx) => SimpleDialog( title: const Text('Choisir une assistante maternelle'), - children: candidates + children: all .map( (am) => SimpleDialogOption( onPressed: () => Navigator.pop(ctx, am), @@ -394,41 +445,13 @@ class _AdminChildDetailModalState extends State { ); if (selected == null || !mounted) return; - setState(() => _placementBusy = true); - try { - // Sans garde : éventuel lien résiduel à clôturer avant rattachement. - final previousAm = _linkedAm; - if (previousAm != null) { - await UserService.detachEnfantFromAm( - amUserId: previousAm.user.id, - enfantId: widget.enfant.id, - ); + setState(() { + _linkedAm = selected; + if (_status == 'sans_garde') { + _status = 'garde'; } - await UserService.attachEnfantToAm( - amUserId: selected.user.id, - enfantId: widget.enfant.id, - ); - if (!mounted) return; - setState(() { - // Le back passe déjà sans_garde → garde à l'attach ; on aligne l'UI. - if (_status == 'sans_garde') { - _status = 'garde'; - } - }); - await _loadLinkedAm(); - widget.onSaved?.call(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Assistante maternelle rattachée')), - ); - } catch (e) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), - ); - } finally { - if (mounted) setState(() => _placementBusy = false); - } + _dirty = true; + }); } Future _detachAm() async { @@ -440,7 +463,8 @@ class _AdminChildDetailModalState extends State { builder: (ctx) => AlertDialog( title: const Text('Détacher l\'assistante'), content: Text( - 'Retirer ${am.user.fullName} de la garde de cet enfant ?', + 'Retirer ${am.user.fullName} de la garde de cet enfant ?\n' + '(Effectif après Sauvegarder.)', ), actions: [ TextButton( @@ -457,57 +481,22 @@ class _AdminChildDetailModalState extends State { ); if (confirmed != true || !mounted) return; - setState(() => _placementBusy = true); - try { - await UserService.detachEnfantFromAm( - amUserId: am.user.id, - enfantId: widget.enfant.id, - ); - if (!mounted) return; - setState(() { - // Le back repasse en sans_garde sauf a_naitre / scolarise. - if (_status != 'a_naitre' && _status != 'scolarise') { - _status = 'sans_garde'; - } - _linkedAm = null; - }); - widget.onSaved?.call(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Assistante maternelle détachée')), - ); - } catch (e) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), - ); - } finally { - if (mounted) setState(() => _placementBusy = false); - } + setState(() { + _linkedAm = null; + if (_status != 'a_naitre' && _status != 'scolarise') { + _status = 'sans_garde'; + } + _dirty = true; + }); } - /// Passe en « Sans garde » : cadre vide + détachement AM si besoin. - Future _applySansGardeStatus() async { - final am = _linkedAm; - if (am == null) return; - - setState(() => _placementBusy = true); - try { - await UserService.detachEnfantFromAm( - amUserId: am.user.id, - enfantId: widget.enfant.id, - ); - if (!mounted) return; - setState(() => _linkedAm = null); - widget.onSaved?.call(); - } catch (e) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), - ); - } finally { - if (mounted) setState(() => _placementBusy = false); - } + /// Passe en « Sans garde » : cadre vide local (détachement au Sauvegarder). + void _applySansGardeStatus() { + if (_linkedAm == null) return; + setState(() { + _linkedAm = null; + _dirty = true; + }); } InputDecoration _inputDecoration({String? hint}) { @@ -952,7 +941,7 @@ class _AdminChildDetailModalState extends State { Widget _placementSection() { if (!_showsAmPlacement) return _scolariseCard(); - if (_loadingAm || _placementBusy) { + if (_loadingAm) { return SizedBox( height: _placementHeight, width: double.infinity,