Add an accommodations reservations module #188
24
geneit_app/package-lock.json
generated
24
geneit_app/package-lock.json
generated
@ -33,6 +33,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-easy-crop": "^5.0.7",
|
"react-easy-crop": "^5.0.7",
|
||||||
|
"react-qr-code": "^2.0.14",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"react-zoom-pan-pinch": "^3.4.4",
|
"react-zoom-pan-pinch": "^3.4.4",
|
||||||
"svg2pdf.js": "^2.2.3",
|
"svg2pdf.js": "^2.2.3",
|
||||||
@ -3483,6 +3484,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/qr.js": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="
|
||||||
|
},
|
||||||
"node_modules/raf": {
|
"node_modules/raf": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||||
@ -3533,6 +3539,24 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-qr-code": {
|
||||||
|
"version": "2.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.14.tgz",
|
||||||
|
"integrity": "sha512-xvAUqmXzFzf7X6aQAAKb6T02YYk9grBBFeqpp1MiVhUAKG3Rg9+hFiOKRYg4+rWc2MiXNxkri0ulAJgS12xh7Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"qr.js": "0.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native-svg": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-native-svg": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.2",
|
"version": "0.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-easy-crop": "^5.0.7",
|
"react-easy-crop": "^5.0.7",
|
||||||
|
"react-qr-code": "^2.0.14",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"react-zoom-pan-pinch": "^3.4.4",
|
"react-zoom-pan-pinch": "^3.4.4",
|
||||||
"svg2pdf.js": "^2.2.3",
|
"svg2pdf.js": "^2.2.3",
|
||||||
|
@ -33,4 +33,11 @@ export class AccommodationsCalendarURLApi {
|
|||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get accommodation calendar URL route
|
||||||
|
*/
|
||||||
|
static CalendarURL(c: AccommodationCalendarURL): string {
|
||||||
|
return `${APIClient.backendURL()}/acccommodations_calendar/${c.token}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
OutlinedInput,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
AccommodationCalendarURL,
|
||||||
|
AccommodationsCalendarURLApi,
|
||||||
|
} from "../../api/accommodations/AccommodationsCalendarURLApi";
|
||||||
|
import { VisibilityOff, Visibility } from "@mui/icons-material";
|
||||||
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||||
|
import { CopyToClipboard } from "../../widgets/CopyToClipboard";
|
||||||
|
import QRCode from "react-qr-code";
|
||||||
|
|
||||||
|
export function InstallCalendarDialog(p: {
|
||||||
|
cal?: AccommodationCalendarURL;
|
||||||
|
onClose: () => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
if (!p.cal) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onClose={p.onClose}>
|
||||||
|
<DialogTitle>Installation du calendrier</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
<Typography>
|
||||||
|
Afin d'installer le calendrier <i>{p.cal.name}</i> sur votre
|
||||||
|
appareil, veuillez utiliser l'URL suivante :
|
||||||
|
</Typography>
|
||||||
|
<br />
|
||||||
|
<FormControl fullWidth variant="outlined">
|
||||||
|
<InputLabel>URL</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
value={AccommodationsCalendarURLApi.CalendarURL(p.cal!)}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<CopyToClipboard
|
||||||
|
content={AccommodationsCalendarURLApi.CalendarURL(p.cal!)}
|
||||||
|
>
|
||||||
|
<IconButton>
|
||||||
|
<ContentCopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
label="Password"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: "20px auto",
|
||||||
|
padding: "20px",
|
||||||
|
backgroundColor: "white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QRCode
|
||||||
|
value={AccommodationsCalendarURLApi.CalendarURL(p.cal!)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={p.onClose}>Fermer</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { PropsWithChildren } from "react";
|
||||||
|
import { AccommodationCalendarURL } from "../../../api/accommodations/AccommodationsCalendarURLApi";
|
||||||
|
import { InstallCalendarDialog } from "../../../dialogs/accommodations/InstallCalendarDialog";
|
||||||
|
|
||||||
|
type DialogContext = (cal: AccommodationCalendarURL) => Promise<void>;
|
||||||
|
|
||||||
|
const DialogContextK = React.createContext<DialogContext | null>(null);
|
||||||
|
|
||||||
|
export function InstallCalendarDialogProvider(
|
||||||
|
p: PropsWithChildren
|
||||||
|
): React.ReactElement {
|
||||||
|
const [cal, setCal] = React.useState<AccommodationCalendarURL | undefined>();
|
||||||
|
|
||||||
|
const cb = React.useRef<null | (() => void)>(null);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setCal(undefined);
|
||||||
|
|
||||||
|
if (cb.current !== null) cb.current();
|
||||||
|
cb.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hook: DialogContext = (c) => {
|
||||||
|
setCal(c);
|
||||||
|
|
||||||
|
return new Promise((res) => {
|
||||||
|
cb.current = res;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContextK.Provider value={hook}>
|
||||||
|
{p.children}
|
||||||
|
</DialogContextK.Provider>
|
||||||
|
|
||||||
|
{cal && <InstallCalendarDialog cal={cal} onClose={handleClose} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInstallCalendarDialog(): DialogContext {
|
||||||
|
return React.useContext(DialogContextK)!;
|
||||||
|
}
|
@ -24,6 +24,8 @@ import { TimeWidget } from "../../../widgets/TimeWidget";
|
|||||||
import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute";
|
import { useAccommodations } from "../../../widgets/accommodations/BaseAccommodationsRoute";
|
||||||
import { useCreateAccommodationCalendarURL } from "../../../hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider";
|
import { useCreateAccommodationCalendarURL } from "../../../hooks/context_providers/accommodations/CreateAccommodationCalendarURLDialogProvider";
|
||||||
import { AccommodationsCalendarURLApi } from "../../../api/accommodations/AccommodationsCalendarURLApi";
|
import { AccommodationsCalendarURLApi } from "../../../api/accommodations/AccommodationsCalendarURLApi";
|
||||||
|
import { useInstallCalendarDialog } from "../../../hooks/context_providers/accommodations/InstallCalendarDialogProvider";
|
||||||
|
import { InstallCalendarDialog } from "../../../dialogs/accommodations/InstallCalendarDialog";
|
||||||
|
|
||||||
export function AccommodationsSettingsRoute(): React.ReactElement {
|
export function AccommodationsSettingsRoute(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
@ -224,6 +226,7 @@ function AccommodationsCalURLsCard(): React.ReactElement {
|
|||||||
const family = useFamily();
|
const family = useFamily();
|
||||||
|
|
||||||
const createCalendarURLDialog = useCreateAccommodationCalendarURL();
|
const createCalendarURLDialog = useCreateAccommodationCalendarURL();
|
||||||
|
const calendarURLDialog = useInstallCalendarDialog();
|
||||||
|
|
||||||
const createCalendarURL = async () => {
|
const createCalendarURL = async () => {
|
||||||
try {
|
try {
|
||||||
@ -241,8 +244,8 @@ function AccommodationsCalURLsCard(): React.ReactElement {
|
|||||||
setSuccess("Le calendrier a été créé avec succès !");
|
setSuccess("Le calendrier a été créé avec succès !");
|
||||||
|
|
||||||
// TODO : reload URLS list
|
// TODO : reload URLS list
|
||||||
// TODO : show QrCode dialog
|
|
||||||
console.log(cal);
|
calendarURLDialog(cal);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to create new accommodation calendar URL!", e);
|
console.error("Failed to create new accommodation calendar URL!", e);
|
||||||
setError(`Échec de la création du calendrier! ${e}`);
|
setError(`Échec de la création du calendrier! ${e}`);
|
||||||
|
30
geneit_app/src/widgets/CopyToClipboard.tsx
Normal file
30
geneit_app/src/widgets/CopyToClipboard.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ButtonBase } from "@mui/material";
|
||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
|
||||||
|
|
||||||
|
export function CopyToClipboard(
|
||||||
|
p: PropsWithChildren<{ content: string }>
|
||||||
|
): React.ReactElement {
|
||||||
|
const snackbar = useSnackbar();
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard.writeText(p.content);
|
||||||
|
snackbar(`${p.content} copied to clipboard.`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonBase
|
||||||
|
onClick={copy}
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
alignItems: "unset",
|
||||||
|
textAlign: "unset",
|
||||||
|
position: "relative",
|
||||||
|
padding: "0px",
|
||||||
|
}}
|
||||||
|
disableRipple
|
||||||
|
>
|
||||||
|
{p.children}
|
||||||
|
</ButtonBase>
|
||||||
|
);
|
||||||
|
}
|
@ -8,6 +8,7 @@ import { CreateAccommodationCalendarURLDialogProvider } from "../../hooks/contex
|
|||||||
import { UpdateAccommodationDialogProvider } from "../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider";
|
import { UpdateAccommodationDialogProvider } from "../../hooks/context_providers/accommodations/UpdateAccommodationDialogProvider";
|
||||||
import { AsyncWidget } from "../AsyncWidget";
|
import { AsyncWidget } from "../AsyncWidget";
|
||||||
import { useFamily } from "../BaseFamilyRoute";
|
import { useFamily } from "../BaseFamilyRoute";
|
||||||
|
import { InstallCalendarDialogProvider } from "../../hooks/context_providers/accommodations/InstallCalendarDialogProvider";
|
||||||
|
|
||||||
interface AccommodationsContext {
|
interface AccommodationsContext {
|
||||||
accommodations: AccommodationsList;
|
accommodations: AccommodationsList;
|
||||||
@ -63,7 +64,9 @@ export function BaseAccommodationsRoute(): React.ReactElement {
|
|||||||
>
|
>
|
||||||
<UpdateAccommodationDialogProvider>
|
<UpdateAccommodationDialogProvider>
|
||||||
<CreateAccommodationCalendarURLDialogProvider>
|
<CreateAccommodationCalendarURLDialogProvider>
|
||||||
<Outlet />
|
<InstallCalendarDialogProvider>
|
||||||
|
<Outlet />
|
||||||
|
</InstallCalendarDialogProvider>
|
||||||
</CreateAccommodationCalendarURLDialogProvider>
|
</CreateAccommodationCalendarURLDialogProvider>
|
||||||
</UpdateAccommodationDialogProvider>
|
</UpdateAccommodationDialogProvider>
|
||||||
</AccommodationsContextK.Provider>
|
</AccommodationsContextK.Provider>
|
||||||
|
Loading…
Reference in New Issue
Block a user