Create basic couple route
This commit is contained in:
parent
0652fbadc8
commit
328eada9ec
@ -28,6 +28,11 @@ import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
|
|||||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
|
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
|
||||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
|
import { BaseLoginPage } from "./widgets/BaseLoginpage";
|
||||||
import { FamilyMembersListRoute } from "./routes/family/FamilyMembersListRoute";
|
import { FamilyMembersListRoute } from "./routes/family/FamilyMembersListRoute";
|
||||||
|
import {
|
||||||
|
FamilyCoupleRoute,
|
||||||
|
FamilyCreateCoupleRoute,
|
||||||
|
FamilyEditCoupleRoute,
|
||||||
|
} from "./routes/family/FamilyCoupleRoute";
|
||||||
|
|
||||||
interface AuthContext {
|
interface AuthContext {
|
||||||
signedIn: boolean;
|
signedIn: boolean;
|
||||||
@ -58,6 +63,7 @@ export function App(): React.ReactElement {
|
|||||||
<Route path="profile" element={<ProfileRoute />} />
|
<Route path="profile" element={<ProfileRoute />} />
|
||||||
<Route path="family/:familyId/*" element={<BaseFamilyRoute />}>
|
<Route path="family/:familyId/*" element={<BaseFamilyRoute />}>
|
||||||
<Route path="" element={<FamilyHomeRoute />} />
|
<Route path="" element={<FamilyHomeRoute />} />
|
||||||
|
|
||||||
<Route path="members" element={<FamilyMembersListRoute />} />
|
<Route path="members" element={<FamilyMembersListRoute />} />
|
||||||
<Route
|
<Route
|
||||||
path="member/create"
|
path="member/create"
|
||||||
@ -68,6 +74,17 @@ export function App(): React.ReactElement {
|
|||||||
path="member/:memberId/edit"
|
path="member/:memberId/edit"
|
||||||
element={<FamilyEditMemberRoute />}
|
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="settings" element={<FamilySettingsRoute />} />
|
||||||
<Route path="users" element={<FamilyUsersListRoute />} />
|
<Route path="users" element={<FamilyUsersListRoute />} />
|
||||||
<Route path="*" element={<NotFoundRoute />} />
|
<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 { APIClient } from "./ApiClient";
|
||||||
|
import { Couple } from "./CoupleApi";
|
||||||
import { Member } from "./MemberApi";
|
import { Member } from "./MemberApi";
|
||||||
|
|
||||||
interface FamilyAPI {
|
interface FamilyAPI {
|
||||||
@ -62,6 +63,15 @@ export class Family implements FamilyAPI {
|
|||||||
`/family/${this.family_id}/member/${member.id}` + (edit ? "/edit" : "")
|
`/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 {
|
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;
|
familyId: number;
|
||||||
reloadFamilyInfo: () => void;
|
reloadFamilyInfo: () => void;
|
||||||
reloadMembersList: () => Promise<void>;
|
reloadMembersList: () => Promise<void>;
|
||||||
|
reloadCouplesList: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FamilyContextK = React.createContext<FamilyContext | null>(null);
|
const FamilyContextK = React.createContext<FamilyContext | null>(null);
|
||||||
@ -116,6 +117,7 @@ export function BaseFamilyRoute(): React.ReactElement {
|
|||||||
familyId: family!.family_id,
|
familyId: family!.family_id,
|
||||||
reloadFamilyInfo: onReload,
|
reloadFamilyInfo: onReload,
|
||||||
reloadMembersList: onReload,
|
reloadMembersList: onReload,
|
||||||
|
reloadCouplesList: onReload,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user