feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../bloc/evenements_bloc.dart';
|
||||
import '../../bloc/evenements_event.dart';
|
||||
import '../../bloc/evenements_state.dart';
|
||||
import '../../data/models/evenement_model.dart';
|
||||
|
||||
/// Dialogue de création d'événement
|
||||
@@ -19,7 +20,8 @@ class CreateEventDialog extends StatefulWidget {
|
||||
|
||||
class _CreateEventDialogState extends State<CreateEventDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
bool _createSent = false;
|
||||
|
||||
// Contrôleurs de texte
|
||||
final _titreController = TextEditingController();
|
||||
final _descriptionController = TextEditingController();
|
||||
@@ -46,7 +48,29 @@ class _CreateEventDialogState extends State<CreateEventDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
return BlocListener<EvenementsBloc, EvenementsState>(
|
||||
listenWhen: (_, state) => _createSent && (state is EvenementCreated || state is EvenementsError),
|
||||
listener: (context, state) {
|
||||
if (!_createSent || !mounted) return;
|
||||
if (state is EvenementsError) {
|
||||
setState(() => _createSent = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (state is EvenementCreated) {
|
||||
setState(() => _createSent = false);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Événement créé avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Dialog(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
constraints: const BoxConstraints(maxHeight: 600),
|
||||
@@ -297,6 +321,7 @@ class _CreateEventDialogState extends State<CreateEventDialog> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -409,19 +434,8 @@ class _CreateEventDialogState extends State<CreateEventDialog> {
|
||||
statut: StatutEvenement.planifie,
|
||||
);
|
||||
|
||||
// Envoyer l'événement au BLoC
|
||||
setState(() => _createSent = true);
|
||||
context.read<EvenementsBloc>().add(CreateEvenement(evenement));
|
||||
|
||||
// Fermer le dialogue
|
||||
Navigator.pop(context);
|
||||
|
||||
// Afficher un message de succès
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Événement créé avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../bloc/evenements_bloc.dart';
|
||||
import '../../bloc/evenements_event.dart';
|
||||
import '../../bloc/evenements_state.dart';
|
||||
import '../../data/models/evenement_model.dart';
|
||||
|
||||
/// Dialogue de modification d'événement
|
||||
@@ -24,6 +25,7 @@ class EditEventDialog extends StatefulWidget {
|
||||
|
||||
class _EditEventDialogState extends State<EditEventDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _updateSent = false;
|
||||
|
||||
// Contrôleurs de texte
|
||||
late final TextEditingController _titreController;
|
||||
@@ -74,7 +76,29 @@ class _EditEventDialogState extends State<EditEventDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
return BlocListener<EvenementsBloc, EvenementsState>(
|
||||
listenWhen: (_, state) => _updateSent && (state is EvenementUpdated || state is EvenementsError),
|
||||
listener: (context, state) {
|
||||
if (!_updateSent || !mounted) return;
|
||||
if (state is EvenementsError) {
|
||||
setState(() => _updateSent = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (state is EvenementUpdated) {
|
||||
setState(() => _updateSent = false);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Événement modifié avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Dialog(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
constraints: const BoxConstraints(maxHeight: 600),
|
||||
@@ -356,6 +380,7 @@ class _EditEventDialogState extends State<EditEventDialog> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -492,19 +517,8 @@ class _EditEventDialogState extends State<EditEventDialog> {
|
||||
estPublic: _estPublic,
|
||||
);
|
||||
|
||||
// Envoyer l'événement au BLoC
|
||||
setState(() => _updateSent = true);
|
||||
context.read<EvenementsBloc>().add(UpdateEvenement(widget.evenement.id!.toString(), evenementUpdated));
|
||||
|
||||
// Fermer le dialogue
|
||||
Navigator.pop(context);
|
||||
|
||||
// Afficher un message de succès
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Événement modifié avec succès'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../bloc/evenements_bloc.dart';
|
||||
import '../../bloc/evenements_event.dart';
|
||||
import '../../bloc/evenements_state.dart';
|
||||
import '../../data/models/evenement_model.dart';
|
||||
|
||||
class InscriptionEventDialog extends StatefulWidget {
|
||||
@@ -24,6 +25,7 @@ class InscriptionEventDialog extends StatefulWidget {
|
||||
class _InscriptionEventDialogState extends State<InscriptionEventDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _commentaireController = TextEditingController();
|
||||
bool _actionSent = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -33,7 +35,38 @@ class _InscriptionEventDialogState extends State<InscriptionEventDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
return BlocListener<EvenementsBloc, EvenementsState>(
|
||||
listenWhen: (_, state) =>
|
||||
_actionSent &&
|
||||
(state is EvenementInscrit ||
|
||||
state is EvenementDesinscrit ||
|
||||
state is EvenementsError),
|
||||
listener: (context, state) {
|
||||
if (!_actionSent || !mounted) return;
|
||||
if (state is EvenementsError) {
|
||||
setState(() => _actionSent = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (state is EvenementInscrit || state is EvenementDesinscrit) {
|
||||
setState(() => _actionSent = false);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
state is EvenementInscrit
|
||||
? 'Inscription réussie'
|
||||
: 'Désinscription réussie',
|
||||
),
|
||||
backgroundColor:
|
||||
state is EvenementInscrit ? Colors.green : Colors.orange,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Dialog(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
constraints: const BoxConstraints(maxHeight: 500),
|
||||
@@ -67,6 +100,7 @@ class _InscriptionEventDialogState extends State<InscriptionEventDialog> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,30 +324,13 @@ class _InscriptionEventDialogState extends State<InscriptionEventDialog> {
|
||||
}
|
||||
|
||||
void _submitForm() {
|
||||
setState(() => _actionSent = true);
|
||||
if (widget.isInscrit) {
|
||||
// Désinscription
|
||||
context.read<EvenementsBloc>().add(DesinscrireEvenement(widget.evenement.id!.toString()));
|
||||
Navigator.pop(context);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Désinscription réussie'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Inscription
|
||||
context.read<EvenementsBloc>().add(
|
||||
InscrireEvenement(widget.evenement.id!.toString()),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Inscription réussie'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user