Add an accommodations reservations module #188
@ -50,6 +50,13 @@ export class AccommodationsList {
|
||||
}
|
||||
}
|
||||
|
||||
export interface UpdateAccommodation {
|
||||
name: string;
|
||||
need_validation: boolean;
|
||||
description?: string;
|
||||
open_to_reservations: boolean;
|
||||
}
|
||||
|
||||
export class AccommodationListApi {
|
||||
/**
|
||||
* Get the list of accommodation of a family
|
||||
@ -64,4 +71,20 @@ export class AccommodationListApi {
|
||||
|
||||
return new AccommodationsList(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new accommodation
|
||||
*/
|
||||
static async Create(
|
||||
family: Family,
|
||||
accommodation: UpdateAccommodation
|
||||
): Promise<Accommodation> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
method: "POST",
|
||||
uri: `/family/${family.family_id}/accommodations/list/create`,
|
||||
jsonData: accommodation,
|
||||
})
|
||||
).data;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,140 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { ServerApi } from "../../api/ServerApi";
|
||||
import { UpdateAccommodation } from "../../api/accommodations/AccommodationListApi";
|
||||
import { checkConstraint } from "../../utils/from_utils";
|
||||
import { PropCheckbox } from "../../widgets/forms/PropCheckbox";
|
||||
import { PropEdit } from "../../widgets/forms/PropEdit";
|
||||
|
||||
export function UpdateAccommodationDialog(p: {
|
||||
open: boolean;
|
||||
create: boolean;
|
||||
onClose: () => void;
|
||||
onSubmitted: (c: UpdateAccommodation) => void;
|
||||
accommodation: UpdateAccommodation | undefined;
|
||||
}): React.ReactElement {
|
||||
const [accommodation, setAccommodation] = React.useState<
|
||||
UpdateAccommodation | undefined
|
||||
>();
|
||||
|
||||
const nameErr = checkConstraint(
|
||||
ServerApi.Config.constraints.accommodation_name_len,
|
||||
accommodation?.name
|
||||
);
|
||||
const descriptionErr = checkConstraint(
|
||||
ServerApi.Config.constraints.accommodation_description_len,
|
||||
accommodation?.description
|
||||
);
|
||||
|
||||
const clearForm = () => {
|
||||
setAccommodation(undefined);
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
clearForm();
|
||||
p.onClose();
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
clearForm();
|
||||
p.onSubmitted(accommodation!);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!accommodation) setAccommodation(p.accommodation);
|
||||
}, [p.open, p.accommodation]);
|
||||
|
||||
return (
|
||||
<Dialog open={p.open} onClose={cancel}>
|
||||
<DialogTitle>
|
||||
{p.create ? "Création" : "Mise à jour"} d'un logement
|
||||
</DialogTitle>
|
||||
<DialogContent style={{ display: "flex", flexDirection: "column" }}>
|
||||
<PropEdit
|
||||
editable
|
||||
label="Nom"
|
||||
value={accommodation?.name}
|
||||
onValueChange={(s) =>
|
||||
setAccommodation((a) => {
|
||||
return {
|
||||
...a!,
|
||||
name: s!,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.accommodation_name_len}
|
||||
helperText={nameErr}
|
||||
/>
|
||||
|
||||
<PropEdit
|
||||
editable
|
||||
label="Description"
|
||||
value={accommodation?.description}
|
||||
onValueChange={(s) =>
|
||||
setAccommodation((a) => {
|
||||
return {
|
||||
...a!,
|
||||
description: s!,
|
||||
};
|
||||
})
|
||||
}
|
||||
size={ServerApi.Config.constraints.accommodation_description_len}
|
||||
helperText={descriptionErr}
|
||||
/>
|
||||
|
||||
<PropCheckbox
|
||||
editable
|
||||
label="Ouvert aux réservations"
|
||||
checked={accommodation?.open_to_reservations === true}
|
||||
onValueChange={(c) =>
|
||||
setAccommodation((a) => {
|
||||
return {
|
||||
...a!,
|
||||
open_to_reservations: c,
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
"Permet de spécifier si un administrateur de la famille doit valider manuellement les demandes de réservation pour qu'elles soient validées"
|
||||
}
|
||||
>
|
||||
<PropCheckbox
|
||||
checkboxAlwaysVisible
|
||||
editable={accommodation?.open_to_reservations === true}
|
||||
label="Validation des réservations requise"
|
||||
checked={accommodation?.need_validation === true}
|
||||
onValueChange={(c) =>
|
||||
setAccommodation((a) => {
|
||||
return {
|
||||
...a!,
|
||||
need_validation: c,
|
||||
};
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={cancel}>Annuler</Button>
|
||||
<Button
|
||||
onClick={submit}
|
||||
disabled={
|
||||
!!nameErr || (!!accommodation?.description && !!descriptionErr)
|
||||
}
|
||||
>
|
||||
{p.create ? "Créer" : "Mettre à jour"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { UpdateAccommodation } from "../../../api/accommodations/AccommodationListApi";
|
||||
import { UpdateAccommodationDialog } from "../../../dialogs/accommodations/UpdateAccommodationDialog";
|
||||
|
||||
type DialogContext = (
|
||||
accommodation: UpdateAccommodation,
|
||||
create: boolean
|
||||
) => Promise<UpdateAccommodation | undefined>;
|
||||
|
||||
const DialogContextK = React.createContext<DialogContext | null>(null);
|
||||
|
||||
export function UpdateAccommodationDialogProvider(
|
||||
p: PropsWithChildren
|
||||
): React.ReactElement {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const [accommodation, setAccommodation] = React.useState<
|
||||
UpdateAccommodation | undefined
|
||||
>(undefined);
|
||||
const [create, setCreate] = React.useState(false);
|
||||
|
||||
const cb = React.useRef<
|
||||
null | ((a: UpdateAccommodation | undefined) => void)
|
||||
>(null);
|
||||
|
||||
const handleClose = (res?: UpdateAccommodation) => {
|
||||
setOpen(false);
|
||||
|
||||
if (cb.current !== null) cb.current(res);
|
||||
cb.current = null;
|
||||
};
|
||||
|
||||
const hook: DialogContext = (accommodation, create) => {
|
||||
setAccommodation(accommodation);
|
||||
setCreate(create);
|
||||
setOpen(true);
|
||||
|
||||
return new Promise((res) => {
|
||||
cb.current = res;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContextK.Provider value={hook}>
|
||||
{p.children}
|
||||
</DialogContextK.Provider>
|
||||
|
||||
{open && (
|
||||
<UpdateAccommodationDialog
|
||||
open={open}
|
||||
accommodation={accommodation}
|
||||
create={create}
|
||||
onClose={handleClose}
|
||||
onSubmitted={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUpdateAccommodation(): DialogContext {
|
||||
return React.useContext(DialogContextK)!;
|
||||
}
|
@ -6,6 +6,10 @@ import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessa
|
||||
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 { AccommodationListApi } from "../../../api/accommodations/AccommodationListApi";
|
||||
import { useSnackbar } from "../../../hooks/context_providers/SnackbarProvider";
|
||||
import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute";
|
||||
|
||||
export function AccommodationsSettingsRoute(): React.ReactElement {
|
||||
return (
|
||||
@ -19,13 +23,43 @@ function AccommodationsListCard(): React.ReactElement {
|
||||
const loading = useLoadingMessage();
|
||||
const confirm = useConfirm();
|
||||
const alert = useAlert();
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const family = useFamily();
|
||||
const accommodations = useAccommodations();
|
||||
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [success, setSuccess] = React.useState<string>();
|
||||
|
||||
const createAccommodation = () => {};
|
||||
const updateAccommodation = useUpdateAccommodation();
|
||||
|
||||
const createAccommodation = async () => {
|
||||
try {
|
||||
const accommodation = await updateAccommodation(
|
||||
{
|
||||
name: "",
|
||||
open_to_reservations: true,
|
||||
need_validation: false,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (!accommodation) return;
|
||||
|
||||
loading.show("Création du logement en cours...");
|
||||
|
||||
await AccommodationListApi.Create(family.family, accommodation);
|
||||
|
||||
snackbar("Le logement a été créé avec succès !");
|
||||
|
||||
await accommodations.reloadAccommodationsList();
|
||||
} catch (e) {
|
||||
console.error("Failed to create accommodation!", e);
|
||||
alert(`Echec de la création du logement! ${e}`);
|
||||
} finally {
|
||||
loading.hide();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FamilyCard error={error} success={success}>
|
||||
|
21
geneit_app/src/utils/from_utils.ts
Normal file
21
geneit_app/src/utils/from_utils.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { LenConstraint } from "../api/ServerApi";
|
||||
|
||||
/**
|
||||
* Check if a constraint was respected or not
|
||||
*
|
||||
* @returns An error message appropriate for the constraint
|
||||
* violation, if any, or undefined otherwise
|
||||
*/
|
||||
export function checkConstraint(
|
||||
constraint: LenConstraint,
|
||||
value: string | undefined
|
||||
): string | undefined {
|
||||
value = value ?? "";
|
||||
if (value.length < constraint.min)
|
||||
return `Veuillez indiquer au moins ${constraint.min} caractères !`;
|
||||
|
||||
if (value.length > constraint.max)
|
||||
return `Veuillez indiquer au maximum ${constraint.min} caractères !`;
|
||||
|
||||
return undefined;
|
||||
}
|
@ -6,6 +6,7 @@ import {
|
||||
} from "../../api/accommodations/AccommodationListApi";
|
||||
import { AsyncWidget } from "../AsyncWidget";
|
||||
import { useFamily } from "../BaseFamilyRoute";
|
||||
import { UpdateAccommodationDialogProvider } from "../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider";
|
||||
|
||||
interface AccommodationsContext {
|
||||
accommodations: AccommodationsList;
|
||||
@ -59,7 +60,9 @@ export function BaseAccommodationsRoute(): React.ReactElement {
|
||||
reloadAccommodationsList: onReload,
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
<UpdateAccommodationDialogProvider>
|
||||
<Outlet />
|
||||
</UpdateAccommodationDialogProvider>
|
||||
</AccommodationsContextK.Provider>
|
||||
);
|
||||
}}
|
||||
|
@ -5,16 +5,20 @@ export function PropCheckbox(p: {
|
||||
label: string;
|
||||
checked: boolean | undefined;
|
||||
onValueChange: (v: boolean) => void;
|
||||
checkboxAlwaysVisible?: boolean;
|
||||
}): React.ReactElement {
|
||||
if (!p.editable && p.checked)
|
||||
return <Typography variant="body2">{p.label}</Typography>;
|
||||
if (!p.checkboxAlwaysVisible) {
|
||||
if (!p.editable && p.checked)
|
||||
return <Typography variant="body2">{p.label}</Typography>;
|
||||
|
||||
if (!p.editable) return <></>;
|
||||
if (!p.editable) return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={!p.editable}
|
||||
checked={p.checked}
|
||||
onChange={(e) => p.onValueChange(e.target.checked)}
|
||||
/>
|
||||
|
@ -14,6 +14,7 @@ export function PropEdit(p: {
|
||||
multiline?: boolean;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
helperText?: string;
|
||||
}): React.ReactElement {
|
||||
if (((!p.editable && p.value) ?? "") === "") return <></>;
|
||||
|
||||
@ -44,6 +45,7 @@ export function PropEdit(p: {
|
||||
!p.checkValue(p.value)) ||
|
||||
false
|
||||
}
|
||||
helperText={p.helperText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user