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,11 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import '../models/dashboard_stats_model.dart';
|
||||
import '../cache/dashboard_cache_manager.dart';
|
||||
import '../../../../core/storage/dashboard_cache_manager.dart';
|
||||
|
||||
/// Service de mode hors ligne avec synchronisation pour le Dashboard
|
||||
class DashboardOfflineService {
|
||||
@@ -14,7 +15,7 @@ class DashboardOfflineService {
|
||||
static const String _offlineModeKey = 'dashboard_offline_mode';
|
||||
|
||||
final DashboardCacheManager _cacheManager;
|
||||
final Dio _dio;
|
||||
final ApiClient _apiClient;
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
|
||||
SharedPreferences? _prefs;
|
||||
@@ -35,7 +36,7 @@ class DashboardOfflineService {
|
||||
Stream<OfflineStatus> get statusStream => _statusController.stream;
|
||||
Stream<SyncProgress> get syncStream => _syncController.stream;
|
||||
|
||||
DashboardOfflineService(this._cacheManager, this._dio);
|
||||
DashboardOfflineService(this._cacheManager, this._apiClient);
|
||||
|
||||
/// Initialise le service hors ligne
|
||||
Future<void> initialize() async {
|
||||
@@ -216,14 +217,13 @@ class DashboardOfflineService {
|
||||
final userId = action.data['userId'] as String?;
|
||||
if (orgId == null || userId == null) return;
|
||||
|
||||
final response = await _dio.get('/api/dashboard/stats', queryParameters: {
|
||||
final response = await _apiClient.get('/api/dashboard/stats', queryParameters: {
|
||||
'organisationId': orgId,
|
||||
});
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
await _cacheManager.cacheDashboardData(
|
||||
DashboardDataModel.fromJson(response.data as Map<String, dynamic>),
|
||||
orgId,
|
||||
userId,
|
||||
await _cacheManager.setKey(
|
||||
'dashboard_${orgId}_$userId',
|
||||
response.data as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ class DashboardOfflineService {
|
||||
final preferences = action.data['preferences'] as Map<String, dynamic>?;
|
||||
if (userId == null || preferences == null) return;
|
||||
|
||||
await _dio.put('/api/membres/$userId/preferences', data: preferences);
|
||||
await _apiClient.put('/api/membres/$userId/preferences', data: preferences);
|
||||
}
|
||||
|
||||
/// Synchronise le marquage d'activité comme lue
|
||||
@@ -242,7 +242,7 @@ class DashboardOfflineService {
|
||||
final activityId = action.data['activityId'] as String?;
|
||||
if (activityId == null) return;
|
||||
|
||||
await _dio.put('/api/notifications/$activityId/read');
|
||||
await _apiClient.put('/api/notifications/$activityId/read');
|
||||
}
|
||||
|
||||
/// Synchronise l'inscription à un événement
|
||||
@@ -251,7 +251,7 @@ class DashboardOfflineService {
|
||||
final membreId = action.data['membreId'] as String?;
|
||||
if (eventId == null || membreId == null) return;
|
||||
|
||||
await _dio.post('/api/evenements/$eventId/inscription', data: {
|
||||
await _apiClient.post('/api/evenements/$eventId/inscription', data: {
|
||||
'membreId': membreId,
|
||||
});
|
||||
}
|
||||
@@ -262,7 +262,7 @@ class DashboardOfflineService {
|
||||
final params = action.data['params'] as Map<String, dynamic>?;
|
||||
if (reportType == null) return;
|
||||
|
||||
await _dio.post('/api/export/$reportType', data: params ?? {});
|
||||
await _apiClient.post('/api/export/$reportType', data: params ?? {});
|
||||
}
|
||||
|
||||
/// Sauvegarde les actions en attente
|
||||
@@ -315,7 +315,7 @@ class DashboardOfflineService {
|
||||
}
|
||||
|
||||
/// Force une synchronisation manuelle
|
||||
Future<void> forcSync() async {
|
||||
Future<void> forceSync() async {
|
||||
if (!_isOnline) {
|
||||
throw Exception('Impossible de synchroniser hors ligne');
|
||||
}
|
||||
@@ -328,7 +328,8 @@ class DashboardOfflineService {
|
||||
String organizationId,
|
||||
String userId,
|
||||
) async {
|
||||
return await _cacheManager.getCachedDashboardData(organizationId, userId);
|
||||
final m = _cacheManager.getKey<Map<String, dynamic>>('dashboard_${organizationId}_$userId');
|
||||
return m != null ? DashboardDataModel.fromJson(m) : null;
|
||||
}
|
||||
|
||||
/// Vérifie si des données sont disponibles hors ligne
|
||||
|
||||
@@ -19,7 +19,8 @@ class DashboardPerformanceMonitor {
|
||||
|
||||
bool _isMonitoring = false;
|
||||
DateTime _startTime = DateTime.now();
|
||||
|
||||
int _alertsGeneratedCount = 0;
|
||||
|
||||
// Seuils d'alerte configurables
|
||||
final double _memoryThreshold = DashboardConfig.getAlertThreshold('memoryUsage');
|
||||
final double _cpuThreshold = DashboardConfig.getAlertThreshold('cpuUsage');
|
||||
@@ -147,18 +148,16 @@ class DashboardPerformanceMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtient la latence réseau
|
||||
/// Obtient la latence réseau (hôte/port depuis DashboardConfig.apiBaseUrl).
|
||||
Future<int> _getNetworkLatency() async {
|
||||
try {
|
||||
final uri = Uri.parse(DashboardConfig.apiBaseUrl);
|
||||
final host = uri.host.isNotEmpty ? uri.host : 'localhost';
|
||||
final port = uri.hasPort ? uri.port : 8085;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
// Ping vers le serveur de l'API
|
||||
final socket = await Socket.connect('localhost', 8080)
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
final socket = await Socket.connect(host, port).timeout(const Duration(seconds: 5));
|
||||
stopwatch.stop();
|
||||
await socket.close();
|
||||
|
||||
return stopwatch.elapsedMilliseconds;
|
||||
} catch (e) {
|
||||
return _simulateNetworkLatency();
|
||||
@@ -228,6 +227,7 @@ class DashboardPerformanceMonitor {
|
||||
void _checkAlerts(PerformanceMetrics metrics) {
|
||||
// Alerte mémoire
|
||||
if (metrics.memoryUsage > _memoryThreshold) {
|
||||
_alertsGeneratedCount++;
|
||||
_alertController.add(PerformanceAlert(
|
||||
type: AlertType.memory,
|
||||
severity: AlertSeverity.warning,
|
||||
@@ -240,6 +240,7 @@ class DashboardPerformanceMonitor {
|
||||
|
||||
// Alerte CPU
|
||||
if (metrics.cpuUsage > _cpuThreshold) {
|
||||
_alertsGeneratedCount++;
|
||||
_alertController.add(PerformanceAlert(
|
||||
type: AlertType.cpu,
|
||||
severity: AlertSeverity.warning,
|
||||
@@ -252,6 +253,7 @@ class DashboardPerformanceMonitor {
|
||||
|
||||
// Alerte latence réseau
|
||||
if (metrics.networkLatency > _networkLatencyThreshold) {
|
||||
_alertsGeneratedCount++;
|
||||
_alertController.add(PerformanceAlert(
|
||||
type: AlertType.network,
|
||||
severity: AlertSeverity.error,
|
||||
@@ -264,6 +266,7 @@ class DashboardPerformanceMonitor {
|
||||
|
||||
// Alerte frame rate
|
||||
if (metrics.frameRate < _frameRateThreshold) {
|
||||
_alertsGeneratedCount++;
|
||||
_alertController.add(PerformanceAlert(
|
||||
type: AlertType.performance,
|
||||
severity: AlertSeverity.warning,
|
||||
@@ -298,8 +301,7 @@ class DashboardPerformanceMonitor {
|
||||
if (_snapshots.isEmpty) {
|
||||
return PerformanceStats.empty();
|
||||
}
|
||||
|
||||
return PerformanceStats.fromSnapshots(_snapshots);
|
||||
return PerformanceStats.fromSnapshots(_snapshots, alertsGenerated: _alertsGeneratedCount);
|
||||
}
|
||||
|
||||
/// Méthodes de simulation pour le développement
|
||||
@@ -508,7 +510,7 @@ class PerformanceStats {
|
||||
);
|
||||
}
|
||||
|
||||
factory PerformanceStats.fromSnapshots(List<PerformanceSnapshot> snapshots) {
|
||||
factory PerformanceStats.fromSnapshots(List<PerformanceSnapshot> snapshots, {int alertsGenerated = 0}) {
|
||||
if (snapshots.isEmpty) return PerformanceStats.empty();
|
||||
|
||||
final metrics = snapshots.map((s) => s.metrics).toList();
|
||||
@@ -520,7 +522,7 @@ class PerformanceStats {
|
||||
peakMemoryUsage: metrics.map((m) => m.memoryUsage).reduce((a, b) => a > b ? a : b),
|
||||
averageCpuUsage: metrics.map((m) => m.cpuUsage).reduce((a, b) => a + b) / metrics.length,
|
||||
peakCpuUsage: metrics.map((m) => m.cpuUsage).reduce((a, b) => a > b ? a : b),
|
||||
alertsGenerated: 0, // À implémenter si nécessaire
|
||||
alertsGenerated: alertsGenerated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user