From 316998f5f74423e8b381cd73d92b42477b49f73a Mon Sep 17 00:00:00 2001 From: honzapatCZ Date: Tue, 25 Aug 2026 13:27:50 +0200 Subject: [PATCH] Better dates and cleanData --- Form/Core/DateFields.js | 14 +++++++++++--- Form/Core/DateOnlyField.js | 1 + Form/Core/DateTimeField.js | 6 +++--- Form/FormikForm.tsx | 10 +++++++++- Form/cleanEmptyStrings.ts | 36 ++++++++++++++++++++++++++++++++++++ Form/withForm.tsx | 7 ++++++- 6 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 Form/Core/DateOnlyField.js create mode 100644 Form/cleanEmptyStrings.ts diff --git a/Form/Core/DateFields.js b/Form/Core/DateFields.js index 46da066..91887ad 100644 --- a/Form/Core/DateFields.js +++ b/Form/Core/DateFields.js @@ -10,15 +10,23 @@ export default function DateField(props) { const [{onChange, value, ...field}, meta, helpers] = useField(props); - const parsedDate = dayjs(value); + const parsedDate = typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) + ? dayjs(value.slice(0, 10)) + : dayjs(value); const isValid = value && parsedDate.isValid(); + const displayDate = isValid ? parsedDate.toDate() : null; const {value: _x, onChange: _y, ...restProps} = props; return onChange({ target: { value: val, name: field.name } })} + value={displayDate} + onChange={(val)=>{ + const outDate = val && dayjs(val).isValid() ? dayjs(val).format("YYYY-MM-DD") : null; + onChange({ target: { value: outDate, name: field.name } }); + }} {...field} {...restProps} />; } + +export const DateOnlyField = DateField; diff --git a/Form/Core/DateOnlyField.js b/Form/Core/DateOnlyField.js new file mode 100644 index 0000000..9f0e0eb --- /dev/null +++ b/Form/Core/DateOnlyField.js @@ -0,0 +1 @@ +export { default, DateOnlyField } from "./DateFields"; diff --git a/Form/Core/DateTimeField.js b/Form/Core/DateTimeField.js index 001733a..ef579af 100644 --- a/Form/Core/DateTimeField.js +++ b/Form/Core/DateTimeField.js @@ -13,16 +13,16 @@ export default function DateTimeField(props) { const [{onChange, value, ...field}, meta, helpers] = useField(props); - const parsedDate = dayjs.utc(value); + const parsedDate = dayjs(value); const isValid = value && parsedDate.isValid(); - const displayDate = isValid ? dayjs(parsedDate.format("YYYY-MM-DDTHH:mm:ss")).toDate() : null; + const displayDate = isValid ? parsedDate.toDate() : null; const {value: _x, onChange: _y, ...restProps} = props; return { - const outDate = val ? dayjs.utc(dayjs(val).format("YYYY-MM-DDTHH:mm:ss")).toDate() : null; + const outDate = val && dayjs(val).isValid() ? dayjs(val).format("YYYY-MM-DDTHH:mm:ss") : null; onChange({ target: { value: outDate, name: field.name } }); }} {...field} diff --git a/Form/FormikForm.tsx b/Form/FormikForm.tsx index b1d4eba..bc0c5a7 100644 --- a/Form/FormikForm.tsx +++ b/Form/FormikForm.tsx @@ -1,6 +1,7 @@ import { useTranslation } from "react-i18next"; import { Form, Formik, FormikConfig, FormikErrors, FormikProps } from "formik"; import { Button, Divider } from "@mantine/core"; +import { cleanEmptyStrings } from "./cleanEmptyStrings"; //formik is defined as //export declare function Formik(props: FormikConfig & ExtraProps): React.JSX.Element; @@ -13,6 +14,7 @@ export function FormikForm({ onDelete = null, closeModal = null, createForm = null, + sendEmptyKeys = null, children, ...props }: Omit, "initialValues"> & { @@ -22,13 +24,19 @@ export function FormikForm({ onDelete?: () => void; closeModal?: () => void; createForm?: (values, handleChange, errors, touched) => React.ReactNode; + sendEmptyKeys?: Set | string[]; children?: React.ReactNode; }): JSX.Element { const { t } = useTranslation(); + const handleCleanSubmit = (values: Values, formikHelpers: any) => { + const cleaned = cleanEmptyStrings(values, sendEmptyKeys); + return onSubmit(cleaned, formikHelpers); + }; + return initialValues={data} - onSubmit={onSubmit} + onSubmit={handleCleanSubmit} validationSchema={schema} {...props} > diff --git a/Form/cleanEmptyStrings.ts b/Form/cleanEmptyStrings.ts new file mode 100644 index 0000000..6832d24 --- /dev/null +++ b/Form/cleanEmptyStrings.ts @@ -0,0 +1,36 @@ +/** + * Recursively cleans empty string values ("" or whitespace only) to null in form submission payloads, + * unless explicitly exempted by sendEmpty configuration. + */ +export function cleanEmptyStrings(values: T, sendEmptyKeys?: Set | string[]): T { + const exemptions = sendEmptyKeys instanceof Set ? sendEmptyKeys : new Set(sendEmptyKeys || []); + + function clean(val: any, currentKey?: string): any { + if (val === null || val === undefined) { + return null; + } + + if (typeof val === "string") { + if (val.trim() === "") { + return currentKey && exemptions.has(currentKey) ? "" : null; + } + return val; + } + + if (Array.isArray(val)) { + return val.map((item) => clean(item, currentKey)); + } + + if (typeof val === "object" && !(val instanceof Date) && !(val instanceof Blob) && !(val instanceof File)) { + const cleanedObj: Record = {}; + for (const [k, v] of Object.entries(val)) { + cleanedObj[k] = clean(v, k); + } + return cleanedObj; + } + + return val; + } + + return clean(values); +} diff --git a/Form/withForm.tsx b/Form/withForm.tsx index 5a23c2e..82b47ce 100644 --- a/Form/withForm.tsx +++ b/Form/withForm.tsx @@ -3,6 +3,7 @@ import { useApiClient } from "@utils/NejManager/NejProvider"; import useMethod from "../macros/useMethod.macro"; import useQuery from "../macros/useQuery.macro"; import { CancelablePromise, ApiClient, OwnerResponse, useOwner } from "@services/api"; +import { cleanEmptyStrings } from "./cleanEmptyStrings"; type FormProps = { refresh: () => void; @@ -30,7 +31,11 @@ export function withForm>(WrappedForm: return ( { submit(values); afterSubmit && afterSubmit(values) }} + onSubmit={(values) => { + const cleaned = cleanEmptyStrings(values); + submit(cleaned); + afterSubmit && afterSubmit(cleaned); + }} submitText={submitText} >