import { DateSelectArg, EventClickArg } from "@fullcalendar/core"; import frLocale from "@fullcalendar/core/locales/fr"; import dayGridPlugin from "@fullcalendar/daygrid"; import interactionPlugin from "@fullcalendar/interaction"; import listPlugin from "@fullcalendar/list"; import FullCalendar from "@fullcalendar/react"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import RuleIcon from "@mui/icons-material/Rule"; import { Alert, Avatar, Card, CardActions, CardContent, CardHeader, Checkbox, FormControl, FormControlLabel, FormGroup, FormLabel, IconButton, Menu, MenuItem, Popover, Tooltip, Typography, } from "@mui/material"; import { red } from "@mui/material/colors"; import React from "react"; import { FamilyApi, FamilyUser } from "../../../api/FamilyApi"; import { Accommodation } from "../../../api/accommodations/AccommodationListApi"; import { AccommodationReservation, AccommodationsReservationsApi, AccommodationsReservationsList, } from "../../../api/accommodations/AccommodationsReservationsApi"; import { useAlert } from "../../../hooks/context_providers/AlertDialogProvider"; import { useConfirm } from "../../../hooks/context_providers/ConfirmDialogProvider"; import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessageProvider"; import { useSnackbar } from "../../../hooks/context_providers/SnackbarProvider"; import { useUpdateAccommodationReservation } from "../../../hooks/context_providers/accommodations/UpdateReservationDialogProvider"; import { fmtUnixDate, fmtUnixDateFullCalendar, } from "../../../utils/time_utils"; import { AsyncWidget } from "../../../widgets/AsyncWidget"; import { useUser } from "../../../widgets/BaseAuthenticatedPage"; import { useFamily } from "../../../widgets/BaseFamilyRoute"; import { FamilyPageTitle } from "../../../widgets/FamilyPageTitle"; import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute"; export function AccommodationsReservationsRoute(): React.ReactElement { const snackbar = useSnackbar(); const alert = useAlert(); const confirm = useConfirm(); const loadingMessage = useLoadingMessage(); const loadKey = React.useRef(1); const user = useUser(); const family = useFamily(); const accommodations = useAccommodations(); const updateReservation = useUpdateAccommodationReservation(); const [reservations, setReservations] = React.useState< AccommodationsReservationsList | undefined >(); const [users, setUsers] = React.useState(null); const [showValidated, setShowValidated] = React.useState(true); const [showRejected, setShowRejected] = React.useState(true); const [showPending, setShowPending] = React.useState(true); const [hiddenPeople, setHiddenPeople] = React.useState>( new Set() ); const [hiddenAccommodations, setHiddenAccommodations] = React.useState< Set >(new Set()); const eventPopupAnchor = React.useRef(null); const [activeEvent, setActiveEvent] = React.useState< | undefined | { user: FamilyUser; accommodation: Accommodation; reservation: AccommodationReservation; x: number; y: number; w: number; h: number; } >(); const [validateResaAnchorEl, setValidateResaAnchorEl] = React.useState(null); const load = async () => { setReservations( await AccommodationsReservationsApi.FullListOfFamily(family.family) ); setUsers(await FamilyApi.GetUsersList(family.family.family_id)); }; const reload = async () => { loadKey.current += 1; setUsers(null); }; const visibleReservations = React.useMemo(() => { return reservations?.filter((r) => { if (!showValidated && r.validated === true) return false; if (!showPending && r.validated === null) return false; if (!showRejected && r.validated === false) return false; if (hiddenPeople.has(r.user_id)) return false; if (hiddenAccommodations.has(r.accommodation_id)) return false; return true; }); }, [ showValidated, showRejected, showPending, hiddenPeople, hiddenAccommodations, reservations, ]); const onSelect = async (d: DateSelectArg) => { try { const resa = await updateReservation( { accommodation_id: -1, start: Math.floor(d.start.getTime() / 1000), end: Math.floor(d.end.getTime() / 1000), }, true ); if (!resa) return; loadingMessage.show("Création de la réservation en cours..."); await AccommodationsReservationsApi.Create(family.family, resa); reload(); snackbar("La réservation a été créée avec succès !"); } catch (e) { console.error("Failed to create a reservation!", e); alert("Échec de la création de la réservation!"); } finally { loadingMessage.hide(); } }; const onEventClick = (ev: EventClickArg) => { const id: number = ev.event.extendedProps.id; const resa = reservations?.get(id)!; const acc = accommodations.accommodations.get(resa.accommodation_id)!; const user = users?.find((u) => u.user_id === resa.user_id); if (!user) { console.error(`User ${resa.user_id} not found!`); return; } const loc = ev.el.getBoundingClientRect(); setActiveEvent({ reservation: resa, accommodation: acc, user: user, x: loc.left, y: loc.top, w: loc.width, h: loc.height, }); }; const respondToResaRequest = async ( r: AccommodationReservation, validate: boolean ) => { try { loadingMessage.show("Validation de la réservation en cours..."); setActiveEvent(undefined); await AccommodationsReservationsApi.Validate(r, validate); reload(); snackbar("La réservation a été mise à jour avec succès !"); } catch (e) { console.error("Failed to respond to reservation request!", e); alert(`Echec de l'enregistrement de la réponse à la réservation ! ${e}`); } finally { loadingMessage.hide(); } }; const validateReservation = async (r: AccommodationReservation) => { respondToResaRequest(r, true); }; const rejectReservation = async (r: AccommodationReservation) => { if ( !(await confirm( "Voulez-vous vraiment rejeter cette demande de réservation ?" )) ) return; respondToResaRequest(r, false); }; const changeReservation = async (r: AccommodationReservation) => { try { const ac = accommodations.accommodations.get(r.accommodation_id); if ( ac?.need_validation && !(await confirm( "Voulez-vous vraiment changer cette réservation ? Celle-ci devra être de nouveau validée !" )) ) return; const newResa = await updateReservation( { reservation_id: r.id, accommodation_id: r.accommodation_id, start: r.reservation_start, end: r.reservation_end, }, false ); if (!newResa) return; setActiveEvent(undefined); loadingMessage.show("Mise à jour de la réservation en cours..."); await AccommodationsReservationsApi.Update(family.family, newResa); reload(); snackbar("La réservation a été mise à jour avec succès !"); } catch (e) { console.error("Failed to update a reservation!", e); alert("Échec de la mise à jour de la réservation!"); } finally { loadingMessage.hide(); } }; const deleteReservation = async (r: AccommodationReservation) => { try { if ( !(await confirm( "Voulez-vous vraiment supprimer cette réservation ? L'opération n'est pas réversible !" )) ) return; setActiveEvent(undefined); loadingMessage.show("Suppression de la réservation en cours..."); await AccommodationsReservationsApi.Delete(r); reload(); snackbar("La réservation a été supprimée avec succès !"); } catch (e) { console.error("Failed to delete a reservation!", e); alert("Échec de la suppression de la réservation!"); } finally { loadingMessage.hide(); } }; return ( <> (
Cliquez sur le calendrier pour créer une réservation. {/* Invitation status */} Status setShowValidated(v)} color="success" /> } label="Validées" /> setShowRejected(v)} color="error" /> } label="Rejetées" /> setShowPending(v)} color="info" /> } label="En attente de validation" /> {/* Accommodations */} Logements {accommodations.accommodations.fullList.map((a) => ( { if (v) hiddenAccommodations.delete(a.id); else hiddenAccommodations.add(a.id); setHiddenAccommodations( new Set(hiddenAccommodations) ); }} /> } label={a.name} /> ))} {/* People */} Personnes {users?.map((u) => ( { if (v) hiddenPeople.delete(u.user_id); else hiddenPeople.add(u.user_id); setHiddenPeople(new Set(hiddenPeople)); }} /> } label={u.user_name} /> ))}
{/* The calendar */}
{ const a = accommodations.accommodations.get( r.accommodation_id )!; const u = users?.find((u) => u.user_id === r.user_id); return { title: `${u?.user_name} - ${a.name}`, start: fmtUnixDateFullCalendar(r.reservation_start, false), end: fmtUnixDateFullCalendar(r.reservation_end, true), allDay: true, color: a.color ? "#" + a.color : undefined, borderColor: r.validated === true ? "green" : r.validated === false ? "red dotted" : "grey dotted", extendedProps: { id: r.id, }, }; })} />
{/* Calendar event popover */}
{ setActiveEvent(undefined); }} anchorOrigin={{ vertical: "bottom", horizontal: "left", }} > {activeEvent?.user.user_name .substring(0, 1) .toLocaleUpperCase()} } title={activeEvent?.user.user_name} subheader={activeEvent?.user.user_mail} />

Réservation de {activeEvent?.accommodation.name}
{activeEvent?.accommodation.description}

Du{" "} {fmtUnixDate( activeEvent?.reservation.reservation_start ?? 0 )}{" "}
Au{" "} {fmtUnixDate( activeEvent?.reservation.reservation_end ?? 0 )}

{activeEvent?.reservation.validated === false ? ( Refusée ) : activeEvent?.reservation.validated === true ? ( Validée ) : ( En attente de validation )}

{activeEvent?.accommodation.need_validation && family.family.is_admin && ( <> setValidateResaAnchorEl(e.currentTarget) } > setValidateResaAnchorEl(null)} > validateReservation(activeEvent.reservation) } > Valider rejectReservation(activeEvent.reservation) } > Rejeter )} {user.user.id === activeEvent?.reservation.user_id && ( <> changeReservation(activeEvent?.reservation) } > deleteReservation(activeEvent?.reservation) } > )}
)} /> ); }