diff --git a/geneit_app/src/api/accommodations/AccommodationsReservationsApi.tsx b/geneit_app/src/api/accommodations/AccommodationsReservationsApi.tsx index 845372a..7c426b9 100644 --- a/geneit_app/src/api/accommodations/AccommodationsReservationsApi.tsx +++ b/geneit_app/src/api/accommodations/AccommodationsReservationsApi.tsx @@ -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 { + 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); + } } diff --git a/geneit_app/src/dialogs/accommodations/UpdateReservationDialog.tsx b/geneit_app/src/dialogs/accommodations/UpdateReservationDialog.tsx index 362d608..3ac396a 100644 --- a/geneit_app/src/dialogs/accommodations/UpdateReservationDialog.tsx +++ b/geneit_app/src/dialogs/accommodations/UpdateReservationDialog.tsx @@ -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 ( @@ -94,7 +149,22 @@ export function UpdateReservationDialog(p: { minDate={reservation?.start} /> - {/* TODO : la suite */} + {conflicts && conflicts.length > 0 && ( + +

+ Cette réservation est en conflit avec d'autres réservations sur + les intervalles suivants : +

+
    + {conflicts.map((c, num) => ( +
  • + Réservation du {fmtUnixDate(c.reservation_start)} au{" "} + {fmtUnixDate(c.reservation_end)} +
  • + ))} +
+
+ )} @@ -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 ) } > diff --git a/geneit_app/src/routes/family/accommodations/AccommodationsReservationsRoute.tsx b/geneit_app/src/routes/family/accommodations/AccommodationsReservationsRoute.tsx index 5d979a8..324f9bd 100644 --- a/geneit_app/src/routes/family/accommodations/AccommodationsReservationsRoute.tsx +++ b/geneit_app/src/routes/family/accommodations/AccommodationsReservationsRoute.tsx @@ -74,7 +74,7 @@ export function AccommodationsReservationsRoute(): React.ReactElement { return ( <> - + - - { - 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} - /> - - + + { + 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} + /> +
+
); } diff --git a/geneit_backend/src/controllers/accommodations_reservations_controller.rs b/geneit_backend/src/controllers/accommodations_reservations_controller.rs index bda86ca..8c3fba5 100644 --- a/geneit_backend/src/controllers/accommodations_reservations_controller.rs +++ b/geneit_backend/src/controllers/accommodations_reservations_controller.rs @@ -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, +) -> 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() diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index ff815fc..b4b0f74 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -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),