diff --git a/geneit_app/src/App.tsx b/geneit_app/src/App.tsx
index 7346164..def7092 100644
--- a/geneit_app/src/App.tsx
+++ b/geneit_app/src/App.tsx
@@ -17,6 +17,10 @@ import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
import { BaseLoginPage } from "./widgets/BaseLoginpage";
import { FamilyUsersListRoute } from "./routes/family/FamilyUsersListRoute";
import { FamilySettingsRoute } from "./routes/family/FamilySettingsRoute";
+import {
+ FamilyCreateMemberRoute,
+ FamilyMemberRoute,
+} from "./routes/family/FamilyMemberRoute";
interface AuthContext {
signedIn: boolean;
@@ -47,6 +51,11 @@ export function App(): React.ReactElement {
} />
}>
} />
+ }
+ />
+ } />
} />
} />
} />
diff --git a/geneit_app/src/api/MemberApi.ts b/geneit_app/src/api/MemberApi.ts
new file mode 100644
index 0000000..7df8e86
--- /dev/null
+++ b/geneit_app/src/api/MemberApi.ts
@@ -0,0 +1,88 @@
+export type Sex = "M" | "F";
+
+export interface MemberApi {
+ id: number;
+ family_id: number;
+ first_name?: string;
+ last_name?: string;
+ birth_last_name?: string;
+ photo_id?: number;
+ signed_photo_id?: number;
+ email?: string;
+ phone?: string;
+ address?: string;
+ city?: string;
+ postal_code?: string;
+ country?: string;
+ sex?: Sex;
+ time_create?: number;
+ time_update?: number;
+ mother?: number;
+ father?: number;
+ birth_year?: number;
+ birth_month?: number;
+ birth_day?: number;
+ dead: boolean;
+ death_year?: number;
+ death_month?: number;
+ death_day?: number;
+ note?: string;
+}
+
+export class Member implements MemberApi {
+ id: number;
+ family_id: number;
+ first_name?: string;
+ last_name?: string;
+ birth_last_name?: string;
+ photo_id?: number;
+ signed_photo_id?: number;
+ email?: string;
+ phone?: string;
+ address?: string;
+ city?: string;
+ postal_code?: string;
+ country?: string;
+ sex?: Sex;
+ time_create?: number;
+ time_update?: number;
+ mother?: number;
+ father?: number;
+ birth_year?: number;
+ birth_month?: number;
+ birth_day?: number;
+ dead!: boolean;
+ death_year?: number;
+ death_month?: number;
+ death_day?: number;
+ note?: string;
+
+ constructor(m: MemberApi) {
+ this.id = m.id;
+ this.family_id = m.family_id;
+ this.first_name = m.first_name;
+ this.last_name = m.last_name;
+ this.birth_last_name = m.birth_last_name;
+ this.photo_id = m.photo_id;
+ this.signed_photo_id = m.signed_photo_id;
+ this.email = m.email;
+ this.phone = m.phone;
+ this.address = m.address;
+ this.city = m.city;
+ this.postal_code = m.postal_code;
+ this.country = m.country;
+ this.sex = m.sex;
+ this.time_create = m.time_create;
+ this.time_update = m.time_update;
+ this.mother = m.mother;
+ this.father = m.father;
+ this.birth_year = m.birth_year;
+ this.birth_month = m.birth_month;
+ this.birth_day = m.birth_day;
+ this.dead = m.dead;
+ this.death_year = m.death_year;
+ this.death_month = m.death_month;
+ this.death_day = m.death_day;
+ this.note = m.note;
+ }
+}
diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts
index 129f008..4c847eb 100644
--- a/geneit_app/src/api/ServerApi.ts
+++ b/geneit_app/src/api/ServerApi.ts
@@ -1,16 +1,37 @@
import { APIClient } from "./ApiClient";
-interface LenConstraint {
+interface NumberConstraint {
+ min: number;
+ max: number;
+}
+
+export interface LenConstraint {
min: number;
max: number;
}
interface Constraints {
+ date_year: NumberConstraint;
+ date_month: NumberConstraint;
+ date_day: NumberConstraint;
+ photo_allowed_types: string[];
+ photo_max_size: number;
mail_len: LenConstraint;
user_name_len: LenConstraint;
password_len: LenConstraint;
family_name_len: LenConstraint;
invitation_code_len: LenConstraint;
+ member_first_name: LenConstraint;
+ member_last_name: LenConstraint;
+ member_birth_last_name: LenConstraint;
+ member_email: LenConstraint;
+ member_phone: LenConstraint;
+ member_address: LenConstraint;
+ member_city: LenConstraint;
+ member_postal_code: LenConstraint;
+ member_country: LenConstraint;
+ member_sex: LenConstraint;
+ member_note: LenConstraint;
}
interface OIDCProvider {
@@ -18,10 +39,24 @@ interface OIDCProvider {
name: string;
}
+export interface Country {
+ code: string;
+ en: string;
+ fr: string;
+}
+
+export interface CouplesStates {
+ code: string;
+ en: string;
+ fr: string;
+}
+
export interface ServerConfig {
constraints: Constraints;
mail: string;
oidc_providers: OIDCProvider[];
+ countries: Country[];
+ couples_states: CouplesStates[];
}
let config: ServerConfig | null = null;
diff --git a/geneit_app/src/routes/family/FamilyMemberRoute.tsx b/geneit_app/src/routes/family/FamilyMemberRoute.tsx
new file mode 100644
index 0000000..05b64a3
--- /dev/null
+++ b/geneit_app/src/routes/family/FamilyMemberRoute.tsx
@@ -0,0 +1,112 @@
+import React from "react";
+import { Member } from "../../api/MemberApi";
+import { useFamily } from "../../widgets/BaseFamilyRoute";
+import { Grid, Typography } from "@mui/material";
+import { PropertiesBox } from "../../widgets/PropertiesBox";
+import { PropEdit } from "../../widgets/PropEdit";
+import { ServerApi } from "../../api/ServerApi";
+import { SexSelection } from "../../widgets/SexSelection";
+
+/**
+ * Create a new member route
+ */
+export function FamilyCreateMemberRoute(): React.ReactElement {
+ const family = useFamily();
+ const member = new Member({
+ id: 0,
+ dead: false,
+ family_id: family.family.family_id,
+ });
+ return ;
+}
+
+/**
+ * Edit existing member route
+ */
+export function FamilyMemberRoute(): React.ReactElement {
+ return
TODO
;
+}
+
+export function MemberPage(p: {
+ member: Member;
+ forceEdit: boolean;
+ creating: boolean;
+}): React.ReactElement {
+ // TODO : add confirmation when leaving page https://dev.to/bangash1996/detecting-user-leaving-page-with-react-router-dom-v602-33ni
+ const [editing, setEditing] = React.useState(p.forceEdit);
+ const [member, setMember] = React.useState(structuredClone(p.member));
+
+ const updatedMember = () => {
+ // TODO : add confirmation
+ setMember(structuredClone(member));
+ };
+
+ return (
+
+
Fiche de membre
+
+
+
+ {/* Sex */}
+ {
+ member.sex = v;
+ updatedMember();
+ }}
+ />
+
+ {/* First name */}
+ {
+ member.first_name = v;
+ updatedMember();
+ }}
+ size={ServerApi.Config.constraints.member_first_name}
+ />
+
+ {/* Last name */}
+ {
+ member.last_name = v;
+ updatedMember();
+ }}
+ size={ServerApi.Config.constraints.member_last_name}
+ />
+ {
+ member.birth_last_name = v;
+ updatedMember();
+ }}
+ size={ServerApi.Config.constraints.member_birth_last_name}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ TODO
+
+
+ TODO
+
+
+
+ );
+}
diff --git a/geneit_app/src/widgets/PropEdit.tsx b/geneit_app/src/widgets/PropEdit.tsx
new file mode 100644
index 0000000..56739ad
--- /dev/null
+++ b/geneit_app/src/widgets/PropEdit.tsx
@@ -0,0 +1,35 @@
+import { TextField } from "@mui/material";
+import { LenConstraint } from "../api/ServerApi";
+
+/**
+ * Couple / Member property edition
+ */
+export function PropEdit(p: {
+ label: string;
+ editable: boolean;
+ value?: string;
+ onValueChange: (newVal: string | undefined) => void;
+ size: LenConstraint;
+}): React.ReactElement {
+ if (((!p.editable && p.value) ?? "") === "") return <>>;
+
+ return (
+
+ p.onValueChange(
+ e.target.value.length === 0 ? undefined : e.target.value
+ )
+ }
+ inputProps={{
+ maxLength: p.size.max,
+ }}
+ InputProps={{
+ readOnly: !p.editable,
+ }}
+ variant={p.editable ? "filled" : "standard"}
+ style={{ width: "100%", marginBottom: "15px" }}
+ />
+ );
+}
diff --git a/geneit_app/src/widgets/PropertiesBox.tsx b/geneit_app/src/widgets/PropertiesBox.tsx
new file mode 100644
index 0000000..628efd2
--- /dev/null
+++ b/geneit_app/src/widgets/PropertiesBox.tsx
@@ -0,0 +1,14 @@
+import { Paper, Typography } from "@mui/material";
+
+export function PropertiesBox(
+ p: React.PropsWithChildren<{ title: string }>
+): React.ReactElement {
+ return (
+
+
+ {p.title}
+
+ {p.children}
+
+ );
+}
diff --git a/geneit_app/src/widgets/SexSelection.tsx b/geneit_app/src/widgets/SexSelection.tsx
new file mode 100644
index 0000000..cf420b8
--- /dev/null
+++ b/geneit_app/src/widgets/SexSelection.tsx
@@ -0,0 +1,28 @@
+import {
+ FormControl,
+ FormLabel,
+ RadioGroup,
+ FormControlLabel,
+ Radio,
+} from "@mui/material";
+import { Sex } from "../api/MemberApi";
+
+export function SexSelection(p: {
+ current?: Sex;
+ onChange: (s: Sex) => void;
+}): React.ReactElement {
+ return (
+
+ Sexe
+ p.onChange(e.target.value as Sex)}
+ >
+ } label="Homme" />
+ } label="Femme" />
+
+
+ );
+}