feat: recherche d'événements et gestion avancée de la suppression d'équipement
- **Recherche d'événements** : Ajout d'une fonctionnalité de recherche (titre, description, lieu) dans le calendrier et d'une nouvelle fonction Cloud `searchEvents` avec gestion des permissions.
- **Suppression d'équipement avec forçage** :
- Mise à jour de la fonction Cloud `deleteEquipment` pour détecter les assignations à des événements futurs.
- Ajout d'une option `forceDelete` pour passer outre les conflits d'assignation.
- Création de `EquipmentDeleteUtils` pour gérer uniformément les dialogues de confirmation et les erreurs de conflit (HTTP 409).
- Intégration de la logique de suppression sécurisée dans `EquipmentDetailPage` et `EquipmentManagementPage`.
- **Calendrier** :
- Refonte de l'interface mobile pour intégrer la barre de recherche.
- Optimisation du chargement des événements lors de la sélection d'un résultat de recherche (lazy loading du mois concerné).
- Amélioration de la stabilité de la sélection d'événements et du filtrage par utilisateur.
- **Services & Providers** :
- Amélioration de la gestion des erreurs dans `ApiService` pour faciliter le re-throw des exceptions personnalisées.
- Ajout du support de la suppression forcée dans `DataService` et `EquipmentProvider`.
- **Refactoring** : Nettoyage du code, amélioration du formatage et ajout de logs de debug dans les services de données et d'équipements.
This commit is contained in:
@@ -19,7 +19,8 @@ class EventProvider with ChangeNotifier {
|
||||
bool _lastCanViewAll = false;
|
||||
|
||||
// Nouveau: Cache par mois pour le lazy loading
|
||||
final Map<String, List<EventModel>> _eventsByMonth = {}; // "2026-02" => [events]
|
||||
final Map<String, List<EventModel>> _eventsByMonth =
|
||||
{}; // "2026-02" => [events]
|
||||
String? _currentMonth; // Mois actuellement affiché
|
||||
|
||||
List<EventModel> get events => _events;
|
||||
@@ -28,7 +29,8 @@ class EventProvider with ChangeNotifier {
|
||||
/// 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;
|
||||
if (_lastUserId != userId || _lastCanViewAll != canViewAllEvents)
|
||||
return true;
|
||||
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(_lastLoadTime!);
|
||||
@@ -36,12 +38,14 @@ class EventProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Charger les événements d'un utilisateur via l'API
|
||||
Future<void> loadUserEvents(String userId, {bool canViewAllEvents = false, bool forceReload = false}) async {
|
||||
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)');
|
||||
print(
|
||||
'Using cached events (loaded ${DateTime.now().difference(_lastLoadTime!).inSeconds}s ago)');
|
||||
PerformanceMonitor.end('EventProvider.loadUserEvents');
|
||||
return;
|
||||
}
|
||||
@@ -50,7 +54,8 @@ class EventProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
print('Loading events for user: $userId (canViewAllEvents: $canViewAllEvents)');
|
||||
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
|
||||
@@ -61,9 +66,8 @@ class EventProvider with ChangeNotifier {
|
||||
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>)
|
||||
);
|
||||
_usersCache = usersData
|
||||
.map((key, value) => MapEntry(key, value as Map<String, dynamic>));
|
||||
|
||||
print('Found ${eventsData.length} events from API');
|
||||
|
||||
@@ -74,7 +78,8 @@ class EventProvider with ChangeNotifier {
|
||||
// Parser chaque événement
|
||||
for (var eventData in eventsData) {
|
||||
try {
|
||||
final event = EventModel.fromMap(eventData, eventData['id'] as String);
|
||||
final event =
|
||||
EventModel.fromMap(eventData, eventData['id'] as String);
|
||||
allEvents.add(event);
|
||||
} catch (e) {
|
||||
print('Failed to parse event ${eventData['id']}: $e');
|
||||
@@ -88,7 +93,8 @@ class EventProvider with ChangeNotifier {
|
||||
_lastUserId = userId;
|
||||
_lastCanViewAll = canViewAllEvents;
|
||||
|
||||
print('Successfully loaded ${_events.length} events ($failedCount failed)');
|
||||
print(
|
||||
'Successfully loaded ${_events.length} events ($failedCount failed)');
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
@@ -104,8 +110,9 @@ class EventProvider with ChangeNotifier {
|
||||
|
||||
/// 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 {
|
||||
|
||||
{bool canViewAllEvents = false,
|
||||
bool forceReload = false,
|
||||
bool silent = false}) async {
|
||||
final monthKey = '$year-${month.toString().padLeft(2, '0')}';
|
||||
|
||||
// Vérifier le cache
|
||||
@@ -130,19 +137,15 @@ class EventProvider with ChangeNotifier {
|
||||
|
||||
PerformanceMonitor.start('EventProvider.loadMonthEvents_API');
|
||||
final result = await _dataService.getEventsByMonth(
|
||||
userId: userId,
|
||||
year: year,
|
||||
month: month
|
||||
);
|
||||
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>))
|
||||
);
|
||||
_usersCache.addAll(usersData
|
||||
.map((key, value) => MapEntry(key, value as Map<String, dynamic>)));
|
||||
|
||||
print('[EventProvider] Found ${eventsData.length} events for $monthKey');
|
||||
|
||||
@@ -153,7 +156,8 @@ class EventProvider with ChangeNotifier {
|
||||
// Parser les événements
|
||||
for (var eventData in eventsData) {
|
||||
try {
|
||||
final event = EventModel.fromMap(eventData, eventData['id'] as String);
|
||||
final event =
|
||||
EventModel.fromMap(eventData, eventData['id'] as String);
|
||||
monthEvents.add(event);
|
||||
} catch (e) {
|
||||
print('[EventProvider] Failed to parse event ${eventData['id']}: $e');
|
||||
@@ -176,7 +180,8 @@ class EventProvider with ChangeNotifier {
|
||||
_lastUserId = userId;
|
||||
_lastCanViewAll = canViewAllEvents;
|
||||
|
||||
print('[EventProvider] Successfully loaded ${monthEvents.length} events for $monthKey ($failedCount failed)');
|
||||
print(
|
||||
'[EventProvider] Successfully loaded ${monthEvents.length} events for $monthKey ($failedCount failed)');
|
||||
|
||||
if (!silent) {
|
||||
_isLoading = false;
|
||||
@@ -195,7 +200,6 @@ class EventProvider with ChangeNotifier {
|
||||
/// 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;
|
||||
@@ -230,8 +234,10 @@ class EventProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Recharger les événements (utilise le dernier userId)
|
||||
Future<void> refreshEvents(String userId, {bool canViewAllEvents = false}) async {
|
||||
await loadUserEvents(userId, canViewAllEvents: canViewAllEvents, forceReload: true);
|
||||
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
|
||||
@@ -243,6 +249,41 @@ class EventProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche des événements accessibles à l'utilisateur.
|
||||
Future<List<EventModel>> searchEvents({
|
||||
required String userId,
|
||||
required String query,
|
||||
int limit = 20,
|
||||
}) async {
|
||||
final trimmedQuery = query.trim();
|
||||
if (trimmedQuery.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final result = await _dataService.searchEvents(
|
||||
userId: userId,
|
||||
query: trimmedQuery,
|
||||
limit: limit,
|
||||
);
|
||||
|
||||
final events = <EventModel>[];
|
||||
for (final eventData in result) {
|
||||
try {
|
||||
final eventId = eventData['id'] as String?;
|
||||
if (eventId == null || eventId.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
events.add(EventModel.fromMap(eventData, eventId));
|
||||
} catch (e) {
|
||||
print('Failed to parse searched event ${eventData['id']}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
events.sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
||||
return events;
|
||||
}
|
||||
|
||||
/// Ajouter un nouvel événement
|
||||
Future<void> addEvent(EventModel event) async {
|
||||
try {
|
||||
@@ -250,7 +291,8 @@ class EventProvider with ChangeNotifier {
|
||||
_events.add(event);
|
||||
|
||||
// Ajouter dans le cache par mois
|
||||
final monthKey = '${event.startDateTime.year}-${event.startDateTime.month.toString().padLeft(2, '0')}';
|
||||
final monthKey =
|
||||
'${event.startDateTime.year}-${event.startDateTime.month.toString().padLeft(2, '0')}';
|
||||
if (_eventsByMonth.containsKey(monthKey)) {
|
||||
_eventsByMonth[monthKey]!.add(event);
|
||||
}
|
||||
@@ -272,8 +314,10 @@ class EventProvider with ChangeNotifier {
|
||||
_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')}';
|
||||
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) {
|
||||
@@ -286,7 +330,8 @@ class EventProvider with ChangeNotifier {
|
||||
} else {
|
||||
// Même mois, juste mettre à jour
|
||||
if (_eventsByMonth.containsKey(newMonthKey)) {
|
||||
final monthIndex = _eventsByMonth[newMonthKey]!.indexWhere((e) => e.id == event.id);
|
||||
final monthIndex = _eventsByMonth[newMonthKey]!
|
||||
.indexWhere((e) => e.id == event.id);
|
||||
if (monthIndex != -1) {
|
||||
_eventsByMonth[newMonthKey]![monthIndex] = event;
|
||||
}
|
||||
@@ -308,7 +353,8 @@ class EventProvider with ChangeNotifier {
|
||||
|
||||
// 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')}';
|
||||
final monthKey =
|
||||
'${eventToDelete.startDateTime.year}-${eventToDelete.startDateTime.month.toString().padLeft(2, '0')}';
|
||||
|
||||
// Supprimer de _events
|
||||
_events.removeWhere((event) => event.id == eventId);
|
||||
|
||||
Reference in New Issue
Block a user