365 lines
12 KiB
Dart
365 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:em2rp/models/event_model.dart';
|
|
import 'package:em2rp/services/data_service.dart';
|
|
import 'package:em2rp/services/api_service.dart';
|
|
import 'package:em2rp/utils/performance_monitor.dart';
|
|
|
|
class EventProvider with ChangeNotifier {
|
|
final DataService _dataService = DataService(FirebaseFunctionsApiService());
|
|
List<EventModel> _events = [];
|
|
bool _isLoading = false;
|
|
|
|
// Cache des utilisateurs chargés depuis getEvents
|
|
Map<String, Map<String, dynamic>> _usersCache = {};
|
|
|
|
// Cache pour éviter les rechargements inutiles (ancien système)
|
|
DateTime? _lastLoadTime;
|
|
String? _lastUserId;
|
|
bool _lastCanViewAll = false;
|
|
|
|
// Nouveau: Cache par mois pour le lazy loading
|
|
Map<String, List<EventModel>> _eventsByMonth = {}; // "2026-02" => [events]
|
|
String? _currentMonth; // Mois actuellement affiché
|
|
|
|
List<EventModel> get events => _events;
|
|
bool get isLoading => _isLoading;
|
|
|
|
/// Vérifie si les données doivent être rechargées (cache de 30 secondes)
|
|
bool _shouldReload(String userId, bool canViewAllEvents) {
|
|
if (_lastLoadTime == null) return true;
|
|
if (_lastUserId != userId || _lastCanViewAll != canViewAllEvents) return true;
|
|
|
|
final now = DateTime.now();
|
|
final difference = now.difference(_lastLoadTime!);
|
|
return difference.inSeconds > 30;
|
|
}
|
|
|
|
/// Charger les événements d'un utilisateur via l'API
|
|
Future<void> loadUserEvents(String userId, {bool canViewAllEvents = false, bool forceReload = false}) async {
|
|
PerformanceMonitor.start('EventProvider.loadUserEvents');
|
|
|
|
// Éviter les rechargements inutiles
|
|
if (!forceReload && !_shouldReload(userId, canViewAllEvents)) {
|
|
print('Using cached events (loaded ${DateTime.now().difference(_lastLoadTime!).inSeconds}s ago)');
|
|
PerformanceMonitor.end('EventProvider.loadUserEvents');
|
|
return;
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
print('Loading events for user: $userId (canViewAllEvents: $canViewAllEvents)');
|
|
|
|
PerformanceMonitor.start('EventProvider.getEvents_API');
|
|
// Charger via l'API - les permissions sont vérifiées côté serveur
|
|
final result = await _dataService.getEvents(userId: userId);
|
|
PerformanceMonitor.end('EventProvider.getEvents_API');
|
|
|
|
final eventsData = result['events'] as List<Map<String, dynamic>>;
|
|
final usersData = result['users'] as Map<String, dynamic>;
|
|
|
|
// Stocker les utilisateurs dans le cache
|
|
_usersCache = usersData.map((key, value) =>
|
|
MapEntry(key, value as Map<String, dynamic>)
|
|
);
|
|
|
|
print('Found ${eventsData.length} events from API');
|
|
|
|
PerformanceMonitor.start('EventProvider.parseEvents');
|
|
List<EventModel> allEvents = [];
|
|
int failedCount = 0;
|
|
|
|
// Parser chaque événement
|
|
for (var eventData in eventsData) {
|
|
try {
|
|
final event = EventModel.fromMap(eventData, eventData['id'] as String);
|
|
allEvents.add(event);
|
|
} catch (e) {
|
|
print('Failed to parse event ${eventData['id']}: $e');
|
|
failedCount++;
|
|
}
|
|
}
|
|
PerformanceMonitor.end('EventProvider.parseEvents');
|
|
|
|
_events = allEvents;
|
|
_lastLoadTime = DateTime.now();
|
|
_lastUserId = userId;
|
|
_lastCanViewAll = canViewAllEvents;
|
|
|
|
print('Successfully loaded ${_events.length} events (${failedCount} failed)');
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
PerformanceMonitor.end('EventProvider.loadUserEvents');
|
|
} catch (e) {
|
|
print('Error loading events: $e');
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
PerformanceMonitor.end('EventProvider.loadUserEvents');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Charger les événements d'un mois spécifique (lazy loading optimisé)
|
|
Future<void> loadMonthEvents(String userId, int year, int month,
|
|
{bool canViewAllEvents = false, bool forceReload = false, bool silent = false}) async {
|
|
|
|
final monthKey = '$year-${month.toString().padLeft(2, '0')}';
|
|
|
|
// Vérifier le cache
|
|
if (!forceReload && _eventsByMonth.containsKey(monthKey)) {
|
|
print('[EventProvider] Using cached events for $monthKey');
|
|
|
|
if (!silent) {
|
|
_currentMonth = monthKey;
|
|
_events = _eventsByMonth[monthKey]!;
|
|
notifyListeners();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!silent) {
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
}
|
|
|
|
try {
|
|
print('[EventProvider] Loading events for month: $monthKey');
|
|
|
|
PerformanceMonitor.start('EventProvider.loadMonthEvents_API');
|
|
final result = await _dataService.getEventsByMonth(
|
|
userId: userId,
|
|
year: year,
|
|
month: month
|
|
);
|
|
PerformanceMonitor.end('EventProvider.loadMonthEvents_API');
|
|
|
|
final eventsData = result['events'] as List<Map<String, dynamic>>;
|
|
final usersData = result['users'] as Map<String, dynamic>;
|
|
|
|
// Mettre à jour le cache utilisateurs (addAll pour cumuler)
|
|
_usersCache.addAll(
|
|
usersData.map((key, value) => MapEntry(key, value as Map<String, dynamic>))
|
|
);
|
|
|
|
print('[EventProvider] Found ${eventsData.length} events for $monthKey');
|
|
|
|
PerformanceMonitor.start('EventProvider.parseMonthEvents');
|
|
List<EventModel> monthEvents = [];
|
|
int failedCount = 0;
|
|
|
|
// Parser les événements
|
|
for (var eventData in eventsData) {
|
|
try {
|
|
final event = EventModel.fromMap(eventData, eventData['id'] as String);
|
|
monthEvents.add(event);
|
|
} catch (e) {
|
|
print('[EventProvider] Failed to parse event ${eventData['id']}: $e');
|
|
failedCount++;
|
|
}
|
|
}
|
|
PerformanceMonitor.end('EventProvider.parseMonthEvents');
|
|
|
|
// Stocker dans le cache par mois
|
|
_eventsByMonth[monthKey] = monthEvents;
|
|
|
|
// Mettre à jour _events et _currentMonth seulement si ce n'est pas un préchargement silencieux
|
|
if (!silent) {
|
|
_currentMonth = monthKey;
|
|
_events = monthEvents;
|
|
}
|
|
|
|
// Mettre à jour les infos de cache global
|
|
_lastLoadTime = DateTime.now();
|
|
_lastUserId = userId;
|
|
_lastCanViewAll = canViewAllEvents;
|
|
|
|
print('[EventProvider] Successfully loaded ${monthEvents.length} events for $monthKey (${failedCount} failed)');
|
|
|
|
if (!silent) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
} catch (e) {
|
|
print('[EventProvider] Error loading month events: $e');
|
|
if (!silent) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Précharger les mois adjacents en arrière-plan
|
|
void preloadAdjacentMonths(String userId, int year, int month,
|
|
{bool canViewAllEvents = false}) {
|
|
|
|
// Mois précédent
|
|
final prevMonth = month == 1 ? 12 : month - 1;
|
|
final prevYear = month == 1 ? year - 1 : year;
|
|
|
|
// Mois suivant
|
|
final nextMonth = month == 12 ? 1 : month + 1;
|
|
final nextYear = month == 12 ? year + 1 : year;
|
|
|
|
print('[EventProvider] Preloading adjacent months...');
|
|
|
|
// Charger en arrière-plan (sans bloquer l'UI ni notifier)
|
|
Future.microtask(() async {
|
|
try {
|
|
await loadMonthEvents(userId, prevYear, prevMonth,
|
|
canViewAllEvents: canViewAllEvents, silent: true);
|
|
await loadMonthEvents(userId, nextYear, nextMonth,
|
|
canViewAllEvents: canViewAllEvents, silent: true);
|
|
print('[EventProvider] Adjacent months preloaded successfully');
|
|
} catch (e) {
|
|
print('[EventProvider] Error preloading adjacent months: $e');
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Recharger les événements (utilise le dernier userId)
|
|
Future<void> refreshEvents(String userId, {bool canViewAllEvents = false}) async {
|
|
await loadUserEvents(userId, canViewAllEvents: canViewAllEvents, forceReload: true);
|
|
}
|
|
|
|
/// Récupérer un événement spécifique par ID
|
|
EventModel? getEventById(String eventId) {
|
|
try {
|
|
return _events.firstWhere((event) => event.id == eventId);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Ajouter un nouvel événement
|
|
Future<void> addEvent(EventModel event) async {
|
|
try {
|
|
// Ajouter l'événement localement dans _events
|
|
_events.add(event);
|
|
|
|
// Ajouter dans le cache par mois
|
|
final monthKey = '${event.startDateTime.year}-${event.startDateTime.month.toString().padLeft(2, '0')}';
|
|
if (_eventsByMonth.containsKey(monthKey)) {
|
|
_eventsByMonth[monthKey]!.add(event);
|
|
}
|
|
|
|
notifyListeners();
|
|
} catch (e) {
|
|
print('Error adding event: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Mettre à jour un événement
|
|
Future<void> updateEvent(EventModel event) async {
|
|
try {
|
|
// Mise à jour dans _events
|
|
final index = _events.indexWhere((e) => e.id == event.id);
|
|
if (index != -1) {
|
|
final oldEvent = _events[index];
|
|
_events[index] = event;
|
|
|
|
// Mettre à jour dans le cache par mois
|
|
final oldMonthKey = '${oldEvent.startDateTime.year}-${oldEvent.startDateTime.month.toString().padLeft(2, '0')}';
|
|
final newMonthKey = '${event.startDateTime.year}-${event.startDateTime.month.toString().padLeft(2, '0')}';
|
|
|
|
// Si le mois a changé, supprimer de l'ancien et ajouter au nouveau
|
|
if (oldMonthKey != newMonthKey) {
|
|
if (_eventsByMonth.containsKey(oldMonthKey)) {
|
|
_eventsByMonth[oldMonthKey]!.removeWhere((e) => e.id == event.id);
|
|
}
|
|
if (_eventsByMonth.containsKey(newMonthKey)) {
|
|
_eventsByMonth[newMonthKey]!.add(event);
|
|
}
|
|
} else {
|
|
// Même mois, juste mettre à jour
|
|
if (_eventsByMonth.containsKey(newMonthKey)) {
|
|
final monthIndex = _eventsByMonth[newMonthKey]!.indexWhere((e) => e.id == event.id);
|
|
if (monthIndex != -1) {
|
|
_eventsByMonth[newMonthKey]![monthIndex] = event;
|
|
}
|
|
}
|
|
}
|
|
|
|
notifyListeners();
|
|
}
|
|
} catch (e) {
|
|
print('Error updating event: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Supprimer un événement
|
|
Future<void> deleteEvent(String eventId) async {
|
|
try {
|
|
await _dataService.deleteEvent(eventId);
|
|
|
|
// Trouver l'événement pour obtenir sa date avant de le supprimer
|
|
final eventToDelete = _events.firstWhere((e) => e.id == eventId);
|
|
final monthKey = '${eventToDelete.startDateTime.year}-${eventToDelete.startDateTime.month.toString().padLeft(2, '0')}';
|
|
|
|
// Supprimer de _events
|
|
_events.removeWhere((event) => event.id == eventId);
|
|
|
|
// Supprimer du cache par mois
|
|
if (_eventsByMonth.containsKey(monthKey)) {
|
|
_eventsByMonth[monthKey]!.removeWhere((event) => event.id == eventId);
|
|
}
|
|
|
|
notifyListeners();
|
|
} catch (e) {
|
|
print('Error deleting event: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Récupérer les données d'un utilisateur depuis le cache
|
|
Map<String, dynamic>? getUserFromCache(String uid) {
|
|
return _usersCache[uid];
|
|
}
|
|
|
|
/// Récupérer les utilisateurs de la workforce d'un événement
|
|
List<Map<String, dynamic>> getWorkforceUsers(EventModel event) {
|
|
final users = <Map<String, dynamic>>[];
|
|
|
|
for (final dynamic userRef in event.workforce) {
|
|
try {
|
|
String? uid;
|
|
|
|
// Tenter d'extraire l'UID
|
|
if (userRef is String) {
|
|
uid = userRef;
|
|
} else {
|
|
// Essayer d'extraire l'ID si c'est une DocumentReference
|
|
final ref = userRef as DocumentReference?;
|
|
uid = ref?.id;
|
|
}
|
|
|
|
if (uid != null) {
|
|
final userData = getUserFromCache(uid);
|
|
if (userData != null) {
|
|
users.add(userData);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Ignorer les références invalides
|
|
print('Skipping invalid workforce reference: $userRef');
|
|
}
|
|
}
|
|
|
|
return users;
|
|
}
|
|
|
|
/// Vider la liste des événements
|
|
void clearEvents() {
|
|
_events = [];
|
|
_lastLoadTime = null;
|
|
_lastUserId = null;
|
|
_lastCanViewAll = false;
|
|
notifyListeners();
|
|
}
|
|
}
|