246 lines
7.3 KiB
Dart
246 lines
7.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:p_tits_pas/models/dossier_list_item.dart';
|
|
import 'package:p_tits_pas/utils/enfant_status_utils.dart';
|
|
import 'package:p_tits_pas/models/enfant_admin_model.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/admin/common/admin_child_detail_modal.dart';
|
|
import 'package:p_tits_pas/widgets/admin/common/admin_enfant_user_card.dart';
|
|
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
|
|
import 'package:p_tits_pas/widgets/admin/common/user_list.dart';
|
|
|
|
/// Onglet liste globale des enfants (doc 28 §6.2, ticket #137).
|
|
class EnfantManagementWidget extends StatefulWidget {
|
|
final String searchQuery;
|
|
final String? statusFilter;
|
|
|
|
const EnfantManagementWidget({
|
|
super.key,
|
|
required this.searchQuery,
|
|
this.statusFilter,
|
|
});
|
|
|
|
@override
|
|
State<EnfantManagementWidget> createState() => _EnfantManagementWidgetState();
|
|
}
|
|
|
|
class _EnfantManagementWidgetState extends State<EnfantManagementWidget> {
|
|
bool _isLoading = false;
|
|
String? _error;
|
|
List<EnfantAdminModel> _enfants = [];
|
|
bool _canDelete = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadRights();
|
|
_loadEnfants();
|
|
}
|
|
|
|
Future<void> _loadRights() async {
|
|
final user = await AuthService.getCurrentUser();
|
|
if (!mounted) return;
|
|
setState(() => _canDelete = canDeleteMetier(user?.role));
|
|
}
|
|
|
|
Future<void> _loadEnfants() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
_error = null;
|
|
});
|
|
try {
|
|
final list = await UserService.getEnfants();
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_enfants = list;
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_error = e.toString();
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _openEnfant(EnfantAdminModel enfant) async {
|
|
await showDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => AdminChildDetailModal(
|
|
enfant: enfant,
|
|
onSaved: _loadEnfants,
|
|
onDeleted: _loadEnfants,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<({String? numero, String famille, bool isLast, String? amLabel})>
|
|
_resolveContext(
|
|
EnfantAdminModel enfant,
|
|
) async {
|
|
String? amLabel;
|
|
try {
|
|
final am = await UserService.findAmForEnfant(enfant.id);
|
|
if (am != null) {
|
|
final label = formatDossierPersonLabel(
|
|
nom: am.user.nom,
|
|
prenom: am.user.prenom,
|
|
email: am.user.email,
|
|
);
|
|
if (label.isNotEmpty) amLabel = label;
|
|
}
|
|
} catch (_) {}
|
|
|
|
final parentId = enfant.parentLinks
|
|
.map((l) => l.parentId.trim())
|
|
.firstWhere((id) => id.isNotEmpty, orElse: () => '');
|
|
if (parentId.isEmpty) {
|
|
return (numero: null, famille: '', isLast: true, amLabel: amLabel);
|
|
}
|
|
try {
|
|
final parent = await UserService.getParent(parentId);
|
|
final num = (parent.user.numeroDossier ?? '').trim();
|
|
final famille = parent.user.fullName.isNotEmpty
|
|
? parent.user.fullName
|
|
: parent.user.email;
|
|
if (num.isEmpty) {
|
|
return (
|
|
numero: null,
|
|
famille: famille,
|
|
isLast: true,
|
|
amLabel: amLabel,
|
|
);
|
|
}
|
|
final dossier = await UserService.getDossier(num);
|
|
if (!dossier.isFamily) {
|
|
return (
|
|
numero: num,
|
|
famille: famille,
|
|
isLast: true,
|
|
amLabel: amLabel,
|
|
);
|
|
}
|
|
final n = dossier.asFamily.enfants.length;
|
|
final names = dossier.asFamily.parents
|
|
.map((p) => formatDossierPersonLabel(
|
|
nom: p.nom,
|
|
prenom: p.prenom,
|
|
email: p.email,
|
|
))
|
|
.where((s) => s.isNotEmpty)
|
|
.join(' - ');
|
|
return (
|
|
numero: num,
|
|
famille: names.isNotEmpty ? names : famille,
|
|
isLast: n <= 1,
|
|
amLabel: amLabel,
|
|
);
|
|
} catch (_) {
|
|
return (numero: null, famille: '', isLast: false, amLabel: amLabel);
|
|
}
|
|
}
|
|
|
|
Future<void> _confirmDelete(EnfantAdminModel enfant) async {
|
|
final ctx = await _resolveContext(enfant);
|
|
if (!mounted) return;
|
|
|
|
bool deleteDossier = false;
|
|
if (ctx.isLast && (ctx.numero ?? '').isNotEmpty) {
|
|
final choice = await showDernierEnfantSuppressionDialog(
|
|
context,
|
|
enfantName: enfant.fullName,
|
|
familleLabel: ctx.famille,
|
|
numeroDossier: ctx.numero!,
|
|
amLabel: ctx.amLabel,
|
|
);
|
|
if (choice == null || !mounted) return;
|
|
deleteDossier =
|
|
choice == DernierEnfantSuppressionChoice.dossierAussi;
|
|
} else {
|
|
final confirmed = await showSuppressionConfirmDialog(
|
|
context,
|
|
title: 'Supprimer l\'enfant',
|
|
subtitle: (ctx.numero ?? '').isEmpty
|
|
? null
|
|
: 'Dossier ${ctx.numero}',
|
|
people: [SuppressionPersonLine.enfant(enfant.fullName)],
|
|
footnotes: enfantSuppressionFootnotes(
|
|
numeroDossier: ctx.numero,
|
|
familleLabel: ctx.famille,
|
|
amLabel: ctx.amLabel,
|
|
),
|
|
);
|
|
if (!confirmed || !mounted) return;
|
|
}
|
|
|
|
try {
|
|
final result = await UserService.deleteEnfant(
|
|
enfant.id,
|
|
deleteDossier: deleteDossier,
|
|
);
|
|
if (!mounted) return;
|
|
final msg = (result['message'] ?? 'Enfant supprimé.').toString();
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
|
await _loadEnfants();
|
|
} 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.toLowerCase();
|
|
final filtered = _enfants.where((e) {
|
|
final matchesName = e.fullName.toLowerCase().contains(query);
|
|
final matchesStatus = widget.statusFilter == null ||
|
|
normalizeEnfantStatus(e.status) ==
|
|
normalizeEnfantStatus(widget.statusFilter);
|
|
return matchesName && matchesStatus;
|
|
}).toList()
|
|
..sort((a, b) {
|
|
// Orphelins (#157) en tête, puis ordre alphabétique.
|
|
final ao = a.hasNoResponsable ? 0 : 1;
|
|
final bo = b.hasNoResponsable ? 0 : 1;
|
|
if (ao != bo) return ao.compareTo(bo);
|
|
return a.fullName.toLowerCase().compareTo(b.fullName.toLowerCase());
|
|
});
|
|
|
|
return UserList(
|
|
isLoading: _isLoading,
|
|
error: _error,
|
|
isEmpty: filtered.isEmpty,
|
|
emptyMessage: 'Aucun enfant trouvé.',
|
|
itemCount: filtered.length,
|
|
itemBuilder: (context, index) {
|
|
final enfant = filtered[index];
|
|
return AdminEnfantUserCard.fromEnfant(
|
|
enfant,
|
|
onCardTap: () => _openEnfant(enfant),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.visibility_outlined),
|
|
tooltip: 'Voir / modifier',
|
|
onPressed: () => _openEnfant(enfant),
|
|
),
|
|
if (_canDelete)
|
|
suppressionIconButton(
|
|
onPressed: () => _confirmDelete(enfant),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|