import 'package:flutter/material.dart'; import 'package:p_tits_pas/models/dossier_list_item.dart'; import 'package:p_tits_pas/models/user.dart'; import 'package:p_tits_pas/models/pending_family.dart'; import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/widgets/admin/dossier_list_card.dart'; import 'package:p_tits_pas/widgets/admin/validation_dossier_modal.dart'; /// Section « dossiers à valider » (liste unifiée AM + familles). Ticket #107 / #153. class PendingValidationWidget extends StatefulWidget { final VoidCallback? onRefresh; /// Filtre client (n°, nom, email) — onglet Dossiers (#153). final String searchQuery; /// Si true et liste vide : message court (pas de grand vide centré). final bool compactWhenEmpty; /// Numéros des dossiers pending (pour exclure de « Tous les dossiers »). final ValueChanged>? onPendingNumerosChanged; const PendingValidationWidget({ super.key, this.onRefresh, this.searchQuery = '', this.compactWhenEmpty = false, this.onPendingNumerosChanged, }); @override State createState() => _PendingValidationWidgetState(); } class _PendingValidationWidgetState extends State { bool _isLoading = true; String? _error; List _pendingAM = []; List _pendingFamilies = []; /// Noms enrichis via GET /dossiers/:numero (libelle API = noms seuls). final Map _familyNamesByNumero = {}; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _isLoading = true; _error = null; }); try { final am = await UserService.getPendingUsers(role: 'assistante_maternelle'); final families = await UserService.getPendingFamilies(); final namesByNumero = await _enrichFamilyNames(families); if (!mounted) return; setState(() { _pendingAM = am; _pendingFamilies = families; _familyNamesByNumero ..clear() ..addAll(namesByNumero); _isLoading = false; }); _emitPendingNumeros(); } catch (e) { if (!mounted) return; setState(() { _error = e is Exception ? e.toString().replaceFirst('Exception: ', '') : 'Erreur inconnue'; _isLoading = false; }); widget.onPendingNumerosChanged?.call(const {}); } } /// Complète `NOM Prénom` via le détail dossier (sans changer le back). Future> _enrichFamilyNames( List families, ) async { final out = {}; await Future.wait(families.map((f) async { final num = (f.numeroDossier ?? '').trim(); if (num.isEmpty) return; try { final dossier = await UserService.getDossier(num); if (!dossier.isFamily) return; final labels = []; final seen = {}; for (final p in dossier.asFamily.parents) { final id = p.id.trim(); if (id.isNotEmpty && !seen.add(id)) continue; final label = formatDossierPersonLabel( nom: p.nom, prenom: p.prenom, email: p.email, ); if (label.isNotEmpty) labels.add(label); } if (labels.isNotEmpty) out[num] = labels.join(' - '); } catch (_) { // Repli libellé API ci-dessous. } })); return out; } void _emitPendingNumeros() { final nums = {}; for (final u in _pendingAM) { final n = (u.numeroDossier ?? '').trim(); if (n.isNotEmpty) nums.add(n); } for (final f in _pendingFamilies) { final n = (f.numeroDossier ?? '').trim(); if (n.isNotEmpty) nums.add(n); } widget.onPendingNumerosChanged?.call(nums); } void _onOpenValidation({String? numeroDossier}) { final num = numeroDossier?.trim(); if (num == null || num.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Numéro de dossier manquant.')), ); return; } showDialog( context: context, builder: (context) => ValidationDossierModal( numeroDossier: num, onClose: () => Navigator.of(context).pop(), onSuccess: () { Navigator.of(context).pop(); _load(); widget.onRefresh?.call(); }, ), ); } bool _matchesQuery(String haystack) { final q = widget.searchQuery.trim().toLowerCase(); if (q.isEmpty) return true; return haystack.toLowerCase().contains(q); } List get _filteredAM { return _pendingAM.where((u) { final bits = [ u.numeroDossier ?? '', u.fullName, u.email, u.nom ?? '', u.prenom ?? '', ].join(' '); return _matchesQuery(bits); }).toList(); } List get _filteredFamilies { return _pendingFamilies.where((f) { final num = (f.numeroDossier ?? '').trim(); final enriched = _familyNamesByNumero[num] ?? ''; final bits = [ f.numeroDossier ?? '', f.libelle, enriched, f.emails.join(' '), ].join(' '); return _matchesQuery(bits); }).toList(); } @override Widget build(BuildContext context) { if (_isLoading) { if (widget.compactWhenEmpty) { return const Padding( padding: EdgeInsets.all(24), child: Center(child: CircularProgressIndicator()), ); } return const Center(child: CircularProgressIndicator()); } if (_error != null && _error!.isNotEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_error!, style: const TextStyle(color: Colors.red)), const SizedBox(height: 16), ElevatedButton( onPressed: _load, child: const Text('Réessayer'), ), ], ), ), ); } final pendingAM = _filteredAM; final pendingFamilies = _filteredFamilies; final cards = [ ...pendingAM.map(_buildAMCard), ...pendingFamilies.map(_buildFamilyCard), ]; if (cards.isEmpty) { if (widget.compactWhenEmpty) { final searching = widget.searchQuery.trim().isNotEmpty; return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Text( searching ? 'Aucun dossier en attente ne correspond à la recherche.' : 'Aucun dossier en attente.', style: TextStyle(color: Colors.grey.shade600, fontSize: 13), ), ); } return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_outline, size: 64, color: Colors.grey.shade400), const SizedBox(height: 16), Text( 'Aucun dossier en attente de validation', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.grey.shade600, ), ), ], ), ); } final list = Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Dossiers à valider', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 8), ...cards, ], ); if (widget.compactWhenEmpty) { return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: list, ); } return RefreshIndicator( onRefresh: () async { await _load(); widget.onRefresh?.call(); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), child: list, ), ); } String _amNamesLine(AppUser user) { return formatDossierPersonLabel( nom: user.nom, prenom: user.prenom, email: user.email, ); } Widget _buildAMCard(AppUser user) { return DossierListCard( numeroDossier: user.numeroDossier ?? '', namesLine: _amNamesLine(user), isFamille: false, photoUrl: user.photoUrl, onOpen: () => _onOpenValidation(numeroDossier: user.numeroDossier), ); } Widget _buildFamilyCard(PendingFamily family) { final num = (family.numeroDossier ?? '').trim(); final enriched = num.isNotEmpty ? _familyNamesByNumero[num] : null; final names = (enriched != null && enriched.isNotEmpty) ? enriched : formatDossierFamilyNamesLine(family.libelle); return DossierListCard( numeroDossier: family.numeroDossier ?? '', namesLine: names, isFamille: true, onOpen: () => _onOpenValidation(numeroDossier: family.numeroDossier), ); } }