Better dates and cleanData

This commit is contained in:
honzapatCZ 2026-08-25 13:27:50 +02:00
parent 4400ceb459
commit 316998f5f7
6 changed files with 66 additions and 8 deletions

View File

@ -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 <DatePickerInput
value={isValid ? parsedDate.toDate() : null}
onChange={(val)=>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;

View File

@ -0,0 +1 @@
export { default, DateOnlyField } from "./DateFields";

View File

@ -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 <DateTimePicker
value={displayDate}
onChange={(val)=>{
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}

View File

@ -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<Values extends FormikValues = FormikValues, ExtraProps = {}>(props: FormikConfig<Values> & ExtraProps): React.JSX.Element;
@ -13,6 +14,7 @@ export function FormikForm<Values extends object>({
onDelete = null,
closeModal = null,
createForm = null,
sendEmptyKeys = null,
children,
...props
}: Omit<FormikConfig<Values>, "initialValues"> & {
@ -22,13 +24,19 @@ export function FormikForm<Values extends object>({
onDelete?: () => void;
closeModal?: () => void;
createForm?: (values, handleChange, errors, touched) => React.ReactNode;
sendEmptyKeys?: Set<string> | 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 <Formik<Values>
initialValues={data}
onSubmit={onSubmit}
onSubmit={handleCleanSubmit}
validationSchema={schema}
{...props}
>

36
Form/cleanEmptyStrings.ts Normal file
View File

@ -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<T>(values: T, sendEmptyKeys?: Set<string> | 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<string, any> = {};
for (const [k, v] of Object.entries(val)) {
cleanedObj[k] = clean(v, k);
}
return cleanedObj;
}
return val;
}
return clean(values);
}

View File

@ -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<T> = {
refresh: () => void;
@ -30,7 +31,11 @@ export function withForm<T extends object, P extends FormProps<T>>(WrappedForm:
return (
<FormikForm
data={initialData || {} as T}
onSubmit={(values) => { submit(values); afterSubmit && afterSubmit(values) }}
onSubmit={(values) => {
const cleaned = cleanEmptyStrings(values);
submit(cleaned);
afterSubmit && afterSubmit(cleaned);
}}
submitText={submitText}
>
<WrappedForm {...props} />