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:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -9,6 +9,7 @@ import '../../data/models/organization_model.dart';
import '../../bloc/organizations_bloc.dart';
import '../../bloc/organizations_event.dart';
import '../../bloc/organizations_state.dart';
import 'edit_organization_page.dart';
/// Page de détail d'une organisation avec design system cohérent
class OrganizationDetailPage extends StatefulWidget {
@@ -387,7 +388,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
child: _buildStatItem(
icon: Icons.event,
label: 'Événements',
value: '0', // Nécessite endpoint stats par organisation
value: (organization.nombreEvenements ?? 0).toString(),
color: const Color(0xFF10B981),
),
),
@@ -590,7 +591,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => _showEditDialog(),
onPressed: () => _showEditDialog(organization),
icon: const Icon(Icons.edit),
label: const Text('Modifier'),
style: ElevatedButton.styleFrom(
@@ -725,11 +726,36 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
}
}
/// Affiche le dialog d'édition
void _showEditDialog() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Édition - À implémenter')),
);
/// Ouvre la page d'édition ou le dialog selon le contexte
void _showEditDialog([OrganizationModel? organization]) {
if (organization == null) {
final state = context.read<OrganizationsBloc>().state;
if (state is OrganizationLoaded) {
organization = state.organization;
}
}
if (organization == null || !context.mounted) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Chargement de l\'organisation en cours...')),
);
}
return;
}
final org = organization;
final bloc = context.read<OrganizationsBloc>();
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => BlocProvider.value(
value: bloc,
child: EditOrganizationPage(organization: org),
),
),
).then((_) {
if (context.mounted) {
bloc.add(LoadOrganizationById(widget.organizationId));
}
});
}
/// Affiche la confirmation de suppression

View File

@@ -3,10 +3,15 @@ library organisations_page_wrapper;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../di/organizations_di.dart';
import 'package:get_it/get_it.dart';
import '../../../authentication/data/models/user_role.dart';
import '../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../bloc/organizations_bloc.dart';
import '../../bloc/organizations_event.dart';
import 'organizations_page.dart';
final _getIt = GetIt.instance;
/// Wrapper qui fournit le BLoC pour la page des organisations
class OrganizationsPageWrapper extends StatelessWidget {
const OrganizationsPageWrapper({super.key});
@@ -14,7 +19,15 @@ class OrganizationsPageWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<OrganizationsBloc>(
create: (context) => OrganizationsDI.getOrganizationsBloc(),
create: (context) {
final bloc = _getIt<OrganizationsBloc>();
// Admin d'organisation : ne charger que son/ses organisation(s)
final authState = context.read<AuthBloc>().state;
final useMesOnly = authState is AuthAuthenticated &&
authState.effectiveRole == UserRole.orgAdmin;
bloc.add(LoadOrganizations(useMesOnly: useMesOnly));
return bloc;
},
child: const OrganizationsPage(),
);
}