Compare commits

...
Author SHA1 Message Date
jmartinandCursor a312d9c0fa refactor(#155): option C — widgets partagés sans préfixe Admin.
Déplace les composants Admin* du dashboard partagé vers
widgets/dashboard/ (ChildDetailModal, AmEditModal, UserCard, Select*, …).
Conserve AdminManagementWidget et screens/administrateurs. Rename mécanique,
aucun changement de comportement.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 10:57:58 +02:00
jmartin b5b32062b3 merge(#152): suppression grossesse multiple / est_multiple. 2026-09-14 10:57:28 +02:00
29 changed files with 208 additions and 156 deletions
@@ -0,0 +1,52 @@
# Mini-spec — Rename préfixe `Admin*` dashboard partagé (#155)
**Ticket** : **#155**
**Branche** : `feature/155-rename-admin-prefix-dashboard` (depuis `develop`)
**Décision naming** : **option C** — dossier neutre `widgets/dashboard/` + noms **sans** préfixe `Admin`.
---
## Principe
Les widgets partagés **admin + gestionnaire** ne doivent plus sappeler `Admin*`.
On garde `Admin*` seulement là où cest vraiment le rôle administrateur.
## Gardé `Admin*` (hors rename)
| Élément | Raison |
|---------|--------|
| `AdminManagementWidget` | Onglet **Administrateurs** |
| `screens/administrateurs/*` | `AdminDashboardScreen`, `AdminCreateDialog`, `AdminUserFormDialog` |
| `EnfantAdminModel` | Modèle API (pas un widget) — hors scope ticket |
## Renames faits
| Avant | Après |
|-------|--------|
| `widgets/admin/common/admin_child_detail_modal.dart``AdminChildDetailModal` | `widgets/dashboard/child_detail_modal.dart``ChildDetailModal` |
| `admin_am_edit_modal``AdminAmEditModal` | `am_edit_modal``AmEditModal` |
| `admin_parent_edit_modal``AdminParentEditModal` | `parent_edit_modal``ParentEditModal` |
| `admin_user_card``AdminUserCard` | `user_card``UserCard` |
| `admin_enfant_user_card``AdminEnfantUserCard` | `enfant_user_card``EnfantUserCard` |
| `admin_am_photo_frame``AdminAmPhotoFrame` | `am_photo_frame``AmPhotoFrame` |
| `admin_am_children_capacity_grid` | `am_children_capacity_grid``AmChildrenCapacityGrid` |
| `admin_children_affiliation_panel` | `children_affiliation_panel``ChildrenAffiliationPanel` |
| `admin_select_*` / `AdminSelect*` / `AdminFamilleFoyer` | `select_*` / `Select*` / `FamilleFoyer` |
| `admin_status_capsule` | `status_capsule``StatusCapsule` |
| `admin_list_state``AdminListState` | `user_list_state``UserListState` |
| `admin_detail_modal``AdminDetailModal` / `AdminDetailField` | `detail_modal``DetailModal` / `DetailField` |
| `dashboard_admin.dart` | `user_management_sub_bar.dart` (`DashboardUserManagementSubBar` inchangé) |
Dossier `widgets/admin/` conserve les panels métier (`user_management_panel`, wizards, etc.) + `AdminManagementWidget`.
## Hors scope
- Refonte UX modales (ticket dédié)
- Rename API / back
- Déplacer tout `widgets/admin/``widgets/dashboard/` (panels) — possible follow-up
## Critères
- [x] Plus de préfixe `Admin` sur les composants **partagés** listés
- [ ] Build Flutter / recette dashboard admin + gestionnaire OK
- [x] Pas de changement comportemental (rename mécanique)
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/user.dart'; import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/configuration_service.dart'; import 'package:p_tits_pas/services/configuration_service.dart';
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart'; import 'package:p_tits_pas/widgets/dashboard/user_management_sub_bar.dart';
import 'package:p_tits_pas/widgets/admin/parametres_panel.dart'; import 'package:p_tits_pas/widgets/admin/parametres_panel.dart';
import 'package:p_tits_pas/widgets/admin/user_management_panel.dart'; import 'package:p_tits_pas/widgets/admin/user_management_panel.dart';
import 'package:p_tits_pas/widgets/app_footer.dart'; import 'package:p_tits_pas/widgets/app_footer.dart';
@@ -5,7 +5,7 @@ import 'package:p_tits_pas/screens/administrateurs/creation/gestionnaires_create
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/user_list.dart'; import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
@@ -183,7 +183,7 @@ class _AdminManagementWidgetState extends State<AdminManagementWidget> {
final isSuperAdmin = _isSuperAdmin(user); final isSuperAdmin = _isSuperAdmin(user);
final canEdit = _canEditAdmin(user); final canEdit = _canEditAdmin(user);
final canDelete = _canDeleteAdmin(user); final canDelete = _canDeleteAdmin(user);
return AdminUserCard( return UserCard(
title: user.fullName, title: user.fullName,
fallbackIcon: isSuperAdmin fallbackIcon: isSuperAdmin
? Icons.verified_user_outlined ? Icons.verified_user_outlined
@@ -14,8 +14,8 @@ import 'package:p_tits_pas/utils/name_format_utils.dart';
import 'package:p_tits_pas/utils/nir_utils.dart'; import 'package:p_tits_pas/utils/nir_utils.dart';
import 'package:p_tits_pas/utils/phone_utils.dart'; import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/utils/postal_utils.dart'; import 'package:p_tits_pas/utils/postal_utils.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_photo_frame.dart'; import 'package:p_tits_pas/widgets/dashboard/am_photo_frame.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/detail_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart'; import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
import 'package:p_tits_pas/widgets/admin/validation_refus_form.dart'; import 'package:p_tits_pas/widgets/admin/validation_refus_form.dart';
@@ -221,32 +221,32 @@ class _AmDossierWizardState extends State<AmDossierWizard> {
void _emitStep() => widget.onStepChanged?.call(_step, _stepCount); void _emitStep() => widget.onStepChanged?.call(_step, _stepCount);
List<AdminDetailField> _photoProFields(DossierAM d) { List<DetailField> _photoProFields(DossierAM d) {
final u = d.user; final u = d.user;
return [ return [
AdminDetailField(label: 'NIR', value: _formatNirForDisplay(d.nir)), DetailField(label: 'NIR', value: _formatNirForDisplay(d.nir)),
AdminDetailField( DetailField(
label: 'Date de naissance', label: 'Date de naissance',
value: formatIsoDateFr(u.dateNaissance), value: formatIsoDateFr(u.dateNaissance),
), ),
AdminDetailField( DetailField(
label: 'Ville de naissance', label: 'Ville de naissance',
value: _v(u.lieuNaissanceVille), value: _v(u.lieuNaissanceVille),
), ),
AdminDetailField( DetailField(
label: 'Pays de naissance', label: 'Pays de naissance',
value: _v(u.lieuNaissancePays), value: _v(u.lieuNaissancePays),
), ),
AdminDetailField(label: 'N° Agrément', value: _v(d.numeroAgrement)), DetailField(label: 'N° Agrément', value: _v(d.numeroAgrement)),
AdminDetailField( DetailField(
label: 'Date dagrément', label: 'Date dagrément',
value: formatIsoDateFr(d.dateAgrement), value: formatIsoDateFr(d.dateAgrement),
), ),
AdminDetailField( DetailField(
label: 'Capacité max (enfants)', label: 'Capacité max (enfants)',
value: d.nbMaxEnfants != null ? d.nbMaxEnfants.toString() : '', value: d.nbMaxEnfants != null ? d.nbMaxEnfants.toString() : '',
), ),
AdminDetailField( DetailField(
label: 'Places disponibles', label: 'Places disponibles',
value: d.placesDisponibles != null value: d.placesDisponibles != null
? d.placesDisponibles.toString() ? d.placesDisponibles.toString()
@@ -741,14 +741,14 @@ class _AmDossierWizardState extends State<AmDossierWizard> {
Widget _buildStep1() { Widget _buildStep1() {
// Modale calée sur les TF ; photo étirée sur toute la hauteur utile // Modale calée sur les TF ; photo étirée sur toute la hauteur utile
// (largeur = [AdminAmPhotoFrame.columnWidthForHeight], cadre inclus). // (largeur = [AmPhotoFrame.columnWidthForHeight], cadre inclus).
return LayoutBuilder( return LayoutBuilder(
builder: (context, c) { builder: (context, c) {
final maxRowW = c.maxWidth; final maxRowW = c.maxWidth;
final maxRowH = c.maxHeight.clamp(0.0, double.infinity); final maxRowH = c.maxHeight.clamp(0.0, double.infinity);
final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth) final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth)
.clamp(0.0, double.infinity); .clamp(0.0, double.infinity);
var photoW = AdminAmPhotoFrame.columnWidthForHeight(maxRowH) var photoW = AmPhotoFrame.columnWidthForHeight(maxRowH)
.clamp(_photoColumnMinWidth, 360.0); .clamp(_photoColumnMinWidth, 360.0);
if (photoW > maxPhotoW) photoW = maxPhotoW; if (photoW > maxPhotoW) photoW = maxPhotoW;
@@ -762,7 +762,7 @@ class _AmDossierWizardState extends State<AmDossierWizard> {
final Widget photo; final Widget photo;
if (_isCreate) { if (_isCreate) {
photo = AdminAmPhotoFrame( photo = AmPhotoFrame(
imageBytes: _photoBytes, imageBytes: _photoBytes,
onTap: _pickPhoto, onTap: _pickPhoto,
onClear: _photoBytes != null ? _clearPhoto : null, onClear: _photoBytes != null ? _clearPhoto : null,
@@ -5,8 +5,8 @@ import 'package:p_tits_pas/utils/am_vigilance.dart';
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_edit_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/am_edit_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/user_list.dart'; import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
@@ -130,7 +130,7 @@ class _AssistanteMaternelleManagementWidgetState
itemBuilder: (context, index) { itemBuilder: (context, index) {
final assistante = filteredAssistantes[index]; final assistante = filteredAssistantes[index];
final vigilance = amPlacesVigilanceMessage(assistante); final vigilance = amPlacesVigilanceMessage(assistante);
return AdminUserCard( return UserCard(
title: assistante.user.fullName, title: assistante.user.fullName,
avatarUrl: assistante.user.photoUrl, avatarUrl: assistante.user.photoUrl,
fallbackIcon: Icons.face, fallbackIcon: Icons.face,
@@ -161,7 +161,7 @@ class _AssistanteMaternelleManagementWidgetState
void _openAssistanteDetails(AssistanteMaternelleModel assistante) { void _openAssistanteDetails(AssistanteMaternelleModel assistante) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (context) => AdminAmEditModal( builder: (context) => AmEditModal(
assistante: assistante, assistante: assistante,
onSaved: _loadAssistantes, onSaved: _loadAssistantes,
), ),
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_list_state.dart'; import 'package:p_tits_pas/widgets/dashboard/user_list_state.dart';
class UserList extends StatelessWidget { class UserList extends StatelessWidget {
final bool isLoading; final bool isLoading;
@@ -28,7 +28,7 @@ class UserList extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
AdminListState( UserListState(
isLoading: isLoading, isLoading: isLoading,
error: error, error: error,
isEmpty: isEmpty, isEmpty: isEmpty,
@@ -4,7 +4,7 @@ import 'package:p_tits_pas/utils/email_utils.dart';
import 'package:p_tits_pas/utils/nir_utils.dart'; import 'package:p_tits_pas/utils/nir_utils.dart';
import 'package:p_tits_pas/utils/phone_utils.dart'; import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/utils/postal_utils.dart'; import 'package:p_tits_pas/utils/postal_utils.dart';
import 'admin_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/detail_modal.dart';
/// Réglages des formulaires validation / wizard AM — **jouer sur ces 3 leviers**. /// Réglages des formulaires validation / wizard AM — **jouer sur ces 3 leviers**.
class ValidationFormMetrics { class ValidationFormMetrics {
@@ -60,7 +60,7 @@ class ValidationFormMetrics {
class ValidationDetailSection extends StatelessWidget { class ValidationDetailSection extends StatelessWidget {
/// Si null ou vide, pas de bandeau titre (gain de place vertical, ex. wizard AM). /// Si null ou vide, pas de bandeau titre (gain de place vertical, ex. wizard AM).
final String? title; final String? title;
final List<AdminDetailField> fields; final List<DetailField> fields;
/// Nombre de champs par ligne (1 = plein largeur, 2 = deux côte à côte). Ex. [2, 2, 1, 2] pour identité. /// Nombre de champs par ligne (1 = plein largeur, 2 = deux côte à côte). Ex. [2, 2, 1, 2] pour identité.
final List<int>? rowLayout; final List<int>? rowLayout;
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
/// Carte dossier unifiée (#153) — fond neutre, accent couleur sur licône. /// Carte dossier unifiée (#153) — fond neutre, accent couleur sur licône.
@@ -54,7 +54,7 @@ class DossierListCard extends StatelessWidget {
if (!emptyKids && countLabel != null) countLabel, if (!emptyKids && countLabel != null) countLabel,
]; ];
return AdminUserCard( return UserCard(
title: num, title: num,
subtitleLines: subtitle, subtitleLines: subtitle,
avatarUrl: !isFamille && avatar.isNotEmpty ? avatar : null, avatarUrl: !isFamille && avatar.isNotEmpty ? avatar : null,
@@ -5,8 +5,8 @@ import 'package:p_tits_pas/models/enfant_admin_model.dart';
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/child_detail_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_enfant_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/enfant_user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/user_list.dart'; import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
@@ -68,7 +68,7 @@ class _EnfantManagementWidgetState extends State<EnfantManagementWidget> {
Future<void> _openEnfant(EnfantAdminModel enfant) async { Future<void> _openEnfant(EnfantAdminModel enfant) async {
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminChildDetailModal( builder: (ctx) => ChildDetailModal(
enfant: enfant, enfant: enfant,
onSaved: _loadEnfants, onSaved: _loadEnfants,
onDeleted: _loadEnfants, onDeleted: _loadEnfants,
@@ -224,7 +224,7 @@ class _EnfantManagementWidgetState extends State<EnfantManagementWidget> {
itemCount: filtered.length, itemCount: filtered.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final enfant = filtered[index]; final enfant = filtered[index];
return AdminEnfantUserCard.fromEnfant( return EnfantUserCard.fromEnfant(
enfant, enfant,
onCardTap: () => _openEnfant(enfant), onCardTap: () => _openEnfant(enfant),
actions: [ actions: [
@@ -4,7 +4,7 @@ import 'package:p_tits_pas/screens/administrateurs/creation/gestionnaires_create
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/user_list.dart'; import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
@@ -141,7 +141,7 @@ class _GestionnaireManagementWidgetState
final user = filteredGestionnaires[index]; final user = filteredGestionnaires[index];
final isSelf = final isSelf =
_currentUserId != null && _currentUserId == user.id; _currentUserId != null && _currentUserId == user.id;
return AdminUserCard( return UserCard(
title: user.fullName, title: user.fullName,
fallbackIcon: Icons.assignment_ind_outlined, fallbackIcon: Icons.assignment_ind_outlined,
avatarUrl: user.photoUrl, avatarUrl: user.photoUrl,
@@ -14,7 +14,7 @@ import 'package:p_tits_pas/utils/enfant_status_utils.dart';
import 'package:p_tits_pas/utils/name_format_utils.dart'; import 'package:p_tits_pas/utils/name_format_utils.dart';
import 'package:p_tits_pas/utils/phone_utils.dart'; import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/utils/postal_utils.dart'; import 'package:p_tits_pas/utils/postal_utils.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_photo_frame.dart'; import 'package:p_tits_pas/widgets/dashboard/am_photo_frame.dart';
import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart'; import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
import 'package:p_tits_pas/widgets/admin/validation_refus_form.dart'; import 'package:p_tits_pas/widgets/admin/validation_refus_form.dart';
@@ -860,7 +860,7 @@ class _ParentDossierWizardState extends State<ParentDossierWizard> {
child: SizedBox( child: SizedBox(
width: pw, width: pw,
height: ph, height: ph,
child: AdminAmPhotoFrame( child: AmPhotoFrame(
photoUrl: child.photoBytes == null photoUrl: child.photoBytes == null
? child.existingPhotoUrl ? child.existingPhotoUrl
: null, : null,
@@ -4,8 +4,8 @@ import 'package:p_tits_pas/models/parent_model.dart';
import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/auth_service.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_parent_edit_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/parent_edit_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/user_list.dart'; import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
@@ -146,7 +146,7 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
itemCount: filteredParents.length, itemCount: filteredParents.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final parent = filteredParents[index]; final parent = filteredParents[index];
return AdminUserCard( return UserCard(
title: parent.user.fullName, title: parent.user.fullName,
fallbackIcon: Icons.supervisor_account_outlined, fallbackIcon: Icons.supervisor_account_outlined,
avatarUrl: parent.user.photoUrl, avatarUrl: parent.user.photoUrl,
@@ -189,7 +189,7 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
void _openParentDetails(ParentModel parent) { void _openParentDetails(ParentModel parent) {
showDialog<void>( showDialog<void>(
context: context, context: context,
builder: (context) => AdminParentEditModal( builder: (context) => ParentEditModal(
parent: parent, parent: parent,
onSaved: _loadParents, onSaved: _loadParents,
), ),
@@ -3,8 +3,8 @@ import 'package:p_tits_pas/screens/administrateurs/creation/gestionnaires_create
import 'package:p_tits_pas/widgets/admin/admin_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/admin_management_widget.dart';
import 'package:p_tits_pas/widgets/admin/am_dossier_create_modal.dart'; import 'package:p_tits_pas/widgets/admin/am_dossier_create_modal.dart';
import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/child_detail_modal.dart';
import 'package:p_tits_pas/widgets/admin/dashboard_admin.dart'; import 'package:p_tits_pas/widgets/dashboard/user_management_sub_bar.dart';
import 'package:p_tits_pas/widgets/admin/dossiers_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/dossiers_management_widget.dart';
import 'package:p_tits_pas/widgets/admin/enfant_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/enfant_management_widget.dart';
import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart';
@@ -348,7 +348,7 @@ class _UserManagementPanelState extends State<UserManagementPanel> {
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (dialogContext) { builder: (dialogContext) {
return AdminChildDetailModal.create( return ChildDetailModal.create(
onSaved: () { onSaved: () {
if (!mounted) return; if (!mounted) return;
setState(() => _enfantRefreshTick++); setState(() => _enfantRefreshTick++);
@@ -5,7 +5,7 @@ import 'package:p_tits_pas/utils/date_display_utils.dart';
import 'package:p_tits_pas/widgets/common/auth_network_image.dart'; import 'package:p_tits_pas/widgets/common/auth_network_image.dart';
/// Grille 2×2 des places d'accueil AM (max 4, limitée à [capacity]). /// Grille 2×2 des places d'accueil AM (max 4, limitée à [capacity]).
class AdminAmChildrenCapacityGrid extends StatelessWidget { class AmChildrenCapacityGrid extends StatelessWidget {
static const int _gridSlots = 4; static const int _gridSlots = 4;
static const double _slotHeight = 44; static const double _slotHeight = 44;
static const double _gridPadding = 10; static const double _gridPadding = 10;
@@ -23,7 +23,7 @@ class AdminAmChildrenCapacityGrid extends StatelessWidget {
/// Clic sur une case libre → même flux que « Rattacher un enfant » (#149). /// Clic sur une case libre → même flux que « Rattacher un enfant » (#149).
final VoidCallback? onAttachEmpty; final VoidCallback? onAttachEmpty;
const AdminAmChildrenCapacityGrid({ const AmChildrenCapacityGrid({
super.key, super.key,
required this.children, required this.children,
required this.capacity, required this.capacity,
@@ -7,31 +7,31 @@ import 'package:p_tits_pas/utils/date_display_utils.dart';
import 'package:p_tits_pas/utils/nir_utils.dart'; import 'package:p_tits_pas/utils/nir_utils.dart';
import 'package:p_tits_pas/utils/phone_utils.dart'; import 'package:p_tits_pas/utils/phone_utils.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_children_capacity_grid.dart'; import 'package:p_tits_pas/widgets/dashboard/am_children_capacity_grid.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_photo_frame.dart'; import 'package:p_tits_pas/widgets/dashboard/am_photo_frame.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/child_detail_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_enfant_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_enfant_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_status_capsule.dart'; import 'package:p_tits_pas/widgets/dashboard/status_capsule.dart';
import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart'; import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart';
import 'package:p_tits_pas/widgets/common/identity_block.dart'; import 'package:p_tits_pas/widgets/common/identity_block.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
/// Fiche AM éditable (ticket #131) — identité | fiche pro (photo) | enfants. /// Fiche AM éditable (ticket #131) — identité | fiche pro (photo) | enfants.
class AdminAmEditModal extends StatefulWidget { class AmEditModal extends StatefulWidget {
final AssistanteMaternelleModel assistante; final AssistanteMaternelleModel assistante;
final VoidCallback? onSaved; final VoidCallback? onSaved;
const AdminAmEditModal({ const AmEditModal({
super.key, super.key,
required this.assistante, required this.assistante,
this.onSaved, this.onSaved,
}); });
@override @override
State<AdminAmEditModal> createState() => _AdminAmEditModalState(); State<AmEditModal> createState() => _AmEditModalState();
} }
class _AdminAmEditModalState extends State<AdminAmEditModal> class _AmEditModalState extends State<AmEditModal>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late final TabController _tabCtrl; late final TabController _tabCtrl;
late final TextEditingController _nomCtrl; late final TextEditingController _nomCtrl;
@@ -404,7 +404,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
if (!mounted) return; if (!mounted) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminChildDetailModal( builder: (ctx) => ChildDetailModal(
enfant: enfant, enfant: enfant,
onSaved: _refreshChildrenDetails, onSaved: _refreshChildrenDetails,
), ),
@@ -450,7 +450,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
Future<void> _attachChild() async { Future<void> _attachChild() async {
if (_capacityFull || !mounted) return; if (_capacityFull || !mounted) return;
final selected = await AdminSelectEnfantModal.show( final selected = await SelectEnfantModal.show(
context, context,
excludeIds: _children.map((c) => c.id).toSet(), excludeIds: _children.map((c) => c.id).toSet(),
title: 'Rattacher un enfant', title: 'Rattacher un enfant',
@@ -589,7 +589,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
final maxRowH = final maxRowH =
c.maxHeight.isFinite ? c.maxHeight : _proTabHeight; c.maxHeight.isFinite ? c.maxHeight : _proTabHeight;
final bodyH = maxRowH; final bodyH = maxRowH;
final idealPhotoW = bodyH * AdminAmPhotoFrame.idPhotoAspectRatio + 16; final idealPhotoW = bodyH * AmPhotoFrame.idPhotoAspectRatio + 16;
final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth) final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth)
.clamp(0.0, double.infinity); .clamp(0.0, double.infinity);
var photoW = idealPhotoW.clamp(_photoColumnMinWidth, 220.0); var photoW = idealPhotoW.clamp(_photoColumnMinWidth, 220.0);
@@ -600,7 +600,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
children: [ children: [
SizedBox( SizedBox(
width: photoW, width: photoW,
child: AdminAmPhotoFrame( child: AmPhotoFrame(
photoUrl: widget.assistante.user.photoUrl, photoUrl: widget.assistante.user.photoUrl,
), ),
), ),
@@ -728,7 +728,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
AdminAmChildrenCapacityGrid( AmChildrenCapacityGrid(
children: _children, children: _children,
capacity: capacity, capacity: capacity,
onOpen: _openChild, onOpen: _openChild,
@@ -820,7 +820,7 @@ class _AdminAmEditModalState extends State<AdminAmEditModal>
const SizedBox(width: 12), const SizedBox(width: 12),
SizedBox( SizedBox(
width: 160, width: 160,
child: AdminStatusCapsule( child: StatusCapsule(
statut: _statut, statut: _statut,
onChanged: (v) => setState(() { onChanged: (v) => setState(() {
_statut = v; _statut = v;
@@ -5,7 +5,7 @@ import 'package:p_tits_pas/services/api/api_config.dart';
import 'package:p_tits_pas/widgets/common/auth_network_image.dart'; import 'package:p_tits_pas/widgets/common/auth_network_image.dart';
/// Cadre photo identité AM / enfant (35×45 mm) — même logique que [ValidationAmWizard]. /// Cadre photo identité AM / enfant (35×45 mm) — même logique que [ValidationAmWizard].
class AdminAmPhotoFrame extends StatelessWidget { class AmPhotoFrame extends StatelessWidget {
final String? photoUrl; final String? photoUrl;
final Uint8List? imageBytes; final Uint8List? imageBytes;
final VoidCallback? onTap; final VoidCallback? onTap;
@@ -14,7 +14,7 @@ class AdminAmPhotoFrame extends StatelessWidget {
static const double idPhotoAspectRatio = 35 / 45; static const double idPhotoAspectRatio = 35 / 45;
const AdminAmPhotoFrame({ const AmPhotoFrame({
super.key, super.key,
this.photoUrl, this.photoUrl,
this.imageBytes, this.imageBytes,
@@ -11,24 +11,24 @@ import 'package:p_tits_pas/utils/date_display_utils.dart';
import 'package:p_tits_pas/utils/enfant_status_utils.dart'; import 'package:p_tits_pas/utils/enfant_status_utils.dart';
import 'package:p_tits_pas/utils/name_format_utils.dart'; import 'package:p_tits_pas/utils/name_format_utils.dart';
import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_edit_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/am_edit_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_photo_frame.dart'; import 'package:p_tits_pas/widgets/dashboard/am_photo_frame.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_parent_edit_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/parent_edit_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_am_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_am_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_famille_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_famille_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart'; import 'package:p_tits_pas/widgets/admin/common/validation_detail_section.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
import 'package:p_tits_pas/widgets/common/auth_network_image.dart'; import 'package:p_tits_pas/widgets/common/auth_network_image.dart';
/// Fiche enfant consultation / édition (#138) ou création (#132). /// Fiche enfant consultation / édition (#138) ou création (#132).
class AdminChildDetailModal extends StatefulWidget { class ChildDetailModal extends StatefulWidget {
final EnfantAdminModel? enfant; final EnfantAdminModel? enfant;
final VoidCallback? onSaved; final VoidCallback? onSaved;
final VoidCallback? onDeleted; final VoidCallback? onDeleted;
final bool isCreating; final bool isCreating;
const AdminChildDetailModal({ const ChildDetailModal({
super.key, super.key,
required EnfantAdminModel this.enfant, required EnfantAdminModel this.enfant,
this.onSaved, this.onSaved,
@@ -36,7 +36,7 @@ class AdminChildDetailModal extends StatefulWidget {
}) : isCreating = false; }) : isCreating = false;
/// Création depuis l'onglet Enfants (#132). /// Création depuis l'onglet Enfants (#132).
const AdminChildDetailModal.create({ const ChildDetailModal.create({
super.key, super.key,
this.onSaved, this.onSaved,
}) : enfant = null, }) : enfant = null,
@@ -44,10 +44,10 @@ class AdminChildDetailModal extends StatefulWidget {
isCreating = true; isCreating = true;
@override @override
State<AdminChildDetailModal> createState() => _AdminChildDetailModalState(); State<ChildDetailModal> createState() => _ChildDetailModalState();
} }
class _AdminChildDetailModalState extends State<AdminChildDetailModal> { class _ChildDetailModalState extends State<ChildDetailModal> {
late final TextEditingController _prenomCtrl; late final TextEditingController _prenomCtrl;
late final TextEditingController _nomCtrl; late final TextEditingController _nomCtrl;
late final TextEditingController _birthCtrl; late final TextEditingController _birthCtrl;
@@ -65,7 +65,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
String? _baselineAmUserId; String? _baselineAmUserId;
/// Famille choisie en mode création (#132). /// Famille choisie en mode création (#132).
AdminFamilleFoyer? _selectedFamily; FamilleFoyer? _selectedFamily;
/// Liens parents locaux (édition) — mis à jour après rattachement foyer (#157). /// Liens parents locaux (édition) — mis à jour après rattachement foyer (#157).
List<EnfantParentLink>? _localParentLinks; List<EnfantParentLink>? _localParentLinks;
@@ -283,7 +283,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
if (!mounted) return; if (!mounted) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminParentEditModal( builder: (ctx) => ParentEditModal(
parent: parent, parent: parent,
onSaved: () => widget.onSaved?.call(), onSaved: () => widget.onSaved?.call(),
), ),
@@ -624,7 +624,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
if (am == null) return; if (am == null) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminAmEditModal( builder: (ctx) => AmEditModal(
assistante: am, assistante: am,
onSaved: () async { onSaved: () async {
await _reloadPlacementFromServer(); await _reloadPlacementFromServer();
@@ -676,7 +676,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
} }
if (!mounted) return; if (!mounted) return;
final selected = await AdminSelectAmModal.show( final selected = await SelectAmModal.show(
context, context,
excludeIds: { excludeIds: {
if (_linkedAm != null) _linkedAm!.user.id, if (_linkedAm != null) _linkedAm!.user.id,
@@ -986,7 +986,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
final maxRowW = c.maxWidth; final maxRowW = c.maxWidth;
final maxRowH = c.maxHeight; final maxRowH = c.maxHeight;
final idealPhotoW = final idealPhotoW =
maxRowH * AdminAmPhotoFrame.idPhotoAspectRatio + 16; maxRowH * AmPhotoFrame.idPhotoAspectRatio + 16;
final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth) final maxPhotoW = (maxRowW - _photoProGap - _proColumnMinWidth)
.clamp(0.0, double.infinity); .clamp(0.0, double.infinity);
var photoW = idealPhotoW.clamp(_photoColumnMinWidth, _photoColumnMaxWidth); var photoW = idealPhotoW.clamp(_photoColumnMinWidth, _photoColumnMaxWidth);
@@ -1001,7 +1001,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Expanded( Expanded(
child: AdminAmPhotoFrame( child: AmPhotoFrame(
photoUrl: widget.isCreating photoUrl: widget.isCreating
? null ? null
: widget.enfant?.photoUrl, : widget.enfant?.photoUrl,
@@ -1329,7 +1329,7 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
Future<void> _pickFamily() async { Future<void> _pickFamily() async {
if (_busy) return; if (_busy) return;
final selected = await AdminSelectFamilleModal.show( final selected = await SelectFamilleModal.show(
context, context,
title: 'Choisir une famille', title: 'Choisir une famille',
); );
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/parent_child_summary.dart'; import 'package:p_tits_pas/models/parent_child_summary.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_enfant_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/enfant_user_card.dart';
/// Liste scrollable d'enfants rattachés (fiche parent / fiche AM). /// Liste scrollable d'enfants rattachés (fiche parent / fiche AM).
class AdminChildrenAffiliationPanel extends StatelessWidget { class ChildrenAffiliationPanel extends StatelessWidget {
final List<ParentChildSummary> children; final List<ParentChildSummary> children;
final ScrollController scrollController; final ScrollController scrollController;
final void Function(ParentChildSummary child) onOpen; final void Function(ParentChildSummary child) onOpen;
@@ -14,7 +14,7 @@ class AdminChildrenAffiliationPanel extends StatelessWidget {
static const double _itemHeight = 58; static const double _itemHeight = 58;
static const double defaultViewportHeight = _itemHeight * 2.5 + 8; static const double defaultViewportHeight = _itemHeight * 2.5 + 8;
const AdminChildrenAffiliationPanel({ const ChildrenAffiliationPanel({
super.key, super.key,
required this.children, required this.children,
required this.scrollController, required this.scrollController,
@@ -62,7 +62,7 @@ class AdminChildrenAffiliationPanel extends StatelessWidget {
itemCount: children.length, itemCount: children.length,
itemBuilder: (_, i) { itemBuilder: (_, i) {
final c = children[i]; final c = children[i];
return AdminEnfantUserCard.fromSummary( return EnfantUserCard.fromSummary(
c, c,
onCardTap: () => onOpen(c), onCardTap: () => onOpen(c),
actions: [ actions: [
@@ -1,23 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AdminDetailField { class DetailField {
final String label; final String label;
final String value; final String value;
const AdminDetailField({ const DetailField({
required this.label, required this.label,
required this.value, required this.value,
}); });
} }
class AdminDetailModal extends StatelessWidget { class DetailModal extends StatelessWidget {
final String title; final String title;
final String? subtitle; final String? subtitle;
final List<AdminDetailField> fields; final List<DetailField> fields;
final VoidCallback onEdit; final VoidCallback onEdit;
final VoidCallback onDelete; final VoidCallback onDelete;
const AdminDetailModal({ const DetailModal({
super.key, super.key,
required this.title, required this.title,
this.subtitle, this.subtitle,
@@ -4,7 +4,7 @@ import 'package:p_tits_pas/models/parent_child_summary.dart';
import 'package:p_tits_pas/utils/date_display_utils.dart'; import 'package:p_tits_pas/utils/date_display_utils.dart';
import 'package:p_tits_pas/utils/enfant_status_utils.dart'; import 'package:p_tits_pas/utils/enfant_status_utils.dart';
import 'package:p_tits_pas/utils/enfant_vigilance.dart'; import 'package:p_tits_pas/utils/enfant_vigilance.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
List<String> enfantAdminSubtitleLines({ List<String> enfantAdminSubtitleLines({
required String status, required String status,
@@ -29,7 +29,7 @@ List<String> enfantAdminSubtitleLines({
} }
/// Carte enfant admin (photo, nom, âge) — même rendu onglet Enfants / fiche parent. /// Carte enfant admin (photo, nom, âge) — même rendu onglet Enfants / fiche parent.
class AdminEnfantUserCard extends StatelessWidget { class EnfantUserCard extends StatelessWidget {
final String title; final String title;
final String? photoUrl; final String? photoUrl;
final List<String> subtitleLines; final List<String> subtitleLines;
@@ -40,7 +40,7 @@ class AdminEnfantUserCard extends StatelessWidget {
final Color? borderColor; final Color? borderColor;
final String? vigilanceTooltip; final String? vigilanceTooltip;
const AdminEnfantUserCard({ const EnfantUserCard({
super.key, super.key,
required this.title, required this.title,
this.photoUrl, this.photoUrl,
@@ -53,7 +53,7 @@ class AdminEnfantUserCard extends StatelessWidget {
this.vigilanceTooltip, this.vigilanceTooltip,
}); });
factory AdminEnfantUserCard.fromEnfant( factory EnfantUserCard.fromEnfant(
EnfantAdminModel enfant, { EnfantAdminModel enfant, {
List<String> extraSubtitleLines = const [], List<String> extraSubtitleLines = const [],
List<Widget> actions = const [], List<Widget> actions = const [],
@@ -67,7 +67,7 @@ class AdminEnfantUserCard extends StatelessWidget {
.map((l) => l.parentName ?? 'Parent') .map((l) => l.parentName ?? 'Parent')
.join(', '); .join(', ');
final orphan = enfantHasNoResponsable(enfant); final orphan = enfantHasNoResponsable(enfant);
return AdminEnfantUserCard( return EnfantUserCard(
title: enfant.fullName, title: enfant.fullName,
photoUrl: enfant.photoUrl, photoUrl: enfant.photoUrl,
subtitleLines: enfantAdminSubtitleLines( subtitleLines: enfantAdminSubtitleLines(
@@ -92,7 +92,7 @@ class AdminEnfantUserCard extends StatelessWidget {
); );
} }
factory AdminEnfantUserCard.fromSummary( factory EnfantUserCard.fromSummary(
ParentChildSummary child, { ParentChildSummary child, {
List<String> extraSubtitleLines = const [], List<String> extraSubtitleLines = const [],
List<Widget> actions = const [], List<Widget> actions = const [],
@@ -100,7 +100,7 @@ class AdminEnfantUserCard extends StatelessWidget {
EdgeInsetsGeometry? margin, EdgeInsetsGeometry? margin,
EdgeInsetsGeometry? contentPadding, EdgeInsetsGeometry? contentPadding,
}) { }) {
return AdminEnfantUserCard( return EnfantUserCard(
title: child.fullName, title: child.fullName,
photoUrl: child.photoUrl, photoUrl: child.photoUrl,
subtitleLines: enfantAdminSubtitleLines( subtitleLines: enfantAdminSubtitleLines(
@@ -118,7 +118,7 @@ class AdminEnfantUserCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AdminUserCard( return UserCard(
title: title, title: title,
fallbackIcon: Icons.child_care_outlined, fallbackIcon: Icons.child_care_outlined,
avatarUrl: photoUrl, avatarUrl: photoUrl,
@@ -4,30 +4,30 @@ import 'package:p_tits_pas/models/parent_child_summary.dart';
import 'package:p_tits_pas/models/parent_model.dart'; import 'package:p_tits_pas/models/parent_model.dart';
import 'package:p_tits_pas/models/user.dart'; import 'package:p_tits_pas/models/user.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/child_detail_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_children_affiliation_panel.dart'; import 'package:p_tits_pas/widgets/dashboard/children_affiliation_panel.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_enfant_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_enfant_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_status_capsule.dart'; import 'package:p_tits_pas/widgets/dashboard/status_capsule.dart';
import 'package:p_tits_pas/widgets/common/identity_block.dart'; import 'package:p_tits_pas/widgets/common/identity_block.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
/// Fiche parent éditable (doc 28 §6.1, tickets #131 / #138). /// Fiche parent éditable (doc 28 §6.1, tickets #131 / #138).
/// Shell et typo alignés sur [ValidationDossierModal] / wizards validation. /// Shell et typo alignés sur [ValidationDossierModal] / wizards validation.
class AdminParentEditModal extends StatefulWidget { class ParentEditModal extends StatefulWidget {
final ParentModel parent; final ParentModel parent;
final VoidCallback? onSaved; final VoidCallback? onSaved;
const AdminParentEditModal({ const ParentEditModal({
super.key, super.key,
required this.parent, required this.parent,
this.onSaved, this.onSaved,
}); });
@override @override
State<AdminParentEditModal> createState() => _AdminParentEditModalState(); State<ParentEditModal> createState() => _ParentEditModalState();
} }
class _AdminParentEditModalState extends State<AdminParentEditModal> { class _ParentEditModalState extends State<ParentEditModal> {
late final TextEditingController _nomCtrl; late final TextEditingController _nomCtrl;
late final TextEditingController _prenomCtrl; late final TextEditingController _prenomCtrl;
late final TextEditingController _emailCtrl; late final TextEditingController _emailCtrl;
@@ -129,7 +129,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
if (!mounted) return; if (!mounted) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminParentEditModal( builder: (ctx) => ParentEditModal(
parent: parent, parent: parent,
onSaved: () async { onSaved: () async {
try { try {
@@ -244,7 +244,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
if (!mounted) return; if (!mounted) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminChildDetailModal( builder: (ctx) => ChildDetailModal(
enfant: enfant, enfant: enfant,
onSaved: _reloadChildren, onSaved: _reloadChildren,
), ),
@@ -322,7 +322,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
Future<void> _attachChild() async { Future<void> _attachChild() async {
if (!mounted) return; if (!mounted) return;
final selected = await AdminSelectEnfantModal.show( final selected = await SelectEnfantModal.show(
context, context,
excludeIds: _children.map((c) => c.id).toSet(), excludeIds: _children.map((c) => c.id).toSet(),
title: 'Rattacher un enfant', title: 'Rattacher un enfant',
@@ -350,7 +350,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
} }
Widget _childrenPanel() { Widget _childrenPanel() {
return AdminChildrenAffiliationPanel( return ChildrenAffiliationPanel(
children: _children, children: _children,
scrollController: _childrenScrollCtrl, scrollController: _childrenScrollCtrl,
onOpen: _openChild, onOpen: _openChild,
@@ -437,7 +437,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
const SizedBox(width: 12), const SizedBox(width: 12),
SizedBox( SizedBox(
width: 160, width: 160,
child: AdminStatusCapsule( child: StatusCapsule(
statut: _statut, statut: _statut,
onChanged: (v) => setState(() { onChanged: (v) => setState(() {
_statut = v; _statut = v;
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/assistante_maternelle_model.dart'; import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/am_vigilance.dart'; import 'package:p_tits_pas/utils/am_vigilance.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_am_edit_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/am_edit_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_list_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_list_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
List<String> _amSelectSubtitleLines(AssistanteMaternelleModel am) { List<String> _amSelectSubtitleLines(AssistanteMaternelleModel am) {
@@ -28,22 +28,22 @@ List<String> _amSelectSubtitleLines(AssistanteMaternelleModel am) {
} }
/// Sélection d'une AM à rattacher (fiche enfant) — ticket #147. /// Sélection d'une AM à rattacher (fiche enfant) — ticket #147.
/// S'appuie sur [AdminSelectListModal] (shell partagé avec #146). /// S'appuie sur [SelectListModal] (shell partagé avec #146).
class AdminSelectAmModal { class SelectAmModal {
AdminSelectAmModal._(); SelectAmModal._();
static Future<AssistanteMaternelleModel?> show( static Future<AssistanteMaternelleModel?> show(
BuildContext context, { BuildContext context, {
Set<String> excludeIds = const {}, Set<String> excludeIds = const {},
String title = 'Choisir une assistante maternelle', String title = 'Choisir une assistante maternelle',
}) { }) {
return AdminSelectListModal.show<AssistanteMaternelleModel>( return SelectListModal.show<AssistanteMaternelleModel>(
context, context,
title: title, title: title,
searchHint: 'Rechercher par nom, prénom ou zone…', searchHint: 'Rechercher par nom, prénom ou zone…',
emptyMessage: 'Aucune assistante maternelle disponible', emptyMessage: 'Aucune assistante maternelle disponible',
noResultsMessage: 'Aucune AM avec place libre pour cette recherche', noResultsMessage: 'Aucune AM avec place libre pour cette recherche',
toggleFilter: const AdminSelectToggleFilter<AssistanteMaternelleModel>( toggleFilter: const SelectToggleFilter<AssistanteMaternelleModel>(
label: 'Libre', label: 'Libre',
initialValue: true, initialValue: true,
whenEnabled: amHasFreePlace, whenEnabled: amHasFreePlace,
@@ -76,7 +76,7 @@ class AdminSelectAmModal {
_resolveAmSelection(ctx, am, reload), _resolveAmSelection(ctx, am, reload),
itemBuilder: (context, am, onSelect) { itemBuilder: (context, am, onSelect) {
final full = !amHasFreePlace(am); final full = !amHasFreePlace(am);
return AdminUserCard( return UserCard(
title: am.user.fullName, title: am.user.fullName,
avatarUrl: am.user.photoUrl, avatarUrl: am.user.photoUrl,
fallbackIcon: Icons.face, fallbackIcon: Icons.face,
@@ -150,7 +150,7 @@ class _AmNoPlaceWarningDialogState extends State<_AmNoPlaceWarningDialog> {
if (!mounted) return; if (!mounted) return;
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (ctx) => AdminAmEditModal( builder: (ctx) => AmEditModal(
assistante: _am, assistante: _am,
onSaved: () async { onSaved: () async {
try { try {
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/enfant_admin_model.dart'; import 'package:p_tits_pas/models/enfant_admin_model.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/utils/enfant_status_utils.dart'; import 'package:p_tits_pas/utils/enfant_status_utils.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_enfant_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/enfant_user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_list_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_list_modal.dart';
/// Sélection d'un enfant à rattacher (fiche AM / fiche parent) — ticket #146. /// Sélection d'un enfant à rattacher (fiche AM / fiche parent) — ticket #146.
/// S'appuie sur [AdminSelectListModal] (shell partagé avec #147). /// S'appuie sur [SelectListModal] (shell partagé avec #147).
class AdminSelectEnfantModal { class SelectEnfantModal {
AdminSelectEnfantModal._(); SelectEnfantModal._();
static Future<EnfantAdminModel?> show( static Future<EnfantAdminModel?> show(
BuildContext context, { BuildContext context, {
@@ -17,7 +17,7 @@ class AdminSelectEnfantModal {
/// Affiché uniquement depuis la fiche AM : filtre les enfants déjà en garde. /// Affiché uniquement depuis la fiche AM : filtre les enfants déjà en garde.
bool showSansGardeFilter = false, bool showSansGardeFilter = false,
}) { }) {
return AdminSelectListModal.show<EnfantAdminModel>( return SelectListModal.show<EnfantAdminModel>(
context, context,
title: title, title: title,
searchHint: 'Rechercher par nom ou prénom…', searchHint: 'Rechercher par nom ou prénom…',
@@ -26,7 +26,7 @@ class AdminSelectEnfantModal {
? 'Aucun enfant sans garde pour cette recherche' ? 'Aucun enfant sans garde pour cette recherche'
: 'Aucun résultat pour cette recherche', : 'Aucun résultat pour cette recherche',
toggleFilter: showSansGardeFilter toggleFilter: showSansGardeFilter
? AdminSelectToggleFilter<EnfantAdminModel>( ? SelectToggleFilter<EnfantAdminModel>(
label: 'Sans garde', label: 'Sans garde',
initialValue: true, initialValue: true,
whenEnabled: (e) => whenEnabled: (e) =>
@@ -50,7 +50,7 @@ class AdminSelectEnfantModal {
return name.contains(q) || fn.contains(q) || ln.contains(q); return name.contains(q) || fn.contains(q) || ln.contains(q);
}, },
itemBuilder: (context, e, onSelect) { itemBuilder: (context, e, onSelect) {
return AdminEnfantUserCard.fromEnfant( return EnfantUserCard.fromEnfant(
e, e,
onCardTap: onSelect, onCardTap: onSelect,
margin: const EdgeInsets.only(bottom: 4), margin: const EdgeInsets.only(bottom: 4),
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/parent_model.dart'; import 'package:p_tits_pas/models/parent_model.dart';
import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_select_list_modal.dart'; import 'package:p_tits_pas/widgets/dashboard/select_list_modal.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_user_card.dart'; import 'package:p_tits_pas/widgets/dashboard/user_card.dart';
/// Foyer / famille sélectionnable pour rattacher un nouvel enfant (#132 / #157). /// Foyer / famille sélectionnable pour rattacher un nouvel enfant (#132 / #157).
class AdminFamilleFoyer { class FamilleFoyer {
/// Parent pivot pour `POST /enfants` (`parent_user_id`). /// Parent pivot pour `POST /enfants` (`parent_user_id`).
final String pivotParentUserId; final String pivotParentUserId;
/// Co-parent éventuel (rattachement foyer #157). /// Co-parent éventuel (rattachement foyer #157).
@@ -14,7 +14,7 @@ class AdminFamilleFoyer {
final String displayTitle; final String displayTitle;
final List<String> parentNames; final List<String> parentNames;
const AdminFamilleFoyer({ const FamilleFoyer({
required this.pivotParentUserId, required this.pivotParentUserId,
required this.displayTitle, required this.displayTitle,
required this.parentNames, required this.parentNames,
@@ -42,10 +42,10 @@ class AdminFamilleFoyer {
} }
/// Construit la liste des foyers uniques à partir de `GET /parents`. /// Construit la liste des foyers uniques à partir de `GET /parents`.
List<AdminFamilleFoyer> buildFamilleFoyers(List<ParentModel> parents) { List<FamilleFoyer> buildFamilleFoyers(List<ParentModel> parents) {
final seenDossiers = <String>{}; final seenDossiers = <String>{};
final seenUserIds = <String>{}; final seenUserIds = <String>{};
final foyers = <AdminFamilleFoyer>[]; final foyers = <FamilleFoyer>[];
for (final p in parents) { for (final p in parents) {
final dossier = (p.user.numeroDossier ?? '').trim(); final dossier = (p.user.numeroDossier ?? '').trim();
@@ -70,7 +70,7 @@ List<AdminFamilleFoyer> buildFamilleFoyers(List<ParentModel> parents) {
: (names.isNotEmpty ? names.first : 'Famille'); : (names.isNotEmpty ? names.first : 'Famille');
foyers.add( foyers.add(
AdminFamilleFoyer( FamilleFoyer(
pivotParentUserId: p.user.id, pivotParentUserId: p.user.id,
coParentUserId: co?.id, coParentUserId: co?.id,
numeroDossier: dossier.isNotEmpty ? dossier : null, numeroDossier: dossier.isNotEmpty ? dossier : null,
@@ -87,14 +87,14 @@ List<AdminFamilleFoyer> buildFamilleFoyers(List<ParentModel> parents) {
} }
/// Sélection d'une famille / dossier pour créer un enfant — ticket #132. /// Sélection d'une famille / dossier pour créer un enfant — ticket #132.
class AdminSelectFamilleModal { class SelectFamilleModal {
AdminSelectFamilleModal._(); SelectFamilleModal._();
static Future<AdminFamilleFoyer?> show( static Future<FamilleFoyer?> show(
BuildContext context, { BuildContext context, {
String title = 'Choisir une famille', String title = 'Choisir une famille',
}) { }) {
return AdminSelectListModal.show<AdminFamilleFoyer>( return SelectListModal.show<FamilleFoyer>(
context, context,
title: title, title: title,
searchHint: 'Rechercher par dossier, nom…', searchHint: 'Rechercher par dossier, nom…',
@@ -111,7 +111,7 @@ class AdminSelectFamilleModal {
return dossier.contains(q) || title.contains(q) || names.contains(q); return dossier.contains(q) || title.contains(q) || names.contains(q);
}, },
itemBuilder: (context, f, onSelect) { itemBuilder: (context, f, onSelect) {
return AdminUserCard( return UserCard(
title: f.displayTitle, title: f.displayTitle,
fallbackIcon: Icons.family_restroom, fallbackIcon: Icons.family_restroom,
subtitleLines: [ subtitleLines: [
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart'; import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
/// Filtre optionnel (switch) sur la même ligne que la barre de recherche. /// Filtre optionnel (switch) sur la même ligne que la barre de recherche.
class AdminSelectToggleFilter<T> { class SelectToggleFilter<T> {
final String label; final String label;
final bool initialValue; final bool initialValue;
@@ -10,7 +10,7 @@ class AdminSelectToggleFilter<T> {
/// [whenEnabled] renvoie `true`. /// [whenEnabled] renvoie `true`.
final bool Function(T item) whenEnabled; final bool Function(T item) whenEnabled;
const AdminSelectToggleFilter({ const SelectToggleFilter({
required this.label, required this.label,
required this.whenEnabled, required this.whenEnabled,
this.initialValue = true, this.initialValue = true,
@@ -19,7 +19,7 @@ class AdminSelectToggleFilter<T> {
/// Shell générique « rechercher + liste + sélection » pour les modales admin. /// Shell générique « rechercher + liste + sélection » pour les modales admin.
/// Utilisé par la sélection d'enfant (#146) et la sélection d'AM (#147). /// Utilisé par la sélection d'enfant (#146) et la sélection d'AM (#147).
class AdminSelectListModal<T> extends StatefulWidget { class SelectListModal<T> extends StatefulWidget {
final String title; final String title;
final String searchHint; final String searchHint;
final Future<List<T>> Function() loadItems; final Future<List<T>> Function() loadItems;
@@ -40,7 +40,7 @@ class AdminSelectListModal<T> extends StatefulWidget {
final int minVisibleCards; final int minVisibleCards;
/// Switch optionnel à droite du champ de recherche (ex. « Sans garde », « Libre »). /// Switch optionnel à droite du champ de recherche (ex. « Sans garde », « Libre »).
final AdminSelectToggleFilter<T>? toggleFilter; final SelectToggleFilter<T>? toggleFilter;
/// Si fourni, appelé avant de valider la sélection. /// Si fourni, appelé avant de valider la sélection.
/// Retourne l'élément à pop (éventuellement rafraîchi), ou `null` pour annuler. /// Retourne l'élément à pop (éventuellement rafraîchi), ou `null` pour annuler.
@@ -50,7 +50,7 @@ class AdminSelectListModal<T> extends StatefulWidget {
Future<void> Function() reload, Future<void> Function() reload,
)? resolveSelect; )? resolveSelect;
const AdminSelectListModal({ const SelectListModal({
super.key, super.key,
required this.title, required this.title,
required this.loadItems, required this.loadItems,
@@ -82,7 +82,7 @@ class AdminSelectListModal<T> extends StatefulWidget {
double modalWidth = 930, double modalWidth = 930,
double cardExtent = 52, double cardExtent = 52,
int minVisibleCards = 8, int minVisibleCards = 8,
AdminSelectToggleFilter<T>? toggleFilter, SelectToggleFilter<T>? toggleFilter,
Future<T?> Function( Future<T?> Function(
BuildContext context, BuildContext context,
T item, T item,
@@ -91,7 +91,7 @@ class AdminSelectListModal<T> extends StatefulWidget {
}) { }) {
return showDialog<T>( return showDialog<T>(
context: context, context: context,
builder: (ctx) => AdminSelectListModal<T>( builder: (ctx) => SelectListModal<T>(
title: title, title: title,
loadItems: loadItems, loadItems: loadItems,
matchesQuery: matchesQuery, matchesQuery: matchesQuery,
@@ -109,11 +109,11 @@ class AdminSelectListModal<T> extends StatefulWidget {
} }
@override @override
State<AdminSelectListModal<T>> createState() => State<SelectListModal<T>> createState() =>
_AdminSelectListModalState<T>(); _SelectListModalState<T>();
} }
class _AdminSelectListModalState<T> extends State<AdminSelectListModal<T>> { class _SelectListModalState<T> extends State<SelectListModal<T>> {
final _searchCtrl = TextEditingController(); final _searchCtrl = TextEditingController();
List<T> _all = []; List<T> _all = [];
bool _loading = true; bool _loading = true;
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// Gélule de sélection du statut utilisateur (fiches admin parent / AM). /// Gélule de sélection du statut utilisateur (fiches admin parent / AM).
class AdminStatusCapsule extends StatelessWidget { class StatusCapsule extends StatelessWidget {
final String statut; final String statut;
final ValueChanged<String>? onChanged; final ValueChanged<String>? onChanged;
static const statuts = ['actif', 'en_attente', 'suspendu', 'refuse']; static const statuts = ['actif', 'en_attente', 'suspendu', 'refuse'];
const AdminStatusCapsule({ const StatusCapsule({
super.key, super.key,
required this.statut, required this.statut,
this.onChanged, this.onChanged,
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:p_tits_pas/services/api/api_config.dart'; import 'package:p_tits_pas/services/api/api_config.dart';
import 'package:p_tits_pas/widgets/common/auth_network_image.dart'; import 'package:p_tits_pas/widgets/common/auth_network_image.dart';
class AdminUserCard extends StatefulWidget { class UserCard extends StatefulWidget {
final String title; final String title;
final List<String> subtitleLines; final List<String> subtitleLines;
final String? avatarUrl; final String? avatarUrl;
@@ -21,7 +21,7 @@ class AdminUserCard extends StatefulWidget {
final EdgeInsetsGeometry? margin; final EdgeInsetsGeometry? margin;
final EdgeInsetsGeometry? contentPadding; final EdgeInsetsGeometry? contentPadding;
const AdminUserCard({ const UserCard({
super.key, super.key,
required this.title, required this.title,
required this.subtitleLines, required this.subtitleLines,
@@ -41,10 +41,10 @@ class AdminUserCard extends StatefulWidget {
}); });
@override @override
State<AdminUserCard> createState() => _AdminUserCardState(); State<UserCard> createState() => _UserCardState();
} }
class _AdminUserCardState extends State<AdminUserCard> { class _UserCardState extends State<UserCard> {
bool _isHovered = false; bool _isHovered = false;
@override @override
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AdminListState extends StatelessWidget { class UserListState extends StatelessWidget {
final bool isLoading; final bool isLoading;
final String? error; final String? error;
final bool isEmpty; final bool isEmpty;
final String emptyMessage; final String emptyMessage;
final Widget list; final Widget list;
const AdminListState({ const UserListState({
super.key, super.key,
required this.isLoading, required this.isLoading,
required this.error, required this.error,