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 _dirty = false;
|
||||||
bool _saving = false;
|
bool _saving = false;
|
||||||
bool _deleting = false;
|
bool _deleting = false;
|
||||||
bool _placementBusy = false;
|
|
||||||
bool _loadingAm = true;
|
bool _loadingAm = true;
|
||||||
String? _currentUserRole;
|
String? _currentUserRole;
|
||||||
AssistanteMaternelleModel? _linkedAm;
|
AssistanteMaternelleModel? _linkedAm;
|
||||||
|
/// AM rattachée au chargement (pour sync différé au Sauvegarder).
|
||||||
|
String? _baselineAmUserId;
|
||||||
|
|
||||||
static const double _modalWidth = 930;
|
static const double _modalWidth = 930;
|
||||||
static const double _mainRowHeight = 380;
|
static const double _mainRowHeight = 380;
|
||||||
@ -74,7 +75,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
bool get _canDelete =>
|
bool get _canDelete =>
|
||||||
(_currentUserRole ?? '').toLowerCase() == 'super_admin';
|
(_currentUserRole ?? '').toLowerCase() == 'super_admin';
|
||||||
|
|
||||||
bool get _busy => _saving || _deleting || _placementBusy;
|
bool get _busy => _saving || _deleting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -113,11 +114,15 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_linkedAm = am;
|
_linkedAm = am;
|
||||||
|
_baselineAmUserId = am?.user.id;
|
||||||
_loadingAm = false;
|
_loadingAm = false;
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _loadingAm = false);
|
setState(() {
|
||||||
|
_baselineAmUserId = null;
|
||||||
|
_loadingAm = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +253,8 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
if (!_dirty) return;
|
if (!_dirty) return;
|
||||||
setState(() => _saving = true);
|
setState(() => _saving = true);
|
||||||
try {
|
try {
|
||||||
|
await _syncAmPlacement();
|
||||||
|
|
||||||
await UserService.updateEnfant(
|
await UserService.updateEnfant(
|
||||||
enfantId: widget.enfant.id,
|
enfantId: widget.enfant.id,
|
||||||
body: {
|
body: {
|
||||||
@ -268,6 +275,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_dirty = false;
|
_dirty = false;
|
||||||
_saving = false;
|
_saving = false;
|
||||||
|
_baselineAmUserId = _linkedAm?.user.id;
|
||||||
});
|
});
|
||||||
widget.onSaved?.call();
|
widget.onSaved?.call();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
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 {
|
Future<void> _delete() async {
|
||||||
if (!_canDelete || _deleting) return;
|
if (!_canDelete || _deleting) return;
|
||||||
|
|
||||||
@ -338,14 +365,39 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AdminAmEditModal(
|
builder: (ctx) => AdminAmEditModal(
|
||||||
assistante: am,
|
assistante: am,
|
||||||
onSaved: () {
|
onSaved: () async {
|
||||||
_loadLinkedAm();
|
await _reloadPlacementFromServer();
|
||||||
widget.onSaved?.call();
|
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 {
|
Future<void> _attachAm() async {
|
||||||
List<AssistanteMaternelleModel> all;
|
List<AssistanteMaternelleModel> all;
|
||||||
try {
|
try {
|
||||||
@ -368,8 +420,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final candidates = all;
|
if (all.isEmpty) {
|
||||||
if (candidates.isEmpty) {
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Aucune assistante maternelle disponible')),
|
const SnackBar(content: Text('Aucune assistante maternelle disponible')),
|
||||||
@ -382,7 +433,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => SimpleDialog(
|
builder: (ctx) => SimpleDialog(
|
||||||
title: const Text('Choisir une assistante maternelle'),
|
title: const Text('Choisir une assistante maternelle'),
|
||||||
children: candidates
|
children: all
|
||||||
.map(
|
.map(
|
||||||
(am) => SimpleDialogOption(
|
(am) => SimpleDialogOption(
|
||||||
onPressed: () => Navigator.pop(ctx, am),
|
onPressed: () => Navigator.pop(ctx, am),
|
||||||
@ -394,41 +445,13 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
);
|
);
|
||||||
if (selected == null || !mounted) return;
|
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(() {
|
setState(() {
|
||||||
// Le back passe déjà sans_garde → garde à l'attach ; on aligne l'UI.
|
_linkedAm = selected;
|
||||||
if (_status == 'sans_garde') {
|
if (_status == 'sans_garde') {
|
||||||
_status = '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 {
|
Future<void> _detachAm() async {
|
||||||
@ -440,7 +463,8 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: const Text('Détacher l\'assistante'),
|
title: const Text('Détacher l\'assistante'),
|
||||||
content: Text(
|
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: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -457,57 +481,22 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
);
|
);
|
||||||
if (confirmed != true || !mounted) return;
|
if (confirmed != true || !mounted) return;
|
||||||
|
|
||||||
setState(() => _placementBusy = true);
|
|
||||||
try {
|
|
||||||
await UserService.detachEnfantFromAm(
|
|
||||||
amUserId: am.user.id,
|
|
||||||
enfantId: widget.enfant.id,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
// Le back repasse en sans_garde sauf a_naitre / scolarise.
|
_linkedAm = null;
|
||||||
if (_status != 'a_naitre' && _status != 'scolarise') {
|
if (_status != 'a_naitre' && _status != 'scolarise') {
|
||||||
_status = 'sans_garde';
|
_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.
|
/// Passe en « Sans garde » : cadre vide local (détachement au Sauvegarder).
|
||||||
Future<void> _applySansGardeStatus() async {
|
void _applySansGardeStatus() {
|
||||||
final am = _linkedAm;
|
if (_linkedAm == null) return;
|
||||||
if (am == null) return;
|
setState(() {
|
||||||
|
_linkedAm = null;
|
||||||
setState(() => _placementBusy = true);
|
_dirty = 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDecoration _inputDecoration({String? hint}) {
|
InputDecoration _inputDecoration({String? hint}) {
|
||||||
@ -952,7 +941,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
Widget _placementSection() {
|
Widget _placementSection() {
|
||||||
if (!_showsAmPlacement) return _scolariseCard();
|
if (!_showsAmPlacement) return _scolariseCard();
|
||||||
|
|
||||||
if (_loadingAm || _placementBusy) {
|
if (_loadingAm) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: _placementHeight,
|
height: _placementHeight,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user