fix(#131): activer Sauvegarder sur statut, enfants et champs AM.

Compare l'état de la fiche à un snapshot initial (liste enfants, statut, places, formulaire) et corrige la gélule statut pour les valeurs hors liste.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-07-07 23:11:24 +02:00
parent 66c7f22280
commit d03a8e6c8b
2 changed files with 100 additions and 18 deletions

View File

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:p_tits_pas/models/assistante_maternelle_model.dart'; import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
@ -57,6 +58,25 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
bool _saving = false; bool _saving = false;
bool _dirty = false; bool _dirty = false;
late String _baseStatut;
late bool _baseDisponible;
late int? _basePlacesAvailable;
late List<String> _baseChildIds;
late String _baseNom;
late String _basePrenom;
late String _baseEmail;
late String _baseTelephone;
late String _baseAdresse;
late String _baseVille;
late String _baseCodePostal;
late String _baseAgrement;
late String _baseNir;
late String? _baseDateNaissanceIso;
late String _baseLieuNaissanceVille;
late String _baseLieuNaissancePays;
late String? _baseAgreementDateIso;
late int? _baseCapacite;
static const double _modalWidth = 930; static const double _modalWidth = 930;
static const double _photoProGap = 24; static const double _photoProGap = 24;
static const double _proColumnMinWidth = 260; static const double _proColumnMinWidth = 260;
@ -147,16 +167,77 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
_nomCtrl.addListener(_onNameFieldChanged); _nomCtrl.addListener(_onNameFieldChanged);
_prenomCtrl.addListener(_onNameFieldChanged); _prenomCtrl.addListener(_onNameFieldChanged);
_tabCtrl.addListener(_onTabChanged); _tabCtrl.addListener(_onTabChanged);
_captureBaseline();
_reloadChildren(); _reloadChildren();
} }
void _captureBaseline() {
_baseStatut = _statut;
_baseDisponible = _disponible;
_basePlacesAvailable = _placesAvailable;
_baseChildIds = _children.map((c) => c.id).toList()..sort();
_baseNom = _nomCtrl.text.trim();
_basePrenom = _prenomCtrl.text.trim();
_baseEmail = _emailCtrl.text.trim();
_baseTelephone = normalizePhone(_telCtrl.text);
_baseAdresse = _adresseCtrl.text.trim();
_baseVille = _villeCtrl.text.trim();
_baseCodePostal = _cpCtrl.text.trim();
_baseAgrement = _agrementCtrl.text.trim();
_baseNir = nirToRaw(_nirCtrl.text);
_baseDateNaissanceIso = _frDateToIso(_dateNaissanceCtrl.text);
_baseLieuNaissanceVille = _lieuNaissanceVilleCtrl.text.trim();
_baseLieuNaissancePays = _lieuNaissancePaysCtrl.text.trim();
_baseAgreementDateIso = _frDateToIso(_dateAgrementCtrl.text);
_baseCapacite = _capaciteMax();
}
bool _hasUnsavedChanges() {
if (_statut != _baseStatut) return true;
if (_disponible != _baseDisponible) return true;
if (_placesAvailable != _basePlacesAvailable) return true;
final childIds = _children.map((c) => c.id).toList()..sort();
if (!listEquals(childIds, _baseChildIds)) return true;
if (_nomCtrl.text.trim() != _baseNom) return true;
if (_prenomCtrl.text.trim() != _basePrenom) return true;
if (_emailCtrl.text.trim() != _baseEmail) return true;
if (normalizePhone(_telCtrl.text) != _baseTelephone) return true;
if (_adresseCtrl.text.trim() != _baseAdresse) return true;
if (_villeCtrl.text.trim() != _baseVille) return true;
if (_cpCtrl.text.trim() != _baseCodePostal) return true;
if (_agrementCtrl.text.trim() != _baseAgrement) return true;
if (nirToRaw(_nirCtrl.text) != _baseNir) return true;
if (_frDateToIso(_dateNaissanceCtrl.text) != _baseDateNaissanceIso) {
return true;
}
if (_lieuNaissanceVilleCtrl.text.trim() != _baseLieuNaissanceVille) {
return true;
}
if (_lieuNaissancePaysCtrl.text.trim() != _baseLieuNaissancePays) {
return true;
}
if (_frDateToIso(_dateAgrementCtrl.text) != _baseAgreementDateIso) {
return true;
}
if (_capaciteMax() != _baseCapacite) return true;
return false;
}
void _syncDirtyState() {
final next = _hasUnsavedChanges();
if (next != _dirty) setState(() => _dirty = next);
}
void _onTabChanged() { void _onTabChanged() {
if (!_tabCtrl.indexIsChanging) setState(() {}); if (!_tabCtrl.indexIsChanging) setState(() {});
} }
void _onNameFieldChanged() => setState(() {}); void _onNameFieldChanged() => setState(() {});
void _onCapacityChanged() => setState(() {}); void _onCapacityChanged() {
setState(() {});
_syncDirtyState();
}
@override @override
void dispose() { void dispose() {
@ -182,9 +263,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
super.dispose(); super.dispose();
} }
void _markDirty() { void _markDirty() => _syncDirtyState();
if (!_dirty) setState(() => _dirty = true);
}
String _headerTitle() { String _headerTitle() {
final fn = _prenomCtrl.text.trim(); final fn = _prenomCtrl.text.trim();
@ -240,10 +319,8 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
void _applyPlacesCorrection() { void _applyPlacesCorrection() {
final expected = _computedPlacesAvailable(); final expected = _computedPlacesAvailable();
if (expected == null) return; if (expected == null) return;
setState(() { setState(() => _placesAvailable = expected);
_placesAvailable = expected; _syncDirtyState();
_dirty = true;
});
} }
String? _placesInconsistencyMessage() { String? _placesInconsistencyMessage() {
@ -318,6 +395,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
_dirty = false; _dirty = false;
_saving = false; _saving = false;
}); });
_captureBaseline();
widget.onSaved?.call(); widget.onSaved?.call();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fiche assistante maternelle enregistrée')), const SnackBar(content: Text('Fiche assistante maternelle enregistrée')),
@ -346,6 +424,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
if (!mounted) return; if (!mounted) return;
setState(() => _children = kids); setState(() => _children = kids);
_syncDirtyState();
} catch (_) {} } catch (_) {}
} }
@ -574,10 +653,10 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
style: TextStyle(fontSize: 13), style: TextStyle(fontSize: 13),
), ),
value: _disponible, value: _disponible,
onChanged: (v) => setState(() { onChanged: (v) {
_disponible = v; setState(() => _disponible = v);
_dirty = true; _syncDirtyState();
}), },
), ),
], ],
), ),
@ -756,10 +835,10 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
width: 160, width: 160,
child: AdminStatusCapsule( child: AdminStatusCapsule(
statut: _statut, statut: _statut,
onChanged: (v) => setState(() { onChanged: (v) {
_statut = v; setState(() => _statut = v);
_dirty = true; _syncDirtyState();
}), },
), ),
), ),
IconButton( IconButton(

View File

@ -30,7 +30,10 @@ class AdminStatusCapsule extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final value = statuts.contains(statut) ? statut : statuts.first; final value = statut;
final items = statuts.contains(statut)
? statuts
: <String>[statut, ...statuts];
return Container( return Container(
height: 34, height: 34,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -46,7 +49,7 @@ class AdminStatusCapsule extends StatelessWidget {
isDense: true, isDense: true,
style: const TextStyle(fontSize: 13, color: Colors.black87), style: const TextStyle(fontSize: 13, color: Colors.black87),
icon: const Icon(Icons.arrow_drop_down, size: 20), icon: const Icon(Icons.arrow_drop_down, size: 20),
items: statuts items: items
.map( .map(
(s) => DropdownMenuItem( (s) => DropdownMenuItem(
value: s, value: s,