diff --git a/frontend/src/pages/CalendarPage.jsx b/frontend/src/pages/CalendarPage.jsx
index 6af938d..f8751af 100644
--- a/frontend/src/pages/CalendarPage.jsx
+++ b/frontend/src/pages/CalendarPage.jsx
@@ -1,13 +1,8 @@
import React, {
-
useEffect,
-
useLayoutEffect,
-
useMemo,
-
useRef,
-
useState,
} from 'react';
@@ -17,13 +12,10 @@ import LoadingCard from '@/components/LoadingCard';
import MainCard from '@/components/MainCard';
import HoverInfo from '@/components/HoverInfo';
-
import { DatePicker, TimePicker } from '@/components/ResponsivePickers';
import {
-
Card,
-
CardContent,
} from '@/components/ui/card';
@@ -31,17 +23,11 @@ import {
import { Button } from '@/components/ui/button';
import {
-
Dialog,
-
DialogContent,
-
DialogDescription,
-
DialogFooter,
-
DialogHeader,
-
DialogTitle,
} from '@/components/ui/dialog';
@@ -53,18 +39,13 @@ import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import {
-
ChevronLeft,
-
ChevronRight,
-
Calendar,
-
Plus,
-
Pencil,
-
Trash2,
+ ArrowLeftRight,
} from 'lucide-react';
@@ -73,57 +54,33 @@ import { eventApi } from '../api/event';
import { Alert, AlertDescription } from '@/components/ui/alert';
const MONTHS = [
-
'Janvier',
-
'Février',
-
'Mars',
-
'Avril',
-
'Mai',
-
'Juin',
-
'Juillet',
-
'Août',
-
'Septembre',
-
'Octobre',
-
'Novembre',
-
'Décembre',
];
const DAYS_IN_MONTH = [
-
31,
-
28,
-
31,
-
30,
-
31,
-
30,
-
31,
-
31,
-
30,
-
31,
-
30,
-
31,
];
@@ -145,3449 +102,1766 @@ const EVENT_LANE_GAP = 11;
const EVENT_LEFT_PADDING = 5;
const CalendarPage = () => {
-
const [requestError, setRequestError] = useState('');
-
const [year, setYear] = useState(
-
new Date().getFullYear(),
-
);
+ const [schoolView, setSchoolView] = useState(false);
const [selectedMonth, setSelectedMonth] = useState(
-
new Date().getMonth(),
-
);
-
const [isMobile, setIsMobile] =
-
useState(false);
-
const [availableHeight, setAvailableHeight] =
-
useState(600);
-
const [events, setEvents] =
-
useState([]);
-
const [loadingEvents, setLoadingEvents] =
-
useState(true);
-
const [hoveredEvent, setHoveredEvent] =
-
useState(null);
-
const [hoverPosition, setHoverPosition] =
-
useState(null);
-
/*
-
* ============================================================
-
* MENU CONTEXTUEL
-
* ============================================================
-
*/
-
const [contextMenu, setContextMenu] =
-
useState(null);
-
/*
-
* contextMenu :
-
*
-
* {
-
* x: number,
-
* y: number,
-
* type: 'day' | 'event',
-
* date?: 'YYYY-MM-DD',
-
* event?: object
-
* }
-
*/
-
/*
-
* ============================================================
-
* DIALOG ÉVÉNEMENT
-
* ============================================================
-
*/
-
const [eventDialogOpen, setEventDialogOpen] =
-
useState(false);
-
const [editingEvent, setEditingEvent] =
-
useState(null);
-
const [savingEvent, setSavingEvent] =
-
useState(false);
-
const [eventForm, setEventForm] =
-
useState({
-
name: '',
-
description: '',
-
start_date: '',
-
end_date: '',
-
start_time: '',
-
end_time: '',
-
color: DEFAULT_EVENT_COLOR,
-
});
-
const calendarRef = useRef(null);
-
/* SÉLECTION D'UNE PÉRIODE */
-
const [selectionStart, setSelectionStart] = useState(null);
-
const [selectionEnd, setSelectionEnd] = useState(null);
-
const [isSelecting, setIsSelecting] = useState(false);
-
const selectionEndRef = useRef(null);
-
const isSelectingRef = useRef(false);
-
/*
-
* ============================================================
-
* UTILITAIRES
-
* ============================================================
-
*/
-
const isLeapYear = (value) => {
-
return (
-
(value % 4 === 0 &&
-
value % 100 !== 0) ||
-
value % 400 === 0
-
);
-
};
-
+ const getMonthYear = (monthIndex) =>
+ !isMobile && schoolView && monthIndex < 8
+ ? year + 1
+ : year;
const getDaysInMonth = (monthIndex) => {
-
if (
-
monthIndex === 1 &&
-
- isLeapYear(year)
-
+ isLeapYear(getMonthYear(monthIndex))
) {
-
return 29;
-
}
-
return DAYS_IN_MONTH[monthIndex];
-
};
-
const formatDateForInput = (
-
monthIndex,
-
day,
-
) => {
-
- return `${year}-${String(
-
+ return `${getMonthYear(monthIndex)}-${String(
monthIndex + 1,
-
).padStart(2, '0')}-${String(day).padStart(
-
2,
-
'0',
-
)}`;
-
};
-
const parseDate = (value) => {
-
if (!value) return null;
-
const [
-
yearValue,
-
monthValue,
-
dayValue,
-
] = String(value)
-
.split('-')
-
.map(Number);
-
if (
-
!yearValue ||
-
!monthValue ||
-
!dayValue
-
) {
-
return null;
-
}
-
return new Date(
-
yearValue,
-
monthValue - 1,
-
dayValue,
-
);
-
};
-
const getEventStartDate = (
-
event,
-
) => {
-
return event?.start_date ?? '';
-
};
-
const getEventEndDate = (
-
event,
-
) => {
-
return (
-
event?.end_date ??
-
event?.start_date ??
-
''
-
);
-
};
-
const isDateInEvent = (
-
event,
-
monthIndex,
-
day,
-
) => {
-
const eventStart =
-
parseDate(
-
getEventStartDate(event),
-
);
-
const eventEnd =
-
parseDate(
-
getEventEndDate(event),
-
);
-
if (
-
!eventStart ||
-
!eventEnd
-
) {
-
return false;
-
}
-
const cellDate = new Date(
-
- year,
-
+ getMonthYear(monthIndex),
monthIndex,
-
day,
-
);
-
return (
-
cellDate >= eventStart &&
-
cellDate <= eventEnd
-
);
-
};
-
const isSingleDayEvent = (
-
event,
-
) => {
-
return (
-
getEventStartDate(event) ===
-
getEventEndDate(event)
-
);
-
};
-
const getPeriodEventPosition = (
-
event,
-
monthIndex,
-
day,
-
) => {
-
const startDate =
-
parseDate(
-
getEventStartDate(event),
-
);
-
const endDate =
-
parseDate(
-
getEventEndDate(event),
-
);
-
if (
-
!startDate ||
-
!endDate
-
) {
-
return null;
-
}
-
const currentDate = new Date(
-
- year,
-
+ getMonthYear(monthIndex),
monthIndex,
-
day,
-
);
-
return {
-
isStart:
-
currentDate.getTime() ===
-
startDate.getTime(),
-
isEnd:
-
currentDate.getTime() ===
-
endDate.getTime(),
-
};
-
};
-
const getOrderedSelection = () => {
-
if (!selectionStart || !selectionEnd) return null;
-
return selectionStart <= selectionEnd
-
? { start: selectionStart, end: selectionEnd }
-
: { start: selectionEnd, end: selectionStart };
-
};
-
const isDateSelected = (date) => {
-
- const selection = isMobile ? null : getOrderedSelection();
-
+ const selection = getOrderedSelection();
return Boolean(
-
selection &&
-
date >= selection.start &&
-
date <= selection.end,
-
);
-
};
-
const clearSelection = () => {
-
setSelectionStart(null);
-
setSelectionEnd(null);
-
setIsSelecting(false);
-
selectionEndRef.current = null;
-
isSelectingRef.current = false;
-
};
-
const beginSelection = (pointerEvent, date) => {
-
- if (isMobile || pointerEvent.pointerType !== 'mouse' || pointerEvent.button !== 0) return;
-
+ if (pointerEvent.button !== 0) return;
pointerEvent.preventDefault();
-
closeContextMenu();
-
setHoveredEvent(null);
-
setHoverPosition(null);
-
selectionEndRef.current = date;
-
isSelectingRef.current = true;
-
setSelectionStart(date);
-
setSelectionEnd(date);
-
setIsSelecting(true);
-
};
-
const updateSelectionFromPointer = (pointerEvent) => {
-
- if (!isSelectingRef.current || isMobile) return;
-
- const element = document.elementFromPoint(
-
- pointerEvent.clientX,
-
- pointerEvent.clientY,
-
- );
-
- const cell = element?.closest?.('[data-calendar-date]');
-
- const date = cell?.dataset?.calendarDate;
-
- if (!date || date === selectionEndRef.current) return;
-
- selectionEndRef.current = date;
-
- setSelectionEnd(date);
-
- };
-
- const finishSelection = () => {
-
if (!isSelectingRef.current) return;
-
+ const element = document.elementFromPoint(
+ pointerEvent.clientX,
+ pointerEvent.clientY,
+ );
+ const cell = element?.closest?.('[data-calendar-date]');
+ const date = cell?.dataset?.calendarDate;
+ if (!date || date === selectionEndRef.current) return;
+ selectionEndRef.current = date;
+ setSelectionEnd(date);
+ };
+ const finishSelection = () => {
+ if (!isSelectingRef.current) return;
isSelectingRef.current = false;
-
setIsSelecting(false);
-
};
-
/*
-
* ============================================================
-
* CHARGEMENT DES ÉVÉNEMENTS
-
* ============================================================
-
*/
-
const loadEvents = async () => {
-
try {
-
setRequestError('');
-
setLoadingEvents(true);
-
const data =
-
await eventApi.getAllEvents();
-
const loadedEvents =
-
Array.isArray(data)
-
? data
-
: data?.results ?? [];
-
setEvents(loadedEvents);
-
} catch (error) {
-
setRequestError(error.message);
-
console.error(
-
'Erreur lors du chargement des événements :',
-
error,
-
);
-
setEvents([]);
-
} finally {
-
setLoadingEvents(false);
-
}
-
};
-
useEffect(() => {
-
loadEvents();
-
}, []);
-
/*
-
* ============================================================
-
* RESPONSIVE
-
* ============================================================
-
*/
-
useEffect(() => {
-
const updateMobile = () => {
-
setIsMobile(
-
window.innerWidth < 768,
-
);
-
};
-
updateMobile();
-
window.addEventListener(
-
'resize',
-
updateMobile,
-
);
-
return () => {
-
window.removeEventListener(
-
'resize',
-
updateMobile,
-
);
-
};
-
}, []);
-
useEffect(() => {
-
const handlePointerMove = (pointerEvent) => {
-
updateSelectionFromPointer(pointerEvent);
-
};
-
const handlePointerUp = () => {
-
finishSelection();
-
};
-
window.addEventListener('pointermove', handlePointerMove);
-
window.addEventListener('pointerup', handlePointerUp);
-
window.addEventListener('pointercancel', handlePointerUp);
-
return () => {
-
window.removeEventListener('pointermove', handlePointerMove);
-
window.removeEventListener('pointerup', handlePointerUp);
-
window.removeEventListener('pointercancel', handlePointerUp);
-
};
-
}, []);
-
/*
-
* ============================================================
-
* DIMENSIONS CALENDRIER
-
* ============================================================
-
*/
-
useLayoutEffect(() => {
-
if (loadingEvents) return;
-
const updateDimensions = () => {
-
const calendar =
-
calendarRef.current;
-
if (!calendar) return;
-
const cardContent =
-
calendar.closest(
-
'[data-slot="card-content"]',
-
);
-
if (!cardContent) return;
-
const calendarTop =
-
calendar.getBoundingClientRect()
-
.top;
-
const contentRect =
-
cardContent.getBoundingClientRect();
-
const footer =
-
document.querySelector('footer');
-
const footerTop = footer
-
? footer.getBoundingClientRect()
-
.top
-
: window.innerHeight;
-
const bottom = Math.min(
-
contentRect.bottom,
-
footerTop,
-
window.innerHeight,
-
);
-
const height =
-
bottom - calendarTop - 4;
-
setAvailableHeight(
-
Math.max(300, height),
-
);
-
};
-
updateDimensions();
-
window.addEventListener(
-
'resize',
-
updateDimensions,
-
);
-
let observer = null;
-
if (
-
typeof ResizeObserver !==
-
'undefined' &&
-
calendarRef.current
-
) {
-
observer = new ResizeObserver(
-
updateDimensions,
-
);
-
observer.observe(
-
calendarRef.current,
-
);
-
}
-
return () => {
-
window.removeEventListener(
-
'resize',
-
updateDimensions,
-
);
-
observer?.disconnect();
-
};
-
}, [year, selectedMonth, isMobile, loadingEvents]);
-
/*
-
* ============================================================
-
* ANNÉE
-
* ============================================================
-
*/
-
const previousYear = () => {
-
setYear((current) => current - 1);
-
};
const nextYear = () => {
-
setYear((current) => current + 1);
-
};
const currentYear = () => {
-
setYear(new Date().getFullYear());
-
};
const previousPeriod = () => {
-
if (!isMobile) {
-
previousYear();
-
return;
-
}
if (selectedMonth === 0) {
-
setSelectedMonth(11);
-
setYear((current) => current - 1);
-
return;
-
}
setSelectedMonth((current) => current - 1);
-
};
const nextPeriod = () => {
-
if (!isMobile) {
-
nextYear();
-
return;
-
}
if (selectedMonth === 11) {
-
setSelectedMonth(0);
-
setYear((current) => current + 1);
-
return;
-
}
setSelectedMonth((current) => current + 1);
-
};
const currentPeriod = () => {
-
const now = new Date();
-
- setYear(now.getFullYear());
+ setYear(
+ !isMobile && schoolView && now.getMonth() < 8
+ ? now.getFullYear() - 1
+ : now.getFullYear(),
+ );
if (isMobile) {
-
setSelectedMonth(now.getMonth());
-
}
-
};
-
/*
-
* ============================================================
-
* SURVOL
-
* ============================================================
-
*/
-
const handleEventMouseEnter = (
-
event,
-
mouseEvent,
-
) => {
-
setHoveredEvent(event);
-
setHoverPosition({
-
x: mouseEvent.clientX,
-
y: mouseEvent.clientY,
-
});
-
};
-
const handleEventMouseMove = (
-
mouseEvent,
-
) => {
-
setHoverPosition({
-
x: mouseEvent.clientX,
-
y: mouseEvent.clientY,
-
});
-
};
-
const handleEventMouseLeave = () => {
-
setHoveredEvent(null);
-
setHoverPosition(null);
-
};
-
/*
-
* ============================================================
-
* MENU CONTEXTUEL
-
* ============================================================
-
*/
-
const closeContextMenu = () => {
-
setContextMenu(null);
-
};
-
const handleDayContextMenu = (
-
mouseEvent,
-
date,
-
) => {
-
mouseEvent.preventDefault();
-
mouseEvent.stopPropagation();
-
setHoveredEvent(null);
-
setHoverPosition(null);
-
- const selection = isMobile ? null : getOrderedSelection();
-
+ const selection = getOrderedSelection();
const useSelection = Boolean(
-
selection &&
-
date >= selection.start &&
-
date <= selection.end,
-
);
-
setContextMenu({
-
x: mouseEvent.clientX,
-
y: mouseEvent.clientY,
-
type: useSelection ? 'selection' : 'day',
-
date,
-
startDate: useSelection ? selection.start : date,
-
endDate: useSelection ? selection.end : date,
-
});
-
};
-
const handleEventContextMenu = (
-
mouseEvent,
-
event,
-
) => {
-
mouseEvent.preventDefault();
-
mouseEvent.stopPropagation();
-
setHoveredEvent(null);
-
setHoverPosition(null);
-
setContextMenu({
-
x: mouseEvent.clientX,
-
y: mouseEvent.clientY,
-
type: 'event',
-
event,
-
});
-
};
-
useEffect(() => {
-
if (!contextMenu) return;
-
const handleOutsideClick = () => {
-
closeContextMenu();
-
};
-
const handleEscape = (
-
keyboardEvent,
-
) => {
-
if (
-
keyboardEvent.key === 'Escape'
-
) {
-
closeContextMenu();
-
}
-
};
-
document.addEventListener(
-
'mousedown',
-
handleOutsideClick,
-
);
-
document.addEventListener(
-
'keydown',
-
handleEscape,
-
);
-
return () => {
-
document.removeEventListener(
-
'mousedown',
-
handleOutsideClick,
-
);
-
document.removeEventListener(
-
'keydown',
-
handleEscape,
-
);
-
};
-
}, [contextMenu]);
-
/*
-
* ============================================================
-
* FORMULAIRE ÉVÉNEMENT
-
* ============================================================
-
*/
-
const resetEventForm = () => {
-
setEventForm({
-
name: '',
-
description: '',
-
start_date: '',
-
end_date: '',
-
start_time: '',
-
end_time: '',
-
color: DEFAULT_EVENT_COLOR,
-
});
-
};
-
const openCreateDialog = (
-
startDate = '',
-
endDate = startDate,
-
) => {
-
setEditingEvent(null);
-
setEventForm({
-
name: '',
-
description: '',
-
start_date: startDate,
-
end_date: endDate || startDate,
-
start_time: '',
-
end_time: '',
-
color: DEFAULT_EVENT_COLOR,
-
});
-
clearSelection();
-
closeContextMenu();
-
setEventDialogOpen(true);
-
};
-
const openEditDialog = (
-
event,
-
) => {
-
setEditingEvent(event);
-
setEventForm({
-
name: event?.name ?? '',
-
description:
-
event?.description ?? '',
-
start_date:
-
event?.start_date ?? '',
-
end_date:
-
event?.end_date ??
-
event?.start_date ??
-
'',
-
start_time:
-
event?.start_time
-
? String(
-
event.start_time,
-
).slice(0, 5)
-
: '',
-
end_time:
-
event?.end_time
-
? String(
-
event.end_time,
-
).slice(0, 5)
-
: '',
-
color:
-
event?.color ||
-
DEFAULT_EVENT_COLOR,
-
});
-
closeContextMenu();
-
setEventDialogOpen(true);
-
};
-
const handleFormChange = (
-
field,
-
value,
-
) => {
-
setEventForm(
-
(current) => ({
-
...current,
-
[field]: value,
-
}),
-
);
-
};
-
const handleStartDateChange = (
-
value,
-
) => {
-
setEventForm(
-
(current) => {
-
const next = {
-
...current,
-
start_date: value,
-
};
-
/*
-
* Si la date de fin est vide ou
-
* antérieure à la nouvelle date
-
* de début, on la recale sur
-
* la date de début.
-
*/
-
if (
-
!current.end_date ||
-
current.end_date < value
-
) {
-
next.end_date = value;
-
}
-
return next;
-
},
-
);
-
};
-
const handleSaveEvent = async (
-
submitEvent,
-
) => {
-
setRequestError('');
-
if (!eventForm.name.trim()) {
-
return;
-
}
-
if (!eventForm.start_date) {
-
return;
-
}
-
const startDate =
-
parseDate(
-
eventForm.start_date,
-
);
-
const endDate =
-
parseDate(
-
eventForm.end_date ||
-
eventForm.start_date,
-
);
-
if (
-
!startDate ||
-
!endDate ||
-
endDate < startDate
-
) {
-
return;
-
}
-
try {
-
setSavingEvent(true);
-
const payload = {
-
name: eventForm.name.trim(),
-
description:
-
eventForm.description.trim(),
-
start_date:
-
eventForm.start_date,
-
end_date:
-
eventForm.end_date ||
-
eventForm.start_date,
-
start_time:
-
eventForm.start_time || null,
-
end_time:
-
eventForm.end_time || null,
-
color:
-
eventForm.color ||
-
DEFAULT_EVENT_COLOR,
-
};
-
if (submitEvent) {
-
await eventApi.updateEvent(
-
editingEvent.id,
-
payload,
-
);
-
} else {
-
await eventApi.createEvent(
-
payload,
-
);
-
}
-
setEventDialogOpen(false);
-
setEditingEvent(null);
-
resetEventForm();
-
await loadEvents();
-
} catch (error) {
-
setRequestError(error.message);
-
console.error(
-
submitEvent
-
? 'Erreur lors de la modification de l’événement :'
-
: 'Erreur lors de la création de l’événement :',
-
error,
-
);
-
} finally {
-
setSavingEvent(false);
-
}
-
};
-
const handleDeleteEvent = async (
-
event,
-
) => {
-
closeContextMenu();
-
const confirmed =
-
window.confirm(
-
`Supprimer l’événement « ${
-
event?.name || 'Sans nom'
-
} » ?`,
-
);
-
if (!confirmed) {
-
return;
-
}
-
try {
-
await eventApi.deleteEvent(
-
event.id,
-
);
-
await loadEvents();
-
} catch (error) {
-
setRequestError(error.message);
-
console.error(
-
'Erreur lors de la suppression de l’événement :',
-
error,
-
);
-
}
-
};
-
/*
-
* ============================================================
-
* AUJOURD'HUI
-
* ============================================================
-
*/
-
const today = new Date();
-
const isToday = (
-
monthIndex,
-
day,
-
) => {
-
return (
-
- today.getFullYear() === year &&
-
+ today.getFullYear() === getMonthYear(monthIndex) &&
today.getMonth() === monthIndex &&
-
today.getDate() === day
-
);
-
};
-
/*
-
* ============================================================
-
* ÉVÉNEMENTS DE L'ANNÉE
-
* ============================================================
-
*/
-
const yearEvents = events.filter(
-
(event) => {
-
const start = parseDate(
-
getEventStartDate(event),
-
);
-
const end = parseDate(
-
getEventEndDate(event),
-
);
-
if (!start || !end) {
-
return false;
-
}
-
- const yearStart = new Date(
-
- year,
-
- 0,
-
- 1,
-
- );
-
- const yearEnd = new Date(
-
- year,
-
- 11,
-
- 31,
-
- );
-
+ const yearStart = new Date(year, !isMobile && schoolView ? 8 : 0, 1);
+ const yearEnd = !isMobile && schoolView
+ ? new Date(year + 1, 7, 31)
+ : new Date(year, 11, 31);
return (
-
end >= yearStart &&
-
start <= yearEnd
-
);
-
},
-
);
-
/*
-
* ============================================================
-
* LANES DES ÉVÉNEMENTS CONTINUS
-
* ============================================================
-
*/
-
const eventLanes = useMemo(() => {
-
const periodEvents = yearEvents
-
.filter((event) => !isSingleDayEvent(event))
-
.map((event) => ({
-
event,
-
start: parseDate(getEventStartDate(event)),
-
end: parseDate(getEventEndDate(event)),
-
}))
-
.filter(({ start, end }) => start && end)
-
.sort((a, b) => {
-
const startDifference = a.start - b.start;
-
if (startDifference !== 0) return startDifference;
-
const endDifference = b.end - a.end;
-
if (endDifference !== 0) return endDifference;
-
return String(a.event.id).localeCompare(String(b.event.id));
-
});
-
const laneEndDates = [];
-
const lanes = new Map();
-
periodEvents.forEach(({ event, start, end }) => {
-
let lane = laneEndDates.findIndex(
-
(laneEndDate) => laneEndDate < start,
-
);
-
if (lane === -1) {
-
lane = laneEndDates.length;
-
}
-
laneEndDates[lane] = end;
-
lanes.set(String(event.id), lane);
-
});
-
return lanes;
-
}, [yearEvents]);
-
/*
-
* ============================================================
-
* DIMENSIONS
-
* ============================================================
-
*/
-
const displayedMonthIndices = isMobile
-
? [selectedMonth]
-
- : MONTHS.map((_, index) => index);
+ : schoolView
+ ? [8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7]
+ : MONTHS.map((_, index) => index);
const rowCount = isMobile
-
? getDaysInMonth(selectedMonth)
-
: 31;
const availableGridHeight = Math.max(
-
availableHeight - HEADER_HEIGHT,
-
rowCount * MIN_ROW_HEIGHT,
-
);
const rowHeight = Math.max(
-
MIN_ROW_HEIGHT,
-
availableGridHeight / rowCount,
-
);
const minimumGridWidth = isMobile
-
? 0
-
: DAY_COLUMN_WIDTH +
-
MONTHS.length * MIN_MONTH_WIDTH;
const gridTemplateColumns = isMobile
-
? `${DAY_COLUMN_WIDTH}px minmax(0, 1fr)`
-
: `
-
${DAY_COLUMN_WIDTH}px
-
repeat(
-
12,
-
minmax(
-
${MIN_MONTH_WIDTH}px,
-
1fr
-
)
-
)
-
`;
-
/*
-
* ============================================================
-
* RENDER
-
* ============================================================
-
*/
-
if (loadingEvents) {
-
return (
-