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:
@@ -1,7 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../../shared/design_system/dashboard_theme.dart';
|
||||
import '../../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../pages/connected_dashboard_page.dart';
|
||||
import '../../pages/advanced_dashboard_page.dart';
|
||||
import '../../../../settings/presentation/pages/language_settings_page.dart';
|
||||
import '../../../../settings/presentation/pages/system_settings_page.dart';
|
||||
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
|
||||
import '../../../../members/presentation/pages/members_page_wrapper.dart';
|
||||
import '../../../../events/presentation/pages/events_page_wrapper.dart';
|
||||
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
|
||||
|
||||
/// Widget de navigation pour les différents types de dashboard
|
||||
class DashboardNavigation extends StatefulWidget {
|
||||
@@ -80,11 +86,11 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
Widget _buildBottomNavigationBar() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: DashboardTheme.white,
|
||||
color: Theme.of(context).cardColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: DashboardTheme.grey900.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
@@ -92,10 +98,10 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
child: BottomAppBar(
|
||||
shape: const CircularNotchedRectangle(),
|
||||
notchMargin: 8,
|
||||
color: DashboardTheme.white,
|
||||
color: Theme.of(context).cardColor,
|
||||
elevation: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: DashboardTheme.spacing8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: _tabs.asMap().entries.map((entry) {
|
||||
@@ -121,23 +127,24 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
onTap: () => setState(() => _currentIndex = index),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: DashboardTheme.spacing12,
|
||||
horizontal: DashboardTheme.spacing16,
|
||||
vertical: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isActive ? tab.activeIcon : tab.icon,
|
||||
color: isActive ? DashboardTheme.royalBlue : DashboardTheme.grey400,
|
||||
size: 24,
|
||||
color: isActive ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing4),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
tab.title,
|
||||
style: DashboardTheme.bodySmall.copyWith(
|
||||
color: isActive ? DashboardTheme.royalBlue : DashboardTheme.grey400,
|
||||
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: isActive ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
||||
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -147,21 +154,14 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
}
|
||||
|
||||
Widget _buildFloatingActionButton() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: DashboardTheme.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
boxShadow: DashboardTheme.elevatedShadow,
|
||||
),
|
||||
child: FloatingActionButton(
|
||||
onPressed: _showQuickActions,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
color: DashboardTheme.white,
|
||||
size: 28,
|
||||
),
|
||||
return FloatingActionButton(
|
||||
onPressed: _showQuickActions,
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
elevation: 4,
|
||||
child: const Icon(
|
||||
Icons.add_outlined,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -169,9 +169,9 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
Widget _buildReportsPage() {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Rapports'),
|
||||
backgroundColor: DashboardTheme.royalBlue,
|
||||
foregroundColor: DashboardTheme.white,
|
||||
title: Text('Rapports'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 1.1)),
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
@@ -179,20 +179,20 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.assessment,
|
||||
size: 64,
|
||||
color: DashboardTheme.grey400,
|
||||
Icons.assessment_outlined,
|
||||
size: 48,
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing16),
|
||||
const Text(
|
||||
'Page Rapports',
|
||||
style: DashboardTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing8),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Fonctionnalité en cours de développement',
|
||||
style: DashboardTheme.bodyMedium.copyWith(
|
||||
color: DashboardTheme.grey500,
|
||||
'Page Rapports'.toUpperCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'En cours de développement',
|
||||
style: AppTypography.bodyTextSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -204,64 +204,64 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
Widget _buildSettingsPage() {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Paramètres'),
|
||||
backgroundColor: DashboardTheme.royalBlue,
|
||||
foregroundColor: DashboardTheme.white,
|
||||
title: Text('Paramètres'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 1.1)),
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(DashboardTheme.spacing16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
_buildSettingsSection(
|
||||
'Apparence',
|
||||
[
|
||||
_buildSettingsTile(
|
||||
'Thème',
|
||||
'Bleu Roi & Pétrole',
|
||||
Icons.palette,
|
||||
() {},
|
||||
'Design System UnionFlow',
|
||||
Icons.palette_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
_buildSettingsTile(
|
||||
'Langue',
|
||||
'Français',
|
||||
Icons.language,
|
||||
() {},
|
||||
Icons.language_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const LanguageSettingsPage())),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing24),
|
||||
const SizedBox(height: 24),
|
||||
_buildSettingsSection(
|
||||
'Notifications',
|
||||
[
|
||||
_buildSettingsTile(
|
||||
'Notifications push',
|
||||
'Activées',
|
||||
Icons.notifications,
|
||||
() {},
|
||||
Icons.notifications_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
_buildSettingsTile(
|
||||
'Emails',
|
||||
'Quotidien',
|
||||
Icons.email,
|
||||
() {},
|
||||
Icons.email_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing24),
|
||||
const SizedBox(height: 24),
|
||||
_buildSettingsSection(
|
||||
'Données',
|
||||
[
|
||||
_buildSettingsTile(
|
||||
'Synchronisation',
|
||||
'Automatique',
|
||||
Icons.sync,
|
||||
() {},
|
||||
Icons.sync_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
_buildSettingsTile(
|
||||
'Cache',
|
||||
'Vider le cache',
|
||||
Icons.storage,
|
||||
() {},
|
||||
Icons.storage_outlined,
|
||||
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -275,12 +275,16 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: DashboardTheme.titleMedium,
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: AppColors.primaryGreen, fontSize: 10),
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing12),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
decoration: DashboardTheme.cardDecoration,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
),
|
||||
child: Column(children: children),
|
||||
),
|
||||
],
|
||||
@@ -294,12 +298,13 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
VoidCallback onTap,
|
||||
) {
|
||||
return ListTile(
|
||||
leading: Icon(icon, color: DashboardTheme.royalBlue),
|
||||
title: Text(title, style: DashboardTheme.bodyMedium),
|
||||
subtitle: Text(subtitle, style: DashboardTheme.bodySmall),
|
||||
leading: Icon(icon, color: AppColors.primaryGreen, size: 20),
|
||||
title: Text(title, style: AppTypography.actionText.copyWith(fontSize: 13)),
|
||||
subtitle: Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
color: DashboardTheme.grey400,
|
||||
Icons.chevron_right_outlined,
|
||||
color: AppColors.textSecondaryLight,
|
||||
size: 16,
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
@@ -310,14 +315,14 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: DashboardTheme.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(DashboardTheme.borderRadiusLarge),
|
||||
topRight: Radius.circular(DashboardTheme.borderRadiusLarge),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(DashboardTheme.spacing20),
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -325,61 +330,60 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: DashboardTheme.grey300,
|
||||
color: AppColors.lightBorder,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing20),
|
||||
const Text(
|
||||
'Actions Rapides',
|
||||
style: DashboardTheme.titleMedium,
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'ACTIONS RAPIDES',
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing20),
|
||||
const SizedBox(height: 20),
|
||||
GridView.count(
|
||||
crossAxisCount: 3,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisSpacing: DashboardTheme.spacing16,
|
||||
mainAxisSpacing: DashboardTheme.spacing16,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
children: [
|
||||
_buildQuickActionItem('Nouveau\nMembre', Icons.person_add, DashboardTheme.success),
|
||||
_buildQuickActionItem('Créer\nÉvénement', Icons.event_available, DashboardTheme.royalBlue),
|
||||
_buildQuickActionItem('Ajouter\nContribution', Icons.payment, DashboardTheme.tealBlue),
|
||||
_buildQuickActionItem('Envoyer\nMessage', Icons.message, DashboardTheme.warning),
|
||||
_buildQuickActionItem('Générer\nRapport', Icons.assessment, DashboardTheme.info),
|
||||
_buildQuickActionItem('Paramètres', Icons.settings, DashboardTheme.grey600),
|
||||
_buildQuickActionItem(context, 'Nouveau\nMembre', Icons.person_add_outlined, AppColors.success, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper()))),
|
||||
_buildQuickActionItem(context, 'Créer\nÉvénement', Icons.event_available_outlined, AppColors.primaryGreen, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper()))),
|
||||
_buildQuickActionItem(context, 'Ajouter\nContribution', Icons.account_balance_wallet_outlined, AppColors.brandGreen, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()))),
|
||||
_buildQuickActionItem(context, 'Générer\nRapport', Icons.assessment_outlined, AppColors.info, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper()))),
|
||||
_buildQuickActionItem(context, 'Paramètres', Icons.settings_outlined, AppColors.textSecondaryLight, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage()))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: DashboardTheme.spacing20),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickActionItem(String title, IconData icon, Color color) {
|
||||
Widget _buildQuickActionItem(BuildContext context, String title, IconData icon, Color color, VoidCallback onNavigate) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
// Action rapide non encore connectée
|
||||
onNavigate();
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.all(DashboardTheme.spacing12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24),
|
||||
const SizedBox(height: DashboardTheme.spacing8),
|
||||
Icon(icon, color: color, size: 20),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
title,
|
||||
style: DashboardTheme.bodySmall.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textPrimaryLight,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user