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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user