From f312b64145a5a66f095019100b682251a331cd44 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Mon, 5 Aug 2024 18:17:59 +0200 Subject: [PATCH 01/11] Refactor form field --- CHANGELOG.md | 1 + src/client/src/AppTheme.ts | 25 +++++++++++++++++++ .../src/components/form/{form.js => form.jsx} | 20 ++++++--------- src/client/src/components/form/formInput.jsx | 12 +++------ .../src/components/form/formMultiSelect.jsx | 18 ++++--------- src/client/src/components/form/formSelect.jsx | 10 +++----- 6 files changed, 45 insertions(+), 41 deletions(-) rename src/client/src/components/form/{form.js => form.jsx} (60%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7667c3d..1908a98d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Upgraded to PostgreSQL 15 and PostGIS 3.4. - Removed unused `IsViewer` flag from user. - Removed unused `UserEvent` from user. +- Use `filled` style for form components. ### Fixed diff --git a/src/client/src/AppTheme.ts b/src/client/src/AppTheme.ts index 4ad220edc..783ef2274 100644 --- a/src/client/src/AppTheme.ts +++ b/src/client/src/AppTheme.ts @@ -125,6 +125,31 @@ export const theme = createTheme({ }, }, }, + MuiFormControl: { + styleOverrides: { + root: { + borderRadius: "4px", + flex: "1", + marginTop: "10px !important", + marginRight: "10px !important", + "& .MuiInputBase-input": { + minHeight: "26px !important", + }, + "& .MuiFilledInput-root": { + backgroundColor: "#F8F9FA", + }, + "& .MuiFilledInput-root:hover:not(.Mui-disabled, .Mui-error):before": { + borderColor: "#4FA7BC", + }, + "& .MuiFilledInput-root:before": { + borderColor: "#4FA7BC", + }, + "& .MuiFilledInput-root:after": { + borderColor: "#4FA7BC", + }, + }, + }, + }, MuiTab: { styleOverrides: { root: { diff --git a/src/client/src/components/form/form.js b/src/client/src/components/form/form.jsx similarity index 60% rename from src/client/src/components/form/form.js rename to src/client/src/components/form/form.jsx index 4e3500418..79a867c23 100644 --- a/src/client/src/components/form/form.js +++ b/src/client/src/components/form/form.jsx @@ -1,10 +1,10 @@ import { TextField } from "@mui/material"; -import { styled } from "@mui/system"; import { theme } from "../../AppTheme"; +import { forwardRef } from "react"; -export const getFormFieldBackgroundColor = (fieldName, errors) => { +export const getFormFieldError = (fieldName, errors) => { if (typeof fieldName !== "string") { - return "transparent"; + return undefined; } const fieldNameElements = fieldName ? fieldName.split(".") : []; @@ -15,18 +15,12 @@ export const getFormFieldBackgroundColor = (fieldName, errors) => { break; } } - return currentElement ? theme.palette.error.background : "transparent"; + return currentElement ? theme.palette.error.background : undefined; }; -export const FormField = styled(TextField)(() => ({ - borderRadius: "4px", - flex: "1", - marginTop: "10px !important", - marginRight: "10px !important", - "& .MuiInputBase-input": { - minHeight: "26px !important", - }, -})); +export const FormField = forwardRef((props, ref) => { + return ; +}); export { FormInput } from "./formInput"; export { FormSelect } from "./formSelect"; diff --git a/src/client/src/components/form/formInput.jsx b/src/client/src/components/form/formInput.jsx index aef0d5c79..999ff1cea 100644 --- a/src/client/src/components/form/formInput.jsx +++ b/src/client/src/components/form/formInput.jsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; import { useFormContext } from "react-hook-form"; -import { FormField, getFormFieldBackgroundColor } from "./form"; +import { FormField, getFormFieldError } from "./form"; export const FormInput = props => { const { fieldName, label, required, disabled, type, multiline, rows, value, sx, inputProps, onUpdate } = props; @@ -24,19 +24,15 @@ export const FormInput = props => { { if (value === "") { return true; diff --git a/src/client/src/components/form/formMultiSelect.jsx b/src/client/src/components/form/formMultiSelect.jsx index e229df007..a7915d0a6 100644 --- a/src/client/src/components/form/formMultiSelect.jsx +++ b/src/client/src/components/form/formMultiSelect.jsx @@ -2,7 +2,7 @@ import { Box, Chip, MenuItem } from "@mui/material"; import CancelIcon from "@mui/icons-material/Cancel"; import { useTranslation } from "react-i18next"; import { Controller, useFormContext } from "react-hook-form"; -import { FormField, getFormFieldBackgroundColor } from "./form"; +import { FormField, getFormFieldError } from "./form"; import { useState } from "react"; export const FormMultiSelect = props => { @@ -71,13 +71,8 @@ export const FormMultiSelect = props => { renderValue: selection => ChipBox(selection), }} required={required || false} - sx={{ - backgroundColor: getFormFieldBackgroundColor(fieldName, formState?.errors), - ...sx, - }} - size="small" + sx={{ ...sx }} label={t(label)} - variant="outlined" {...register(fieldName, { required: required || false, onChange: e => { @@ -90,6 +85,7 @@ export const FormMultiSelect = props => { }, })} value={field.value || []} + error={getFormFieldError(fieldName, formState.errors)} disabled={disabled || false} data-cy={fieldName + "-formMultiSelect"} InputLabelProps={{ shrink: true }}> @@ -106,13 +102,9 @@ export const FormMultiSelect = props => { { const { fieldName, label, required, disabled, selected, values, sx, onUpdate } = props; @@ -38,13 +38,9 @@ export const FormSelect = props => { Date: Tue, 6 Aug 2024 09:20:29 +0200 Subject: [PATCH 02/11] Fix lint warning --- src/client/src/components/form/{form.jsx => form.js} | 10 ++-------- src/client/src/components/form/formField.jsx | 6 ++++++ src/client/src/components/form/formInput.jsx | 3 ++- src/client/src/components/form/formMultiSelect.jsx | 3 ++- src/client/src/components/form/formSelect.jsx | 3 ++- 5 files changed, 14 insertions(+), 11 deletions(-) rename src/client/src/components/form/{form.jsx => form.js} (69%) create mode 100644 src/client/src/components/form/formField.jsx diff --git a/src/client/src/components/form/form.jsx b/src/client/src/components/form/form.js similarity index 69% rename from src/client/src/components/form/form.jsx rename to src/client/src/components/form/form.js index 79a867c23..352d47fad 100644 --- a/src/client/src/components/form/form.jsx +++ b/src/client/src/components/form/form.js @@ -1,6 +1,4 @@ -import { TextField } from "@mui/material"; import { theme } from "../../AppTheme"; -import { forwardRef } from "react"; export const getFormFieldError = (fieldName, errors) => { if (typeof fieldName !== "string") { @@ -8,8 +6,8 @@ export const getFormFieldError = (fieldName, errors) => { } const fieldNameElements = fieldName ? fieldName.split(".") : []; - var currentElement = errors; - for (var i = 0; i < fieldNameElements.length; i++) { + let currentElement = errors; + for (let i = 0; i < fieldNameElements.length; i++) { currentElement = currentElement[fieldNameElements[i]]; if (!currentElement) { break; @@ -18,10 +16,6 @@ export const getFormFieldError = (fieldName, errors) => { return currentElement ? theme.palette.error.background : undefined; }; -export const FormField = forwardRef((props, ref) => { - return ; -}); - export { FormInput } from "./formInput"; export { FormSelect } from "./formSelect"; export { FormMultiSelect } from "./formMultiSelect"; diff --git a/src/client/src/components/form/formField.jsx b/src/client/src/components/form/formField.jsx new file mode 100644 index 000000000..41da946a2 --- /dev/null +++ b/src/client/src/components/form/formField.jsx @@ -0,0 +1,6 @@ +import { forwardRef } from "react"; +import { TextField } from "@mui/material"; + +export const FormField = forwardRef((props, ref) => { + return ; +}); diff --git a/src/client/src/components/form/formInput.jsx b/src/client/src/components/form/formInput.jsx index 999ff1cea..d37214e33 100644 --- a/src/client/src/components/form/formInput.jsx +++ b/src/client/src/components/form/formInput.jsx @@ -1,6 +1,7 @@ import { useTranslation } from "react-i18next"; import { useFormContext } from "react-hook-form"; -import { FormField, getFormFieldError } from "./form"; +import { getFormFieldError } from "./form"; +import { FormField } from "./formField"; export const FormInput = props => { const { fieldName, label, required, disabled, type, multiline, rows, value, sx, inputProps, onUpdate } = props; diff --git a/src/client/src/components/form/formMultiSelect.jsx b/src/client/src/components/form/formMultiSelect.jsx index a7915d0a6..22e2367ec 100644 --- a/src/client/src/components/form/formMultiSelect.jsx +++ b/src/client/src/components/form/formMultiSelect.jsx @@ -2,7 +2,8 @@ import { Box, Chip, MenuItem } from "@mui/material"; import CancelIcon from "@mui/icons-material/Cancel"; import { useTranslation } from "react-i18next"; import { Controller, useFormContext } from "react-hook-form"; -import { FormField, getFormFieldError } from "./form"; +import { getFormFieldError } from "./form"; +import { FormField } from "./formField"; import { useState } from "react"; export const FormMultiSelect = props => { diff --git a/src/client/src/components/form/formSelect.jsx b/src/client/src/components/form/formSelect.jsx index 18d0d80dd..3932d6265 100644 --- a/src/client/src/components/form/formSelect.jsx +++ b/src/client/src/components/form/formSelect.jsx @@ -1,7 +1,8 @@ import { MenuItem } from "@mui/material"; import { useTranslation } from "react-i18next"; import { Controller, useFormContext } from "react-hook-form"; -import { FormField, getFormFieldError } from "./form"; +import { getFormFieldError } from "./form"; +import { FormField } from "./formField"; export const FormSelect = props => { const { fieldName, label, required, disabled, selected, values, sx, onUpdate } = props; From d8b14f884eb51dd3e7bebd0c9fade84c602e9d34 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Tue, 6 Aug 2024 09:58:21 +0200 Subject: [PATCH 03/11] Fix console warning --- src/client/src/components/form/form.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client/src/components/form/form.js b/src/client/src/components/form/form.js index 352d47fad..e6ba202d6 100644 --- a/src/client/src/components/form/form.js +++ b/src/client/src/components/form/form.js @@ -1,8 +1,6 @@ -import { theme } from "../../AppTheme"; - export const getFormFieldError = (fieldName, errors) => { if (typeof fieldName !== "string") { - return undefined; + return false; } const fieldNameElements = fieldName ? fieldName.split(".") : []; @@ -13,7 +11,7 @@ export const getFormFieldError = (fieldName, errors) => { break; } } - return currentElement ? theme.palette.error.background : undefined; + return !!currentElement; }; export { FormInput } from "./formInput"; From 679139473ee9a43d77387e899ce9ee23f045288a Mon Sep 17 00:00:00 2001 From: tschumpr Date: Tue, 6 Aug 2024 10:06:45 +0200 Subject: [PATCH 04/11] Fix styling --- src/client/src/AppTheme.ts | 11 ++--------- src/client/src/components/form/formField.jsx | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/client/src/AppTheme.ts b/src/client/src/AppTheme.ts index 783ef2274..c0906e27a 100644 --- a/src/client/src/AppTheme.ts +++ b/src/client/src/AppTheme.ts @@ -128,23 +128,16 @@ export const theme = createTheme({ MuiFormControl: { styleOverrides: { root: { - borderRadius: "4px", - flex: "1", - marginTop: "10px !important", - marginRight: "10px !important", - "& .MuiInputBase-input": { - minHeight: "26px !important", - }, "& .MuiFilledInput-root": { backgroundColor: "#F8F9FA", }, "& .MuiFilledInput-root:hover:not(.Mui-disabled, .Mui-error):before": { borderColor: "#4FA7BC", }, - "& .MuiFilledInput-root:before": { + "& .MuiFilledInput-root:not(.Mui-error):before": { borderColor: "#4FA7BC", }, - "& .MuiFilledInput-root:after": { + "& .MuiFilledInput-root:not(.Mui-error):after": { borderColor: "#4FA7BC", }, }, diff --git a/src/client/src/components/form/formField.jsx b/src/client/src/components/form/formField.jsx index 41da946a2..baa2c28f3 100644 --- a/src/client/src/components/form/formField.jsx +++ b/src/client/src/components/form/formField.jsx @@ -2,5 +2,19 @@ import { forwardRef } from "react"; import { TextField } from "@mui/material"; export const FormField = forwardRef((props, ref) => { - return ; + return ( + + ); }); From be381390cdb59ab13b5c0b977cd7bdf32fdb09af Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 11:16:46 +0200 Subject: [PATCH 05/11] Migrate form to typescript --- src/client/src/appInterfaces.ts | 6 + .../src/components/form/{form.js => form.ts} | 20 ++- .../{formCheckbox.jsx => formCheckbox.tsx} | 14 +- .../src/components/form/formDisplay.jsx | 76 ----------- .../src/components/form/formDisplay.tsx | 75 +++++++++++ .../src/components/form/formDisplayType.js | 6 - src/client/src/components/form/formField.jsx | 20 --- src/client/src/components/form/formField.tsx | 28 ++++ .../form/{formInput.jsx => formInput.tsx} | 53 ++++++-- ...ormMultiSelect.jsx => formMultiSelect.tsx} | 42 ++++-- .../form/{formSelect.jsx => formSelect.tsx} | 42 +++++- .../src/components/header/languagePopup.tsx | 11 +- .../domain/domainInterface.ts | 4 + .../detail/form/borehole/geometryImport.jsx | 2 +- .../detail/form/borehole/sectionDisplay.jsx | 24 ++-- .../detail/form/borehole/sectionInput.jsx | 33 ++--- .../form/completion/backfillDisplay.jsx | 6 +- .../detail/form/completion/backfillInput.jsx | 18 ++- .../detail/form/completion/casingDisplay.jsx | 10 +- .../detail/form/completion/casingInput.jsx | 40 ++++-- .../completion/completionHeaderDisplay.jsx | 8 +- .../form/completion/completionHeaderInput.jsx | 4 +- .../completion/instrumentationDisplay.jsx | 6 +- .../form/completion/instrumentationInput.jsx | 18 ++- .../hydrogeology/fieldMeasurementDisplay.jsx | 2 +- .../hydrogeology/fieldMeasurementInput.jsx | 4 +- .../groundwaterLevelMeasurementDisplay.jsx | 4 +- .../groundwaterLevelMeasurementInput.jsx | 6 +- .../form/hydrogeology/hydrotestDisplay.jsx | 10 +- .../form/hydrogeology/hydrotestInput.jsx | 8 +- .../form/hydrogeology/observationDisplay.jsx | 8 +- .../form/hydrogeology/observationInput.jsx | 34 ++++- .../form/hydrogeology/waterIngressDisplay.jsx | 6 +- .../form/hydrogeology/waterIngressInput.jsx | 2 +- src/client/tsconfig.json | 124 +++--------------- 35 files changed, 433 insertions(+), 341 deletions(-) create mode 100644 src/client/src/appInterfaces.ts rename src/client/src/components/form/{form.js => form.ts} (54%) rename src/client/src/components/form/{formCheckbox.jsx => formCheckbox.tsx} (61%) delete mode 100644 src/client/src/components/form/formDisplay.jsx create mode 100644 src/client/src/components/form/formDisplay.tsx delete mode 100644 src/client/src/components/form/formDisplayType.js delete mode 100644 src/client/src/components/form/formField.jsx create mode 100644 src/client/src/components/form/formField.tsx rename src/client/src/components/form/{formInput.jsx => formInput.tsx} (52%) rename src/client/src/components/form/{formMultiSelect.jsx => formMultiSelect.tsx} (80%) rename src/client/src/components/form/{formSelect.jsx => formSelect.tsx} (66%) diff --git a/src/client/src/appInterfaces.ts b/src/client/src/appInterfaces.ts new file mode 100644 index 000000000..bf9cae066 --- /dev/null +++ b/src/client/src/appInterfaces.ts @@ -0,0 +1,6 @@ +export enum Language { + DE = "de", + EN = "en", + FR = "fr", + IT = "it", +} diff --git a/src/client/src/components/form/form.js b/src/client/src/components/form/form.ts similarity index 54% rename from src/client/src/components/form/form.js rename to src/client/src/components/form/form.ts index e6ba202d6..40bbe37af 100644 --- a/src/client/src/components/form/form.js +++ b/src/client/src/components/form/form.ts @@ -1,5 +1,11 @@ -export const getFormFieldError = (fieldName, errors) => { - if (typeof fieldName !== "string") { +import { FieldError, FieldErrorsImpl } from "react-hook-form/dist/types/errors"; +import { Merge } from "react-hook-form"; + +export const getFormFieldError = ( + fieldName: string | undefined, + errors: FieldError | Merge | undefined, +) => { + if (!fieldName || !errors) { return false; } @@ -14,9 +20,17 @@ export const getFormFieldError = (fieldName, errors) => { return !!currentElement; }; +export enum FormValueType { + Text = "text", + Number = "number", + Date = "date", + DateTime = "datetime-local", + Boolean = "boolean", + Domain = "domain", +} + export { FormInput } from "./formInput"; export { FormSelect } from "./formSelect"; export { FormMultiSelect } from "./formMultiSelect"; export { FormCheckbox } from "./formCheckbox"; export { FormDisplay } from "./formDisplay"; -export { FormDisplayType } from "./formDisplayType"; diff --git a/src/client/src/components/form/formCheckbox.jsx b/src/client/src/components/form/formCheckbox.tsx similarity index 61% rename from src/client/src/components/form/formCheckbox.jsx rename to src/client/src/components/form/formCheckbox.tsx index db82903e9..bc1ad6889 100644 --- a/src/client/src/components/form/formCheckbox.jsx +++ b/src/client/src/components/form/formCheckbox.tsx @@ -1,9 +1,17 @@ -import { Checkbox, FormControlLabel } from "@mui/material"; +import { Checkbox, FormControlLabel, SxProps } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useFormContext } from "react-hook-form"; +import { FC } from "react"; -export const FormCheckbox = props => { - const { fieldName, label, checked, disabled, sx } = props; +export interface FormCheckboxProps { + fieldName: string; + label: string; + checked: boolean; + disabled?: boolean; + sx?: SxProps; +} + +export const FormCheckbox: FC = ({ fieldName, label, checked, disabled, sx }) => { const { t } = useTranslation(); const { register } = useFormContext(); diff --git a/src/client/src/components/form/formDisplay.jsx b/src/client/src/components/form/formDisplay.jsx deleted file mode 100644 index 74055de2b..000000000 --- a/src/client/src/components/form/formDisplay.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { FormDisplayType } from "./formDisplayType"; - -/** - * Renders a form display component. - * - * @component - * @param {Object} props - The component props. - * @param {string} props.prefix - The prefix for the data-cy attribute. - * @param {string} props.label - The label for the form display. - * @param {any} props.value - The value to be displayed. - * @param {string} props.type - The type of the form display. - * @param {Object} props.sx - The custom styling for the component. - * @returns {JSX.Element} The rendered form display component. - */ -export const FormDisplay = props => { - const { prefix, label, value, type, sx } = props; - const { t, i18n } = useTranslation(); - - const convert = value => { - if ((value !== 0 && value == null) || value === "") { - return "-"; - } else if (type === FormDisplayType.Date) { - const date = new Date(value); - const dateTimeFormat = new Intl.DateTimeFormat("de-CH", { - year: "numeric", - month: "short", - day: "2-digit", - }); - return dateTimeFormat.format(date); - } else if (type === FormDisplayType.DateTime) { - const date = new Date(value); - const dateTimeFormat = new Intl.DateTimeFormat("de-CH", { - year: "numeric", - month: "short", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - timeZone: "UTC", - }); - return dateTimeFormat.format(date); - } else if (type === FormDisplayType.Boolean) { - return value ? t("yes") : t("no"); - } else if (type === FormDisplayType.Domain) { - return value?.[i18n.language]; - } else { - return value; - } - }; - - const formatValue = value => { - if (Array.isArray(value)) { - if (value.length === 0) { - return "-"; - } - return value.map(v => convert(v)).join(", "); - } else { - return convert(value); - } - }; - - return ( - - {t(label)} - - {formatValue(value)} - - - ); -}; diff --git a/src/client/src/components/form/formDisplay.tsx b/src/client/src/components/form/formDisplay.tsx new file mode 100644 index 000000000..b5ded786f --- /dev/null +++ b/src/client/src/components/form/formDisplay.tsx @@ -0,0 +1,75 @@ +import { Stack, SxProps, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { FormValueType } from "./form"; +import { FC } from "react"; +import { Codelist } from "../legacyComponents/domain/domainInterface.ts"; +import { Language } from "../../appInterfaces.ts"; + +export interface FormDisplayProps { + prefix?: string; + label: string; + value: string | string[] | number | number[] | boolean | Codelist | Codelist[]; + type?: FormValueType; + sx?: SxProps; +} + +export const FormDisplay: FC = ({ prefix, label, value, type, sx }) => { + const { t, i18n } = useTranslation(); + + const convert = (value: string | number | boolean | Codelist | undefined): string => { + if ((value !== 0 && value == undefined) || value === "") { + return "-"; + } else if (type === FormValueType.Date) { + const date = new Date(value as string); + const dateTimeFormat = new Intl.DateTimeFormat("de-CH", { + year: "numeric", + month: "short", + day: "2-digit", + }); + return dateTimeFormat.format(date); + } else if (type === FormValueType.DateTime) { + const date = new Date(value as string); + const dateTimeFormat = new Intl.DateTimeFormat("de-CH", { + year: "numeric", + month: "short", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + timeZone: "UTC", + }); + return dateTimeFormat.format(date); + } else if (type === FormValueType.Boolean) { + return value ? t("yes") : t("no"); + } else if (type === FormValueType.Domain) { + const codelist = value as Codelist; + return codelist[i18n.language as Language]; + } else { + return value as string; + } + }; + + const formatValue = (value: string | string[] | number | number[] | boolean | Codelist | Codelist[]): string => { + if (Array.isArray(value)) { + if (value.length === 0) { + return "-"; + } + return value.map(v => convert(v)).join(", "); + } else { + return convert(value); + } + }; + + return ( + + {t(label)} + + {formatValue(value)} + + + ); +}; diff --git a/src/client/src/components/form/formDisplayType.js b/src/client/src/components/form/formDisplayType.js deleted file mode 100644 index 45c3ad24a..000000000 --- a/src/client/src/components/form/formDisplayType.js +++ /dev/null @@ -1,6 +0,0 @@ -export const FormDisplayType = { - Date: "date", - DateTime: "datetime", - Boolean: "boolean", - Domain: "domain", -}; diff --git a/src/client/src/components/form/formField.jsx b/src/client/src/components/form/formField.jsx deleted file mode 100644 index baa2c28f3..000000000 --- a/src/client/src/components/form/formField.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { forwardRef } from "react"; -import { TextField } from "@mui/material"; - -export const FormField = forwardRef((props, ref) => { - return ( - - ); -}); diff --git a/src/client/src/components/form/formField.tsx b/src/client/src/components/form/formField.tsx new file mode 100644 index 000000000..860f1fce7 --- /dev/null +++ b/src/client/src/components/form/formField.tsx @@ -0,0 +1,28 @@ +import { ForwardedRef, forwardRef } from "react"; +import { SxProps, TextField } from "@mui/material"; + +interface FormFieldProps { + sx?: SxProps; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const FormField = forwardRef((props: FormFieldProps, ref: ForwardedRef) => { + return ( + + ); +}); diff --git a/src/client/src/components/form/formInput.jsx b/src/client/src/components/form/formInput.tsx similarity index 52% rename from src/client/src/components/form/formInput.jsx rename to src/client/src/components/form/formInput.tsx index d37214e33..28052f89e 100644 --- a/src/client/src/components/form/formInput.jsx +++ b/src/client/src/components/form/formInput.tsx @@ -1,18 +1,46 @@ import { useTranslation } from "react-i18next"; import { useFormContext } from "react-hook-form"; -import { getFormFieldError } from "./form"; +import { FormValueType, getFormFieldError } from "./form"; import { FormField } from "./formField"; +import { FC } from "react"; +import { InputProps, SxProps } from "@mui/material"; +import { isValid } from "date-fns"; -export const FormInput = props => { - const { fieldName, label, required, disabled, type, multiline, rows, value, sx, inputProps, onUpdate } = props; +export interface FormInputProps { + fieldName: string; + label: string; + required?: boolean; + disabled?: boolean; + type?: FormValueType; + multiline?: boolean; + rows?: number; + value?: string | number; + sx?: SxProps; + inputProps?: InputProps; + onUpdate?: (value: string) => void; +} + +export const FormInput: FC = ({ + fieldName, + label, + required, + disabled, + type, + multiline, + rows, + value, + sx, + inputProps, + onUpdate, +}) => { const { t } = useTranslation(); const { formState, register, setValue } = useFormContext(); - const getDefaultValue = value => { - if (value != null) { - if (type === "datetime-local") { + const getDefaultValue = (value: string | number | undefined) => { + if (value) { + if (type === FormValueType.DateTime) { // re-format from 'YYYY-MM-DDTHH:mm:ss.sssZ' to 'YYYY-MM-DDTHH:mm'. - return value.slice(0, 16); + return (value as string).slice(0, 16); } else { return value; } @@ -23,24 +51,23 @@ export const FormInput = props => { return ( { if (value === "") { return true; } - if (type === "date" || type === "datetime-local") { - var date = new Date(value); - return !isNaN(date) && date.getFullYear() > 1800 && date.getFullYear() < 3000; + if (type === FormValueType.Date || type === FormValueType.DateTime) { + const date = new Date(value); + return isValid(date) && date.getFullYear() > 1800 && date.getFullYear() < 3000; } return true; }, diff --git a/src/client/src/components/form/formMultiSelect.jsx b/src/client/src/components/form/formMultiSelect.tsx similarity index 80% rename from src/client/src/components/form/formMultiSelect.jsx rename to src/client/src/components/form/formMultiSelect.tsx index 22e2367ec..9f95dc689 100644 --- a/src/client/src/components/form/formMultiSelect.jsx +++ b/src/client/src/components/form/formMultiSelect.tsx @@ -1,13 +1,37 @@ -import { Box, Chip, MenuItem } from "@mui/material"; +import { Box, Chip, MenuItem, SxProps } from "@mui/material"; import CancelIcon from "@mui/icons-material/Cancel"; import { useTranslation } from "react-i18next"; import { Controller, useFormContext } from "react-hook-form"; import { getFormFieldError } from "./form"; import { FormField } from "./formField"; -import { useState } from "react"; +import { FC, useState } from "react"; -export const FormMultiSelect = props => { - const { fieldName, label, tooltipLabel, required, disabled, selected, values, sx } = props; +export interface FormMultiSelectProps { + fieldName: string; + label: string; + tooltipLabel?: string; + required?: boolean; + disabled?: boolean; + selected?: number[]; + values?: FormMultiSelectValue[]; + sx?: SxProps; +} + +export interface FormMultiSelectValue { + key: number; + name: string; +} + +export const FormMultiSelect: FC = ({ + fieldName, + label, + tooltipLabel, + required, + disabled, + selected, + values, + sx, +}) => { const { t } = useTranslation(); const { formState, register, setValue, getValues, control } = useFormContext(); const [open, setOpen] = useState(false); @@ -20,7 +44,7 @@ export const FormMultiSelect = props => { setOpen(true); }; - const ChipBox = selection => { + const ChipBox = (selection: number[]) => { return ( { onClick={e => e.stopPropagation()} onDelete={e => { e.stopPropagation(); - var selectedValues = getValues()[fieldName]; - var updatedValues = selectedValues.filter(value => value !== selectedValue); + const selectedValues = getValues()[fieldName]; + const updatedValues = selectedValues.filter((value: number) => value !== selectedValue); setValue(fieldName, updatedValues, { shouldValidate: true }); }} /> @@ -60,7 +84,7 @@ export const FormMultiSelect = props => { defaultValue={selected || []} render={({ field }) => ( <> - {values?.length > 0 ? ( + {Array.isArray(values) && values.length > 0 ? ( { open: open, onClose: handleClose, onOpen: handleOpen, - renderValue: selection => ChipBox(selection), + renderValue: (selection: number[]) => ChipBox(selection), }} required={required || false} sx={{ ...sx }} diff --git a/src/client/src/components/form/formSelect.jsx b/src/client/src/components/form/formSelect.tsx similarity index 66% rename from src/client/src/components/form/formSelect.jsx rename to src/client/src/components/form/formSelect.tsx index 3932d6265..b9077f311 100644 --- a/src/client/src/components/form/formSelect.jsx +++ b/src/client/src/components/form/formSelect.tsx @@ -1,16 +1,48 @@ -import { MenuItem } from "@mui/material"; +import { MenuItem, SxProps } from "@mui/material"; import { useTranslation } from "react-i18next"; import { Controller, useFormContext } from "react-hook-form"; import { getFormFieldError } from "./form"; import { FormField } from "./formField"; +import { FC } from "react"; -export const FormSelect = props => { - const { fieldName, label, required, disabled, selected, values, sx, onUpdate } = props; +export interface FormSelectProps { + fieldName: string; + label: string; + required?: boolean; + disabled?: boolean; + selected?: number[]; + values?: FormSelectValue[]; + sx?: SxProps; + onUpdate?: (value: number) => void; +} + +export interface FormSelectValue { + key: number; + name: string; +} + +export interface FormSelectMenuItem { + key: number; + value?: number; + label: string; + italic?: boolean; +} + +export const FormSelect: FC = ({ + fieldName, + label, + required, + disabled, + selected, + values, + sx, + onUpdate, +}) => { const { t } = useTranslation(); const { control } = useFormContext(); - var menuItems = []; - menuItems.push({ key: "0", value: "", label: t("reset"), italic: true }); + const menuItems: FormSelectMenuItem[] = []; + menuItems.push({ key: 0, value: undefined, label: t("reset"), italic: true }); if (values) { values.forEach(value => { diff --git a/src/client/src/components/header/languagePopup.tsx b/src/client/src/components/header/languagePopup.tsx index f3b2bd872..2f87f91be 100644 --- a/src/client/src/components/header/languagePopup.tsx +++ b/src/client/src/components/header/languagePopup.tsx @@ -5,13 +5,15 @@ import CheckIcon from "@mui/icons-material/Check"; import ArrowDownIcon from "../../assets/icons/arrow_down.svg?react"; import ArrowUpIcon from "../../assets/icons/arrow_up.svg?react"; import i18n from "../../i18n"; +import { Language } from "../../appInterfaces"; -const languages = ["de", "fr", "it", "en"]; +const defaultLanguage = Language.DE; export function LanguagePopup() { - const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); + const [selectedLanguage, setSelectedLanguage] = useState(defaultLanguage); const [anchorEl, setAnchorEl] = useState(); const isOpen = Boolean(anchorEl); + const languages: string[] = Object.values(Language); const handleClick = (event: MouseEvent) => { setAnchorEl(event.currentTarget); @@ -25,9 +27,9 @@ export function LanguagePopup() { const handleLanguageChange = () => { const languageIndex = languages.indexOf(i18n.language); if (languageIndex !== -1) { - setSelectedLanguage(languages[languageIndex]); + setSelectedLanguage(languages[languageIndex] as Language); } else { - setSelectedLanguage(languages[0]); + setSelectedLanguage(defaultLanguage); } }; handleLanguageChange(); @@ -37,6 +39,7 @@ export function LanguagePopup() { return () => { i18n.off("languageChanged", handleLanguageChange); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const onLanguageChanged = (language: string) => { diff --git a/src/client/src/components/legacyComponents/domain/domainInterface.ts b/src/client/src/components/legacyComponents/domain/domainInterface.ts index 344ec938d..0d91c61a1 100644 --- a/src/client/src/components/legacyComponents/domain/domainInterface.ts +++ b/src/client/src/components/legacyComponents/domain/domainInterface.ts @@ -7,4 +7,8 @@ export interface Codelist { id: number; geolcode: number; schema: string; + de: string; + en: string; + fr: string; + it: string; } diff --git a/src/client/src/pages/detail/form/borehole/geometryImport.jsx b/src/client/src/pages/detail/form/borehole/geometryImport.jsx index af46ec1bd..54cc509e6 100644 --- a/src/client/src/pages/detail/form/borehole/geometryImport.jsx +++ b/src/client/src/pages/detail/form/borehole/geometryImport.jsx @@ -20,7 +20,7 @@ import { useBoreholeGeometryMutations, } from "../../../../api/fetchApiV2.js"; import { Controller, FormProvider, useForm, useWatch } from "react-hook-form"; -import { FormSelect } from "../../../../components/form/form.js"; +import { FormSelect } from "../../../../components/form/form"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import { AlertContext } from "../../../../components/alert/alertContext.tsx"; import { StackHalfWidth } from "../../../../components/styledComponents.js"; diff --git a/src/client/src/pages/detail/form/borehole/sectionDisplay.jsx b/src/client/src/pages/detail/form/borehole/sectionDisplay.jsx index 7328a7d18..2d2a99a34 100644 --- a/src/client/src/pages/detail/form/borehole/sectionDisplay.jsx +++ b/src/client/src/pages/detail/form/borehole/sectionDisplay.jsx @@ -1,5 +1,5 @@ import { StackFullWidth } from "../../../../components/styledComponents.js"; -import { FormDisplay, FormDisplayType } from "../../../../components/form/form.js"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard.jsx"; import { deleteSection, useDomains } from "../../../../api/fetchApiV2.js"; import { Divider } from "@mui/material"; @@ -20,22 +20,22 @@ const SectionDisplay = ({ item, isEditable }) => { prefix={`${index}.`} label="fromdepth" value={element.fromDepth} - type={FormDisplayType.Number} + type={FormValueType.Number} /> - + d.id === element.drillingMethodId)} - type={FormDisplayType.Domain} + type={FormValueType.Domain} /> d.id === element.cuttingsId)} - type={FormDisplayType.Domain} + type={FormValueType.Domain} /> @@ -43,13 +43,13 @@ const SectionDisplay = ({ item, isEditable }) => { prefix={`${index}.`} label="drilling_mud_type" value={domains?.data?.find(d => d.id === element.drillingMudTypeId)} - type={FormDisplayType.Domain} + type={FormValueType.Domain} /> d.id === element.drillingMudSubtypeId)} - type={FormDisplayType.Domain} + type={FormValueType.Domain} /> @@ -57,13 +57,13 @@ const SectionDisplay = ({ item, isEditable }) => { prefix={`${index}.`} label="drilling_start_date" value={element.drillingStartDate} - type={FormDisplayType.Date} + type={FormValueType.Date} /> @@ -71,20 +71,20 @@ const SectionDisplay = ({ item, isEditable }) => { prefix={`${index}.`} label="drill_diameter" value={element.drillingDiameter} - type={FormDisplayType.Number} + type={FormValueType.Number} /> {index < item.sectionElements.length - 1 && } diff --git a/src/client/src/pages/detail/form/borehole/sectionInput.jsx b/src/client/src/pages/detail/form/borehole/sectionInput.jsx index 30cfa70d6..3622c2eb2 100644 --- a/src/client/src/pages/detail/form/borehole/sectionInput.jsx +++ b/src/client/src/pages/detail/form/borehole/sectionInput.jsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; -import { Checkbox, Divider, FormControlLabel, IconButton } from "@mui/material"; +import { Divider, IconButton } from "@mui/material"; import { StackFullWidth } from "../../../../components/styledComponents.js"; -import { FormDisplayType, FormInput, FormSelect } from "../../../../components/form/form.js"; +import { FormCheckbox, FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { addSection, updateSection, useDomains } from "../../../../api/fetchApiV2.js"; import { useContext, useEffect } from "react"; import { FormProvider, useFieldArray, useForm } from "react-hook-form"; @@ -14,7 +14,7 @@ import { DevTool } from "../../../../../hookformDevtools.ts"; const SectionInput = ({ item, parentId }) => { const { triggerReload, selectCard } = useContext(DataCardContext); const { data: domains } = useDomains(); - const { t, i18n } = useTranslation(); + const { i18n } = useTranslation(); const sectionElementDefaults = { fromDepth: null, @@ -114,14 +114,14 @@ const SectionInput = ({ item, parentId }) => { fieldName={`sectionElements.${index}.fromDepth`} label="fromdepth" value={field.fromDepth} - type="number" + type={FormValueType.Number} required={true} /> @@ -180,13 +180,13 @@ const SectionInput = ({ item, parentId }) => { fieldName={`sectionElements.${index}.drillingStartDate`} label="drilling_start_date" value={field.drillingStartDate} - type={FormDisplayType.Date} + type={FormValueType.Date} /> @@ -194,13 +194,13 @@ const SectionInput = ({ item, parentId }) => { fieldName={`sectionElements.${index}.drillingDiameter`} label="drill_diameter" value={field.drillingDiameter} - type={FormDisplayType.Number} + type={FormValueType.Number} /> { spacing={1} justifyContent={"space-between"} alignItems={"center"}> - - } - label={t("overcoring")} + {index === fields.length - 1 && ( { - - + + diff --git a/src/client/src/pages/detail/form/completion/backfillInput.jsx b/src/client/src/pages/detail/form/completion/backfillInput.jsx index 8e5f06d21..90496e612 100644 --- a/src/client/src/pages/detail/form/completion/backfillInput.jsx +++ b/src/client/src/pages/detail/form/completion/backfillInput.jsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { addBackfill, getCasings, updateBackfill, useDomains } from "../../../../api/fetchApiV2"; import { completionSchemaConstants } from "./completionSchemaConstants"; -import { FormInput, FormSelect } from "../../../../components/form/form"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { DataInputCard } from "../../../../components/dataCard/dataInputCard"; import { StackFullWidth, StackHalfWidth } from "../../../../components/styledComponents"; import { prepareCasingDataForSubmit, useGetCasingOptions } from "./casingUtils"; @@ -35,8 +35,20 @@ const BackfillInput = ({ item, parentId }) => { promptLabel="backfill" prepareFormDataForSubmit={prepareFormDataForSubmit}> - - + + { - - + + - - + + {t("casingElements")} diff --git a/src/client/src/pages/detail/form/completion/casingInput.jsx b/src/client/src/pages/detail/form/completion/casingInput.jsx index e2b5ed90e..c750b0b85 100644 --- a/src/client/src/pages/detail/form/completion/casingInput.jsx +++ b/src/client/src/pages/detail/form/completion/casingInput.jsx @@ -6,7 +6,7 @@ import Delete from "@mui/icons-material/Delete"; import { useTranslation } from "react-i18next"; import { addCasing, updateCasing, useDomains } from "../../../../api/fetchApiV2"; import { completionSchemaConstants } from "./completionSchemaConstants"; -import { FormInput, FormSelect } from "../../../../components/form/form"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { DataCardButtonContainer } from "../../../../components/dataCard/dataCard"; import { AddButton, CancelButton, SaveButton } from "../../../../components/buttons/buttons.tsx"; import { extractCasingDepth } from "./casingUtils.jsx"; @@ -149,12 +149,34 @@ const CasingInput = props => { - - + + - - + + @@ -185,7 +207,7 @@ const CasingInput = props => { fieldName={`casingElements.${index}.fromDepth`} label="fromdepth" value={field.fromDepth} - type="number" + type={FormValueType.Number} required={true} onUpdate={updateDepth} /> @@ -193,7 +215,7 @@ const CasingInput = props => { fieldName={`casingElements.${index}.toDepth`} label="todepth" value={field.toDepth} - type="number" + type={FormValueType.Number} required={true} onUpdate={updateDepth} /> @@ -228,13 +250,13 @@ const CasingInput = props => { fieldName={`casingElements.${index}.innerDiameter`} label="casingInnerDiameter" value={field.innerDiameter} - type="number" + type={FormValueType.Number} /> diff --git a/src/client/src/pages/detail/form/completion/completionHeaderDisplay.jsx b/src/client/src/pages/detail/form/completion/completionHeaderDisplay.jsx index 623bb0b5f..127fa9c80 100644 --- a/src/client/src/pages/detail/form/completion/completionHeaderDisplay.jsx +++ b/src/client/src/pages/detail/form/completion/completionHeaderDisplay.jsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { IconButton, Stack } from "@mui/material"; -import { FormDisplay, FormDisplayType } from "../../../../components/form/form.js"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import { CopyButton, DeleteButton, EditButton } from "../../../../components/buttons/buttons.tsx"; import { DataCardButtonContainer } from "../../../../components/dataCard/dataCard.jsx"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; @@ -19,8 +19,8 @@ const CompletionHeaderDisplay = props => { - - + + {expanded && ( @@ -30,7 +30,7 @@ const CompletionHeaderDisplay = props => { diff --git a/src/client/src/pages/detail/form/completion/completionHeaderInput.jsx b/src/client/src/pages/detail/form/completion/completionHeaderInput.jsx index 3b414fe41..53dcfe1c9 100644 --- a/src/client/src/pages/detail/form/completion/completionHeaderInput.jsx +++ b/src/client/src/pages/detail/form/completion/completionHeaderInput.jsx @@ -5,7 +5,7 @@ import { Stack } from "@mui/material"; import { fetchApiV2 } from "../../../../api/fetchApiV2.js"; import { completionSchemaConstants } from "./completionSchemaConstants.js"; import { DataCardButtonContainer } from "../../../../components/dataCard/dataCard.jsx"; -import { FormCheckbox, FormInput, FormSelect } from "../../../../components/form/form.js"; +import { FormCheckbox, FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { CancelButton, SaveButton } from "../../../../components/buttons/buttons.tsx"; import { PromptContext } from "../../../../components/prompt/promptContext.tsx"; @@ -137,7 +137,7 @@ const CompletionHeaderInput = props => { diff --git a/src/client/src/pages/detail/form/completion/instrumentationDisplay.jsx b/src/client/src/pages/detail/form/completion/instrumentationDisplay.jsx index c826c7dea..26424c5ae 100644 --- a/src/client/src/pages/detail/form/completion/instrumentationDisplay.jsx +++ b/src/client/src/pages/detail/form/completion/instrumentationDisplay.jsx @@ -1,5 +1,5 @@ import { StackFullWidth } from "../../../../components/styledComponents.js"; -import { FormDisplay, FormDisplayType } from "../../../../components/form/form.js"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard.jsx"; import { useGetCasingName } from "./casingUtils.jsx"; import { deleteInstrumentation } from "../../../../api/fetchApiV2.js"; @@ -19,8 +19,8 @@ const InstrumentationDisplay = props => { - - + + diff --git a/src/client/src/pages/detail/form/completion/instrumentationInput.jsx b/src/client/src/pages/detail/form/completion/instrumentationInput.jsx index 3768e3cd8..a3a18d0dc 100644 --- a/src/client/src/pages/detail/form/completion/instrumentationInput.jsx +++ b/src/client/src/pages/detail/form/completion/instrumentationInput.jsx @@ -3,7 +3,7 @@ import { Stack } from "@mui/material"; import { useTranslation } from "react-i18next"; import { addInstrumentation, getCasings, updateInstrumentation, useDomains } from "../../../../api/fetchApiV2.js"; import { completionSchemaConstants } from "./completionSchemaConstants.js"; -import { FormInput, FormSelect } from "../../../../components/form/form.js"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { DataInputCard } from "../../../../components/dataCard/dataInputCard.jsx"; import { prepareCasingDataForSubmit, useGetCasingOptions } from "./casingUtils.jsx"; @@ -35,8 +35,20 @@ const InstrumentationInput = ({ item, parentId }) => { promptLabel="instrument" prepareFormDataForSubmit={prepareFormDataForSubmit}> - - + + diff --git a/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementDisplay.jsx b/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementDisplay.jsx index 0cb7b48a1..c8d33a0d6 100644 --- a/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementDisplay.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementDisplay.jsx @@ -4,7 +4,7 @@ import { TableCell } from "@mui/material"; import { getFieldMeasurementParameterUnits } from "./parameterUnits"; import { deleteFieldMeasurement, useDomains } from "../../../../api/fetchApiV2.js"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard.jsx"; -import { FormResultTableDisplay } from "../../../../components/form/formResultTableDisplay.tsx"; +import { FormResultTableDisplay } from "../../../../components/form/formResultTableDisplay"; const FieldMeasurementDisplay = props => { const { item, isEditable } = props; diff --git a/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementInput.jsx b/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementInput.jsx index 313f153d1..f2720d95b 100644 --- a/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementInput.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/fieldMeasurementInput.jsx @@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from "react"; import { FormProvider, useFieldArray, useForm } from "react-hook-form"; import { Box, IconButton, InputAdornment, Stack, Typography } from "@mui/material"; import { AddButton, CancelButton, SaveButton } from "../../../../components/buttons/buttons.tsx"; -import { FormInput, FormSelect } from "../../../../components/form/form"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { DataCardButtonContainer } from "../../../../components/dataCard/dataCard"; import { addFieldMeasurement, updateFieldMeasurement, useDomains } from "../../../../api/fetchApiV2"; import { DataCardContext, DataCardSwitchContext } from "../../../../components/dataCard/dataCardContext"; @@ -188,7 +188,7 @@ const FieldMeasurementInput = props => { fieldName={`fieldMeasurementResults.${index}.value`} label="value" value={field.value} - type="number" + type={FormValueType.Number} required={true} inputProps={{ endAdornment: {units[index] ? units[index] : ""}, diff --git a/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementDisplay.jsx b/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementDisplay.jsx index 3a78997bf..f0f1b142f 100644 --- a/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementDisplay.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementDisplay.jsx @@ -1,4 +1,4 @@ -import { FormDisplay, FormDisplayType } from "../../../../components/form/form"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import ObservationDisplay from "./observationDisplay"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard.jsx"; import { deleteGroundwaterLevelMeasurement } from "../../../../api/fetchApiV2.js"; @@ -11,7 +11,7 @@ const GroundwaterLevelMeasurementDisplay = props => { - + diff --git a/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementInput.jsx b/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementInput.jsx index 2b5bc3006..8e646782c 100644 --- a/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementInput.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/groundwaterLevelMeasurementInput.jsx @@ -1,5 +1,5 @@ import { Stack } from "@mui/material"; -import { FormInput, FormSelect } from "../../../../components/form/form"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { addGroundwaterLevelMeasurement, @@ -60,8 +60,8 @@ const GroundwaterLevelMeasurementInput = props => { /> - - + + ); diff --git a/src/client/src/pages/detail/form/hydrogeology/hydrotestDisplay.jsx b/src/client/src/pages/detail/form/hydrogeology/hydrotestDisplay.jsx index 36a37731c..0e748c663 100644 --- a/src/client/src/pages/detail/form/hydrogeology/hydrotestDisplay.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/hydrotestDisplay.jsx @@ -5,8 +5,8 @@ import { getHydrotestParameterUnits } from "./parameterUnits"; import { deleteHydrotest, useDomains } from "../../../../api/fetchApiV2.js"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard.jsx"; import { StackFullWidth } from "../../../../components/styledComponents.js"; -import { FormDisplay, FormDisplayType } from "../../../../components/form/form"; -import { FormResultTableDisplay } from "../../../../components/form/formResultTableDisplay.tsx"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; +import { FormResultTableDisplay } from "../../../../components/form/formResultTableDisplay"; const HydrotestDisplay = props => { const { item, isEditable } = props; @@ -17,11 +17,11 @@ const HydrotestDisplay = props => { - - + + - + {item?.hydrotestResults?.length > 0 && ( { fieldName={`hydrotestResults.${index}.value`} label="value" value={field.value} - type="number" + type={FormValueType.Number} inputProps={{ endAdornment: ( {units[index] ? units[index] : ""} @@ -316,7 +316,7 @@ const HydrotestInput = props => { fieldName={`hydrotestResults.${index}.minValue`} label="minValue" value={field.minValue} - type="number" + type={FormValueType.Number} inputProps={{ endAdornment: ( {units[index] ? units[index] : ""} @@ -327,7 +327,7 @@ const HydrotestInput = props => { fieldName={`hydrotestResults.${index}.maxValue`} label="maxValue" value={field.maxValue} - type="number" + type={FormValueType.Number} inputProps={{ endAdornment: ( {units[index] ? units[index] : ""} diff --git a/src/client/src/pages/detail/form/hydrogeology/observationDisplay.jsx b/src/client/src/pages/detail/form/hydrogeology/observationDisplay.jsx index 53fae2d23..da829ba28 100644 --- a/src/client/src/pages/detail/form/hydrogeology/observationDisplay.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/observationDisplay.jsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { FormDisplay, FormDisplayType } from "../../../../components/form/form"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import { StackFullWidth } from "../../../../components/styledComponents.js"; import { useGetCasingName } from "../completion/casingUtils.jsx"; @@ -38,8 +38,8 @@ const ObservationDisplay = props => { - - + + { } /> - + diff --git a/src/client/src/pages/detail/form/hydrogeology/observationInput.jsx b/src/client/src/pages/detail/form/hydrogeology/observationInput.jsx index 268391a09..62cdd910e 100644 --- a/src/client/src/pages/detail/form/hydrogeology/observationInput.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/observationInput.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { Stack } from "@mui/material"; -import { FormInput, FormSelect } from "../../../../components/form/form"; +import { FormInput, FormSelect, FormValueType } from "../../../../components/form/form"; import { useTranslation } from "react-i18next"; import { hydrogeologySchemaConstants } from "./hydrogeologySchemaConstants"; import { useGetCasingOptions } from "../completion/casingUtils.jsx"; @@ -25,16 +25,36 @@ const ObservationInput = props => { return ( <> - - + + - - + + - - + + diff --git a/src/client/src/pages/detail/form/hydrogeology/waterIngressDisplay.jsx b/src/client/src/pages/detail/form/hydrogeology/waterIngressDisplay.jsx index 9ba5f74c0..b7f60b6df 100644 --- a/src/client/src/pages/detail/form/hydrogeology/waterIngressDisplay.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/waterIngressDisplay.jsx @@ -1,4 +1,4 @@ -import { FormDisplay, FormDisplayType } from "../../../../components/form/form"; +import { FormDisplay, FormValueType } from "../../../../components/form/form"; import DataDisplayCard from "../../../../components/dataCard/dataDisplayCard"; import ObservationDisplay from "./observationDisplay"; import { deleteWaterIngress } from "../../../../api/fetchApiV2"; @@ -11,8 +11,8 @@ const WaterIngressDisplay = props => { - - + + ); diff --git a/src/client/src/pages/detail/form/hydrogeology/waterIngressInput.jsx b/src/client/src/pages/detail/form/hydrogeology/waterIngressInput.jsx index 3a104194a..c43f0bce4 100644 --- a/src/client/src/pages/detail/form/hydrogeology/waterIngressInput.jsx +++ b/src/client/src/pages/detail/form/hydrogeology/waterIngressInput.jsx @@ -5,7 +5,7 @@ import { ObservationType } from "./observationType"; import { hydrogeologySchemaConstants } from "./hydrogeologySchemaConstants"; import { prepareCasingDataForSubmit } from "../completion/casingUtils.jsx"; import DataInputCard from "../../../../components/dataCard/dataInputCard.jsx"; -import { FormSelect } from "../../../../components/form/form.js"; +import { FormSelect } from "../../../../components/form/form"; import { addWaterIngress, updateWaterIngress, useDomains } from "../../../../api/fetchApiV2.js"; const WaterIngressInput = props => { diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 3192c59ac..74b7d9ddd 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -1,117 +1,29 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "ESNext", "lib": [ "dom", "dom.iterable", "esnext" - ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - "jsx": "react-jsx" /* Specify what JSX code is generated. */, - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "ESNext" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + ], + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "node", "types": [ "vite/client", "vite-plugin-svgr/client" - ] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true /* Disable emitting files from a compilation. */, - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + ], + "allowImportingTsExtensions": true, + "allowJs": true, + "noEmit": true, + "isolatedModules": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "suppressImplicitAnyIndexErrors": true, + "skipLibCheck": true }, - "exclude": ["src/client/cypress"] + "exclude": [ + "src/client/cypress" + ] } From 0f6deb2eb15535c60a859cd3fbe96f53f1251cd4 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 11:28:23 +0200 Subject: [PATCH 06/11] Replace deprecated config --- src/client/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 74b7d9ddd..e43372ea9 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -20,7 +20,7 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "suppressImplicitAnyIndexErrors": true, + "noImplicitAnyIndexErrors": false, "skipLibCheck": true }, "exclude": [ From ac691d0e69b7a3c0f2f3cc21b81a9ea293cea770 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 11:45:39 +0200 Subject: [PATCH 07/11] Fix warning --- src/client/src/components/form/form.ts | 2 ++ src/client/tsconfig.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/src/components/form/form.ts b/src/client/src/components/form/form.ts index 40bbe37af..42fd5971e 100644 --- a/src/client/src/components/form/form.ts +++ b/src/client/src/components/form/form.ts @@ -12,6 +12,8 @@ export const getFormFieldError = ( const fieldNameElements = fieldName ? fieldName.split(".") : []; let currentElement = errors; for (let i = 0; i < fieldNameElements.length; i++) { + // @ts-expect-error - we know that currentElement either has a key of fieldNameElements[i] or it doesn't, + // which is what we're checking for currentElement = currentElement[fieldNameElements[i]]; if (!currentElement) { break; diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index e43372ea9..762163afc 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -20,7 +20,6 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "noImplicitAnyIndexErrors": false, "skipLibCheck": true }, "exclude": [ From 4b6a2ee56722a26aa339b955d639449d92a16af9 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 12:21:21 +0200 Subject: [PATCH 08/11] Fix setting 0 as default value --- src/client/src/components/form/formInput.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/client/src/components/form/formInput.tsx b/src/client/src/components/form/formInput.tsx index 28052f89e..6b26304be 100644 --- a/src/client/src/components/form/formInput.tsx +++ b/src/client/src/components/form/formInput.tsx @@ -37,15 +37,13 @@ export const FormInput: FC = ({ const { formState, register, setValue } = useFormContext(); const getDefaultValue = (value: string | number | undefined) => { - if (value) { - if (type === FormValueType.DateTime) { - // re-format from 'YYYY-MM-DDTHH:mm:ss.sssZ' to 'YYYY-MM-DDTHH:mm'. - return (value as string).slice(0, 16); - } else { - return value; - } - } else { + if (value === undefined) { return ""; + } else if (type === FormValueType.DateTime) { + // re-format from 'YYYY-MM-DDTHH:mm:ss.sssZ' to 'YYYY-MM-DDTHH:mm'. + return (value as string).slice(0, 16); + } else { + return value; } }; From 205dbf940f09b126fa452c2da42e8b321880e79f Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 12:21:40 +0200 Subject: [PATCH 09/11] Fix console warning --- src/client/src/components/dataCard/dataCards.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/src/components/dataCard/dataCards.jsx b/src/client/src/components/dataCard/dataCards.jsx index fc63c7eff..8b3cb2141 100644 --- a/src/client/src/components/dataCard/dataCards.jsx +++ b/src/client/src/components/dataCard/dataCards.jsx @@ -85,8 +85,8 @@ export const DataCards = props => { cardLabel = `${cyLabel}-card.${index}.edit`; } return ( - - + + {isEditable && isSelected ? renderInput({ item: item, From 0dc7d4d1602f314fb2135728093ae29e3948cdf8 Mon Sep 17 00:00:00 2001 From: tschumpr Date: Wed, 7 Aug 2024 13:23:49 +0200 Subject: [PATCH 10/11] Use loose equality check for null and undefined --- src/client/src/components/form/formInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/src/components/form/formInput.tsx b/src/client/src/components/form/formInput.tsx index 6b26304be..078c8854a 100644 --- a/src/client/src/components/form/formInput.tsx +++ b/src/client/src/components/form/formInput.tsx @@ -37,7 +37,7 @@ export const FormInput: FC = ({ const { formState, register, setValue } = useFormContext(); const getDefaultValue = (value: string | number | undefined) => { - if (value === undefined) { + if (value == undefined) { return ""; } else if (type === FormValueType.DateTime) { // re-format from 'YYYY-MM-DDTHH:mm:ss.sssZ' to 'YYYY-MM-DDTHH:mm'. From 57c2e32dd433e69874eae1e07c5efe02d76d429c Mon Sep 17 00:00:00 2001 From: tschumpr Date: Mon, 12 Aug 2024 12:19:33 +0200 Subject: [PATCH 11/11] Use correct type --- src/client/src/components/form/formField.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/src/components/form/formField.tsx b/src/client/src/components/form/formField.tsx index 860f1fce7..acc0363ef 100644 --- a/src/client/src/components/form/formField.tsx +++ b/src/client/src/components/form/formField.tsx @@ -8,8 +8,7 @@ interface FormFieldProps { [key: string]: any; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const FormField = forwardRef((props: FormFieldProps, ref: ForwardedRef) => { +export const FormField = forwardRef((props: FormFieldProps, ref: ForwardedRef) => { return (