All checks were successful
continuous-integration/drone/push Build is passing
Add a new module to enable accommodations reservation  Reviewed-on: #188
411 lines
12 KiB
TypeScript
411 lines
12 KiB
TypeScript
import AddIcon from "@mui/icons-material/Add";
|
|
import CheckIcon from "@mui/icons-material/Check";
|
|
import CloseIcon from "@mui/icons-material/Close";
|
|
import HouseIcon from "@mui/icons-material/House";
|
|
import {
|
|
Alert,
|
|
Button,
|
|
Card,
|
|
CardActions,
|
|
CardContent,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import React from "react";
|
|
import {
|
|
Accommodation,
|
|
AccommodationListApi,
|
|
} from "../../../api/accommodations/AccommodationListApi";
|
|
import {
|
|
AccommodationCalendarURL,
|
|
AccommodationsCalendarURLApi,
|
|
} from "../../../api/accommodations/AccommodationsCalendarURLApi";
|
|
import { useConfirm } from "../../../hooks/context_providers/ConfirmDialogProvider";
|
|
import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessageProvider";
|
|
import { useSnackbar } from "../../../hooks/context_providers/SnackbarProvider";
|
|
import { useCreateAccommodationCalendarURL } from "../../../hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider";
|
|
import { useInstallCalendarDialog } from "../../../hooks/context_providers/accommodations/InstallCalendarDialogProvider";
|
|
import { useUpdateAccommodation } from "../../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider";
|
|
import { AsyncWidget } from "../../../widgets/AsyncWidget";
|
|
import { useFamily } from "../../../widgets/BaseFamilyRoute";
|
|
import { FamilyCard } from "../../../widgets/FamilyCard";
|
|
import { TimeWidget } from "../../../widgets/TimeWidget";
|
|
import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute";
|
|
|
|
const CARDS_WIDTH = "500px";
|
|
|
|
export function AccommodationsSettingsRoute(): React.ReactElement {
|
|
return (
|
|
<>
|
|
<AccommodationsListCard />
|
|
<AccommodationsCalURLsCard />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function AccommodationsListCard(): React.ReactElement {
|
|
const loading = useLoadingMessage();
|
|
const confirm = useConfirm();
|
|
const snackbar = useSnackbar();
|
|
|
|
const family = useFamily();
|
|
const accommodations = useAccommodations();
|
|
|
|
const [error, setError] = React.useState<string>();
|
|
const [success, setSuccess] = React.useState<string>();
|
|
|
|
const updateAccommodation = useUpdateAccommodation();
|
|
|
|
const createAccommodation = async () => {
|
|
setError(undefined);
|
|
setSuccess(undefined);
|
|
try {
|
|
const accommodation = await updateAccommodation(
|
|
{
|
|
name: "",
|
|
open_to_reservations: true,
|
|
need_validation: false,
|
|
color: "2196f3",
|
|
},
|
|
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);
|
|
setError(`Échec de la création du logement! ${e}`);
|
|
} finally {
|
|
loading.hide();
|
|
}
|
|
};
|
|
|
|
const requestUpdateAccommodation = async (a: Accommodation) => {
|
|
setError(undefined);
|
|
setSuccess(undefined);
|
|
try {
|
|
const update = await updateAccommodation(a, false);
|
|
if (!update) return;
|
|
|
|
loading.show("Mise à jour du logement en cours...");
|
|
|
|
await AccommodationListApi.Update(a, update);
|
|
|
|
snackbar("Le logement a été créé avec succès !");
|
|
await accommodations.reloadAccommodationsList();
|
|
} catch (e) {
|
|
console.error("Failed to update accommodation!", e);
|
|
setError(`Échec de la mise à jour du logement! ${e}`);
|
|
} finally {
|
|
loading.hide();
|
|
}
|
|
};
|
|
|
|
const deleteAccommodation = async (a: Accommodation) => {
|
|
setError(undefined);
|
|
setSuccess(undefined);
|
|
try {
|
|
if (
|
|
!(await confirm(
|
|
`Voulez-vous vraiment supprimer le logement '${a.name}' ? Cette opération est définitive !`
|
|
))
|
|
)
|
|
return;
|
|
loading.show("Suppression du logement en cours...");
|
|
|
|
await AccommodationListApi.Delete(a);
|
|
|
|
snackbar("Le logement a été supprimé avec succès !");
|
|
await accommodations.reloadAccommodationsList();
|
|
} catch (e) {
|
|
console.error("Failed to delete accommodation!", e);
|
|
setError(`Échec de la suppression du logement! ${e}`);
|
|
} finally {
|
|
loading.hide();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<FamilyCard error={error} success={success} style={{ width: CARDS_WIDTH }}>
|
|
<CardContent>
|
|
<Typography gutterBottom variant="h5" component="div">
|
|
Logements
|
|
</Typography>
|
|
|
|
{/* Display the list of accommodations */}
|
|
{accommodations.accommodations.isEmpty && (
|
|
<div style={{ textAlign: "center", margin: "25px" }}>
|
|
Aucun logement enregistré pour le moment !
|
|
</div>
|
|
)}
|
|
{accommodations.accommodations.fullList.map((a) => (
|
|
<AccommodationCard
|
|
accommodation={a}
|
|
onRequestUpdate={requestUpdateAccommodation}
|
|
onRequestDelete={deleteAccommodation}
|
|
/>
|
|
))}
|
|
|
|
{family.family.is_admin && (
|
|
<Button
|
|
startIcon={<AddIcon />}
|
|
variant="outlined"
|
|
color="info"
|
|
fullWidth
|
|
onClick={createAccommodation}
|
|
size={"large"}
|
|
>
|
|
Ajouter un logement
|
|
</Button>
|
|
)}
|
|
</CardContent>
|
|
</FamilyCard>
|
|
);
|
|
}
|
|
|
|
function AccommodationCard(p: {
|
|
accommodation: Accommodation;
|
|
onRequestUpdate: (a: Accommodation) => void;
|
|
onRequestDelete: (a: Accommodation) => void;
|
|
}): React.ReactElement {
|
|
const family = useFamily();
|
|
return (
|
|
<Card sx={{ minWidth: 275, margin: "10px 0px" }} variant="outlined">
|
|
<CardContent>
|
|
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
|
|
Mis à jour il y a <TimeWidget time={p.accommodation.time_update} />
|
|
</Typography>
|
|
<Typography variant="h5" component="div">
|
|
<HouseIcon sx={{ color: "#" + p.accommodation.color }} />{" "}
|
|
{p.accommodation.name}
|
|
</Typography>
|
|
<Typography sx={{ mb: 1.5 }} color="text.secondary">
|
|
{p.accommodation.description}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
<BoolIcon checked={p.accommodation.open_to_reservations} /> Ouvert aux
|
|
réservations
|
|
<br />
|
|
<BoolIcon checked={!p.accommodation.need_validation} /> Réservation
|
|
sans validation d'un administrateur
|
|
</Typography>
|
|
</CardContent>
|
|
{family.family.is_admin && (
|
|
<CardActions>
|
|
<span style={{ flex: 1 }}></span>
|
|
<Button
|
|
size="small"
|
|
onClick={() => p.onRequestUpdate(p.accommodation)}
|
|
>
|
|
Modifier
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
color="error"
|
|
onClick={() => p.onRequestDelete(p.accommodation)}
|
|
>
|
|
Supprimer
|
|
</Button>
|
|
</CardActions>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function BoolIcon(p: { checked?: boolean }): React.ReactElement {
|
|
return p.checked ? (
|
|
<CheckIcon color="success" />
|
|
) : (
|
|
<CloseIcon color="error" />
|
|
);
|
|
}
|
|
|
|
function AccommodationsCalURLsCard(): React.ReactElement {
|
|
const key = React.useRef(0);
|
|
|
|
const confirm = useConfirm();
|
|
const loading = useLoadingMessage();
|
|
|
|
const [error, setError] = React.useState<string>();
|
|
const [success, setSuccess] = React.useState<string>();
|
|
|
|
const [list, setList] = React.useState<
|
|
AccommodationCalendarURL[] | undefined
|
|
>();
|
|
|
|
const family = useFamily();
|
|
|
|
const createCalendarURLDialog = useCreateAccommodationCalendarURL();
|
|
const calendarURLDialog = useInstallCalendarDialog();
|
|
|
|
const load = async () => {
|
|
setList(await AccommodationsCalendarURLApi.GetList(family.family));
|
|
};
|
|
|
|
const reload = () => {
|
|
key.current += 1;
|
|
setList(undefined);
|
|
};
|
|
|
|
const onRequestDelete = async (c: AccommodationCalendarURL) => {
|
|
setError(undefined);
|
|
setSuccess(undefined);
|
|
try {
|
|
if (
|
|
!(await confirm(
|
|
`Voulez-vous vraiment supprimer le calendrier '${c.name}' ? Cette opération est définitive !`
|
|
))
|
|
)
|
|
return;
|
|
|
|
loading.show("Suppression du calendrier en cours...");
|
|
|
|
await AccommodationsCalendarURLApi.Delete(c);
|
|
|
|
setSuccess("Le calendrier a été supprimé avec succès !");
|
|
reload();
|
|
} catch (e) {
|
|
console.error("Failed to delete accommodation!", e);
|
|
setError(`Échec de la suppression du logement! ${e}`);
|
|
} finally {
|
|
loading.hide();
|
|
}
|
|
};
|
|
|
|
const createCalendarURL = async () => {
|
|
try {
|
|
const newCal = await createCalendarURLDialog();
|
|
|
|
if (!newCal) return;
|
|
|
|
loading.show("Création du calendrier en cours...");
|
|
|
|
const cal = await AccommodationsCalendarURLApi.Create(
|
|
family.family,
|
|
newCal
|
|
);
|
|
|
|
setSuccess("Le calendrier a été créé avec succès !");
|
|
|
|
reload();
|
|
|
|
calendarURLDialog(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 (
|
|
<FamilyCard error={error} success={success} style={{ width: CARDS_WIDTH }}>
|
|
<CardContent>
|
|
<Typography gutterBottom variant="h5" component="div">
|
|
URL de calendriers
|
|
</Typography>
|
|
<Typography>
|
|
Vous pouvez, si vous le souhaitez, importer dans votre application de
|
|
calendrier le planning de réservation des logements. Pour ce faire, il
|
|
vous suffit de créer une URL de calendrier.
|
|
</Typography>
|
|
|
|
<Alert severity="info">
|
|
Les calendriers créés ici ne sont visible que par vous. Vous ne pouvez
|
|
pas manipuler les calendriers créés par les autres membres de la
|
|
famille.
|
|
</Alert>
|
|
|
|
<Button
|
|
startIcon={<AddIcon />}
|
|
variant="outlined"
|
|
color="info"
|
|
fullWidth
|
|
onClick={createCalendarURL}
|
|
size={"large"}
|
|
>
|
|
Créer un calendrier
|
|
</Button>
|
|
|
|
<br />
|
|
<br />
|
|
|
|
<AsyncWidget
|
|
ready={list !== undefined}
|
|
loadKey={key.current}
|
|
load={load}
|
|
errMsg="Echec du chargement de la liste des calendriers !"
|
|
build={() =>
|
|
list?.length === 0 ? (
|
|
<>
|
|
<p style={{ textAlign: "center" }}>
|
|
Vous n'avez créé aucun calendrier pour le moment !
|
|
</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
{list?.map((c) => (
|
|
<CalendarItem c={c} onRequestDelete={onRequestDelete} />
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
/>
|
|
</CardContent>
|
|
</FamilyCard>
|
|
);
|
|
}
|
|
|
|
function CalendarItem(p: {
|
|
c: AccommodationCalendarURL;
|
|
onRequestDelete: (c: AccommodationCalendarURL) => void;
|
|
}): React.ReactElement {
|
|
const accommodations = useAccommodations();
|
|
|
|
const installCal = useInstallCalendarDialog();
|
|
|
|
return (
|
|
<Card sx={{ minWidth: 275, margin: "10px 0px" }} variant="outlined">
|
|
<CardContent>
|
|
<Typography
|
|
sx={{ fontSize: 14 }}
|
|
color="text.secondary"
|
|
gutterBottom
|
|
></Typography>
|
|
<Typography variant="h5" component="div">
|
|
{p.c.name}
|
|
</Typography>
|
|
<Typography sx={{ mb: 1.5 }} color="text.secondary">
|
|
{p.c.accommodation_id
|
|
? accommodations.accommodations.get(p.c.accommodation_id)?.name
|
|
: "Tous les logements"}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
Créé il y a <TimeWidget time={p.c.time_create} />
|
|
<br />
|
|
Utilisé il y a <TimeWidget time={p.c.time_used} />
|
|
</Typography>
|
|
</CardContent>
|
|
|
|
<CardActions>
|
|
<span style={{ flex: 1 }}></span>
|
|
<Button size="small" onClick={() => installCal(p.c)}>
|
|
Installer
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
color="error"
|
|
onClick={() => p.onRequestDelete(p.c)}
|
|
>
|
|
Supprimer
|
|
</Button>
|
|
</CardActions>
|
|
</Card>
|
|
);
|
|
}
|