Add an accommodations reservations module #188
@ -117,4 +117,14 @@ export class AccommodationsReservationsApi {
|
||||
|
||||
return new AccommodationsReservationsList(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a reservation
|
||||
*/
|
||||
static async Delete(r: AccommodationReservation): Promise<void> {
|
||||
await APIClient.exec({
|
||||
method: "DELETE",
|
||||
uri: `/family/${r.family_id}/accommodations/reservation/${r.id}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,29 +4,44 @@ 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 {
|
||||
Alert,
|
||||
Avatar,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Popover,
|
||||
Tooltip,
|
||||
Typography,
|
||||
fabClasses,
|
||||
} 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 { fmtUnixDateFullCalendar } from "../../../utils/time_utils";
|
||||
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";
|
||||
@ -34,10 +49,12 @@ import { useAccommodations } from "../../../widgets/accommodations/BaseAccommoda
|
||||
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();
|
||||
@ -62,6 +79,10 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
||||
const [activeEvent, setActiveEvent] = React.useState<
|
||||
| undefined
|
||||
| {
|
||||
user: FamilyUser;
|
||||
accommodation: Accommodation;
|
||||
reservation: AccommodationReservation;
|
||||
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
@ -127,9 +148,52 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
||||
};
|
||||
|
||||
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({ x: loc.left, y: loc.top, w: loc.width, h: loc.height });
|
||||
console.log(ev);
|
||||
setActiveEvent({
|
||||
reservation: resa,
|
||||
accommodation: acc,
|
||||
user: user,
|
||||
|
||||
x: loc.left,
|
||||
y: loc.top,
|
||||
w: loc.width,
|
||||
h: loc.height,
|
||||
});
|
||||
};
|
||||
|
||||
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 (
|
||||
@ -283,6 +347,9 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
||||
: r.validated === false
|
||||
? "red dotted"
|
||||
: "grey dotted",
|
||||
extendedProps: {
|
||||
id: r.id,
|
||||
},
|
||||
};
|
||||
})}
|
||||
/>
|
||||
@ -299,6 +366,7 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
||||
width: activeEvent?.w + "px",
|
||||
height: activeEvent?.h + "px",
|
||||
backgroundColor: "pink",
|
||||
zIndex: 0,
|
||||
}}
|
||||
></div>
|
||||
<Popover
|
||||
@ -312,7 +380,67 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ p: 2 }}>The content of the Popover.</Typography>
|
||||
<Card sx={{ maxWidth: 345 }} elevation={6}>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar sx={{ bgcolor: red[500] }}>
|
||||
{activeEvent?.user.user_name
|
||||
.substring(0, 1)
|
||||
.toLocaleUpperCase()}
|
||||
</Avatar>
|
||||
}
|
||||
title={activeEvent?.user.user_name}
|
||||
subheader={activeEvent?.user.user_mail}
|
||||
/>
|
||||
|
||||
<CardContent>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<p>
|
||||
Réservation de {activeEvent?.accommodation.name}
|
||||
<br />
|
||||
<em>{activeEvent?.accommodation.description}</em>
|
||||
</p>
|
||||
<p>
|
||||
Du{" "}
|
||||
{fmtUnixDate(
|
||||
activeEvent?.reservation.reservation_start ?? 0
|
||||
)}{" "}
|
||||
<br />
|
||||
Au{" "}
|
||||
{fmtUnixDate(
|
||||
activeEvent?.reservation.reservation_end ?? 0
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
{activeEvent?.reservation.validated === false ? (
|
||||
<span style={{ color: "#f44336" }}>Refusée</span>
|
||||
) : activeEvent?.reservation.validated === true ? (
|
||||
<span style={{ color: "#66bb6a" }}>Validée</span>
|
||||
) : (
|
||||
<span style={{ color: "#29b6f6" }}>
|
||||
En attente de validation
|
||||
</span>
|
||||
)}
|
||||
</strong>
|
||||
</p>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions disableSpacing>
|
||||
{user.user.id === activeEvent?.reservation.user_id && (
|
||||
<Tooltip title="Supprimer la réservation" arrow>
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() =>
|
||||
deleteReservation(activeEvent?.reservation)
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
Loading…
Reference in New Issue
Block a user