From 5b06886c7168796b4bedc593d72286839bba2222 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Tue, 27 Jun 2023 17:13:12 +0200 Subject: [PATCH] Can create a family from the GUI --- geneit_app/src/App.tsx | 2 + geneit_app/src/api/FamilyApi.ts | 16 ++ geneit_app/src/api/ServerApi.ts | 1 + geneit_app/src/dialogs/CreateFamilyDialog.tsx | 55 +++++++ geneit_app/src/dialogs/TextInputDialog.tsx | 147 ++++++++++++++++++ geneit_app/src/routes/FamiliesListRoute.tsx | 104 +++++++++++++ 6 files changed, 325 insertions(+) create mode 100644 geneit_app/src/api/FamilyApi.ts create mode 100644 geneit_app/src/dialogs/CreateFamilyDialog.tsx create mode 100644 geneit_app/src/dialogs/TextInputDialog.tsx create mode 100644 geneit_app/src/routes/FamiliesListRoute.tsx diff --git a/geneit_app/src/App.tsx b/geneit_app/src/App.tsx index 9c8085e..3543794 100644 --- a/geneit_app/src/App.tsx +++ b/geneit_app/src/App.tsx @@ -12,6 +12,7 @@ import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute"; import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { BaseLoginPage } from "./widgets/BaseLoginpage"; import { DeleteAccountRoute } from "./routes/DeleteAccountRoute"; +import { FamiliesListRoute } from "./routes/FamiliesListRoute"; interface AuthContext { signedIn: boolean; @@ -38,6 +39,7 @@ export function App(): React.ReactElement { {signedIn ? ( }> + } /> } /> } /> diff --git a/geneit_app/src/api/FamilyApi.ts b/geneit_app/src/api/FamilyApi.ts new file mode 100644 index 0000000..a79e99e --- /dev/null +++ b/geneit_app/src/api/FamilyApi.ts @@ -0,0 +1,16 @@ +import { APIClient } from "./ApiClient"; + +export interface Family {} + +export class FamilyApi { + /** + * Create a new family + */ + static async CreateFamily(name: string): Promise { + await APIClient.exec({ + method: "POST", + uri: "/family/create", + jsonData: { name: name }, + }); + } +} diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts index a16fbec..274dcc5 100644 --- a/geneit_app/src/api/ServerApi.ts +++ b/geneit_app/src/api/ServerApi.ts @@ -9,6 +9,7 @@ interface Constraints { mail_len: LenConstraint; user_name_len: LenConstraint; password_len: LenConstraint; + family_name_len: LenConstraint; } interface OIDCProvider { diff --git a/geneit_app/src/dialogs/CreateFamilyDialog.tsx b/geneit_app/src/dialogs/CreateFamilyDialog.tsx new file mode 100644 index 0000000..c2bbb1f --- /dev/null +++ b/geneit_app/src/dialogs/CreateFamilyDialog.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { TextInputDialog } from "./TextInputDialog"; +import { ServerApi } from "../api/ServerApi"; +import { FamilyApi } from "../api/FamilyApi"; + +export function CreateFamilyDialog(p: { + open: boolean; + onClose: () => void; + onCreated: () => void; +}): React.ReactElement { + const [name, setName] = React.useState(""); + const [creating, setCreating] = React.useState(false); + const [error, setError] = React.useState(false); + + const cancel = () => { + setName(""); + setCreating(false); + setError(false); + p.onClose(); + }; + + const createFamily = async () => { + setCreating(true); + try { + await FamilyApi.CreateFamily(name); + setName(""); + p.onCreated(); + } catch (e) { + console.error(e); + setError(true); + } + setCreating(false); + }; + + return ( + + ); +} diff --git a/geneit_app/src/dialogs/TextInputDialog.tsx b/geneit_app/src/dialogs/TextInputDialog.tsx new file mode 100644 index 0000000..a1309c4 --- /dev/null +++ b/geneit_app/src/dialogs/TextInputDialog.tsx @@ -0,0 +1,147 @@ +import { + Box, + Button, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + TextField, +} from "@mui/material"; + +export function TextInputDialog(p: { + /** + * Minimum amount of text that must be entered before the text can be validated + */ + minLen?: number; + + /** + * Max len of text than can be entered + */ + maxLen?: number; + + /** + * The title of the dialog + */ + title?: string; + + /** + * The message shown above the input dialog + */ + text: string; + + /** + * The label of the text field + */ + label: string; + + /** + * The current value in the dialog + */ + value: string; + + /** + * The type of value + */ + type?: React.HTMLInputTypeAttribute; + + /** + * Function called when the value entered by the user is changed + */ + onValueChange: (v: string) => void; + + /** + * Specify whether the value is being checked + */ + isChecking?: boolean; + + /** + * The message shown while the value is checked + */ + checkingMessage?: string; + + /** + * The text of the submit button + */ + submitButton?: string; + + /** + * Callback function called when the user click on the submit button + */ + onSubmit: (value: string) => void; + + /** + * Callback function called when the user click on the submit button + */ + onCancel: () => void; + + /** + * Specify whether the dialog should be open or not + */ + open: boolean; + + /** + * Callback function called when the user click outside the dialog + */ + onClose: () => void; + + /** + * Error message to show on the input text + */ + error?: string; + + /** + * If set to true (the default), when an error is set, the submit button will be disabled + */ + errorIsBlocking?: boolean; +}): React.ReactElement { + const canSubmit = + p.value.length >= (p.minLen ?? 0) && + (p.errorIsBlocking === false || p.error === undefined); + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (!canSubmit) return; + + p.onSubmit(p.value); + }; + + return ( + + + {p.title && {p.title}} + + {p.text} + p.onValueChange(e.target.value)} + inputProps={{ maxLength: p.maxLen ?? 255 }} + error={p.error !== undefined} + helperText={p.error} + /> + + {p.isChecking && ( + + {" "} + {p.checkingMessage ?? "Contrôle en cours..."} + + )} + + + + + + + + ); +} diff --git a/geneit_app/src/routes/FamiliesListRoute.tsx b/geneit_app/src/routes/FamiliesListRoute.tsx new file mode 100644 index 0000000..772dd6f --- /dev/null +++ b/geneit_app/src/routes/FamiliesListRoute.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { AsyncWidget } from "../widgets/AsyncWidget"; +import { Family } from "../api/FamilyApi"; +import { Button, Typography } from "@mui/material"; +import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog"; + +export function FamiliesListRoute(): React.ReactElement { + const loadKey = React.useRef(1); + + const [families, setFamilies] = React.useState(null); + + const [createFamily, setCreateFamily] = React.useState(false); + + const load = async () => { + // TODO : implement + setFamilies([]); + }; + + const reload = () => { + loadKey.current += 1; + }; + + const onRequestCreateFamily = async () => { + setCreateFamily(true); + }; + + return ( + ( + <> + {families!!.length === 0 ? ( + + ) : ( + + )} + + {/** Create family dialog anchor */} + setCreateFamily(false)} + onCreated={() => { + setCreateFamily(false); + reload(); + }} + /> + + )} + /> + ); +} + +function NoFamilyWidget(p: { + onRequestCreateFamily: () => void; +}): React.ReactElement { + return ( +
+ + Vous n'appartenez à aucune famille ! + + +
+ + + {}} /> +
+
+ ); +} + +function NoFamilyButton(p: { + label: string; + onClick: () => void; +}): React.ReactElement { + return ( + + ); +} + +function HasFamilysWidget(p: { + families: Family[]; + onReload: () => void; +}): React.ReactElement { + return

todo has families

; +}