import 'package:flutter/material.dart'; import 'package:p_tits_pas/models/enfant_admin_model.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/services/user_service.dart'; import 'package:p_tits_pas/utils/phone_utils.dart'; import 'package:p_tits_pas/widgets/admin/common/admin_child_detail_modal.dart'; /// Fiche parent éditable (doc 28 §6.1, tickets #131 / #138). 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; bool _saving = false; bool _dirty = false; static const _statuts = ['actif', 'en_attente', 'suspendu', 'refuse']; @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: u.telephone ?? ''); _adresseCtrl = TextEditingController(text: u.adresse ?? ''); _villeCtrl = TextEditingController(text: u.ville ?? ''); _cpCtrl = TextEditingController(text: u.codePostal ?? ''); _statut = u.statut ?? 'en_attente'; _children = List.of(widget.parent.children); for (final c in [_nomCtrl, _prenomCtrl, _emailCtrl, _telCtrl, _adresseCtrl, _villeCtrl, _cpCtrl]) { c.addListener(_markDirty); } } @override void dispose() { for (final c in [_nomCtrl, _prenomCtrl, _emailCtrl, _telCtrl, _adresseCtrl, _villeCtrl, _cpCtrl]) { c.dispose(); } super.dispose(); } void _markDirty() { if (!_dirty) setState(() => _dirty = true); } 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 _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': _telCtrl.text.trim(), '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: () async { await _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); if (!mounted) return; setState(() => _children = List.of(refreshed.children)); } 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é.)', ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Annuler')), ElevatedButton( onPressed: () => Navigator.pop(ctx, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red.shade700), child: const Text('Détacher'), ), ], ), ); if (confirmed != true || !mounted) return; try { final updated = await UserService.detachEnfantFromParent( parentUserId: widget.parent.user.id, enfantId: child.id, ); if (!mounted) return; setState(() => _children = List.of(updated.children)); 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 { List all; try { all = await UserService.getEnfants(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString().replaceFirst('Exception: ', ''))), ); return; } final linkedIds = _children.map((c) => c.id).toSet(); final candidates = all.where((e) => !linkedIds.contains(e.id)).toList(); if (candidates.isEmpty) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Aucun enfant disponible à rattacher')), ); return; } if (!mounted) return; final selected = await showDialog( context: context, builder: (ctx) => SimpleDialog( title: const Text('Rattacher un enfant'), children: candidates .map( (e) => SimpleDialogOption( onPressed: () => Navigator.pop(ctx, e), child: Text(e.fullName), ), ) .toList(), ), ); if (selected == null || !mounted) return; try { final updated = await UserService.attachEnfantToParent( parentUserId: widget.parent.user.id, enfantId: selected.id, ); if (!mounted) return; setState(() => _children = List.of(updated.children)); 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 _field(String label, TextEditingController ctrl, {TextInputType? keyboard}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 130, child: Padding( padding: const EdgeInsets.only(top: 12), child: Text(label, style: const TextStyle(fontWeight: FontWeight.w600)), ), ), Expanded( child: TextFormField( controller: ctrl, keyboardType: keyboard, decoration: const InputDecoration(isDense: true, border: OutlineInputBorder()), ), ), ], ), ); } @override Widget build(BuildContext context) { final numero = widget.parent.user.numeroDossier; return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 680, maxHeight: 720), child: Padding( padding: const EdgeInsets.all(18), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.parent.user.fullName.isEmpty ? 'Parent' : widget.parent.user.fullName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700), ), Text(widget.parent.user.email, style: const TextStyle(color: Colors.black54)), ], ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ), const Divider(), Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (numero != null && numero.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ const SizedBox( width: 130, child: Text('N° dossier', style: TextStyle(fontWeight: FontWeight.w600)), ), Expanded(child: Text(numero)), ], ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ const SizedBox( width: 130, child: Text('Statut', style: TextStyle(fontWeight: FontWeight.w600)), ), Expanded( child: DropdownButtonFormField( value: _statuts.contains(_statut) ? _statut : _statuts.first, decoration: const InputDecoration(isDense: true, border: OutlineInputBorder()), items: _statuts .map( (s) => DropdownMenuItem( value: s, child: Text(_displayStatus(s)), ), ) .toList(), onChanged: (v) { if (v == null) return; setState(() { _statut = v; _dirty = true; }); }, ), ), ], ), ), _field('Nom', _nomCtrl), _field('Prénom', _prenomCtrl), _field('Email', _emailCtrl, keyboard: TextInputType.emailAddress), _field('Téléphone', _telCtrl, keyboard: TextInputType.phone), if (_telCtrl.text.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 130, bottom: 4), child: Text( formatPhoneForDisplay(_telCtrl.text), style: const TextStyle(fontSize: 12, color: Colors.black54), ), ), _field('Adresse', _adresseCtrl), _field('Ville', _villeCtrl), _field('Code postal', _cpCtrl), const SizedBox(height: 12), Text( 'Nombre d\'enfants : ${_children.length}', style: const TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.all(8), child: _children.isEmpty ? const Text('Aucun enfant rattaché', style: TextStyle(color: Colors.black54)) : Column( children: _children .map( (c) => ListTile( dense: true, contentPadding: EdgeInsets.zero, title: InkWell( onTap: () => _openChild(c), child: Text( c.fullName, style: const TextStyle( decoration: TextDecoration.underline, color: Colors.blue, ), ), ), subtitle: Text(_childStatusLabel(c.status)), trailing: IconButton( tooltip: 'Détacher', icon: Icon(Icons.link_off, color: Colors.orange.shade800), onPressed: () => _detachChild(c), ), ), ) .toList(), ), ), const SizedBox(height: 8), Align( alignment: Alignment.centerLeft, child: TextButton.icon( onPressed: _attachChild, icon: const Icon(Icons.link), label: const Text('Rattacher un enfant'), ), ), ], ), ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _saving ? null : () => Navigator.of(context).pop(), child: const Text('Fermer'), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: !_dirty || _saving ? null : _save, icon: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save), label: Text(_dirty ? 'Sauvegarder' : 'Aucune modification'), ), ], ), ], ), ), ), ); } String _childStatusLabel(String status) { switch (status) { case 'a_naitre': return 'À naître'; case 'actif': return 'Actif'; case 'scolarise': return 'Scolarisé'; default: return status; } } }