611 lines
22 KiB
Dart
611 lines
22 KiB
Dart
import 'package:em2rp/providers/local_user_provider.dart';
|
|
import 'package:em2rp/providers/event_provider.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:em2rp/views/widgets/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/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;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
initializeDateFormatting('fr_FR', null);
|
|
Future.microtask(() => _loadEvents());
|
|
// Sélection automatique de l'événement le plus proche de maintenant
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
|
final events = eventProvider.events;
|
|
if (events.isNotEmpty) {
|
|
final now = DateTime.now();
|
|
// Pour mobile : sélectionner le premier événement du jour ou le prochain événement à venir
|
|
final todayEvents = events
|
|
.where((e) =>
|
|
e.startDateTime.year == now.year &&
|
|
e.startDateTime.month == now.month &&
|
|
e.startDateTime.day == now.day)
|
|
.toList()
|
|
..sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
EventModel? selected;
|
|
DateTime? selectedDay;
|
|
int selectedEventIndex = 0;
|
|
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];
|
|
selectedDay = DateTime(selected.startDateTime.year,
|
|
selected.startDateTime.month, selected.startDateTime.day);
|
|
} else {
|
|
// Aucun événement à venir, prendre le plus proche dans le passé
|
|
events.sort((a, b) => a.startDateTime.compareTo(b.startDateTime));
|
|
selected = events.last;
|
|
selectedDay = DateTime(selected.startDateTime.year,
|
|
selected.startDateTime.month, selected.startDateTime.day);
|
|
}
|
|
}
|
|
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;
|
|
print('Permissions utilisateur: ${localAuthProvider.permissions}');
|
|
final canViewAllEvents = localAuthProvider.hasPermission('view_all_events');
|
|
print('canViewAllEvents: $canViewAllEvents');
|
|
|
|
if (userId != null) {
|
|
await eventProvider.loadUserEvents(userId,
|
|
canViewAllEvents: canViewAllEvents);
|
|
}
|
|
}
|
|
|
|
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 isAdmin = localUserProvider.hasPermission('view_all_users');
|
|
final isMobile = MediaQuery.of(context).size.width < 600;
|
|
|
|
if (eventProvider.isLoading) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: CustomAppBar(
|
|
title: _getMonthName(_focusedDay.month),
|
|
actions: [
|
|
IconButton(
|
|
icon: Icon(
|
|
_calendarCollapsed
|
|
? Icons.keyboard_arrow_down
|
|
: Icons.keyboard_arrow_up,
|
|
color: AppColors.blanc,
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
_calendarCollapsed = !_calendarCollapsed;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
drawer: const MainDrawer(currentPage: '/calendar'),
|
|
body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(),
|
|
floatingActionButton: isAdmin
|
|
? FloatingActionButton(
|
|
backgroundColor: Colors.white,
|
|
onPressed: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (context) => const EventAddEditPage(),
|
|
),
|
|
);
|
|
},
|
|
child: const Icon(Icons.add, color: Colors.red),
|
|
tooltip: 'Ajouter un événement',
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
Widget _buildDesktopLayout() {
|
|
final eventProvider = Provider.of<EventProvider>(context);
|
|
return Row(
|
|
children: [
|
|
// Calendrier (65% de la largeur)
|
|
Expanded(
|
|
flex: 65,
|
|
child: _buildCalendar(),
|
|
),
|
|
// Détails de l'événement (35% de la largeur)
|
|
Expanded(
|
|
flex: 35,
|
|
child: _selectedEvent != null
|
|
? EventDetails(
|
|
event: _selectedEvent!,
|
|
selectedDate: _selectedDay,
|
|
events: eventProvider.events,
|
|
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() {
|
|
final eventProvider = Provider.of<EventProvider>(context);
|
|
final eventsForSelectedDay = _selectedDay == null
|
|
? []
|
|
: eventProvider.events
|
|
.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: Container(
|
|
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: eventProvider.events,
|
|
onDaySelected: (day) {
|
|
final eventsForDay = eventProvider.events
|
|
.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: Container(
|
|
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() {
|
|
final eventProvider = Provider.of<EventProvider>(context);
|
|
|
|
if (_calendarFormat == CalendarFormat.week) {
|
|
return WeekView(
|
|
focusedDay: _focusedDay,
|
|
events: eventProvider.events,
|
|
onWeekChange: _changeWeek,
|
|
onEventSelected: (event) {
|
|
setState(() {
|
|
_selectedEvent = event;
|
|
_selectedDay = event.startDateTime;
|
|
});
|
|
},
|
|
onSwitchToMonth: () {
|
|
setState(() {
|
|
_calendarFormat = CalendarFormat.month;
|
|
});
|
|
},
|
|
onDaySelected: (selectedDay) {
|
|
final eventsForDay = eventProvider.events
|
|
.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: eventProvider.events,
|
|
onDaySelected: (selectedDay, focusedDay) {
|
|
final eventsForDay = eventProvider.events
|
|
.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) {
|
|
setState(() {
|
|
_focusedDay = focusedDay;
|
|
});
|
|
},
|
|
onEventSelected: (event) {
|
|
setState(() {
|
|
_selectedEvent = event;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|