import 'package:flutter/material.dart'; import 'package:p_tits_pas/models/dossier_list_item.dart'; import 'package:p_tits_pas/services/auth_service.dart'; import 'package:p_tits_pas/services/user_service.dart'; import 'package:p_tits_pas/utils/staff_deletion_rights.dart'; import 'package:p_tits_pas/widgets/dashboard/dossier_list_card.dart'; import 'package:p_tits_pas/widgets/dashboard/pending_validation_widget.dart'; import 'package:p_tits_pas/widgets/dashboard/common/suppression_confirm_dialog.dart'; import 'package:p_tits_pas/widgets/dashboard/validation_dossier_modal.dart'; /// Onglet permanent « Dossiers » (#153) : pending en haut + liste unifiée en bas. class DossiersManagementWidget extends StatefulWidget { final String searchQuery; final VoidCallback? onRefresh; const DossiersManagementWidget({ super.key, this.searchQuery = '', this.onRefresh, }); @override State createState() => _DossiersManagementWidgetState(); } class _DossiersManagementWidgetState extends State { bool _loading = true; String? _error; List _all = []; Set _pendingNumeros = {}; int _pendingRefreshTick = 0; bool _canDelete = false; @override void initState() { super.initState(); _loadRights(); _loadAll(); } Future _loadRights() async { final user = await AuthService.getCurrentUser(); if (!mounted) return; setState(() => _canDelete = canDeleteMetier(user?.role)); } Future _loadAll() async { setState(() { _loading = true; _error = null; }); try { final parents = await UserService.getParents(); final ams = await UserService.getAssistantesMaternelles(); Map sansEnfant = {}; try { sansEnfant = await UserService.getSansEnfantByNumero(); } catch (_) { // Flag optionnel : ne bloque pas la liste. } if (!mounted) return; final items = [ ...DossierListItem.fromParents(parents), ...DossierListItem.fromAssistantes(ams), ].map((item) { if (!item.isFamille) return item; final apiFlag = sansEnfant[item.numeroDossier] == true; final localEmpty = (item.enfantsCount ?? 0) == 0; return item.copyWith( sansEnfant: apiFlag || localEmpty || item.sansEnfant, ); }).toList(); items.sort((a, b) { // Dossiers sans enfant en tête (#160), comme orphelins #157. final ae = a.sansEnfant ? 0 : 1; final be = b.sansEnfant ? 0 : 1; if (ae != be) return ae.compareTo(be); final byNum = a.numeroDossier.compareTo(b.numeroDossier); if (byNum != 0) return byNum; return a.typeLabel.compareTo(b.typeLabel); }); setState(() { _all = items; _loading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = e is Exception ? e.toString().replaceFirst('Exception: ', '') : 'Erreur inconnue'; _loading = false; }); } } Future _refreshEverything() async { setState(() => _pendingRefreshTick++); await _loadAll(); widget.onRefresh?.call(); } void _openDossier(String numeroDossier) { final num = numeroDossier.trim(); if (num.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Numéro de dossier manquant.')), ); return; } showDialog( context: context, builder: (context) => ValidationDossierModal( numeroDossier: num, openAsEdit: true, onClose: () => Navigator.of(context).pop(), onSuccess: () { Navigator.of(context).pop(); _refreshEverything(); }, ), ); } Future _confirmDeleteDossier(DossierListItem item) async { final num = item.numeroDossier.trim(); if (num.isEmpty) return; var people = []; String? fallbackSummary; try { final dossier = await UserService.getDossier(num); if (dossier.isFamily) { final f = dossier.asFamily; people = suppressionPeopleFromDossier( isFamille: true, parents: f.parents .map((p) => ( nom: p.nom ?? '', prenom: p.prenom ?? '', email: p.email, )) .toList(), enfants: f.enfants .map((e) => ( nom: e.lastName ?? '', prenom: e.firstName ?? '', )) .toList(), ); } else { final am = dossier.asAm.user; people = suppressionPeopleFromDossier( isFamille: false, parents: const [], enfants: const [], amName: formatDossierPersonLabel( nom: am.nom, prenom: am.prenom, email: am.email, ), ); } } catch (_) { fallbackSummary = item.isFamille ? 'Tous les parents et enfants rattachés seront supprimés.' : 'Le compte AM sera supprimé ; les enfants accueillis ' 'seront conservés.'; if (item.namesLine.trim().isNotEmpty) { for (final part in item.namesLine.split(' - ')) { final label = part.trim(); if (label.isEmpty) continue; people.add(SuppressionPersonLine( label: label, icon: item.isFamille ? Icons.supervisor_account_outlined : Icons.face, role: item.isFamille ? 'Parent' : 'AM', )); } } } if (!mounted) return; final confirmed = await showDossierSuppressionConfirmDialog( context, numeroDossier: num, isFamille: item.isFamille, people: people, fallbackSummary: fallbackSummary, ); if (!confirmed || !mounted) return; try { final result = await UserService.deleteDossier(num); if (!mounted) return; final msg = (result['message'] ?? 'Dossier supprimé.').toString(); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); await _refreshEverything(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( e is Exception ? e.toString().replaceFirst('Exception: ', '') : 'Erreur suppression', ), ), ); } } @override Widget build(BuildContext context) { final query = widget.searchQuery; // Pending uniquement en haut — exclus de « Tous les dossiers ». final filtered = _all .where((d) => !_pendingNumeros.contains(d.numeroDossier)) .where((d) => (d.statut ?? '').toLowerCase() != 'en_attente') .where((d) => d.matchesQuery(query)) .toList(growable: false); return RefreshIndicator( onRefresh: _refreshEverything, child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter( child: PendingValidationWidget( key: ValueKey('pending-$_pendingRefreshTick'), searchQuery: query, compactWhenEmpty: true, canDelete: _canDelete, onPendingNumerosChanged: (nums) { if (!mounted) return; setState(() => _pendingNumeros = nums); }, onRefresh: () { _loadAll(); widget.onRefresh?.call(); }, ), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Text( 'Tous les dossiers', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Colors.black87, ), ), ), ), if (_loading) const SliverFillRemaining( hasScrollBody: false, child: Center(child: CircularProgressIndicator()), ) else if (_error != null && _error!.isNotEmpty) SliverFillRemaining( hasScrollBody: false, child: 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: _loadAll, child: const Text('Réessayer'), ), ], ), ), ), ) else if (filtered.isEmpty) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 32), child: Text( query.trim().isEmpty ? 'Aucun dossier pour le moment.\n' 'Pour créer un dossier → onglet Parents (+ Parents) ' 'ou Assistantes maternelles (+ Asmat).' : 'Aucun dossier ne correspond à la recherche.', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade600, height: 1.4), ), ), ) else SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final item = filtered[index]; return DossierListCard( numeroDossier: item.numeroDossier, namesLine: item.namesLine, isFamille: item.isFamille, photoUrl: item.photoUrl, sansEnfant: item.sansEnfant, enfantsCount: item.enfantsCount, vigilanceTooltip: item.sansEnfant ? 'Aucun enfant rattaché à ce dossier famille' : null, onOpen: () => _openDossier(item.numeroDossier), onDelete: _canDelete ? () => _confirmDeleteDossier(item) : null, ); }, childCount: filtered.length, ), ), ), ], ), ); } }