286 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:flutter/material.dart';
 | |
| import 'package:table_calendar/table_calendar.dart';
 | |
| import 'package:em2rp/utils/colors.dart';
 | |
| import 'package:em2rp/models/event_model.dart';
 | |
| import 'package:em2rp/utils/calendar_utils.dart';
 | |
| 
 | |
| class MonthView extends StatelessWidget {
 | |
|   final DateTime focusedDay;
 | |
|   final DateTime? selectedDay;
 | |
|   final CalendarFormat calendarFormat;
 | |
|   final Function(DateTime, DateTime) onDaySelected;
 | |
|   final Function(CalendarFormat) onFormatChanged;
 | |
|   final Function(DateTime) onPageChanged;
 | |
|   final List<EventModel> events;
 | |
|   final Function(EventModel) onEventSelected;
 | |
| 
 | |
|   const MonthView({
 | |
|     super.key,
 | |
|     required this.focusedDay,
 | |
|     required this.selectedDay,
 | |
|     required this.calendarFormat,
 | |
|     required this.onDaySelected,
 | |
|     required this.onFormatChanged,
 | |
|     required this.onPageChanged,
 | |
|     required this.events,
 | |
|     required this.onEventSelected,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return LayoutBuilder(
 | |
|       builder: (context, constraints) {
 | |
|         final rowHeight = (constraints.maxHeight - 100) / 6;
 | |
| 
 | |
|         return Container(
 | |
|           height: constraints.maxHeight,
 | |
|           padding: const EdgeInsets.all(8),
 | |
|           child: TableCalendar(
 | |
|             firstDay: DateTime.utc(2020, 1, 1),
 | |
|             lastDay: DateTime.utc(2030, 12, 31),
 | |
|             focusedDay: focusedDay,
 | |
|             calendarFormat: calendarFormat,
 | |
|             startingDayOfWeek: StartingDayOfWeek.monday,
 | |
|             locale: 'fr_FR',
 | |
|             availableCalendarFormats: const {
 | |
|               CalendarFormat.month: 'Mois',
 | |
|               CalendarFormat.week: 'Semaine',
 | |
|             },
 | |
|             selectedDayPredicate: (day) => isSameDay(selectedDay, day),
 | |
|             onDaySelected: onDaySelected,
 | |
|             onFormatChanged: onFormatChanged,
 | |
|             onPageChanged: onPageChanged,
 | |
|             calendarStyle: _buildCalendarStyle(),
 | |
|             rowHeight: rowHeight,
 | |
|             headerStyle: _buildHeaderStyle(),
 | |
|             calendarBuilders: _buildCalendarBuilders(),
 | |
|           ),
 | |
|         );
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   CalendarStyle _buildCalendarStyle() {
 | |
|     return CalendarStyle(
 | |
|       defaultDecoration: BoxDecoration(
 | |
|         border: Border.all(color: Colors.grey.shade300),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       ),
 | |
|       selectedDecoration: BoxDecoration(
 | |
|         color: AppColors.rouge,
 | |
|         border: Border.all(color: AppColors.rouge),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       ),
 | |
|       todayDecoration: BoxDecoration(
 | |
|         color: AppColors.rouge.withAlpha(26),
 | |
|         border: Border.all(color: AppColors.rouge),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       ),
 | |
|       outsideDecoration: BoxDecoration(
 | |
|         border: Border.all(color: Colors.grey.shade300),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       ),
 | |
|       outsideDaysVisible: false,
 | |
|       cellMargin: EdgeInsets.zero,
 | |
|       cellPadding: EdgeInsets.zero,
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   HeaderStyle _buildHeaderStyle() {
 | |
|     return HeaderStyle(
 | |
|       formatButtonVisible: true,
 | |
|       titleCentered: true,
 | |
|       formatButtonShowsNext: false,
 | |
|       formatButtonDecoration: BoxDecoration(
 | |
|         color: AppColors.rouge,
 | |
|         borderRadius: BorderRadius.circular(16),
 | |
|       ),
 | |
|       formatButtonTextStyle: const TextStyle(color: Colors.white),
 | |
|       leftChevronIcon: const Icon(Icons.chevron_left, color: AppColors.rouge),
 | |
|       rightChevronIcon: const Icon(Icons.chevron_right, color: AppColors.rouge),
 | |
|       headerPadding: const EdgeInsets.symmetric(vertical: 8),
 | |
|       titleTextStyle: const TextStyle(
 | |
|         fontSize: 18,
 | |
|         fontWeight: FontWeight.bold,
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   CalendarBuilders _buildCalendarBuilders() {
 | |
|     return CalendarBuilders(
 | |
|       dowBuilder: (context, day) {
 | |
|         return Center(
 | |
|           child: Text(
 | |
|             CalendarUtils.getShortDayName(day.weekday),
 | |
|             style: const TextStyle(
 | |
|               fontWeight: FontWeight.bold,
 | |
|             ),
 | |
|           ),
 | |
|         );
 | |
|       },
 | |
|       defaultBuilder: (context, day, focusedDay) {
 | |
|         return _buildDayCell(day, false);
 | |
|       },
 | |
|       selectedBuilder: (context, day, focusedDay) {
 | |
|         return _buildDayCell(day, true);
 | |
|       },
 | |
|       todayBuilder: (context, day, focusedDay) {
 | |
|         return _buildDayCell(day, false, isToday: true);
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildDayCell(DateTime day, bool isSelected, {bool isToday = false}) {
 | |
|     final dayEvents = CalendarUtils.getEventsForDay(day, events);
 | |
|     final textColor =
 | |
|         isSelected ? Colors.white : (isToday ? AppColors.rouge : null);
 | |
|     final badgeColor = isSelected ? Colors.white : AppColors.rouge;
 | |
|     final badgeTextColor = isSelected ? AppColors.rouge : Colors.white;
 | |
| 
 | |
|     BoxDecoration decoration;
 | |
|     if (isSelected) {
 | |
|       decoration = BoxDecoration(
 | |
|         color: AppColors.rouge,
 | |
|         border: Border.all(color: AppColors.rouge),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       );
 | |
|     } else if (isToday) {
 | |
|       decoration = BoxDecoration(
 | |
|         color: AppColors.rouge.withAlpha(26),
 | |
|         border: Border.all(color: AppColors.rouge),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       );
 | |
|     } else {
 | |
|       decoration = BoxDecoration(
 | |
|         color: null,
 | |
|         border: Border.all(color: Colors.grey.shade300),
 | |
|         borderRadius: BorderRadius.circular(4),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return Container(
 | |
|       margin: const EdgeInsets.all(4),
 | |
|       decoration: decoration,
 | |
|       child: Stack(
 | |
|         children: [
 | |
|           Positioned(
 | |
|             top: 4,
 | |
|             left: 4,
 | |
|             child: Text(
 | |
|               day.day.toString(),
 | |
|               style: TextStyle(color: textColor),
 | |
|             ),
 | |
|           ),
 | |
|           if (dayEvents.isNotEmpty)
 | |
|             Positioned(
 | |
|               top: 4,
 | |
|               right: 4,
 | |
|               child: Container(
 | |
|                 padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
 | |
|                 decoration: BoxDecoration(
 | |
|                   color: badgeColor,
 | |
|                   borderRadius: BorderRadius.circular(10),
 | |
|                 ),
 | |
|                 child: Text(
 | |
|                   dayEvents.length.toString(),
 | |
|                   style: TextStyle(
 | |
|                     color: badgeTextColor,
 | |
|                     fontSize: 12,
 | |
|                     fontWeight: FontWeight.bold,
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           if (dayEvents.isNotEmpty)
 | |
|             Positioned(
 | |
|               bottom: 2,
 | |
|               left: 2,
 | |
|               right: 2,
 | |
|               top: 28,
 | |
|               child: SingleChildScrollView(
 | |
|                 child: Column(
 | |
|                   crossAxisAlignment: CrossAxisAlignment.start,
 | |
|                   children: dayEvents
 | |
|                       .map((event) => _buildEventItem(event, isSelected, day))
 | |
|                       .toList(),
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildEventItem(
 | |
|       EventModel event, bool isSelected, DateTime currentDay) {
 | |
|     Color color;
 | |
|     Color textColor;
 | |
|     IconData icon;
 | |
|     switch (event.status) {
 | |
|       case EventStatus.confirmed:
 | |
|         color = Colors.green;
 | |
|         textColor = Colors.white;
 | |
|         icon = Icons.check;
 | |
|         break;
 | |
|       case EventStatus.canceled:
 | |
|         color = Colors.red;
 | |
|         textColor = Colors.white;
 | |
|         icon = Icons.close;
 | |
|         break;
 | |
|       case EventStatus.waitingForApproval:
 | |
|       default:
 | |
|         color = Colors.amber;
 | |
|         textColor = Colors.black;
 | |
|         icon = Icons.hourglass_empty;
 | |
|         break;
 | |
|     }
 | |
|     return GestureDetector(
 | |
|       onTap: () {
 | |
|         onDaySelected(currentDay, currentDay);
 | |
|         onEventSelected(event);
 | |
|       },
 | |
|       child: Container(
 | |
|         margin: const EdgeInsets.only(bottom: 2),
 | |
|         padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
 | |
|         decoration: BoxDecoration(
 | |
|           color: isSelected ? color.withAlpha(220) : color.withOpacity(0.18),
 | |
|           borderRadius: BorderRadius.circular(4),
 | |
|         ),
 | |
|         child: Row(
 | |
|           crossAxisAlignment: CrossAxisAlignment.center,
 | |
|           children: [
 | |
|             Icon(icon, color: textColor, size: 16),
 | |
|             const SizedBox(width: 4),
 | |
|             Expanded(
 | |
|               child: Column(
 | |
|                 crossAxisAlignment: CrossAxisAlignment.start,
 | |
|                 children: [
 | |
|                   Text(
 | |
|                     event.name,
 | |
|                     style: TextStyle(
 | |
|                       fontSize: 12,
 | |
|                       color: textColor,
 | |
|                       fontWeight: FontWeight.bold,
 | |
|                     ),
 | |
|                     maxLines: 1,
 | |
|                     overflow: TextOverflow.ellipsis,
 | |
|                   ),
 | |
|                   if (CalendarUtils.isMultiDayEvent(event))
 | |
|                     Text(
 | |
|                       'Jour ${CalendarUtils.calculateDayNumber(event.startDateTime, currentDay)}/${CalendarUtils.calculateTotalDays(event)}',
 | |
|                       style: TextStyle(
 | |
|                         fontSize: 10,
 | |
|                         color: textColor,
 | |
|                       ),
 | |
|                       maxLines: 1,
 | |
|                     ),
 | |
|                 ],
 | |
|               ),
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |