petitspas/frontend/lib/widgets/admin/validation_dossier_modal.dart
Julien Martin cde676c4f9 feat: alignement master sur develop (squash)
- Dossiers unifiés #119, pending-families enrichi, validation admin (wizards)
- Front: modèles dossier_unifie / pending_family, NIR, auth
- Migrations dossier_famille, scripts de test API
- Résolution conflits: parents.*, docs tickets, auth_service, nir_utils

Made-with: Cursor
2026-03-26 00:20:47 +01:00

174 lines
5.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/services/user_service.dart';
import 'package:p_tits_pas/widgets/admin/validation_am_wizard.dart';
import 'package:p_tits_pas/widgets/admin/validation_family_wizard.dart';
/// Modale (dialog) : charge le dossier par numéro puis affiche le wizard AM ou Famille. Ticket #107, #119.
class ValidationDossierModal extends StatefulWidget {
final String numeroDossier;
final VoidCallback onClose;
final VoidCallback? onSuccess;
const ValidationDossierModal({
super.key,
required this.numeroDossier,
required this.onClose,
this.onSuccess,
});
@override
State<ValidationDossierModal> createState() => _ValidationDossierModalState();
}
class _ValidationDossierModalState extends State<ValidationDossierModal> {
bool _loading = true;
String? _error;
DossierUnifie? _dossier;
int? _stepIndex;
int? _stepTotal;
void _onStepChanged(int step, int total) {
// step = 0-based dans les wizards, affichage 1-based dans l'en-tête.
if (!mounted) return;
setState(() {
_stepIndex = step;
_stepTotal = total;
});
}
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() {
_loading = true;
_error = null;
_dossier = null;
_stepIndex = null;
_stepTotal = null;
});
try {
final d = await UserService.getDossier(widget.numeroDossier);
if (!mounted) return;
setState(() {
_dossier = d;
_loading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = e is Exception
? e.toString().replaceFirst('Exception: ', '')
: 'Erreur inconnue';
_loading = false;
});
}
}
void _onSuccess() {
widget.onSuccess?.call();
// La modale est fermée par lappelant dans onSuccess (Navigator.pop).
}
/// Largeur modale = 1,5 × 620.
static const double _modalWidth = 930; // 620 * 1.5
// Hauteur uniforme (ajustée +5px pour éviter l'overflow des étapes parents sans scroll).
static const double _bodyHeight = 435;
@override
Widget build(BuildContext context) {
final maxH = MediaQuery.of(context).size.height * 0.85;
final showStep =
_stepIndex != null && _stepTotal != null && (_stepTotal ?? 0) > 0;
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: _modalWidth, maxHeight: maxH),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(18, 18, 0, 12),
child: Text(
'Dossier ${widget.numeroDossier}',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w700),
),
),
const Spacer(),
if (showStep) ...[
Text(
'Étape ${(_stepIndex ?? 0) + 1}/${_stepTotal ?? 1}',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Colors.black54,
fontStyle: FontStyle.italic,
),
),
const SizedBox(width: 8),
],
IconButton(
icon: const Icon(Icons.close),
onPressed: widget.onClose,
tooltip: 'Fermer',
),
],
),
const Divider(height: 1),
SizedBox(
height: _bodyHeight,
child: _buildBody(),
),
],
),
),
);
}
Widget _buildBody() {
if (_loading) {
return const Padding(
padding: EdgeInsets.all(48),
child: Center(child: CircularProgressIndicator()),
);
}
if (_error != null && _error!.isNotEmpty) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(_error!,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(onPressed: _load, child: const Text('Réessayer')),
const SizedBox(height: 8),
TextButton(onPressed: widget.onClose, child: const Text('Fermer')),
],
),
);
}
final d = _dossier!;
if (d.isAm) {
return ValidationAmWizard(
dossier: d.asAm,
onClose: widget.onClose,
onSuccess: _onSuccess,
onStepChanged: _onStepChanged,
);
}
return ValidationFamilyWizard(
dossier: d.asFamily,
onClose: widget.onClose,
onSuccess: _onSuccess,
onStepChanged: _onStepChanged,
);
}
}