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" /> + + + ); +}