Add an accommodations reservations module #188
@@ -16,29 +16,31 @@ import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
 | 
			
		||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
			
		||||
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
 | 
			
		||||
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
 | 
			
		||||
import { FamilyHomeRoute } from "./routes/family/genealogy/FamilyHomeRoute";
 | 
			
		||||
import {
 | 
			
		||||
  FamilyCreateMemberRoute,
 | 
			
		||||
  FamilyEditMemberRoute,
 | 
			
		||||
  FamilyMemberRoute,
 | 
			
		||||
} from "./routes/family/genealogy/FamilyMemberRoute";
 | 
			
		||||
import { FamilySettingsRoute } from "./routes/family/FamilySettingsRoute";
 | 
			
		||||
import { FamilyUsersListRoute } from "./routes/family/FamilyUsersListRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
			
		||||
import { FamilyMembersListRoute } from "./routes/family/genealogy/FamilyMembersListRoute";
 | 
			
		||||
import { AccommodationsSettingsRoute } from "./routes/family/accommodations/AccommodationsSettingsRoute";
 | 
			
		||||
import {
 | 
			
		||||
  FamilyCoupleRoute,
 | 
			
		||||
  FamilyCreateCoupleRoute,
 | 
			
		||||
  FamilyEditCoupleRoute,
 | 
			
		||||
} from "./routes/family/genealogy/FamilyCoupleRoute";
 | 
			
		||||
import { FamilyCouplesListRoute } from "./routes/family/genealogy/FamilyCouplesListRoute";
 | 
			
		||||
import { FamilyTreeRoute } from "./routes/family/genealogy/FamilyTreeRoute";
 | 
			
		||||
import { FamilyHomeRoute } from "./routes/family/genealogy/FamilyHomeRoute";
 | 
			
		||||
import {
 | 
			
		||||
  FamilyCreateMemberRoute,
 | 
			
		||||
  FamilyEditMemberRoute,
 | 
			
		||||
  FamilyMemberRoute,
 | 
			
		||||
} from "./routes/family/genealogy/FamilyMemberRoute";
 | 
			
		||||
import { FamilyMemberTreeRoute } from "./routes/family/genealogy/FamilyMemberTreeRoute";
 | 
			
		||||
import { GenealogyHomeRoute } from "./routes/family/genealogy/GenealogyHomeRoute";
 | 
			
		||||
import { BaseGenealogyRoute } from "./widgets/genealogy/BaseGenealogyRoute";
 | 
			
		||||
import { FamilyMembersListRoute } from "./routes/family/genealogy/FamilyMembersListRoute";
 | 
			
		||||
import { FamilyTreeRoute } from "./routes/family/genealogy/FamilyTreeRoute";
 | 
			
		||||
import { GenalogySettingsRoute } from "./routes/family/genealogy/GenalogySettingsRoute";
 | 
			
		||||
import { GenealogyHomeRoute } from "./routes/family/genealogy/GenealogyHomeRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
			
		||||
import { BaseAccommodationsRoute } from "./widgets/accommodations/BaseAccommodationsRoute";
 | 
			
		||||
