fix(#138): différer le rattachement AM de la fiche enfant au Sauvegarder.
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 <cursoragent@cursor.com>
This commit is contained in:
parent
90b185740c
commit
267fe63aec
@ -42,10 +42,11 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
setState(() {
|
||||
_dirty = false;
|
||||
_saving = false;
|
||||
_baselineAmUserId = _linkedAm?.user.id;
|
||||
});
|
||||
widget.onSaved?.call();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@ -282,6 +290,25 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applique rattachement / détachement AM (différé jusqu'au Sauvegarder).
|
||||
Future<void> _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<void> _delete() async {
|
||||
if (!_canDelete || _deleting) return;
|
||||
|
||||
@ -338,14 +365,39 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
||||
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<void> _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<void> _attachAm() async {
|
||||
List<AssistanteMaternelleModel> all;
|
||||
try {
|
||||
@ -368,8 +420,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
);
|
||||
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,
|
||||
);
|
||||
}
|
||||
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.
|
||||
_linkedAm = selected;
|
||||
if (_status == 'sans_garde') {
|
||||
_status = 'garde';
|
||||
}
|
||||
_dirty = true;
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _detachAm() async {
|
||||
@ -440,7 +463,8 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
||||
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<AdminChildDetailModal> {
|
||||
);
|
||||
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.
|
||||
_linkedAm = null;
|
||||
if (_status != 'a_naitre' && _status != 'scolarise') {
|
||||
_status = 'sans_garde';
|
||||
}
|
||||
_linkedAm = null;
|
||||
_dirty = true;
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Passe en « Sans garde » : cadre vide + détachement AM si besoin.
|
||||
Future<void> _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<AdminChildDetailModal> {
|
||||
Widget _placementSection() {
|
||||
if (!_showsAmPlacement) return _scolariseCard();
|
||||
|
||||
if (_loadingAm || _placementBusy) {
|
||||
if (_loadingAm) {
|
||||
return SizedBox(
|
||||
height: _placementHeight,
|
||||
width: double.infinity,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user