petitspas/frontend/lib/widgets/admin/validation_refus_form.dart
Julien Martin 2e02f60601 feat(admin): onglet À valider, dossier unifié et modales de validation
- Onglet « À valider » (AM + familles), pending-families et détail dossier par numéro.
- Wizards validation AM et famille, modale commune, chargement via GET /dossiers/:num.
- UI : cartes enfants, photo AM (cadre uniforme, ratio), NIR affiché formaté (espaces autour du tiret).
- Backend : DTO / routes parents pending ; scripts de test et mise à jour issue Gitea.

Tickets #107, #119.

Made-with: Cursor
2026-03-26 00:15:18 +01:00

124 lines
4.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 'validation_modal_theme.dart';
/// Page « Motifs du refus » : champ libre + Annuler (ferme la modale), Précédent (retour au choix Valider/Refuser), Envoyer. Ticket #107.
class ValidationRefusForm extends StatefulWidget {
/// Ferme la modale (abandon du flux).
final VoidCallback onCancel;
/// Retour à létape précédente du wizard (écran avec Valider / Refuser).
final VoidCallback onPrevious;
final ValueChanged<String?> onSubmit;
const ValidationRefusForm({
super.key,
required this.onCancel,
required this.onPrevious,
required this.onSubmit,
});
@override
State<ValidationRefusForm> createState() => _ValidationRefusFormState();
}
class _ValidationRefusFormState extends State<ValidationRefusForm> {
final _controller = TextEditingController();
final _formKey = GlobalKey<FormState>();
static const int _minLength = 20;
String? _validateMotifs(String? value) {
final t = value?.trim() ?? '';
if (t.isEmpty) return 'Les motifs du refus sont obligatoires.';
if (t.length < _minLength) {
return 'Veuillez indiquer au moins $_minLength caractères (${t.length}/$_minLength).';
}
return null;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Indiquez les motifs du refus',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
constraints: BoxConstraints.tight(Size(constraints.maxWidth, constraints.maxHeight)),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.grey.shade400),
),
child: TextFormField(
controller: _controller,
maxLines: null,
minLines: 1,
validator: _validateMotifs,
decoration: InputDecoration(
hintText: 'Saisissez les raisons du refus (minimum $_minLength caractères)',
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
alignLabelWithHint: true,
filled: false,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
),
),
);
},
),
),
const SizedBox(height: 24),
Row(
children: [
TextButton(
onPressed: widget.onCancel,
child: const Text('Annuler'),
),
const Spacer(),
Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: widget.onPrevious,
child: const Text('Précédent'),
),
const SizedBox(width: 8),
ElevatedButton(
style: ValidationModalTheme.primaryElevatedStyle,
onPressed: () {
if (_formKey.currentState!.validate()) {
widget.onSubmit(_controller.text.trim());
}
},
child: const Text('Envoyer'),
),
],
),
],
),
],
),
);
}
}