import { BaseGenealogyRoute } from "./widgets/genealogy/BaseGenealogyRoute";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -110,6 +112,17 @@ export function App(): React.ReactElement {
 | 
			
		||||
                <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
              </Route>
 | 
			
		||||
 | 
			
		||||
              <Route
 | 
			
		||||
                path="accommodations/*"
 | 
			
		||||
                element={<BaseAccommodationsRoute />}
 | 
			
		||||
              >
 | 
			
		||||
                <Route
 | 
			
		||||
                  path="settings"
 | 
			
		||||
                  element={<AccommodationsSettingsRoute />}
 | 
			
		||||
                />
 | 
			
		||||
                <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
              </Route>
 | 
			
		||||
 | 
			
		||||
              <Route path="settings" element={<FamilySettingsRoute />} />
 | 
			
		||||
              <Route path="users" element={<FamilyUsersListRoute />} />
 | 
			
		||||
              <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								geneit_app/src/api/accommodations/AccommodationListApi.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								geneit_app/src/api/accommodations/AccommodationListApi.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
import { APIClient } from "../ApiClient";
 | 
			
		||||
import { Family } from "../FamilyApi";
 | 
			
		||||
 | 
			
		||||
export interface Accommodation {
 | 
			
		||||
  id: number;
 | 
			
		||||
  family_id: number;
 | 
			
		||||
  time_create: number;
 | 
			
		||||
  time_update: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  need_validation: boolean;
 | 
			
		||||
  description: string;
 | 
			
		||||
  open_to_reservations: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AccommodationsList {
 | 
			
		||||
  private list: Accommodation[];
 | 
			
		||||
  private map: Map<number, Accommodation>;
 | 
			
		||||
 | 
			
		||||
  constructor(list: Accommodation[]) {
 | 
			
		||||
    this.list = list;
 | 
			
		||||
    this.map = new Map();
 | 
			
		||||
 | 
			
		||||
    for (const m of list) {
 | 
			
		||||
      this.map.set(m.id, m);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.list.sort((a, b) =>
 | 
			
		||||
      a.name.toLowerCase().localeCompare(b.name.toLocaleLowerCase())
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get isEmpty(): boolean {
 | 
			
		||||
    return this.list.length === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get size(): number {
 | 
			
		||||
    return this.list.length;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get fullList(): Accommodation[] {
 | 
			
		||||
    return this.list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filter(predicate: (m: Accommodation) => boolean): Accommodation[] {
 | 
			
		||||
    return this.list.filter(predicate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get(id: number): Accommodation | undefined {
 | 
			
		||||
    return this.map.get(id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AccommodationListApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the list of accommodation of a family
 | 
			
		||||
   */
 | 
			
		||||
  static async GetListOfFamily(family: Family): Promise<AccommodationsList> {
 | 
			
		||||
    const data = (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "GET",
 | 
			
		||||
        uri: `/family/${family.family_id}/accommodations/list/list`,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
 | 
			
		||||
    return new AccommodationsList(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
import { CardContent, Typography, Alert, Button } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useAlert } from "../../../hooks/context_providers/AlertDialogProvider";
 | 
			
		||||
import { useConfirm } from "../../../hooks/context_providers/ConfirmDialogProvider";
 | 
			
		||||
import { useLoadingMessage } from "../../../hooks/context_providers/LoadingMessageProvider";
 | 
			
		||||
import { useFamily } from "../../../widgets/BaseFamilyRoute";
 | 
			
		||||
import { FamilyCard } from "../../../widgets/FamilyCard";
 | 
			
		||||
import AddIcon from "@mui/icons-material/Add";
 | 
			
		||||
 | 
			
		||||
export function AccommodationsSettingsRoute(): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <AccommodationsListCard />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AccommodationsListCard(): React.ReactElement {
 | 
			
		||||
  const loading = useLoadingMessage();
 | 
			
		||||
  const confirm = useConfirm();
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
 | 
			
		||||
  const [error, setError] = React.useState<string>();
 | 
			
		||||
  const [success, setSuccess] = React.useState<string>();
 | 
			
		||||
 | 
			
		||||
  const createAccommodation = () => {};
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FamilyCard error={error} success={success}>
 | 
			
		||||
      <CardContent>
 | 
			
		||||
        <Typography gutterBottom variant="h5" component="div">
 | 
			
		||||
          Logements
 | 
			
		||||
        </Typography>
 | 
			
		||||
        <Button
 | 
			
		||||
          startIcon={<AddIcon />}
 | 
			
		||||
          variant="outlined"
 | 
			
		||||
          color="info"
 | 
			
		||||
          fullWidth
 | 
			
		||||
          onClick={createAccommodation}
 | 
			
		||||
          disabled={!family.family.is_admin}
 | 
			
		||||
          size={"large"}
 | 
			
		||||
        >
 | 
			
		||||
          Ajouter un nouveau logement
 | 
			
		||||
        </Button>
 | 
			
		||||
      </CardContent>
 | 
			
		||||
    </FamilyCard>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ import {
 | 
			
		||||
  mdiCrowd,
 | 
			
		||||
  mdiFamilyTree,
 | 
			
		||||
  mdiFileTree,
 | 
			
		||||
  mdiHomeGroup,
 | 
			
		||||
  mdiHumanMaleFemale,
 | 
			
		||||
  mdiLockCheck,
 | 
			
		||||
  mdiPlus,
 | 
			
		||||
@@ -207,6 +208,14 @@ export function BaseFamilyRoute(): React.ReactElement {
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
 | 
			
		||||
                {family?.enable_accommodations && (
 | 
			
		||||
                  <FamilyLink
 | 
			
		||||
                    icon={<Icon path={mdiHomeGroup} size={1} />}
 | 
			
		||||
                    label="Logements"
 | 
			
		||||
                    uri="accommodations/settings"
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
 | 
			
		||||
                {/* Invitation code */}
 | 
			
		||||
 | 
			
		||||
                <ListItem
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { Outlet } from "react-router-dom";
 | 
			
		||||
import {
 | 
			
		||||
  AccommodationListApi,
 | 
			
		||||
  AccommodationsList,
 | 
			
		||||
} from "../../api/accommodations/AccommodationListApi";
 | 
			
		||||
import { AsyncWidget } from "../AsyncWidget";
 | 
			
		||||
import { useFamily } from "../BaseFamilyRoute";
 | 
			
		||||
 | 
			
		||||
interface AccommodationsContext {
 | 
			
		||||
  accommodations: AccommodationsList;
 | 
			
		||||
  reloadAccommodationsList: () => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AccommodationsContextK =
 | 
			
		||||
  React.createContext<AccommodationsContext | null>(null);
 | 
			
		||||
 | 
			
		||||
export function BaseAccommodationsRoute(): React.ReactElement {
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
 | 
			
		||||
  const [accommodations, setAccommodations] =
 | 
			
		||||
    React.useState<null | AccommodationsList>(null);
 | 
			
		||||
 | 
			
		||||
  const loadKey = React.useRef(1);
 | 
			
		||||
 | 
			
		||||
  const loadPromise = React.useRef<() => void>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setAccommodations(
 | 
			
		||||
      await AccommodationListApi.GetListOfFamily(family.family)
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onReload = async () => {
 | 
			
		||||
    loadKey.current += 1;
 | 
			
		||||
    setAccommodations(null);
 | 
			
		||||
 | 
			
		||||
    return new Promise<void>((res, _rej) => {
 | 
			
		||||
      loadPromise.current = () => res();
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      ready={accommodations !== null}
 | 
			
		||||
      loadKey={`${family.familyId}-${loadKey.current}`}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Échec du chargement des informations sur les logements de la famille !"
 | 
			
		||||
      build={() => {
 | 
			
		||||
        if (loadPromise.current != null) {
 | 
			
		||||
          loadPromise.current?.();
 | 
			
		||||
          loadPromise.current = undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
          <AccommodationsContextK.Provider
 | 
			
		||||
            value={{
 | 
			
		||||
              accommodations: accommodations!,
 | 
			
		||||
              reloadAccommodationsList: onReload,
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Outlet />
 | 
			
		||||
          </AccommodationsContextK.Provider>
 | 
			
		||||
        );
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useAccommodations(): AccommodationsContext {
 | 
			
		||||
  return React.useContext(AccommodationsContextK)!;
 | 
			
		||||
}
 | 
			
		||||
@@ -5,14 +5,14 @@ import { MemberApi, MembersList } from "../../api/genealogy/MemberApi";
 | 
			
		||||
import { AsyncWidget } from "../AsyncWidget";
 | 
			
		||||
import { useFamily } from "../BaseFamilyRoute";
 | 
			
		||||
 | 
			
		||||
interface FamilyContext {
 | 
			
		||||
interface GenealogyContext {
 | 
			
		||||
  members: MembersList;
 | 
			
		||||
  couples: CouplesList;
 | 
			
		||||
  reloadMembersList: () => Promise<void>;
 | 
			
		||||
  reloadCouplesList: () => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const GenealogyContextK = React.createContext<FamilyContext | null>(null);
 | 
			
		||||
const GenealogyContextK = React.createContext<GenealogyContext | null>(null);
 | 
			
		||||
 | 
			
		||||
export function BaseGenealogyRoute(): React.ReactElement {
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
@@ -68,6 +68,6 @@ export function BaseGenealogyRoute(): React.ReactElement {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useGenealogy(): FamilyContext {
 | 
			
		||||
export function useGenealogy(): GenealogyContext {
 | 
			
		||||
  return React.useContext(GenealogyContextK)!;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user