Cette mise à jour refactorise en profondeur le chargement des événements sur la page Calendrier pour améliorer drastiquement les performances et la réactivité de l'application, en particulier pour les utilisateurs avec un grand nombre d'événements. Le système de chargement initial de tous les événements est remplacé par un mécanisme de lazy loading qui ne récupère que les données du mois affiché.
**Changements majeurs :**
- **Lazy Loading Côté Client (`EventProvider`) :**
- Une nouvelle méthode `loadMonthEvents` a été introduite pour charger uniquement les événements d'un mois spécifique (`year`, `month`).
- Un cache par mois (`_eventsByMonth`) a été mis en place pour éviter les rechargements inutiles lors de la navigation entre des mois déjà consultés.
- Ajout d'une fonction `preloadAdjacentMonths` qui charge en arrière-plan et silencieusement les mois précédent et suivant, assurant une navigation fluide dans le calendrier.
- **Nouveau Endpoint Backend (`getEventsByMonth`) :**
- Création d'un nouvel endpoint Cloud Function `getEventsByMonth` optimisé pour ne requêter que les événements dans une plage de dates (début et fin du mois).
- La fonction récupère les utilisateurs associés de manière optimisée en parallélisant les requêtes Firestore (Promise.all).
- La limite du nombre d'IDs par requête 'in' a été augmentée de 10 à 30 pour réduire le nombre d'appels à la base de données.
- **Intégration au Calendrier (`CalendarPage`) :**
- La page charge désormais les événements pour le mois courant au démarrage via `_loadCurrentMonthEvents`.
- Lorsqu'un utilisateur change de mois (`onPageChanged`), la page déclenche le chargement des données pour le nouveau mois, avec un préchargement des mois adjacents pour anticiper la navigation.
- Le chargement initial de tous les événements (`_loadEventsAsync`) a été déprécié.
- **Correction de la Séquence de Démarrage (`main.dart`) :**
- L'appel à `_autoLogin` est maintenant enveloppé dans `WidgetsBinding.instance.addPostFrameCallback`. Cela garantit que la navigation ne se produit qu'après le premier rendu de l'interface, évitant ainsi des erreurs potentielles de build/navigation concurrentes et fiabilisant le chargement initial des données utilisateur.
731 lines
27 KiB
Dart
731 lines
27 KiB
Dart
import 'package:em2rp/providers/local_user_provider.dart';
|
|
import 'package:em2rp/providers/event_provider.dart';
|
|
import 'package:em2rp/utils/performance_monitor.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
|
|
import 'package:em2rp/views/widgets/nav/main_drawer.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:table_calendar/table_calendar.dart';
|
|
import 'package:em2rp/models/event_model.dart';
|
|
import 'package:em2rp/views/widgets/calendar_widgets/event_details.dart';
|
|
import 'package:intl/date_symbol_data_local.dart';
|
|
import 'package:em2rp/views/widgets/calendar_widgets/month_view.dart';
|
|
import 'package:em2rp/views/widgets/calendar_widgets/week_view.dart';
|
|
import 'package:em2rp/views/event_add_page.dart';
|
|
import 'package:em2rp/views/widgets/calendar_widgets/mobile_calendar_view.dart';
|
|
import 'package:em2rp/views/widgets/calendar_widgets/user_filter_dropdown.dart';
|
|
import 'package:em2rp/utils/colors.dart';
|
|
|
|
class CalendarPage extends StatefulWidget {
|
|
const CalendarPage({super.key});
|
|
|
|
@override
|
|
State<CalendarPage> createState() => _CalendarPageState();
|
|
}
|
|
|
|
class _CalendarPageState extends State<CalendarPage> {
|
|
CalendarFormat _calendarFormat = CalendarFormat.month;
|
|
DateTime _focusedDay = DateTime.now();
|
|
DateTime? _selectedDay;
|
|
EventModel? _selectedEvent;
|
|
bool _calendarCollapsed = false;
|
|
int _selectedEventIndex = 0;
|
|
String? _selectedUserId; // Filtre par utilisateur (null = tous les événements)
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
initializeDateFormatting('fr_FR', null);
|
|
// Charger les événements du mois courant après le premier build
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_loadCurrentMonthEvents();
|
|
});
|
|
}
|
|
|
|
/// Charge les événements du mois courant avec lazy loading
|
|
Future<void> _loadCurrentMonthEvents() async {
|
|
PerformanceMonitor.start('CalendarPage.loadCurrentMonthEvents');
|
|
|
|
final localAuthProvider = Provider.of<LocalUserProvider>(context, listen: false);
|
|
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
|
final userId = localAuthProvider.uid;
|
|
final canViewAllEvents = localAuthProvider.hasPermission('view_all_events');
|
|
|
|
if (userId != null) {
|
|
print('[CalendarPage] Loading events for ${_focusedDay.year}-${_focusedDay.month}');
|
|
|
|
await eventProvider.loadMonthEvents(
|
|
userId,
|
|
_focusedDay.year,
|
|
_focusedDay.month,
|
|
canViewAllEvents: canViewAllEvents,
|
|
);
|
|
|
|
// Précharger les mois adjacents en arrière-plan
|
|
eventProvider.preloadAdjacentMonths(
|
|
userId,
|
|
_focusedDay.year,
|
|
_focusedDay.month,
|
|
canViewAllEvents: canViewAllEvents,
|
|
);
|
|
|
|
if (mounted) {
|
|
PerformanceMonitor.start('CalendarPage.selectDefaultEvent');
|
|
_selectDefaultEvent();
|
|
PerformanceMonitor.end('CalendarPage.selectDefaultEvent');
|
|
}
|
|
}
|
|
|
|
PerformanceMonitor.end('CalendarPage.loadCurrentMonthEvents');
|
|
}
|
|
|
|
/// Charge les événements de manière asynchrone et sélectionne l'événement approprié
|
|
/// DEPRECATED: Utiliser _loadCurrentMonthEvents à la place
|
|
Future<void> _loadEventsAsync() async {
|
|
PerformanceMonitor.start('CalendarPage.loadEventsAsync');
|
|
await _loadEvents();
|
|
|
|
// Sélectionner l'événement approprié après le chargement
|
|
if (mounted) {
|
|
PerformanceMonitor.start('CalendarPage.selectDefaultEvent');
|
|
_selectDefaultEvent();
|
|
PerformanceMonitor.end('CalendarPage.selectDefaultEvent');
|
|
}
|
|
PerformanceMonitor.end('CalendarPage.loadEventsAsync');
|
|
}
|
|
|
|
/// Sélectionne automatiquement l'événement le plus proche de maintenant
|
|
void _selectDefaultEvent() {
|
|
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
|
final events = eventProvider.events;
|
|
|
|
if (events.isEmpty) return;
|
|
|
|
final now = DateTime.now();
|
|
|
|
// Trouver les événements d'aujourd'hui
|
|
final todayEvents = events.where((e) {
|
|
final start = e.startDateTime;
|
|
return start.year == now.year &&
|
|
start.month == now.month &&
|
|
start.day == now.day;
|
|
}).toList()..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
|
|
EventModel? selected;
|
|
DateTime? selectedDay;
|
|
|
|
if (todayEvents.isNotEmpty) {
|
|
selected = todayEvents[0];
|
|
selectedDay = DateTime(now.year, now.month, now.day);
|
|
} else {
|
|
// Chercher le prochain événement à venir
|
|
final futureEvents = events
|
|
.where((e) => e.startDateTime.isAfter(now))
|
|
.toList()..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
|
|
if (futureEvents.isNotEmpty) {
|
|
selected = futureEvents[0];
|
|
final start = selected.startDateTime;
|
|
selectedDay = DateTime(start.year, start.month, start.day);
|
|
} else {
|
|
// Aucun événement à venir, prendre le plus récent
|
|
final sortedEvents = events.toList()
|
|
..sort((a, b) => b.startDateTime.compareTo(a.startDateTime));
|
|
selected = sortedEvents.first;
|
|
final start = selected.startDateTime;
|
|
selectedDay = DateTime(start.year, start.month, start.day);
|
|
}
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
_selectedDay = selectedDay;
|
|
_focusedDay = selectedDay!;
|
|
_selectedEventIndex = 0;
|
|
_selectedEvent = selected;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _loadEvents() async {
|
|
final localAuthProvider =
|
|
Provider.of<LocalUserProvider>(context, listen: false);
|
|
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
|
final userId = localAuthProvider.uid;
|
|
final canViewAllEvents = localAuthProvider.hasPermission('view_all_events');
|
|
|
|
if (userId != null) {
|
|
await eventProvider.loadUserEvents(userId,
|
|
canViewAllEvents: canViewAllEvents);
|
|
}
|
|
}
|
|
|
|
/// Filtre les événements selon l'utilisateur sélectionné (si filtre actif)
|
|
/// TEMPORAIREMENT DÉSACTIVÉ - À réactiver quand permission ajoutée dans Firestore
|
|
List<EventModel> _getFilteredEvents(List<EventModel> allEvents) {
|
|
if (_selectedUserId == null) {
|
|
return allEvents; // Pas de filtre, retourner tous les événements
|
|
}
|
|
|
|
// Filtrer les événements où l'utilisateur sélectionné fait partie de la workforce
|
|
return allEvents.where((event) {
|
|
return event.workforce.any((worker) {
|
|
if (worker is String) {
|
|
return worker == _selectedUserId;
|
|
}
|
|
// Si c'est une DocumentReference, on ne peut pas facilement comparer
|
|
// On suppose que les données sont chargées correctement en String
|
|
return false;
|
|
});
|
|
}).toList();
|
|
}
|
|
|
|
void _changeWeek(int delta) {
|
|
setState(() {
|
|
_focusedDay = _focusedDay.add(Duration(days: 7 * delta));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final eventProvider = Provider.of<EventProvider>(context);
|
|
final localUserProvider = Provider.of<LocalUserProvider>(context);
|
|
final canCreateEvents = localUserProvider.hasPermission('create_events');
|
|
final canViewAllUserEvents = localUserProvider.hasPermission('view_all_user_events');
|
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
|
|
|
// Appliquer le filtre utilisateur si actif
|
|
final filteredEvents = _getFilteredEvents(eventProvider.events);
|
|
|
|
// Debug logs
|
|
print('[CalendarPage.build] Total events: ${eventProvider.events.length}, Filtered: ${filteredEvents.length}');
|
|
if (eventProvider.events.isNotEmpty) {
|
|
print('[CalendarPage.build] First event: ${eventProvider.events.first.name} at ${eventProvider.events.first.startDateTime}');
|
|
}
|
|
|
|
if (eventProvider.isLoading) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: CustomAppBar(
|
|
title: "Calendrier",
|
|
),
|
|
drawer: const MainDrawer(currentPage: '/calendar'),
|
|
body: Column(
|
|
children: [
|
|
// Filtre utilisateur dans le corps de la page
|
|
if (canViewAllUserEvents && !isMobile)
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
color: Colors.grey[100],
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.filter_list, color: AppColors.rouge),
|
|
const SizedBox(width: 12),
|
|
const Text(
|
|
'Filtrer par utilisateur :',
|
|
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: UserFilterDropdown(
|
|
selectedUserId: _selectedUserId,
|
|
onUserSelected: (userId) {
|
|
setState(() {
|
|
_selectedUserId = userId;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Corps du calendrier
|
|
Expanded(
|
|
child: isMobile ? _buildMobileLayout(filteredEvents) : _buildDesktopLayout(filteredEvents),
|
|
),
|
|
],
|
|
),
|
|
floatingActionButton: canCreateEvents
|
|
? FloatingActionButton(
|
|
backgroundColor: Colors.white,
|
|
onPressed: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (context) => EventAddEditPage(
|
|
selectedDate: _selectedDay ?? DateTime.now(),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
tooltip: 'Ajouter un événement',
|
|
child: const Icon(Icons.add, color: Colors.red),
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
Widget _buildDesktopLayout(List<EventModel> filteredEvents) {
|
|
return Row(
|
|
children: [
|
|
// Calendrier (65% de la largeur)
|
|
Expanded(
|
|
flex: 65,
|
|
child: _buildCalendar(filteredEvents),
|
|
),
|
|
// Détails de l'événement (35% de la largeur)
|
|
Expanded(
|
|
flex: 35,
|
|
child: _selectedEvent != null
|
|
? EventDetails(
|
|
event: _selectedEvent!,
|
|
selectedDate: _selectedDay,
|
|
events: filteredEvents,
|
|
onSelectEvent: (event, date) {
|
|
setState(() {
|
|
_selectedEvent = event;
|
|
_selectedDay = date;
|
|
});
|
|
},
|
|
)
|
|
: Center(
|
|
child: _selectedDay != null
|
|
? Text('Aucun événement ne démarre à cette date')
|
|
: const Text(
|
|
'Sélectionnez un événement pour voir les détails'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildMobileLayout(List<EventModel> filteredEvents) {
|
|
final eventsForSelectedDay = _selectedDay == null
|
|
? []
|
|
: filteredEvents
|
|
.where((e) =>
|
|
e.startDateTime.year == _selectedDay!.year &&
|
|
e.startDateTime.month == _selectedDay!.month &&
|
|
e.startDateTime.day == _selectedDay!.day)
|
|
.toList()
|
|
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
final hasEvents = eventsForSelectedDay.isNotEmpty;
|
|
final currentEvent =
|
|
hasEvents && _selectedEventIndex < eventsForSelectedDay.length
|
|
? eventsForSelectedDay[_selectedEventIndex]
|
|
: null;
|
|
|
|
// GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois)
|
|
return GestureDetector(
|
|
onVerticalDragEnd: (details) {
|
|
if (details.primaryVelocity != null) {
|
|
if (details.primaryVelocity! < -200) {
|
|
// Swipe vers le haut : plier
|
|
setState(() {
|
|
_calendarCollapsed = true;
|
|
});
|
|
} else if (details.primaryVelocity! > 200) {
|
|
// Swipe vers le bas : déplier
|
|
setState(() {
|
|
_calendarCollapsed = false;
|
|
});
|
|
}
|
|
}
|
|
},
|
|
onHorizontalDragEnd: (details) {
|
|
if (details.primaryVelocity != null) {
|
|
if (details.primaryVelocity! < -200) {
|
|
// Swipe gauche : mois suivant
|
|
setState(() {
|
|
_focusedDay =
|
|
DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
|
|
});
|
|
} else if (details.primaryVelocity! > 200) {
|
|
// Swipe droite : mois précédent
|
|
setState(() {
|
|
_focusedDay =
|
|
DateTime(_focusedDay.year, _focusedDay.month - 1, 1);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
child: Stack(
|
|
children: [
|
|
// Calendrier + détails en dessous
|
|
AnimatedPositioned(
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeInOut,
|
|
top: _calendarCollapsed ? -600 : 0, // cache le calendrier en haut
|
|
left: 0,
|
|
right: 0,
|
|
height: _calendarCollapsed ? 0 : null,
|
|
child: SizedBox(
|
|
height: MediaQuery.of(context).size.height,
|
|
child: Column(
|
|
children: [
|
|
_buildMonthHeader(context),
|
|
if (!_calendarCollapsed)
|
|
// Ajout d'un GestureDetector pour swipe horizontal sur le calendrier
|
|
GestureDetector(
|
|
onHorizontalDragEnd: (details) {
|
|
if (details.primaryVelocity != null) {
|
|
if (details.primaryVelocity! < -200) {
|
|
// Swipe gauche : mois suivant
|
|
setState(() {
|
|
_focusedDay = DateTime(
|
|
_focusedDay.year, _focusedDay.month + 1, 1);
|
|
});
|
|
} else if (details.primaryVelocity! > 200) {
|
|
// Swipe droite : mois précédent
|
|
setState(() {
|
|
_focusedDay = DateTime(
|
|
_focusedDay.year, _focusedDay.month - 1, 1);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
child: MobileCalendarView(
|
|
focusedDay: _focusedDay,
|
|
selectedDay: _selectedDay,
|
|
events: filteredEvents,
|
|
onDaySelected: (day) {
|
|
final eventsForDay = filteredEvents
|
|
.where((e) =>
|
|
e.startDateTime.year == day.year &&
|
|
e.startDateTime.month == day.month &&
|
|
e.startDateTime.day == day.day)
|
|
.toList()
|
|
..sort((a, b) =>
|
|
a.startDateTime.compareTo(b.startDateTime));
|
|
setState(() {
|
|
_selectedDay = day;
|
|
_calendarCollapsed = false;
|
|
_selectedEventIndex = 0;
|
|
_selectedEvent = eventsForDay.isNotEmpty
|
|
? eventsForDay[0]
|
|
: null;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
Expanded(
|
|
child: hasEvents
|
|
// Ajout d'un GestureDetector pour swipe horizontal sur le détail événement
|
|
? GestureDetector(
|
|
onHorizontalDragEnd: (details) {
|
|
if (details.primaryVelocity != null) {
|
|
if (details.primaryVelocity! < -200) {
|
|
// Swipe gauche : événement suivant
|
|
if (_selectedEventIndex <
|
|
eventsForSelectedDay.length - 1) {
|
|
setState(() {
|
|
_selectedEventIndex++;
|
|
_selectedEvent = eventsForSelectedDay[
|
|
_selectedEventIndex];
|
|
});
|
|
}
|
|
} else if (details.primaryVelocity! > 200) {
|
|
// Swipe droite : événement précédent
|
|
if (_selectedEventIndex > 0) {
|
|
setState(() {
|
|
_selectedEventIndex--;
|
|
_selectedEvent = eventsForSelectedDay[
|
|
_selectedEventIndex];
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
child: EventDetails(
|
|
event: eventsForSelectedDay[_selectedEventIndex],
|
|
selectedDate: _selectedDay,
|
|
events: eventsForSelectedDay.cast<EventModel>(),
|
|
onSelectEvent: (event, date) {
|
|
final idx = eventsForSelectedDay
|
|
.indexWhere((e) => e.id == event.id);
|
|
setState(() {
|
|
_selectedEventIndex = idx >= 0 ? idx : 0;
|
|
_selectedEvent = event;
|
|
});
|
|
},
|
|
),
|
|
)
|
|
: Center(
|
|
child: Text(
|
|
'Aucun événement ne démarre à cette date')),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
// Vue détail (prend tout l'espace quand calendrier caché)
|
|
if (_calendarCollapsed && _selectedDay != null)
|
|
AnimatedPositioned(
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeInOut,
|
|
top: _calendarCollapsed ? 0 : 600,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: SizedBox(
|
|
height: MediaQuery.of(context).size.height,
|
|
child: Column(
|
|
children: [
|
|
_buildMonthHeader(context),
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
if (currentEvent != null)
|
|
// Ajout d'un GestureDetector pour swipe horizontal sur le détail événement
|
|
GestureDetector(
|
|
onHorizontalDragEnd: (details) {
|
|
if (details.primaryVelocity != null) {
|
|
if (details.primaryVelocity! < -200) {
|
|
// Swipe gauche : événement suivant
|
|
if (_selectedEventIndex <
|
|
eventsForSelectedDay.length - 1) {
|
|
setState(() {
|
|
_selectedEventIndex++;
|
|
_selectedEvent = eventsForSelectedDay[
|
|
_selectedEventIndex];
|
|
});
|
|
}
|
|
} else if (details.primaryVelocity! > 200) {
|
|
// Swipe droite : événement précédent
|
|
if (_selectedEventIndex > 0) {
|
|
setState(() {
|
|
_selectedEventIndex--;
|
|
_selectedEvent = eventsForSelectedDay[
|
|
_selectedEventIndex];
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
child: EventDetails(
|
|
event: currentEvent,
|
|
selectedDate: _selectedDay,
|
|
events: eventsForSelectedDay.cast<EventModel>(),
|
|
onSelectEvent: (event, date) {
|
|
final idx = eventsForSelectedDay
|
|
.indexWhere((e) => e.id == event.id);
|
|
setState(() {
|
|
_selectedEventIndex = idx >= 0 ? idx : 0;
|
|
_selectedEvent = event;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
if (!hasEvents)
|
|
Center(
|
|
child: Text(
|
|
'Aucun événement ne démarre à cette date'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMonthHeader(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
|
child: Row(
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_left,
|
|
color: AppColors.rouge, size: 28),
|
|
onPressed: () {
|
|
setState(() {
|
|
_focusedDay =
|
|
DateTime(_focusedDay.year, _focusedDay.month - 1, 1);
|
|
});
|
|
},
|
|
),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_calendarCollapsed = !_calendarCollapsed;
|
|
});
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
_getMonthName(_focusedDay.month),
|
|
style: const TextStyle(
|
|
color: AppColors.rouge,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 20,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Icon(
|
|
_calendarCollapsed
|
|
? Icons.keyboard_arrow_down
|
|
: Icons.keyboard_arrow_up,
|
|
color: AppColors.rouge,
|
|
size: 26,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_right,
|
|
color: AppColors.rouge, size: 28),
|
|
onPressed: () {
|
|
setState(() {
|
|
_focusedDay =
|
|
DateTime(_focusedDay.year, _focusedDay.month + 1, 1);
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getMonthName(int month) {
|
|
switch (month) {
|
|
case 1:
|
|
return 'Janvier';
|
|
case 2:
|
|
return 'Février';
|
|
case 3:
|
|
return 'Mars';
|
|
case 4:
|
|
return 'Avril';
|
|
case 5:
|
|
return 'Mai';
|
|
case 6:
|
|
return 'Juin';
|
|
case 7:
|
|
return 'Juillet';
|
|
case 8:
|
|
return 'Août';
|
|
case 9:
|
|
return 'Septembre';
|
|
case 10:
|
|
return 'Octobre';
|
|
case 11:
|
|
return 'Novembre';
|
|
case 12:
|
|
return 'Décembre';
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
Widget _buildCalendar(List<EventModel> filteredEvents) {
|
|
if (_calendarFormat == CalendarFormat.week) {
|
|
return WeekView(
|
|
focusedDay: _focusedDay,
|
|
events: filteredEvents,
|
|
onWeekChange: _changeWeek,
|
|
onEventSelected: (event) {
|
|
setState(() {
|
|
_selectedEvent = event;
|
|
_selectedDay = event.startDateTime;
|
|
});
|
|
},
|
|
onSwitchToMonth: () {
|
|
setState(() {
|
|
_calendarFormat = CalendarFormat.month;
|
|
});
|
|
},
|
|
onDaySelected: (selectedDay) {
|
|
final eventsForDay = filteredEvents
|
|
.where((e) =>
|
|
e.startDateTime.year == selectedDay.year &&
|
|
e.startDateTime.month == selectedDay.month &&
|
|
e.startDateTime.day == selectedDay.day)
|
|
.toList();
|
|
eventsForDay
|
|
.sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
setState(() {
|
|
_selectedDay = selectedDay;
|
|
if (eventsForDay.isNotEmpty) {
|
|
_selectedEvent = eventsForDay.first;
|
|
} else {
|
|
_selectedEvent = null;
|
|
}
|
|
});
|
|
if (eventsForDay.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text("Aucun événement ne démarre à cette date."),
|
|
duration: Duration(seconds: 2),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
selectedEvent: _selectedEvent,
|
|
);
|
|
} else {
|
|
return MonthView(
|
|
focusedDay: _focusedDay,
|
|
selectedDay: _selectedDay,
|
|
calendarFormat: _calendarFormat,
|
|
events: filteredEvents,
|
|
onDaySelected: (selectedDay, focusedDay) {
|
|
final eventsForDay = filteredEvents
|
|
.where((event) =>
|
|
event.startDateTime.year == selectedDay.year &&
|
|
event.startDateTime.month == selectedDay.month &&
|
|
event.startDateTime.day == selectedDay.day)
|
|
.toList()
|
|
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
setState(() {
|
|
_selectedDay = selectedDay;
|
|
_focusedDay = focusedDay;
|
|
if (eventsForDay.isNotEmpty) {
|
|
_selectedEvent = eventsForDay.first;
|
|
} else {
|
|
_selectedEvent = null;
|
|
}
|
|
});
|
|
},
|
|
onFormatChanged: (format) {
|
|
setState(() {
|
|
_calendarFormat = format;
|
|
});
|
|
},
|
|
onPageChanged: (focusedDay) {
|
|
// Détecter si on a changé de mois
|
|
final monthChanged = focusedDay.year != _focusedDay.year ||
|
|
focusedDay.month != _focusedDay.month;
|
|
|
|
setState(() {
|
|
_focusedDay = focusedDay;
|
|
});
|
|
|
|
// Charger les événements du nouveau mois si nécessaire
|
|
if (monthChanged) {
|
|
print('[CalendarPage] Month changed to ${focusedDay.year}-${focusedDay.month}');
|
|
_loadCurrentMonthEvents();
|
|
}
|
|
},
|
|
onEventSelected: (event) {
|
|
setState(() {
|
|
_selectedEvent = event;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|