Create basic couple route
This commit is contained in:
		@@ -28,6 +28,11 @@ import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
			
		||||
import { FamilyMembersListRoute } from "./routes/family/FamilyMembersListRoute";
 | 
			
		||||
import {
 | 
			
		||||
  FamilyCoupleRoute,
 | 
			
		||||
  FamilyCreateCoupleRoute,
 | 
			
		||||
  FamilyEditCoupleRoute,
 | 
			
		||||
} from "./routes/family/FamilyCoupleRoute";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -58,6 +63,7 @@ export function App(): React.ReactElement {
 | 
			
		||||
            <Route path="profile" element={<ProfileRoute />} />
 | 
			
		||||
            <Route path="family/:familyId/*" element={<BaseFamilyRoute />}>
 | 
			
		||||
              <Route path="" element={<FamilyHomeRoute />} />
 | 
			
		||||
 | 
			
		||||
              <Route path="members" element={<FamilyMembersListRoute />} />
 | 
			
		||||
              <Route
 | 
			
		||||
                path="member/create"
 | 
			
		||||
@@ -68,6 +74,17 @@ export function App(): React.ReactElement {
 | 
			
		||||
                path="member/:memberId/edit"
 | 
			
		||||
                element={<FamilyEditMemberRoute />}
 | 
			
		||||
              />
 | 
			
		||||
 | 
			
		||||
              <Route
 | 
			
		||||
                path="couple/create"
 | 
			
		||||
                element={<FamilyCreateCoupleRoute />}
 | 
			
		||||
              />
 | 
			
		||||
              <Route path="couple/:coupleId" element={<FamilyCoupleRoute />} />
 | 
			
		||||
              <Route
 | 
			
		||||
                path="couple/:coupleId/edit"
 | 
			
		||||
                element={<FamilyEditCoupleRoute />}
 | 
			
		||||
              />
 | 
			
		||||
 | 
			
		||||
              <Route path="settings" element={<FamilySettingsRoute />} />
 | 
			
		||||
              <Route path="users" element={<FamilyUsersListRoute />} />
 | 
			
		||||
              <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										195
									
								
								geneit_app/src/api/CoupleApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								geneit_app/src/api/CoupleApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
 | 
			
		||||
interface CoupleApiInterface {
 | 
			
		||||
  id: number;
 | 
			
		||||
  family_id: number;
 | 
			
		||||
  wife?: number;
 | 
			
		||||
  husband?: number;
 | 
			
		||||
  state?: string;
 | 
			
		||||
  photo_id?: string;
 | 
			
		||||
  signed_photo_id?: string;
 | 
			
		||||
  time_create?: number;
 | 
			
		||||
  time_update?: number;
 | 
			
		||||
  wedding_year?: number;
 | 
			
		||||
  wedding_month?: number;
 | 
			
		||||
  wedding_day?: number;
 | 
			
		||||
  divorce_year?: number;
 | 
			
		||||
  divorce_month?: number;
 | 
			
		||||
  divorce_day?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Couple implements CoupleApiInterface {
 | 
			
		||||
  id: number;
 | 
			
		||||
  family_id: number;
 | 
			
		||||
  wife?: number;
 | 
			
		||||
  husband?: number;
 | 
			
		||||
  state?: string;
 | 
			
		||||
  photo_id?: string;
 | 
			
		||||
  signed_photo_id?: string;
 | 
			
		||||
  time_create?: number;
 | 
			
		||||
  time_update?: number;
 | 
			
		||||
  wedding_year?: number;
 | 
			
		||||
  wedding_month?: number;
 | 
			
		||||
  wedding_day?: number;
 | 
			
		||||
  divorce_year?: number;
 | 
			
		||||
  divorce_month?: number;
 | 
			
		||||
  divorce_day?: number;
 | 
			
		||||
 | 
			
		||||
  constructor(int: CoupleApiInterface) {
 | 
			
		||||
    this.id = int.id;
 | 
			
		||||
    this.family_id = int.family_id;
 | 
			
		||||
    this.wife = int.wife;
 | 
			
		||||
    this.husband = int.husband;
 | 
			
		||||
    this.state = int.state;
 | 
			
		||||
    this.photo_id = int.photo_id;
 | 
			
		||||
    this.signed_photo_id = int.signed_photo_id;
 | 
			
		||||
    this.time_create = int.time_create;
 | 
			
		||||
    this.time_update = int.time_update;
 | 
			
		||||
    this.wedding_year = int.wedding_year;
 | 
			
		||||
    this.wedding_month = int.wedding_month;
 | 
			
		||||
    this.wedding_day = int.wedding_day;
 | 
			
		||||
    this.divorce_year = int.divorce_year;
 | 
			
		||||
    this.divorce_month = int.divorce_month;
 | 
			
		||||
    this.divorce_day = int.divorce_day;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create an empty couple object
 | 
			
		||||
   */
 | 
			
		||||
  static New(family_id: number): Couple {
 | 
			
		||||
    return new Couple({
 | 
			
		||||
      id: 0,
 | 
			
		||||
      family_id: family_id,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get hasPhoto(): boolean {
 | 
			
		||||
    return this.photo_id !== null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get photoURL(): string | null {
 | 
			
		||||
    if (!this.signed_photo_id) return null;
 | 
			
		||||
    return `${APIClient.backendURL()}/photo/${this.signed_photo_id}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get thumbnailURL(): string | null {
 | 
			
		||||
    if (!this.signed_photo_id) return null;
 | 
			
		||||
    return `${APIClient.backendURL()}/photo/${this.signed_photo_id}/thumbnail`;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CouplesList {
 | 
			
		||||
  private list: Couple[];
 | 
			
		||||
  private map: Map<number, Couple>;
 | 
			
		||||
 | 
			
		||||
  constructor(list: Couple[]) {
 | 
			
		||||
    this.list = list;
 | 
			
		||||
    this.map = new Map();
 | 
			
		||||
 | 
			
		||||
    for (const m of list) {
 | 
			
		||||
      this.map.set(m.id, m);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get isEmpty(): boolean {
 | 
			
		||||
    return this.list.length === 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get fullList(): Couple[] {
 | 
			
		||||
    return this.list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filter(predicate: (m: Couple) => boolean): Couple[] {
 | 
			
		||||
    return this.list.filter(predicate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get(id: number): Couple | undefined {
 | 
			
		||||
    return this.map.get(id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CoupleApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a new couple
 | 
			
		||||
   */
 | 
			
		||||
  static async Create(m: Couple): Promise<Couple> {
 | 
			
		||||
    const res = await APIClient.exec({
 | 
			
		||||
      uri: `/family/${m.family_id}/couple/create`,
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      jsonData: m,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return new Couple(res.data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the information about a single couple
 | 
			
		||||
   */
 | 
			
		||||
  static async GetSingle(
 | 
			
		||||
    family_id: number,
 | 
			
		||||
    couple_id: number
 | 
			
		||||
  ): Promise<Couple> {
 | 
			
		||||
    const res = await APIClient.exec({
 | 
			
		||||
      uri: `/family/${family_id}/couple/${couple_id}`,
 | 
			
		||||
      method: "GET",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return new Couple(res.data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the entire list of couples of a family
 | 
			
		||||
   */
 | 
			
		||||
  static async GetEntireList(family_id: number): Promise<CouplesList> {
 | 
			
		||||
    const res = await APIClient.exec({
 | 
			
		||||
      uri: `/family/${family_id}/couples`,
 | 
			
		||||
      method: "GET",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return new CouplesList(res.data.map((d: any) => new Couple(d)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update a couple information
 | 
			
		||||
   */
 | 
			
		||||
  static async Update(m: Couple): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      uri: `/family/${m.family_id}/couple/${m.id}`,
 | 
			
		||||
      method: "PUT",
 | 
			
		||||
      jsonData: m,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set a new photo for a couple
 | 
			
		||||
   */
 | 
			
		||||
  static async SetCouplePhoto(m: Couple, b: Blob): Promise<void> {
 | 
			
		||||
    const fd = new FormData();
 | 
			
		||||
    fd.append("photo", b);
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      uri: `/family/${m.family_id}/couple/${m.id}/photo`,
 | 
			
		||||
      method: "PUT",
 | 
			
		||||
      formData: fd,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove the photo of a couple
 | 
			
		||||
   */
 | 
			
		||||
  static async RemoveCouplePhoto(m: Couple): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      uri: `/family/${m.family_id}/couple/${m.id}/photo`,
 | 
			
		||||
      method: "DELETE",
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete a family couple
 | 
			
		||||
   */
 | 
			
		||||
  static async Delete(m: Couple): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      uri: `/family/${m.family_id}/couple/${m.id}`,
 | 
			
		||||
      method: "DELETE",
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
import { Couple } from "./CoupleApi";
 | 
			
		||||
import { Member } from "./MemberApi";
 | 
			
		||||
 | 
			
		||||
interface FamilyAPI {
 | 
			
		||||
@@ -62,6 +63,15 @@ export class Family implements FamilyAPI {
 | 
			
		||||
      `/family/${this.family_id}/member/${member.id}` + (edit ? "/edit" : "")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get application URL for couple page
 | 
			
		||||
   */
 | 
			
		||||
  coupleURL(member: Couple, edit?: boolean): string {
 | 
			
		||||
    return (
 | 
			
		||||
      `/family/${this.family_id}/couple/${member.id}` + (edit ? "/edit" : "")
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum JoinFamilyResult {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										425
									
								
								geneit_app/src/routes/family/FamilyCoupleRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								geneit_app/src/routes/family/FamilyCoupleRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,425 @@
 | 
			
		||||
import ClearIcon from "@mui/icons-material/Clear";
 | 
			
		||||
import DeleteIcon from "@mui/icons-material/Delete";
 | 
			
		||||
import EditIcon from "@mui/icons-material/Edit";
 | 
			
		||||
import FileDownloadIcon from "@mui/icons-material/FileDownload";
 | 
			
		||||
import SaveIcon from "@mui/icons-material/Save";
 | 
			
		||||
import { Button, Grid, Stack } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import { Couple, CoupleApi } from "../../api/CoupleApi";
 | 
			
		||||
import { Member } from "../../api/MemberApi";
 | 
			
		||||
import { useAlert } from "../../hooks/context_providers/AlertDialogProvider";
 | 
			
		||||
import { useConfirm } from "../../hooks/context_providers/ConfirmDialogProvider";
 | 
			
		||||
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
			
		||||
import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
			
		||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
			
		||||
import { ConfirmLeaveWithoutSaveDialog } from "../../widgets/ConfirmLeaveWithoutSaveDialog";
 | 
			
		||||
import { CouplePhoto } from "../../widgets/CouplePhoto";
 | 
			
		||||
import { FamilyPageTitle } from "../../widgets/FamilyPageTitle";
 | 
			
		||||
import { MemberItem } from "../../widgets/MemberItem";
 | 
			
		||||
import { PropertiesBox } from "../../widgets/PropertiesBox";
 | 
			
		||||
import { RouterLink } from "../../widgets/RouterLink";
 | 
			
		||||
import { MemberInput } from "../../widgets/forms/MemberInput";
 | 
			
		||||
import { UploadPhotoButton } from "../../widgets/forms/UploadPhotoButton";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create a new couple route
 | 
			
		||||
 */
 | 
			
		||||
export function FamilyCreateCoupleRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [shouldQuit, setShouldQuit] = React.useState(false);
 | 
			
		||||
  const n = useNavigate();
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
 | 
			
		||||
  const create = async (m: Couple) => {
 | 
			
		||||
    try {
 | 
			
		||||
      const r = await CoupleApi.Create(m);
 | 
			
		||||
 | 
			
		||||
      await family.reloadCouplesList();
 | 
			
		||||
 | 
			
		||||
      setShouldQuit(true);
 | 
			
		||||
      n(family.family.coupleURL(r));
 | 
			
		||||
      snackbar(`La fiche pour le couple a été créée avec succès !`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Echec de la création du couple !");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const cancel = () => {
 | 
			
		||||
    setShouldQuit(true);
 | 
			
		||||
    n(family.family.URL("couples"));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CouplePage
 | 
			
		||||
      couple={Couple.New(family.family.family_id)}
 | 
			
		||||
      creating={true}
 | 
			
		||||
      editing={true}
 | 
			
		||||
      onCancel={cancel}
 | 
			
		||||
      onSave={create}
 | 
			
		||||
      shouldAllowLeaving={shouldQuit}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get existing couple route
 | 
			
		||||
 */
 | 
			
		||||
export function FamilyCoupleRoute(): React.ReactElement {
 | 
			
		||||
  const count = React.useRef(1);
 | 
			
		||||
 | 
			
		||||
  const n = useNavigate();
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const confirm = useConfirm();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
  const { coupleId } = useParams();
 | 
			
		||||
 | 
			
		||||
  const [couple, setCouple] = React.useState<Couple>();
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setCouple(await CoupleApi.GetSingle(family.familyId, Number(coupleId)));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const forceReload = async () => {
 | 
			
		||||
    count.current += 1;
 | 
			
		||||
    setCouple(undefined);
 | 
			
		||||
 | 
			
		||||
    await family.reloadCouplesList();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const deleteCouple = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      if (
 | 
			
		||||
        !(await confirm(
 | 
			
		||||
          "Voulez-vous vraiment supprimer cette fiche de couple ? L'opération n'est pas réversible !"
 | 
			
		||||
        ))
 | 
			
		||||
      )
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
      await CoupleApi.Delete(couple!);
 | 
			
		||||
 | 
			
		||||
      snackbar("La fiche du couple a été supprimée avec succès !");
 | 
			
		||||
      n(family.family.URL("couples"));
 | 
			
		||||
 | 
			
		||||
      await family.reloadCouplesList();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Échec de la suppression du couple !");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={`${coupleId}-${count.current}`}
 | 
			
		||||
      load={load}
 | 
			
		||||
      ready={couple !== undefined}
 | 
			
		||||
      errMsg="Echec du chargement des informations du couple !"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <CouplePage
 | 
			
		||||
          couple={couple!}
 | 
			
		||||
          children={[]} // TODO
 | 
			
		||||
          creating={false}
 | 
			
		||||
          editing={false}
 | 
			
		||||
          onRequestDelete={deleteCouple}
 | 
			
		||||
          onRequestEdit={() => n(family.family.coupleURL(couple!, true))}
 | 
			
		||||
          onForceReload={forceReload}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Edit existing couple route
 | 
			
		||||
 */
 | 
			
		||||
export function FamilyEditCoupleRoute(): React.ReactElement {
 | 
			
		||||
  const n = useNavigate();
 | 
			
		||||
  const { coupleId } = useParams();
 | 
			
		||||
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [shouldQuit, setShouldQuit] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
 | 
			
		||||
  const [couple, setCouple] = React.useState<Couple>();
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setCouple(await CoupleApi.GetSingle(family.familyId, Number(coupleId)));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const cancel = () => {
 | 
			
		||||
    setShouldQuit(true);
 | 
			
		||||
    n(-1);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const save = async (c: Couple) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await CoupleApi.Update(c);
 | 
			
		||||
 | 
			
		||||
      snackbar("Les informations du couple ont été mises à jour avec succès !");
 | 
			
		||||
 | 
			
		||||
      await family.reloadCouplesList();
 | 
			
		||||
 | 
			
		||||
      setShouldQuit(true);
 | 
			
		||||
      n(family.family.coupleURL(c, false));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Échec de la mise à jour des informations du couple !");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={coupleId}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Échec du chargement des informations du couple !"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <CouplePage
 | 
			
		||||
          couple={couple!}
 | 
			
		||||
          creating={false}
 | 
			
		||||
          editing={true}
 | 
			
		||||
          onCancel={cancel}
 | 
			
		||||
          onSave={save}
 | 
			
		||||
          shouldAllowLeaving={shouldQuit}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CouplePage(p: {
 | 
			
		||||
  couple: Couple;
 | 
			
		||||
  editing: boolean;
 | 
			
		||||
  creating: boolean;
 | 
			
		||||
  shouldAllowLeaving?: boolean;
 | 
			
		||||
  children?: Member[];
 | 
			
		||||
  onCancel?: () => void;
 | 
			
		||||
  onSave?: (m: Couple) => void;
 | 
			
		||||
  onRequestEdit?: () => void;
 | 
			
		||||
  onRequestDelete?: () => void;
 | 
			
		||||
  onForceReload?: () => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const confirm = useConfirm();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const family = useFamily();
 | 
			
		||||
 | 
			
		||||
  const [changed, setChanged] = React.useState(false);
 | 
			
		||||
  const [couple, setCouple] = React.useState(
 | 
			
		||||
    new Couple(structuredClone(p.couple))
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const updatedCouple = () => {
 | 
			
		||||
    setChanged(true);
 | 
			
		||||
    setCouple(new Couple(structuredClone(couple)));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const save = () => {
 | 
			
		||||
    p.onSave!(couple);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const cancel = async () => {
 | 
			
		||||
    if (
 | 
			
		||||
      changed &&
 | 
			
		||||
      !(await confirm(
 | 
			
		||||
        "Voulez-vous vraiment retirer les modifications apportées ? Celles-ci seront perdues !"
 | 
			
		||||
      ))
 | 
			
		||||
    )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    p.onCancel!();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const uploadNewPhoto = async (b: Blob) => {
 | 
			
		||||
    await CoupleApi.SetCouplePhoto(couple, b);
 | 
			
		||||
    snackbar("La photo du couple a été mise à jour avec succès !");
 | 
			
		||||
    p.onForceReload?.();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const deletePhoto = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      if (!(await confirm("Voulez-vous supprimer cette photo ?"))) return;
 | 
			
		||||
 | 
			
		||||
      await CoupleApi.RemoveCouplePhoto(couple);
 | 
			
		||||
 | 
			
		||||
      snackbar("La photo du couple a été supprimée avec succès !");
 | 
			
		||||
      p.onForceReload?.();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert("Échec de la suppresion de la photo !");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ maxWidth: "2000px", margin: "auto" }}>
 | 
			
		||||
      <ConfirmLeaveWithoutSaveDialog
 | 
			
		||||
        shouldBlock={changed && p.shouldAllowLeaving !== true}
 | 
			
		||||
      />
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          display: "flex",
 | 
			
		||||
          justifyContent: "space-between",
 | 
			
		||||
          alignItems: "center",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <FamilyPageTitle
 | 
			
		||||
          title={
 | 
			
		||||
            (p.editing
 | 
			
		||||
              ? p.creating
 | 
			
		||||
                ? "Création"
 | 
			
		||||
                : "Édition"
 | 
			
		||||
              : "Visualisation") + " d'une fiche de couple"
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
        <Stack direction="row" spacing={1}>
 | 
			
		||||
          {/* Edit button */}
 | 
			
		||||
          {p.onRequestEdit && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="outlined"
 | 
			
		||||
              startIcon={<EditIcon />}
 | 
			
		||||
              onClick={p.onRequestEdit}
 | 
			
		||||
              size="large"
 | 
			
		||||
            >
 | 
			
		||||
              Editer
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          {/* Delete button */}
 | 
			
		||||
          {p.onRequestDelete && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="outlined"
 | 
			
		||||
              startIcon={<DeleteIcon />}
 | 
			
		||||
              onClick={p.onRequestDelete}
 | 
			
		||||
              size="large"
 | 
			
		||||
              color="error"
 | 
			
		||||
            >
 | 
			
		||||
              Supprimer
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          {/* Save button */}
 | 
			
		||||
          {p.editing && changed && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant={"contained"}
 | 
			
		||||
              startIcon={<SaveIcon />}
 | 
			
		||||
              onClick={save}
 | 
			
		||||
              size="large"
 | 
			
		||||
            >
 | 
			
		||||
              Enregistrer
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          {/* Cancel button */}
 | 
			
		||||
          {p.editing && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="outlined"
 | 
			
		||||
              startIcon={<ClearIcon />}
 | 
			
		||||
              onClick={cancel}
 | 
			
		||||
              size="small"
 | 
			
		||||
            >
 | 
			
		||||
              Annuler les modifications
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
        </Stack>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <Grid container spacing={2}>
 | 
			
		||||
        {/* General info */}
 | 
			
		||||
        <Grid item sm={12} md={6}>
 | 
			
		||||
          <PropertiesBox title="Informations générales">
 | 
			
		||||
            {/* Husband */}
 | 
			
		||||
            <br />
 | 
			
		||||
            <MemberInput
 | 
			
		||||
              editable={p.editing}
 | 
			
		||||
              label="Époux"
 | 
			
		||||
              onValueChange={(m) => {
 | 
			
		||||
                couple.husband = m;
 | 
			
		||||
                updatedCouple();
 | 
			
		||||
              }}
 | 
			
		||||
              filter={(m) => m.sex === "M" || m.sex === undefined}
 | 
			
		||||
              current={couple.husband}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            {/* Wife */}
 | 
			
		||||
            <br />
 | 
			
		||||
            <MemberInput
 | 
			
		||||
              editable={p.editing}
 | 
			
		||||
              label="Épouse"
 | 
			
		||||
              onValueChange={(m) => {
 | 
			
		||||
                couple.wife = m;
 | 
			
		||||
                updatedCouple();
 | 
			
		||||
              }}
 | 
			
		||||
              filter={(m) => m.sex === "F" || m.sex === undefined}
 | 
			
		||||
              current={couple.wife}
 | 
			
		||||
            />
 | 
			
		||||
          </PropertiesBox>
 | 
			
		||||
        </Grid>
 | 
			
		||||
 | 
			
		||||
        {/* Photo */}
 | 
			
		||||
        <Grid item sm={12} md={6}>
 | 
			
		||||
          <PropertiesBox title="Photo">
 | 
			
		||||
            <div style={{ textAlign: "center" }}>
 | 
			
		||||
              <CouplePhoto couple={couple} width={150} />
 | 
			
		||||
              <br />
 | 
			
		||||
              {p.editing ? (
 | 
			
		||||
                <p>
 | 
			
		||||
                  Veuillez enregistrer / annuler les modifications apportées à
 | 
			
		||||
                  la fiche avant de changer la photo du couple.
 | 
			
		||||
                </p>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <>
 | 
			
		||||
                  <UploadPhotoButton
 | 
			
		||||
                    label={couple.hasPhoto ? "Remplacer" : "Ajouter"}
 | 
			
		||||
                    onPhotoSelected={uploadNewPhoto}
 | 
			
		||||
                  />{" "}
 | 
			
		||||
                  {couple.hasPhoto && (
 | 
			
		||||
                    <RouterLink to={couple.photoURL!} target="_blank">
 | 
			
		||||
                      <Button
 | 
			
		||||
                        variant="outlined"
 | 
			
		||||
                        startIcon={<FileDownloadIcon />}
 | 
			
		||||
                      >
 | 
			
		||||
                        Télécharger
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </RouterLink>
 | 
			
		||||
                  )}{" "}
 | 
			
		||||
                  {couple.hasPhoto && (
 | 
			
		||||
                    <Button
 | 
			
		||||
                      variant="outlined"
 | 
			
		||||
                      startIcon={<DeleteIcon />}
 | 
			
		||||
                      color="error"
 | 
			
		||||
                      onClick={deletePhoto}
 | 
			
		||||
                    >
 | 
			
		||||
                      Supprimer
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  )}
 | 
			
		||||
                </>
 | 
			
		||||
              )}{" "}
 | 
			
		||||
            </div>
 | 
			
		||||
          </PropertiesBox>
 | 
			
		||||
        </Grid>
 | 
			
		||||
 | 
			
		||||
        {/* Children */}
 | 
			
		||||
        {p.children && (
 | 
			
		||||
          <Grid item sm={12} md={6}>
 | 
			
		||||
            <PropertiesBox title="Enfants">
 | 
			
		||||
              {p.children.length === 0 ? (
 | 
			
		||||
                <>Aucun enfant</>
 | 
			
		||||
              ) : (
 | 
			
		||||
                p.children.map((c) => (
 | 
			
		||||
                  <RouterLink key={c.id} to={family.family.memberURL(c)}>
 | 
			
		||||
                    <MemberItem member={c} />
 | 
			
		||||
                  </RouterLink>
 | 
			
		||||
                ))
 | 
			
		||||
              )}
 | 
			
		||||
            </PropertiesBox>
 | 
			
		||||
          </Grid>
 | 
			
		||||
        )}
 | 
			
		||||
      </Grid>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -38,6 +38,7 @@ interface FamilyContext {
 | 
			
		||||
  familyId: number;
 | 
			
		||||
  reloadFamilyInfo: () => void;
 | 
			
		||||
  reloadMembersList: () => Promise<void>;
 | 
			
		||||
  reloadCouplesList: () => Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FamilyContextK = React.createContext<FamilyContext | null>(null);
 | 
			
		||||
@@ -116,6 +117,7 @@ export function BaseFamilyRoute(): React.ReactElement {
 | 
			
		||||
              familyId: family!.family_id,
 | 
			
		||||
              reloadFamilyInfo: onReload,
 | 
			
		||||
              reloadMembersList: onReload,
 | 
			
		||||
              reloadCouplesList: onReload,
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Box
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								geneit_app/src/widgets/CouplePhoto.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								geneit_app/src/widgets/CouplePhoto.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import { Avatar } from "@mui/material";
 | 
			
		||||
import { Couple } from "../api/CoupleApi";
 | 
			
		||||
 | 
			
		||||
export function CouplePhoto(p: {
 | 
			
		||||
  couple: Couple;
 | 
			
		||||
  width?: number;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <Avatar
 | 
			
		||||
      sx={
 | 
			
		||||
        p.width
 | 
			
		||||
          ? { width: `${p.width}px`, height: "auto", display: "inline-block" }
 | 
			
		||||
          : undefined
 | 
			
		||||
      }
 | 
			
		||||
      variant="rounded"
 | 
			
		||||
      src={p.couple.thumbnailURL ?? undefined}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user