feat(unionflow): ajout Spec-Kit, constitution, mission mutuelles
- Config Spec-Kit pour Spec-Driven Development - CONSTITUTION.md + .specify/memory/constitution.md - Commandes Cursor /speckit.*, règles projet - Mission: associations + mutuelles d'épargne et de financement - .gitignore: versionner config spec-kit unionflow Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/// Dialog de création d'une demande d'aide
|
||||
library create_demande_aide_dialog;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import '../../bloc/solidarity_bloc.dart';
|
||||
import '../../data/models/demande_aide_model.dart';
|
||||
import '../../../organizations/data/repositories/organization_repository.dart';
|
||||
import '../../../organizations/data/models/organization_model.dart';
|
||||
|
||||
class CreateDemandeAideDialog extends StatefulWidget {
|
||||
final VoidCallback onCreated;
|
||||
|
||||
const CreateDemandeAideDialog({super.key, required this.onCreated});
|
||||
|
||||
@override
|
||||
State<CreateDemandeAideDialog> createState() => _CreateDemandeAideDialogState();
|
||||
}
|
||||
|
||||
class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _titreController = TextEditingController();
|
||||
final _descriptionController = TextEditingController();
|
||||
final _justificationController = TextEditingController();
|
||||
final _montantController = TextEditingController();
|
||||
String? _organisationId;
|
||||
String? _type;
|
||||
List<OrganizationModel> _organisations = [];
|
||||
bool _loading = false;
|
||||
|
||||
static const List<Map<String, String>> _types = [
|
||||
{'value': 'FINANCIERE', 'label': 'Financière'},
|
||||
{'value': 'MATERIELLE', 'label': 'Matérielle'},
|
||||
{'value': 'ALIMENTAIRE', 'label': 'Alimentaire'},
|
||||
{'value': 'MEDICALE', 'label': 'Médicale'},
|
||||
{'value': 'SCOLAIRE', 'label': 'Scolaire'},
|
||||
{'value': 'LOGEMENT', 'label': 'Logement'},
|
||||
{'value': 'AUTRE', 'label': 'Autre'},
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadOrgs();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titreController.dispose();
|
||||
_descriptionController.dispose();
|
||||
_justificationController.dispose();
|
||||
_montantController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadOrgs() async {
|
||||
try {
|
||||
final repo = GetIt.instance<OrganizationRepository>();
|
||||
final list = await repo.getOrganizations(page: 0, size: 100);
|
||||
if (mounted) setState(() => _organisations = list);
|
||||
} catch (_) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _submit() {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
final titre = _titreController.text.trim();
|
||||
final description = _descriptionController.text.trim();
|
||||
if (titre.isEmpty || description.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Titre et description obligatoires')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final montant = double.tryParse(_montantController.text.replaceAll(',', '.'));
|
||||
setState(() => _loading = true);
|
||||
final demande = DemandeAideModel(
|
||||
titre: titre,
|
||||
description: description,
|
||||
justification: _justificationController.text.trim().isEmpty
|
||||
? null
|
||||
: _justificationController.text.trim(),
|
||||
type: _type,
|
||||
montantDemande: montant,
|
||||
organisationId: _organisationId,
|
||||
dateDemande: DateTime.now(),
|
||||
statut: 'BROUILLON',
|
||||
);
|
||||
context.read<SolidarityBloc>().add(CreateDemandeAide(demande));
|
||||
widget.onCreated();
|
||||
if (mounted) {
|
||||
setState(() => _loading = false);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Nouvelle demande d\'aide'),
|
||||
content: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _titreController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Titre *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Obligatoire' : null,
|
||||
enabled: !_loading,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
validator: (v) => (v == null || v.trim().isEmpty) ? 'Obligatoire' : null,
|
||||
enabled: !_loading,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _type,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Type d\'aide',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
items: _types
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e['value'],
|
||||
child: Text(e['label']!),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: _loading ? null : (v) => setState(() => _type = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _montantController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Montant demandé (FCFA, optionnel)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
enabled: !_loading,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _justificationController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Justification',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 2,
|
||||
enabled: !_loading,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _organisationId,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Organisation',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
items: _organisations
|
||||
.map((o) => DropdownMenuItem(
|
||||
value: o.id,
|
||||
child: Text(o.nom),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: _loading ? null : (v) => setState(() => _organisationId = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _loading ? null : () => Navigator.of(context).pop(),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _loading ? null : _submit,
|
||||
child: _loading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Créer'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user