From 91f2c3e1efd48435501947dfc5d85b9375fde0e3 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 24 Sep 2026 16:04:34 +0200 Subject: [PATCH] =?UTF-8?q?fix(#170):=20brancher=20l=E2=80=99API=20couples?= =?UTF-8?q?-garde=20AM=20et=20unifier=20le=20bandeau?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Évite le 403 en appelant /assistantes-maternelles/me/couples-garde, parse parents[], et affiche « Prénom1 et Prénom2 » sans vignette. Co-authored-by: Cursor --- frontend/lib/models/couple_garde.dart | 19 +++++-- .../lib/screens/am/am_dashboard_screen.dart | 6 +- frontend/lib/services/api/api_config.dart | 6 +- .../lib/services/couple_garde_service.dart | 41 ++++++++++++++ .../quotidien/couple_selector_bandeau.dart | 55 +++++++++++++------ 5 files changed, 101 insertions(+), 26 deletions(-) diff --git a/frontend/lib/models/couple_garde.dart b/frontend/lib/models/couple_garde.dart index 1d1f1ab..0ec4f60 100644 --- a/frontend/lib/models/couple_garde.dart +++ b/frontend/lib/models/couple_garde.dart @@ -39,13 +39,15 @@ class CoupleMembre { class CoupleGarde { final String id; final CoupleMembre enfant; - final CoupleMembre am; + final CoupleMembre? am; + final List parents; final bool courant; const CoupleGarde({ required this.id, required this.enfant, - required this.am, + this.am, + this.parents = const [], this.courant = false, }); @@ -55,9 +57,15 @@ class CoupleGarde { enfant: CoupleMembre.fromJson( Map.from(json['enfant'] ?? const {}), ), - am: CoupleMembre.fromJson( - Map.from(json['am'] ?? const {}), - ), + am: json['am'] != null + ? CoupleMembre.fromJson(Map.from(json['am'])) + : null, + parents: json['parents'] != null + ? (json['parents'] as List) + .whereType() + .map((e) => CoupleMembre.fromJson(Map.from(e))) + .toList() + : const [], courant: json['courant'] == true, ); } @@ -67,6 +75,7 @@ class CoupleGarde { id: id, enfant: enfant, am: am, + parents: parents, courant: courant ?? this.courant, ); } diff --git a/frontend/lib/screens/am/am_dashboard_screen.dart b/frontend/lib/screens/am/am_dashboard_screen.dart index 5a2201e..959f656 100644 --- a/frontend/lib/screens/am/am_dashboard_screen.dart +++ b/frontend/lib/screens/am/am_dashboard_screen.dart @@ -45,9 +45,7 @@ class _AmDashboardScreenState extends State { _couplesError = null; }); try { - // TODO: Pour l'instant on utilise le service générique de test (CoupleGardeService.getCouplesGarde). - // Dans le futur, l'API pour l'AM (enfants_accueillis) fournira la vraie liste. - final res = await CoupleGardeService.getCouplesGarde(); + final res = await CoupleGardeService.getAmCouplesGarde(); if (!mounted) return; setState(() { _couples = res.couples; @@ -67,7 +65,7 @@ class _AmDashboardScreenState extends State { if (couple.id == _selectedCoupleId) return; setState(() => _selectedCoupleId = couple.id); try { - final res = await CoupleGardeService.definirCoupleCourant(couple.id); + final res = await CoupleGardeService.definirAmCoupleCourant(couple.id); if (!mounted) return; setState(() { _couples = res.couples; diff --git a/frontend/lib/services/api/api_config.dart b/frontend/lib/services/api/api_config.dart index f063386..4c67371 100644 --- a/frontend/lib/services/api/api_config.dart +++ b/frontend/lib/services/api/api_config.dart @@ -67,7 +67,11 @@ class ApiConfig { static const String parentsCoupleGardeCourant = '/parents/me/couples-garde/courant'; static const String assistantesMaternelles = '/assistantes-maternelles'; - /// Création dossier AM actif par le staff (#156) — body type register AM. + /// Couples de garde de l'AM connectée (#170 / #171). + static const String assistantesMaternellesCouplesGarde = + '/assistantes-maternelles/me/couples-garde'; + static const String assistantesMaternellesCoupleGardeCourant = + '/assistantes-maternelles/me/couples-garde/courant'; static const String assistantesMaternellesDossier = '/assistantes-maternelles/dossier'; static const String enfants = '/enfants'; diff --git a/frontend/lib/services/couple_garde_service.dart b/frontend/lib/services/couple_garde_service.dart index 83afcdf..974219a 100644 --- a/frontend/lib/services/couple_garde_service.dart +++ b/frontend/lib/services/couple_garde_service.dart @@ -73,4 +73,45 @@ class CoupleGardeService { Map.from(decoded as Map), ); } + + /// Liste les couples de garde et le couple courant de l'AM connectée. + static Future getAmCouplesGarde() async { + final response = await http.get( + Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternellesCouplesGarde}'), + headers: await _headers(), + ); + + if (response.statusCode != 200) { + throw Exception( + _extractError(response.body, 'Erreur chargement des couples de garde (AM)'), + ); + } + + final decoded = jsonDecode(response.body); + return CouplesGardeResponse.fromJson( + Map.from(decoded as Map), + ); + } + + /// Persiste le couple courant (AM) et renvoie la liste à jour. + static Future definirAmCoupleCourant( + String coupleId, + ) async { + final response = await http.put( + Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternellesCoupleGardeCourant}'), + headers: await _headers(), + body: jsonEncode({'couple_id': coupleId}), + ); + + if (response.statusCode != 200) { + throw Exception( + _extractError(response.body, 'Erreur sélection du couple de garde (AM)'), + ); + } + + final decoded = jsonDecode(response.body); + return CouplesGardeResponse.fromJson( + Map.from(decoded as Map), + ); + } } diff --git a/frontend/lib/widgets/quotidien/couple_selector_bandeau.dart b/frontend/lib/widgets/quotidien/couple_selector_bandeau.dart index 963d8b3..a95671c 100644 --- a/frontend/lib/widgets/quotidien/couple_selector_bandeau.dart +++ b/frontend/lib/widgets/quotidien/couple_selector_bandeau.dart @@ -37,7 +37,7 @@ class CoupleSelectorBandeau extends StatelessWidget { CoupleGarde? get _selected { if (couples.isEmpty) return null; - if (selectedCoupleId != null) { + if (selectedCoupleId != null) { for (final c in couples) { if (c.id == selectedCoupleId) return c; } @@ -133,17 +133,46 @@ class _CoupleCard extends StatelessWidget { required this.colorIndex, }); + /// Libellé parents côté AM : « Sophie » ou « Thomas et Claire ». + static String _parentsLabel(List parents) { + if (parents.isEmpty) return 'Parents'; + String prenomOf(CoupleMembre p) { + final prenom = (p.prenom ?? '').trim(); + if (prenom.isNotEmpty) return prenom; + return p.displayName(fallback: 'Parent'); + } + if (parents.length == 1) return prenomOf(parents.first); + return '${prenomOf(parents[0])} et ${prenomOf(parents[1])}'; + } + @override Widget build(BuildContext context) { - // Parent mode: enfant | AM - // AM mode: enfant | parent(s) + // Parent mode: enfant | AM (photo + prénom) + // AM mode: enfant | parent(s) — texte seul, pas de vignette parent final isParentMode = mode == CoupleBandeauMode.parent; - final fallbackRoleIcon = isParentMode ? Icons.volunteer_activism : Icons.person_outline; - final fallbackRoleName = isParentMode ? 'Nounou' : 'Parent'; - - // TODO: In AM mode, we should ideally display "parent1+parent2 empilés" or centered. - // For now, CoupleGarde only gives us `am` (which in AM mode might just represent one parent, or the system needs to feed both parents here). - // Assuming backend will populate `am` field with the parent data when called by AM. + + Widget amOrParentsWidget; + + if (isParentMode) { + amOrParentsWidget = _MembreTile( + photoUrl: couple.am?.photoUrl, + name: (couple.am?.prenom != null && couple.am!.prenom!.isNotEmpty) + ? couple.am!.prenom! + : (couple.am?.displayName(fallback: 'Nounou') ?? 'Nounou'), + fallbackIcon: Icons.volunteer_activism, + ); + } else { + amOrParentsWidget = Text( + _parentsLabel(couple.parents), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.merienda( + fontSize: 22, + fontWeight: FontWeight.bold, + color: QuotidienTheme.ink, + ), + ); + } return Container( height: 90, @@ -181,13 +210,7 @@ class _CoupleCard extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.only(left: 12), - child: _MembreTile( - photoUrl: couple.am.photoUrl, - name: (couple.am.prenom != null && couple.am.prenom!.isNotEmpty) - ? couple.am.prenom! - : couple.am.displayName(fallback: fallbackRoleName), - fallbackIcon: fallbackRoleIcon, - ), + child: amOrParentsWidget, ), ), SizedBox(