Files
petitspas/frontend/lib/widgets/admin/dossiers_management_widget.dart
T

202 lines
6.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/dossier_list_item.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/pending_validation_widget.dart';
import 'package:p_tits_pas/widgets/admin/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<DossiersManagementWidget> createState() =>
_DossiersManagementWidgetState();
}
class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
bool _loading = true;
String? _error;
List<DossierListItem> _all = [];
Set<String> _pendingNumeros = {};
int _pendingRefreshTick = 0;
@override
void initState() {
super.initState();
_loadAll();
}
Future<void> _loadAll() async {
setState(() {
_loading = true;
_error = null;
});
try {
final parents = await UserService.getParents();
final ams = await UserService.getAssistantesMaternelles();
if (!mounted) return;
final items = <DossierListItem>[
...DossierListItem.fromParents(parents),
...DossierListItem.fromAssistantes(ams),
];
items.sort((a, b) {
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<void> _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<void>(
context: context,
builder: (context) => ValidationDossierModal(
numeroDossier: num,
onClose: () => Navigator.of(context).pop(),
onSuccess: () {
Navigator.of(context).pop();
_refreshEverything();
},
),
);
}
@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,
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,
onOpen: () => _openDossier(item.numeroDossier),
);
},
childCount: filtered.length,
),
),
),
],
),
);
}
}