Add an accommodations reservations module #188
@@ -16,29 +16,31 @@ import { NewAccountRoute } from "./routes/auth/NewAccountRoute";
 | 
				
			|||||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
					import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
				
			||||||
import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
 | 
					import { PasswordForgottenRoute } from "./routes/auth/PasswordForgottenRoute";
 | 
				
			||||||
import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
 | 
					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 { FamilySettingsRoute } from "./routes/family/FamilySettingsRoute";
 | 
				
			||||||
import { FamilyUsersListRoute } from "./routes/family/FamilyUsersListRoute";
 | 
					import { FamilyUsersListRoute } from "./routes/family/FamilyUsersListRoute";
 | 
				
			||||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
					import { AccommodationsSettingsRoute } from "./routes/family/accommodations/AccommodationsSettingsRoute";
 | 
				
			||||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
					 | 
				
			||||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
					 | 
				
			||||||
import { FamilyMembersListRoute } from "./routes/family/genealogy/FamilyMembersListRoute";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  FamilyCoupleRoute,
 | 
					  FamilyCoupleRoute,
 | 
				
			||||||
  FamilyCreateCoupleRoute,
 | 
					  FamilyCreateCoupleRoute,
 | 
				
			||||||
  FamilyEditCoupleRoute,
 | 
					  FamilyEditCoupleRoute,
 | 
				
			||||||
} from "./routes/family/genealogy/FamilyCoupleRoute";
 | 
					} from "./routes/family/genealogy/FamilyCoupleRoute";
 | 
				
			||||||
import { FamilyCouplesListRoute } from "./routes/family/genealogy/FamilyCouplesListRoute";
 | 
					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 { FamilyMemberTreeRoute } from "./routes/family/genealogy/FamilyMemberTreeRoute";
 | 
				
			||||||
import { GenealogyHomeRoute } from "./routes/family/genealogy/GenealogyHomeRoute";
 | 
					import { FamilyMembersListRoute } from "./routes/family/genealogy/FamilyMembersListRoute";
 | 
				
			||||||
import { BaseGenealogyRoute } from "./widgets/genealogy/BaseGenealogyRoute";
 | 
					import { FamilyTreeRoute } from "./routes/family/genealogy/FamilyTreeRoute";
 | 
				
			||||||
import { GenalogySettingsRoute } from "./routes/family/genealogy/GenalogySettingsRoute";
 | 
					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 {
 | 
					interface AuthContext {
 | 
				
			||||||
  signedIn: boolean;
 | 
					  signedIn: boolean;
 | 
				
			||||||
@@ -110,6 +112,17 @@ export function App(): React.ReactElement {
 | 
				
			|||||||
                <Route path="*" element={<NotFoundRoute />} />
 | 
					                <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
              </Route>
 | 
					              </Route>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <Route
 | 
				
			||||||
 | 
					                path="accommodations/*"
 | 
				
			||||||
 | 
					                element={<BaseAccommodationsRoute />}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <Route
 | 
				
			||||||
 | 
					                  path="settings"
 | 
				
			||||||
 | 
					                  element={<AccommodationsSettingsRoute />}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
 | 
					              </Route>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <Route path="settings" element={<FamilySettingsRoute />} />
 | 
					              <Route path="settings" element={<FamilySettingsRoute />} />
 | 
				
			||||||
              <Route path="users" element={<FamilyUsersListRoute />} />
 | 
					              <Route path="users" element={<FamilyUsersListRoute />} />
 | 
				
			||||||
              <Route path="*" element={<NotFoundRoute />} />
 | 
					              <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,
 | 
					  mdiCrowd,
 | 
				
			||||||
  mdiFamilyTree,
 | 
					  mdiFamilyTree,
 | 
				
			||||||
  mdiFileTree,
 | 
					  mdiFileTree,
 | 
				
			||||||
 | 
					  mdiHomeGroup,
 | 
				
			||||||
  mdiHumanMaleFemale,
 | 
					  mdiHumanMaleFemale,
 | 
				
			||||||
  mdiLockCheck,
 | 
					  mdiLockCheck,
 | 
				
			||||||
  mdiPlus,
 | 
					  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 */}
 | 
					                {/* Invitation code */}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <ListItem
 | 
					                <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 { AsyncWidget } from "../AsyncWidget";
 | 
				
			||||||
import { useFamily } from "../BaseFamilyRoute";
 | 
					import { useFamily } from "../BaseFamilyRoute";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface FamilyContext {
 | 
					interface GenealogyContext {
 | 
				
			||||||
  members: MembersList;
 | 
					  members: MembersList;
 | 
				
			||||||
  couples: CouplesList;
 | 
					  couples: CouplesList;
 | 
				
			||||||
  reloadMembersList: () => Promise<void>;
 | 
					  reloadMembersList: () => Promise<void>;
 | 
				
			||||||
  reloadCouplesList: () => Promise<void>;
 | 
					  reloadCouplesList: () => Promise<void>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const GenealogyContextK = React.createContext<FamilyContext | null>(null);
 | 
					const GenealogyContextK = React.createContext<GenealogyContext | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BaseGenealogyRoute(): React.ReactElement {
 | 
					export function BaseGenealogyRoute(): React.ReactElement {
 | 
				
			||||||
  const family = useFamily();
 | 
					  const family = useFamily();
 | 
				
			||||||
@@ -68,6 +68,6 @@ export function BaseGenealogyRoute(): React.ReactElement {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useGenealogy(): FamilyContext {
 | 
					export function useGenealogy(): GenealogyContext {
 | 
				
			||||||
  return React.useContext(GenealogyContextK)!;
 | 
					  return React.useContext(GenealogyContextK)!;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user