Check accommodation availability directly in create reservation dialog
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
Pierre HUBERT 2024-06-19 22:09:29 +02:00
parent b66a8a8ac9
commit 9929c5db48
6 changed files with 144 additions and 23 deletions

View File

@ -1,5 +1,6 @@
import { APIClient } from "../ApiClient";
import { Family } from "../FamilyApi";
import { Accommodation } from "./AccommodationListApi";
export interface AccommodationReservation {
id: number;
@ -59,6 +60,7 @@ export interface UpdateAccommodationReservation {
start: number;
end: number;
accommodation_id: number;
reservation_id?: number;
}
export class AccommodationsReservationsApi {
@ -77,4 +79,23 @@ export class AccommodationsReservationsApi {
return new AccommodationsReservationsList(data);
}
/**
* Get the reservations of a given time interval for an accommodation
*/
static async ReservationsForInterval(
family: Family,
accommodation: Accommodation,
start: number,
end: number
): Promise<AccommodationsReservationsList> {
const data = (
await APIClient.exec({
method: "GET",
uri: `/family/${family.family_id}/accommodations/reservations/accommodation/${accommodation.id}/for_interval?start=${start}&end=${end}`,
})
).data;
return new AccommodationsReservationsList(data);
}
}

View File

@ -1,15 +1,24 @@
import {
Alert,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Typography,
} from "@mui/material";
import React from "react";
import { UpdateAccommodationReservation } from "../../api/accommodations/AccommodationsReservationsApi";
import {
AccommodationReservation,
AccommodationsReservationsApi,
UpdateAccommodationReservation,
} from "../../api/accommodations/AccommodationsReservationsApi";
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
import { useFamily } from "../../widgets/BaseFamilyRoute";
import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
import { PropDateInput } from "../../widgets/forms/PropDateInput";
import { PropSelect } from "../../widgets/forms/PropSelect";
import { fmtUnixDate } from "../../utils/time_utils";
export function UpdateReservationDialog(p: {
open: boolean;
@ -18,12 +27,19 @@ export function UpdateReservationDialog(p: {
onClose: () => void;
onSubmitted: (c: UpdateAccommodationReservation) => void;
}): React.ReactElement {
const alert = useAlert();
const family = useFamily();
const accommodations = useAccommodations();
const [reservation, setReservation] = React.useState<
UpdateAccommodationReservation | undefined
>();
const [conflicts, setConflicts] = React.useState<
AccommodationReservation[] | undefined
>(undefined);
const clearForm = () => {
setReservation(undefined);
};
@ -42,7 +58,46 @@ export function UpdateReservationDialog(p: {
if (!reservation) setReservation(p.reservation);
}, [p.open, p.reservation]);
// TODO : check availability
React.useEffect(() => {
setConflicts(undefined);
(async () => {
try {
if (
!reservation ||
reservation.accommodation_id < 1 ||
reservation.start < 1 ||
reservation.start > reservation.end
) {
setConflicts([]);
return;
}
setConflicts(
(
await AccommodationsReservationsApi.ReservationsForInterval(
family.family,
accommodations.accommodations.get(reservation.accommodation_id)!,
reservation.start,
reservation.end
)
).filter(
(r) =>
r.id !== p.reservation?.reservation_id && r.validated !== false
)
);
} catch (e) {
console.error(e);
alert(
"Echec de la vérification de la présence de conflits de calendrier !"
);
}
})();
}, [
p.open,
reservation?.accommodation_id,
reservation?.start,
reservation?.end,
]);
return (
<Dialog open={p.open} onClose={cancel}>
@ -94,7 +149,22 @@ export function UpdateReservationDialog(p: {
minDate={reservation?.start}
/>
{/* TODO : la suite */}
{conflicts && conflicts.length > 0 && (
<Alert severity="error">
<p>
Cette réservation est en conflit avec d'autres réservations sur
les intervalles suivants :
</p>
<ul>
{conflicts.map((c, num) => (
<li key={num}>
Réservation du {fmtUnixDate(c.reservation_start)} au{" "}
{fmtUnixDate(c.reservation_end)}
</li>
))}
</ul>
</Alert>
)}
</DialogContent>
<DialogActions>
<Button onClick={cancel}>Annuler</Button>
@ -104,7 +174,8 @@ export function UpdateReservationDialog(p: {
!(
(reservation?.accommodation_id ?? -1) > 0 &&
(reservation?.start ?? -1) > 0 &&
(reservation?.end ?? -1) > (reservation?.start ?? 0)
(reservation?.end ?? -1) > (reservation?.start ?? 0) &&
(conflicts?.length ?? 0) === 0
)
}
>

View File

@ -74,7 +74,7 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
return (
<>
<FamilyPageTitle title="Réservation" />
<FamilyPageTitle title="Réservations" />
<AsyncWidget
loadKey={loadKey.current}
load={load}

View File

@ -39,23 +39,22 @@ export function PropDateInput(p: {
const maxDate = p.maxDate ? dayjs(new Date(p.maxDate * 1000)) : undefined;
return (
<div style={{ margin: "10px auto" }}>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
<DatePicker
label={p.label}
value={value}
onChange={(v) => {
if (v && p.lastSecOfDay) {
v.set("hours", 23);
v.set("minutes", 59);
v.set("seconds", 59);
}
p.onChange?.(v ? v.unix() : undefined);
}}
minDate={minDate}
maxDate={maxDate}
/>
</LocalizationProvider>
</div>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
<DatePicker
label={p.label}
value={value}
onChange={(v) => {
if (v && p.lastSecOfDay) {
v.set("hours", 23);
v.set("minutes", 59);
v.set("seconds", 59);
}
p.onChange?.(v ? v.unix() : undefined);
}}
minDate={minDate}
maxDate={maxDate}
/>
<div style={{ height: "10px" }}></div>
</LocalizationProvider>
);
}

View File

@ -93,6 +93,31 @@ pub async fn get_accommodation_reservations(a: FamilyAndAccommodationInPath) ->
.json(accommodations_reservations_service::get_all_of_accommodation(a.id()).await?))
}
#[derive(serde::Deserialize)]
pub struct CheckAvailabilityQuery {
start: usize,
end: usize,
}
/// Check reservation availability
pub async fn get_accommodation_reservations_for_interval(
a: FamilyAndAccommodationInPath,
req: web::Query<CheckAvailabilityQuery>,
) -> HttpResult {
if req.start > req.end {
return Ok(HttpResponse::BadRequest().json("start should be smaller than end!"));
}
let res = accommodations_reservations_service::get_reservations_for_time_interval(
a.id(),
req.start,
req.end,
)
.await?;
Ok(HttpResponse::Ok().json(res))
}
/// Get the full list of accommodations reservations for a family
pub async fn full_list(m: FamilyInPath) -> HttpResult {
Ok(HttpResponse::Ok()

View File

@ -233,6 +233,11 @@ async fn main() -> std::io::Result<()> {
web::get()
.to(accommodations_reservations_controller::get_accommodation_reservations),
)
.route(
"/family/{id}/accommodations/reservations/accommodation/{accommodation_id}/for_interval",
web::get()
.to(accommodations_reservations_controller::get_accommodation_reservations_for_interval),
)
.route(
"/family/{id}/accommodations/reservations/full_list",
web::get().to(accommodations_reservations_controller::full_list),