From 7525e78009851078c5bcacbe6b8b6d30bbae6bf3 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Thu, 13 Jun 2024 21:44:36 +0200 Subject: [PATCH] Can create accommodation calendars URL from UI --- geneit_app/src/api/ServerApi.ts | 1 + .../AccommodationsCalendarURLApi.tsx | 36 +++++++ .../CreateAccommodationCalendarURLDialog.tsx | 92 ++++++++++++++++++ ...AccommodationCalendarURLDialogProvider.tsx | 52 ++++++++++ .../AccommodationsSettingsRoute.tsx | 96 +++++++++++++++---- geneit_app/src/widgets/FamilyCard.tsx | 8 +- .../BaseAccommodationsRoute.tsx | 7 +- geneit_app/src/widgets/forms/PropSelect.tsx | 3 +- 8 files changed, 274 insertions(+), 21 deletions(-) create mode 100644 geneit_app/src/api/accommodations/AccommodationsCalendarURLApi.tsx create mode 100644 geneit_app/src/dialogs/accommodations/CreateAccommodationCalendarURLDialog.tsx create mode 100644 geneit_app/src/hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider.tsx diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts index 0ac625f..d017de6 100644 --- a/geneit_app/src/api/ServerApi.ts +++ b/geneit_app/src/api/ServerApi.ts @@ -34,6 +34,7 @@ interface Constraints { member_note: LenConstraint; accommodation_name_len: LenConstraint; accommodation_description_len: LenConstraint; + accommodation_calendar_name_len: LenConstraint; } interface OIDCProvider { diff --git a/geneit_app/src/api/accommodations/AccommodationsCalendarURLApi.tsx b/geneit_app/src/api/accommodations/AccommodationsCalendarURLApi.tsx new file mode 100644 index 0000000..7a1d27d --- /dev/null +++ b/geneit_app/src/api/accommodations/AccommodationsCalendarURLApi.tsx @@ -0,0 +1,36 @@ +import { APIClient } from "../ApiClient"; +import { Family } from "../FamilyApi"; + +export interface NewCalendarURL { + accommodation_id?: number; + name: string; +} + +export interface AccommodationCalendarURL { + id: number; + family_id: number; + accommodation_id: number; + user_id: number; + name: string; + token: string; + time_create: number; + time_used: number; +} + +export class AccommodationsCalendarURLApi { + /** + * Create a new accommodation calendar URL + */ + static async Create( + family: Family, + calendar: NewCalendarURL + ): Promise { + return ( + await APIClient.exec({ + method: "POST", + uri: `/family/${family.family_id}/accommodations/reservations_calendars/create`, + jsonData: calendar, + }) + ).data; + } +} diff --git a/geneit_app/src/dialogs/accommodations/CreateAccommodationCalendarURLDialog.tsx b/geneit_app/src/dialogs/accommodations/CreateAccommodationCalendarURLDialog.tsx new file mode 100644 index 0000000..f1fe0b8 --- /dev/null +++ b/geneit_app/src/dialogs/accommodations/CreateAccommodationCalendarURLDialog.tsx @@ -0,0 +1,92 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, +} from "@mui/material"; +import React from "react"; +import { ServerApi } from "../../api/ServerApi"; +import { NewCalendarURL } from "../../api/accommodations/AccommodationsCalendarURLApi"; +import { checkConstraint } from "../../utils/from_utils"; +import { PropEdit } from "../../widgets/forms/PropEdit"; +import { PropSelect } from "../../widgets/forms/PropSelect"; +import { useAccommodations } from "../../widgets/accommodations/BaseAccommodationsRoute"; + +export function CreateAccommodationCalendarURLDialog(p: { + open: boolean; + onClose: () => void; + onSubmitted: (c: NewCalendarURL) => void; +}): React.ReactElement { + const [calendar, setCalendar] = React.useState({ name: "" }); + + const accommodations = useAccommodations(); + + const nameErr = checkConstraint( + ServerApi.Config.constraints.accommodation_calendar_name_len, + calendar?.name + ); + + const clearForm = () => { + setCalendar({ name: "" }); + }; + + const cancel = () => { + clearForm(); + p.onClose(); + }; + + const submit = async () => { + clearForm(); + p.onSubmitted(calendar!); + }; + + return ( + + Création d'un calendrier + + + setCalendar((a) => { + return { + ...a!, + name: s!, + }; + }) + } + size={ServerApi.Config.constraints.accommodation_calendar_name_len} + helperText={nameErr} + /> + + { + setCalendar((a) => { + return { + ...a!, + accommodation_id: v !== "A" && v ? Number(v) : undefined, + }; + }); + }} + options={[ + { label: "Tous les logements", value: "A" }, + ...accommodations.accommodations.fullList.map((a) => { + return { label: a.name, value: a.id.toString() }; + }), + ]} + value={calendar.accommodation_id?.toString() ?? "A"} + /> + + + + + + + ); +} diff --git a/geneit_app/src/hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider.tsx b/geneit_app/src/hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider.tsx new file mode 100644 index 0000000..376329d --- /dev/null +++ b/geneit_app/src/hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider.tsx @@ -0,0 +1,52 @@ +import React, { PropsWithChildren } from "react"; +import { NewCalendarURL } from "../../../api/accommodations/AccommodationsCalendarURLApi"; +import { CreateAccommodationCalendarURLDialog } from "../../../dialogs/accommodations/CreateAccommodationCalendarURLDialog"; + +type DialogContext = () => Promise; + +const DialogContextK = React.createContext(null); + +export function CreateAccommodationCalendarURLDialogProvider( + p: PropsWithChildren +): React.ReactElement { + const [open, setOpen] = React.useState(false); + + const cb = React.useRef void)>( + null + ); + + const handleClose = (res?: NewCalendarURL) => { + setOpen(false); + + if (cb.current !== null) cb.current(res); + cb.current = null; + }; + + const hook: DialogContext = () => { + setOpen(true); + + return new Promise((res) => { + cb.current = res; + }); + }; + + return ( + <> + + {p.children} + + + {open && ( + + )} + + ); +} + +export function useCreateAccommodationCalendarURL(): DialogContext { + return React.useContext(DialogContextK)!; +} diff --git a/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx b/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx index 24bd50b..dfb4495 100644 --- a/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx +++ b/geneit_app/src/routes/family/accommodations/AccommodationsSettingsRoute.tsx @@ -1,33 +1,35 @@ +import AddIcon from "@mui/icons-material/Add"; +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; import { - CardContent, - Typography, - Alert, Button, Card, CardActions, + CardContent, + Typography, } from "@mui/material"; import React from "react"; -import { useAlert } from "../../../hooks/context_providers/AlertDialogProvider"; -import { useConfirm } from "../../../hooks/context_providers/ConfirmDialogProvider"; -import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessageProvider"; -import { useFamily } from "../../../widgets/BaseFamilyRoute"; -import { FamilyCard } from "../../../widgets/FamilyCard"; -import AddIcon from "@mui/icons-material/Add"; -import { useUpdateAccommodation } from "../../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider"; import { Accommodation, AccommodationListApi, } from "../../../api/accommodations/AccommodationListApi"; +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 { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute"; +import { useUpdateAccommodation } from "../../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider"; +import { useFamily } from "../../../widgets/BaseFamilyRoute"; +import { FamilyCard } from "../../../widgets/FamilyCard"; import { TimeWidget } from "../../../widgets/TimeWidget"; -import CheckIcon from "@mui/icons-material/Check"; -import CloseIcon from "@mui/icons-material/Close"; +import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute"; +import { useCreateAccommodationCalendarURL } from "../../../hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider"; +import { AccommodationsCalendarURLApi } from "../../../api/accommodations/AccommodationsCalendarURLApi"; export function AccommodationsSettingsRoute(): React.ReactElement { return ( <> + ); } @@ -35,7 +37,6 @@ export function AccommodationsSettingsRoute(): React.ReactElement { function AccommodationsListCard(): React.ReactElement { const loading = useLoadingMessage(); const confirm = useConfirm(); - const alert = useAlert(); const snackbar = useSnackbar(); const family = useFamily(); @@ -121,7 +122,7 @@ function AccommodationsListCard(): React.ReactElement { }; return ( - + Logements @@ -150,7 +151,7 @@ function AccommodationsListCard(): React.ReactElement { onClick={createAccommodation} size={"large"} > - Ajouter un nouveau logement + Ajouter un logement )} @@ -213,3 +214,66 @@ function BoolIcon(p: { checked?: boolean }): React.ReactElement { ); } + +function AccommodationsCalURLsCard(): React.ReactElement { + const loading = useLoadingMessage(); + + const [error, setError] = React.useState(); + const [success, setSuccess] = React.useState(); + + const family = useFamily(); + + const createCalendarURLDialog = useCreateAccommodationCalendarURL(); + + const createCalendarURL = async () => { + try { + const newCal = await createCalendarURLDialog(); + + if (!newCal) return; + + loading.show("Création du logement en cours..."); + + const cal = await AccommodationsCalendarURLApi.Create( + family.family, + newCal + ); + + setSuccess("Le calendrier a été créé avec succès !"); + + // TODO : reload URLS list + // TODO : show QrCode dialog + console.log(cal); + } catch (e) { + console.error("Failed to create new accommodation calendar URL!", e); + setError(`Échec de la création du calendrier! ${e}`); + } finally { + loading.hide(); + } + }; + + return ( + + + + URL de calendriers + + + Vous pouvez, si vous le souhaitez, importer dans votre application de + calendrier le planning de réservation des logement. Pour ce faire, il + vous suffit de créer une URL de calendrier. + + + + + + ); +} diff --git a/geneit_app/src/widgets/FamilyCard.tsx b/geneit_app/src/widgets/FamilyCard.tsx index c33f300..eb2bb7d 100644 --- a/geneit_app/src/widgets/FamilyCard.tsx +++ b/geneit_app/src/widgets/FamilyCard.tsx @@ -2,10 +2,14 @@ import { Alert, Card } from "@mui/material"; import { PropsWithChildren } from "react"; export function FamilyCard( - p: PropsWithChildren<{ error?: string; success?: string }> + p: PropsWithChildren<{ + error?: string; + success?: string; + style?: React.CSSProperties | undefined; + }> ): React.ReactElement { return ( - + {p.error && {p.error}} {p.success && {p.success}} diff --git a/geneit_app/src/widgets/accommodations/BaseAccommodationsRoute.tsx b/geneit_app/src/widgets/accommodations/BaseAccommodationsRoute.tsx index c543ac9..9516652 100644 --- a/geneit_app/src/widgets/accommodations/BaseAccommodationsRoute.tsx +++ b/geneit_app/src/widgets/accommodations/BaseAccommodationsRoute.tsx @@ -4,9 +4,10 @@ import { AccommodationListApi, AccommodationsList, } from "../../api/accommodations/AccommodationListApi"; +import { CreateAccommodationCalendarURLDialogProvider } from "../../hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider"; +import { UpdateAccommodationDialogProvider } from "../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider"; import { AsyncWidget } from "../AsyncWidget"; import { useFamily } from "../BaseFamilyRoute"; -import { UpdateAccommodationDialogProvider } from "../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider"; interface AccommodationsContext { accommodations: AccommodationsList; @@ -61,7 +62,9 @@ export function BaseAccommodationsRoute(): React.ReactElement { }} > - + + + ); diff --git a/geneit_app/src/widgets/forms/PropSelect.tsx b/geneit_app/src/widgets/forms/PropSelect.tsx index e329784..b09a94c 100644 --- a/geneit_app/src/widgets/forms/PropSelect.tsx +++ b/geneit_app/src/widgets/forms/PropSelect.tsx @@ -2,7 +2,7 @@ import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; import { PropEdit } from "./PropEdit"; export interface SelectOption { - value: string; + value: string | undefined; label: string; } @@ -19,6 +19,7 @@ export function PropSelect(p: { const value = p.options.find((o) => o.value === p.value)?.label; return ; } + return ( {p.label}