Can create accommodation from WebUI
This commit is contained in:
		@@ -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}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user