import 'package:flutter/material.dart'; import 'package:p_tits_pas/utils/phone_utils.dart'; 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/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_children_affiliation_panel.dart'; import 'package:p_tits_pas/widgets/admin/common/admin_select_enfant_modal.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'; /// Fiche parent éditable (doc 28 §6.1, tickets #131 / #138). /// Shell et typo alignés sur [ValidationDossierModal] / wizards validation. class AdminParentEditModal extends StatefulWidget { final ParentModel parent; final VoidCallback? onSaved; const AdminParentEditModal({ super.key, required this.parent, this.onSaved, }); @override State createState() => _AdminParentEditModalState(); } class _AdminParentEditModalState extends State { late final TextEditingController _nomCtrl; late final TextEditingController _prenomCtrl; late final TextEditingController _emailCtrl; late final TextEditingController _telCtrl; late final TextEditingController _adresseCtrl; late final TextEditingController _villeCtrl; late final TextEditingController _cpCtrl; late String _statut; late List _children; AppUser? _coParent; late final ScrollController _childrenScrollCtrl; bool _saving = false; bool _dirty = false; static const double _modalWidth = 930; @override void initState() { super.initState(); final u = widget.parent.user; _nomCtrl = TextEditingController(text: u.nom ?? ''); _prenomCtrl = TextEditingController(text: u.prenom ?? ''); _emailCtrl = TextEditingController(text: u.email); _telCtrl = TextEditingController( text: formatPhoneForDisplay(u.telephone ?? ''), ); _adresseCtrl = TextEditingController(text: u.adresse ?? ''); _villeCtrl = TextEditingController(text: u.ville ?? ''); _cpCtrl = TextEditingController(text: u.codePostal ?? ''); _statut = u.statut ?? 'en_attente'; _coParent = widget.parent.coParent; _children = List.of(widget.parent.children); _childrenScrollCtrl = ScrollController(); for (final c in [ _nomCtrl, _prenomCtrl, _emailCtrl, _telCtrl, _adresseCtrl, _villeCtrl, _cpCtrl, ]) { c.addListener(_markDirty); } _nomCtrl.addListener(_onNameFieldChanged); _prenomCtrl.addListener(_onNameFieldChanged); _reloadChildren(); } void _onNameFieldChanged() { setState(() {}); } @override void dispose() { for (final c in [ _nomCtrl, _prenomCtrl, _emailCtrl, _telCtrl, _adresseCtrl, _villeCtrl, _cpCtrl, ]) { c.dispose(); } _childrenScrollCtrl.dispose(); super.dispose(); } void _markDirty() { if (!_dirty) setState(() => _dirty = true); } String _headerTitle() { final fn = _prenomCtrl.text.trim(); final ln = _nomCtrl.text.trim(); if (fn.isEmpty && ln.isEmpty) { final fallback = widget.parent.user.fullName.trim(); return fallback.isNotEmpty ? fallback : 'Parent'; } return '$fn $ln'.trim(); } String? _coParentName() { final name = _coParent?.fullName.trim() ?? ''; return name.isEmpty ? null : name; } Future _openCoParent() async { if (_saving) return; final co = _coParent; final id = (co?.id ?? '').trim(); if (co == null || id.isEmpty) return; try { final parent = await UserService.getParent(id); if (!mounted) return; await showDialog( context: context, builder: (ctx) => AdminParentEditModal( parent: parent, onSaved: () async { try { final refreshed = await UserService.getParent(widget.parent.user.id); if (!mounted) return; setState(() => _coParent = refreshed.coParent); } catch (_) {} widget.onSaved?.call(); }, ), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); } } Widget? _coParentSubtitle() { final name = _coParentName(); if (name == null) return null; return Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ const Text( 'Co-parent : ', style: TextStyle(fontSize: 13, color: Colors.black54), ), InkWell( onTap: _saving ? null : _openCoParent, borderRadius: BorderRadius.circular(4), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 2), child: Text( name, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: ValidationModalTheme.primaryActionBackground, decoration: TextDecoration.underline, decorationColor: ValidationModalTheme.primaryActionBackground .withValues(alpha: 0.5), ), ), ), ), ], ); } Future _save() async { if (!_dirty) return; final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Confirmer'), content: const Text( 'Enregistrer les modifications de la fiche parent ?', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('Sauvegarder'), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _saving = true); try { await UserService.updateParentFiche( parentUserId: widget.parent.user.id, body: { 'nom': _nomCtrl.text.trim(), 'prenom': _prenomCtrl.text.trim(), 'email': _emailCtrl.text.trim(), 'telephone': normalizePhone(_telCtrl.text), 'adresse': _adresseCtrl.text.trim(), 'ville': _villeCtrl.text.trim(), 'code_postal': _cpCtrl.text.trim(), 'statut': _statut, }, ); if (!mounted) return; setState(() { _dirty = false; _saving = false; }); widget.onSaved?.call(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Fiche parent enregistrée')), ); } catch (e) { if (!mounted) return; setState(() => _saving = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); } } Future _openChild(ParentChildSummary child) async { try { final enfant = await UserService.getEnfant(child.id); if (!mounted) return; await showDialog( context: context, builder: (ctx) => AdminChildDetailModal( enfant: enfant, onSaved: _reloadChildren, ), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); } } Future _reloadChildren() async { try { final refreshed = await UserService.getParent(widget.parent.user.id); final all = await UserService.getEnfants(); final byId = {for (final e in all) e.id: e}; final kids = refreshed.children.map((c) { final full = byId[c.id]; if (full != null) return ParentChildSummary.fromEnfant(full); return c; }).toList(); if (!mounted) return; setState(() { _children = kids; _coParent = refreshed.coParent; }); } catch (_) {} } Future _detachChild(ParentChildSummary child) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Détacher l\'enfant'), content: Text( 'Retirer ${child.fullName} de la fiche de ce parent ?\n' '(L\'enfant ne sera pas supprimé. S\'il n\'a plus d\'autre ' 'responsable, il restera visible dans l\'onglet Enfants avec une alerte.)', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ValidationModalTheme.primaryElevatedStyle, child: const Text('Détacher'), ), ], ), ); if (confirmed != true || !mounted) return; try { await UserService.detachEnfantFromParent( parentUserId: widget.parent.user.id, enfantId: child.id, ); if (!mounted) return; await _reloadChildren(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Enfant détaché')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); } } Future _attachChild() async { if (!mounted) return; final selected = await AdminSelectEnfantModal.show( context, excludeIds: _children.map((c) => c.id).toSet(), title: 'Rattacher un enfant', ); if (selected == null || !mounted) return; try { await UserService.attachEnfantToParent( parentUserId: widget.parent.user.id, enfantId: selected.id, ); if (!mounted) return; await _reloadChildren(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Enfant rattaché')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); } } Widget _childrenPanel() { return AdminChildrenAffiliationPanel( children: _children, scrollController: _childrenScrollCtrl, onOpen: _openChild, onDetach: _detachChild, ); } Widget _identityFields() { return IdentityBlock.editable( nomController: _nomCtrl, prenomController: _prenomCtrl, telephoneController: _telCtrl, emailController: _emailCtrl, adresseController: _adresseCtrl, codePostalController: _cpCtrl, villeController: _villeCtrl, ); } Widget _buildFooter() { return Row( children: [ TextButton( onPressed: _saving ? null : () => Navigator.of(context).pop(), child: const Text('Fermer'), ), const Spacer(), TextButton.icon( onPressed: _attachChild, icon: const Icon(Icons.link, size: 18), label: const Text('Rattacher un enfant'), ), const SizedBox(width: 12), ElevatedButton( style: ValidationModalTheme.primaryElevatedStyle, onPressed: !_dirty || _saving ? null : _save, child: _saving ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text(_dirty ? 'Sauvegarder' : 'Aucune modification'), ), ], ); } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: _modalWidth), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(18, 16, 4, 10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _headerTitle(), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, ), ), if (_coParentSubtitle() != null) ...[ const SizedBox(height: 4), _coParentSubtitle()!, ], ], ), ), const SizedBox(width: 12), SizedBox( width: 160, child: AdminStatusCapsule( statut: _statut, onChanged: (v) => setState(() { _statut = v; _dirty = true; }), ), ), IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints( minWidth: 40, minHeight: 40, ), icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), tooltip: 'Fermer', ), ], ), ), const Divider(height: 1), Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 12), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _identityFields(), const SizedBox(height: 10), Text( 'Nombre d\'enfants : ${_children.length}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 6), _childrenPanel(), const SizedBox(height: 12), _buildFooter(), ], ), ), ], ), ), ); } }