Add an accommodations reservations module #188
@ -1,5 +1,6 @@
|
|||||||
import { APIClient } from "../ApiClient";
|
import { APIClient } from "../ApiClient";
|
||||||
import { Family } from "../FamilyApi";
|
import { Family } from "../FamilyApi";
|
||||||
|
import { Accommodation } from "./AccommodationListApi";
|
||||||
|
|
||||||
export interface AccommodationReservation {
|
export interface AccommodationReservation {
|
||||||
id: number;
|
id: number;
|
||||||
@ -59,6 +60,7 @@ export interface UpdateAccommodationReservation {
|
|||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
accommodation_id: number;
|
accommodation_id: number;
|
||||||
|
reservation_id?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccommodationsReservationsApi {
|
export class AccommodationsReservationsApi {
|
||||||
@ -77,4 +79,23 @@ export class AccommodationsReservationsApi {
|
|||||||
|
|
||||||
return new AccommodationsReservationsList(data);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
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 { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute";
|
||||||
import { PropDateInput } from "../../widgets/forms/PropDateInput";
|
import { PropDateInput } from "../../widgets/forms/PropDateInput";
|
||||||
import { PropSelect } from "../../widgets/forms/PropSelect";
|
import { PropSelect } from "../../widgets/forms/PropSelect";
|
||||||
|
import { fmtUnixDate } from "../../utils/time_utils";
|
||||||
|
|
||||||
export function UpdateReservationDialog(p: {
|
export function UpdateReservationDialog(p: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -18,12 +27,19 @@ export function UpdateReservationDialog(p: {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmitted: (c: UpdateAccommodationReservation) => void;
|
onSubmitted: (c: UpdateAccommodationReservation) => void;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
|
const alert = useAlert();
|
||||||
|
|
||||||
|
const family = useFamily();
|
||||||
const accommodations = useAccommodations();
|
const accommodations = useAccommodations();
|
||||||
|
|
||||||
const [reservation, setReservation] = React.useState<
|
const [reservation, setReservation] = React.useState<
|
||||||
UpdateAccommodationReservation | undefined
|
UpdateAccommodationReservation | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const [conflicts, setConflicts] = React.useState<
|
||||||
|
AccommodationReservation[] | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
const clearForm = () => {
|
const clearForm = () => {
|
||||||
setReservation(undefined);
|
setReservation(undefined);
|
||||||
};
|
};
|
||||||
@ -42,7 +58,46 @@ export function UpdateReservationDialog(p: {
|
|||||||
if (!reservation) setReservation(p.reservation);
|
if (!reservation) setReservation(p.reservation);
|
||||||
}, [p.open, 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 (
|
return (
|
||||||
<Dialog open={p.open} onClose={cancel}>
|
<Dialog open={p.open} onClose={cancel}>
|
||||||
@ -94,7 +149,22 @@ export function UpdateReservationDialog(p: {
|
|||||||
minDate={reservation?.start}
|
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>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={cancel}>Annuler</Button>
|
<Button onClick={cancel}>Annuler</Button>
|
||||||
@ -104,7 +174,8 @@ export function UpdateReservationDialog(p: {
|
|||||||
!(
|
!(
|
||||||
(reservation?.accommodation_id ?? -1) > 0 &&
|
(reservation?.accommodation_id ?? -1) > 0 &&
|
||||||
(reservation?.start ?? -1) > 0 &&
|
(reservation?.start ?? -1) > 0 &&
|
||||||
(reservation?.end ?? -1) > (reservation?.start ?? 0)
|
(reservation?.end ?? -1) > (reservation?.start ?? 0) &&
|
||||||
|
(conflicts?.length ?? 0) === 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -74,7 +74,7 @@ export function AccommodationsReservationsRoute(): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FamilyPageTitle title="Réservation" />
|
<FamilyPageTitle title="Réservations" />
|
||||||
<AsyncWidget
|
<AsyncWidget
|
||||||
loadKey={loadKey.current}
|
loadKey={loadKey.current}
|
||||||
load={load}
|
load={load}
|
||||||
|
@ -39,7 +39,6 @@ export function PropDateInput(p: {
|
|||||||
const maxDate = p.maxDate ? dayjs(new Date(p.maxDate * 1000)) : undefined;
|
const maxDate = p.maxDate ? dayjs(new Date(p.maxDate * 1000)) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ margin: "10px auto" }}>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
|
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
label={p.label}
|
label={p.label}
|
||||||
@ -55,7 +54,7 @@ export function PropDateInput(p: {
|
|||||||
minDate={minDate}
|
minDate={minDate}
|
||||||
maxDate={maxDate}
|
maxDate={maxDate}
|
||||||
/>
|
/>
|
||||||
|
<div style={{ height: "10px" }}></div>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,31 @@ pub async fn get_accommodation_reservations(a: FamilyAndAccommodationInPath) ->
|
|||||||
.json(accommodations_reservations_service::get_all_of_accommodation(a.id()).await?))
|
.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
|
/// Get the full list of accommodations reservations for a family
|
||||||
pub async fn full_list(m: FamilyInPath) -> HttpResult {
|
pub async fn full_list(m: FamilyInPath) -> HttpResult {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
|
@ -233,6 +233,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
web::get()
|
web::get()
|
||||||
.to(accommodations_reservations_controller::get_accommodation_reservations),
|
.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(
|
.route(
|
||||||
"/family/{id}/accommodations/reservations/full_list",
|
"/family/{id}/accommodations/reservations/full_list",
|
||||||
web::get().to(accommodations_reservations_controller::full_list),
|
web::get().to(accommodations_reservations_controller::full_list),
|
||||||
|
Loading…
Reference in New Issue
Block a user