import 'package:flutter/material.dart'; import 'package:intl/intl.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/utils/phone_utils.dart'; import 'package:p_tits_pas/widgets/admin/validation_dossier_modal.dart'; /// Onglet « À valider » : deux listes (AM en attente, familles en attente). Ticket #107. class PendingValidationWidget extends StatefulWidget { final VoidCallback? onRefresh; const PendingValidationWidget({super.key, this.onRefresh}); @override State createState() => _PendingValidationWidgetState(); } class _PendingValidationWidgetState extends State { bool _isLoading = true; String? _error; List _pendingAM = []; List _pendingFamilies = []; @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(); if (!mounted) return; setState(() { _pendingAM = am; _pendingFamilies = families; _isLoading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = e is Exception ? e.toString().replaceFirst('Exception: ', '') : 'Erreur inconnue'; _isLoading = false; }); } } void _onOpenValidation({String? type, String? id, 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(); }, ), ); } @override Widget build(BuildContext context) { if (_isLoading) { 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 hasAM = _pendingAM.isNotEmpty; final hasFamilies = _pendingFamilies.isNotEmpty; if (!hasAM && !hasFamilies) { 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, ), ), ], ), ); } return RefreshIndicator( onRefresh: () async { await _load(); widget.onRefresh?.call(); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (hasAM) ...[ _sectionTitle('Assistantes maternelles en attente'), const SizedBox(height: 8), ..._pendingAM.map((u) => _buildAMCard(u)), const SizedBox(height: 24), ], if (hasFamilies) ...[ _sectionTitle('Familles en attente'), const SizedBox(height: 8), ..._pendingFamilies.map((f) => _buildFamilyCard(f)), ], ], ), ), ); } Widget _sectionTitle(String title) { return Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Colors.black87, ), ); } /// Ligne commune : icône | titre (+ sous-titre) | bouton Ouvrir. /// [titleWidget] remplace [title] si les deux sont fournis : priorité à [titleWidget]. Widget _buildPendingRow({ required IconData icon, String? title, Widget? titleWidget, String? subtitle, TextStyle? subtitleStyle, required VoidCallback onOpen, }) { assert(title != null || titleWidget != null); final titleChild = titleWidget ?? Text( title!, style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, ), ); final subStyle = subtitleStyle ?? TextStyle( fontSize: 12, color: Colors.grey.shade600, ); return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), side: BorderSide(color: Colors.grey.shade300), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, color: Colors.grey.shade600, size: 28), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ titleChild, if (subtitle != null && subtitle.isNotEmpty) ...[ const SizedBox(height: 4), Text( subtitle, style: subStyle, ), ], ], ), ), ElevatedButton.icon( onPressed: onOpen, icon: const Icon(Icons.open_in_new, size: 18), label: const Text('Ouvrir'), ), ], ), ), ); } /// Sous-titre AM : `email - date • tél. • CP ville` (plan affichage lignes À valider). String _amSubtitleLine(AppUser user) { final email = user.email.trim(); final bits = []; bits.add(DateFormat('dd/MM/yyyy').format(user.createdAt.toLocal())); final tel = user.telephone?.trim(); if (tel != null && tel.isNotEmpty) { bits.add(formatPhoneForDisplay(tel)); } final cp = user.codePostal?.trim(); final ville = user.ville?.trim(); final loc = [if (cp != null && cp.isNotEmpty) cp, if (ville != null && ville.isNotEmpty) ville] .join(' ') .trim(); if (loc.isNotEmpty) bits.add(loc); final infos = bits.join(' • '); if (email.isEmpty) return infos; return '$email - $infos'; } Widget _buildAMCard(AppUser user) { final numDossier = user.numeroDossier ?? '–'; final nameBold = user.fullName.isNotEmpty ? user.fullName : (user.email.isNotEmpty ? user.email : '–'); return _buildPendingRow( icon: Icons.person_outline, titleWidget: Text.rich( TextSpan( style: const TextStyle(fontSize: 14, color: Colors.black87), children: [ TextSpan( text: nameBold, style: const TextStyle(fontWeight: FontWeight.w700), ), TextSpan( text: ' - $numDossier', style: const TextStyle(fontWeight: FontWeight.w400), ), ], ), ), subtitle: _amSubtitleLine(user), subtitleStyle: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey.shade600, ), onOpen: () => _onOpenValidation( type: 'AM', id: user.id, numeroDossier: user.numeroDossier, ), ); } /// `email, tél., localisation` par parent, puis `date soumission`, puis `nb enfants`. String _familyParentSegment(PendingParentLine p) { final parts = []; final e = p.email?.trim(); if (e != null && e.isNotEmpty) parts.add(e); final t = p.telephone?.trim(); if (t != null && t.isNotEmpty) parts.add(formatPhoneForDisplay(t)); final cp = p.codePostal?.trim(); final v = p.ville?.trim(); final loc = [if (cp != null && cp.isNotEmpty) cp, if (v != null && v.isNotEmpty) v] .join(' ') .trim(); if (loc.isNotEmpty) parts.add(loc); return parts.join(', '); } String _familySubtitleLine(PendingFamily family) { final blocks = family.parentLines .map(_familyParentSegment) .where((s) => s.isNotEmpty) .join(' - '); final tail = []; final date = family.dateSoumission; if (date != null) { tail.add(DateFormat('dd/MM/yyyy').format(date.toLocal())); } if (family.nombreEnfants > 0) { tail.add( family.nombreEnfants > 1 ? '${family.nombreEnfants} enfants' : '1 enfant', ); } final right = tail.join(' - '); if (blocks.isEmpty && right.isEmpty) return ''; if (blocks.isEmpty) return right; if (right.isEmpty) return blocks; return '$blocks - $right'; } Widget _buildFamilyCard(PendingFamily family) { final numDossier = family.numeroDossier ?? '–'; final nameBold = family.libelle.isNotEmpty ? family.libelle : 'Famille'; return _buildPendingRow( icon: Icons.family_restroom_outlined, titleWidget: Text.rich( TextSpan( style: const TextStyle(fontSize: 14, color: Colors.black87), children: [ TextSpan( text: nameBold, style: const TextStyle(fontWeight: FontWeight.w700), ), TextSpan( text: ' - $numDossier', style: const TextStyle(fontWeight: FontWeight.w400), ), ], ), ), subtitle: _familySubtitleLine(family), subtitleStyle: TextStyle( fontSize: 12, fontStyle: FontStyle.italic, color: Colors.grey.shade600, ), onOpen: () => _onOpenValidation( type: 'famille', id: family.parentIds.isNotEmpty ? family.parentIds.first : null, numeroDossier: family.numeroDossier, ), ); } }