feat(#131): fiche AM éditable en 3 onglets et widgets admin partagés.

Modale admin AM (identité, dossier pro, enfants), API front avec repli, note back affiliation, et extraction gélule statut / liste enfants pour la fiche parent.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-25 17:33:41 +02:00
co-authored by Cursor
parent 2fd97ddecb
commit 479a32b4bf
10 changed files with 1417 additions and 181 deletions
@@ -6,7 +6,8 @@ import 'package:p_tits_pas/models/parent_model.dart';
import 'package:p_tits_pas/models/user.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/admin/common/admin_enfant_user_card.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_children_affiliation_panel.dart';
import 'package:p_tits_pas/widgets/admin/common/admin_status_capsule.dart';
import 'package:p_tits_pas/widgets/common/identity_block.dart';
import 'package:p_tits_pas/widgets/admin/validation_modal_theme.dart';
@@ -42,13 +43,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
bool _saving = false;
bool _dirty = false;
static const _statuts = ['actif', 'en_attente', 'suspendu', 'refuse'];
static const double _modalWidth = 930;
/// Hauteur d'une ligne enfant (carte + marge) — viewport = 2,5 lignes visibles.
static const double _childListItemHeight = 58;
static const double _childrenListViewportHeight =
_childListItemHeight * 2.5 + 8;
@override
void initState() {
@@ -124,21 +119,6 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
return 'Co-parent : $name';
}
String _displayStatus(String status) {
switch (status) {
case 'actif':
return 'Actif';
case 'en_attente':
return 'En attente';
case 'suspendu':
return 'Suspendu';
case 'refuse':
return 'Refusé';
default:
return status;
}
}
Future<void> _save() async {
if (!_dirty) return;
final confirmed = await showDialog<bool>(
@@ -337,39 +317,12 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
}
}
Widget _statusDropdown() {
return Container(
height: 34,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.black26),
),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _statuts.contains(_statut) ? _statut : _statuts.first,
isExpanded: true,
isDense: true,
style: const TextStyle(fontSize: 13, color: Colors.black87),
icon: const Icon(Icons.arrow_drop_down, size: 20),
items: _statuts
.map(
(s) => DropdownMenuItem(
value: s,
child: Text(_displayStatus(s)),
),
)
.toList(),
onChanged: (v) {
if (v == null) return;
setState(() {
_statut = v;
_dirty = true;
});
},
),
),
Widget _childrenPanel() {
return AdminChildrenAffiliationPanel(
children: _children,
scrollController: _childrenScrollCtrl,
onOpen: _openChild,
onDetach: _detachChild,
);
}
@@ -385,49 +338,6 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
);
}
Widget _childrenPanel() {
return Container(
height: _childrenListViewportHeight,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
clipBehavior: Clip.antiAlias,
child: _children.isEmpty
? const Center(
child: Text(
'Aucun enfant rattaché',
style: TextStyle(fontSize: 14, color: Colors.black54),
),
)
: ListView.builder(
controller: _childrenScrollCtrl,
padding: const EdgeInsets.fromLTRB(8, 8, 8, 4),
itemExtent: _childListItemHeight,
itemCount: _children.length,
itemBuilder: (_, i) {
final c = _children[i];
return AdminEnfantUserCard.fromSummary(
c,
actions: [
IconButton(
icon: const Icon(Icons.visibility_outlined),
tooltip: 'Voir / modifier',
onPressed: () => _openChild(c),
),
IconButton(
tooltip: 'Détacher',
icon: Icon(Icons.link_off, color: Colors.orange.shade800),
onPressed: () => _detachChild(c),
),
],
);
},
),
);
}
Widget _buildFooter() {
return Row(
children: [
@@ -465,10 +375,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: _modalWidth,
maxHeight: MediaQuery.of(context).size.height * 0.85,
),
constraints: const BoxConstraints(maxWidth: _modalWidth),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -502,7 +409,16 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
),
),
const SizedBox(width: 12),
SizedBox(width: 160, child: _statusDropdown()),
SizedBox(
width: 160,
child: AdminStatusCapsule(
statut: _statut,
onChanged: (v) => setState(() {
_statut = v;
_dirty = true;
}),
),
),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
@@ -518,13 +434,13 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
),
const Divider(height: 1),
Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_identityFields(),
const SizedBox(height: 12),
const SizedBox(height: 10),
Text(
'Nombre d\'enfants : ${_children.length}',
style: const TextStyle(
@@ -533,9 +449,9 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
color: Colors.black87,
),
),
const SizedBox(height: 8),
const SizedBox(height: 6),
_childrenPanel(),
const SizedBox(height: 16),
const SizedBox(height: 12),
_buildFooter(),
],
),