From a3195b4eaea4f6e47740b9d8b63b3793d28b6cdb Mon Sep 17 00:00:00 2001 From: Jan-Gerke Salomon Date: Wed, 22 Nov 2023 16:52:51 +0100 Subject: [PATCH] fix(de zod schema): add `.optional()` to optional nested value fields --- src/pages/dataElements/Edit.tsx | 3 +- src/pages/dataElements/New.tsx | 3 +- .../form/createDataElementSchema.ts | 34 ------------------ .../dataElements/form/dataElementSchema.ts | 33 +++++++++++++++++ src/pages/dataElements/form/index.ts | 2 +- src/pages/dataElements/form/useValidate.ts | 35 ------------------- src/pages/dataElements/form/validate.ts | 27 ++++++++++++++ 7 files changed, 63 insertions(+), 74 deletions(-) delete mode 100644 src/pages/dataElements/form/createDataElementSchema.ts create mode 100644 src/pages/dataElements/form/dataElementSchema.ts delete mode 100644 src/pages/dataElements/form/useValidate.ts create mode 100644 src/pages/dataElements/form/validate.ts diff --git a/src/pages/dataElements/Edit.tsx b/src/pages/dataElements/Edit.tsx index 7f50b30a..b4889306 100644 --- a/src/pages/dataElements/Edit.tsx +++ b/src/pages/dataElements/Edit.tsx @@ -16,7 +16,7 @@ import { Attribute, DataElement } from '../../types/generated' import { createJsonPatchOperations } from './edit/' import classes from './Edit.module.css' import { useCustomAttributesQuery } from './fields' -import { DataElementFormFields, useValidate } from './form' +import { DataElementFormFields, validate } from './form' import type { FormValues } from './form' type FinalFormFormApi = FormApi @@ -144,7 +144,6 @@ export const Component = () => { const dataElementQuery = useDataElementQuery(dataElementId) const customAttributesQuery = useCustomAttributesQuery() const patchDirtyFields = usePatchDirtyFields() - const validate = useValidate() async function onSubmit(values: FormValues, form: FinalFormFormApi) { const errors = await patchDirtyFields({ diff --git a/src/pages/dataElements/New.tsx b/src/pages/dataElements/New.tsx index 5f6dad82..b505aead 100644 --- a/src/pages/dataElements/New.tsx +++ b/src/pages/dataElements/New.tsx @@ -13,7 +13,7 @@ import { import { SCHEMA_SECTIONS, getSectionPath, useSchemas } from '../../lib' import { Attribute } from '../../types/generated' import { useCustomAttributesQuery } from './fields' -import { DataElementFormFields, useValidate } from './form' +import { DataElementFormFields, validate } from './form' import type { FormValues } from './form' import classes from './New.module.css' @@ -98,7 +98,6 @@ function formatFormValues({ values }: { values: FormValues }) { } export const Component = () => { - const validate = useValidate() const dataEngine = useDataEngine() const navigate = useNavigate() const customAttributesQuery = useCustomAttributesQuery() diff --git a/src/pages/dataElements/form/createDataElementSchema.ts b/src/pages/dataElements/form/createDataElementSchema.ts deleted file mode 100644 index 42d61b71..00000000 --- a/src/pages/dataElements/form/createDataElementSchema.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from 'zod' -import { DataElement } from '../../../types/generated' - -export const createDataElementSchema = () => - z - .object({ - name: z.string().trim(), - shortName: z.string().trim(), - code: z.string().trim(), - description: z.string().trim(), - formName: z.string().trim(), - url: z.string().trim(), - fieldMask: z.string().trim(), - style: z.object({ - color: z.string(), - icon: z.string(), - }), - domainType: z.union([z.literal('AGGREGATE'), z.literal('TRACKER')]), - valueType: z.nativeEnum(DataElement.valueType), - aggregationType: z.nativeEnum(DataElement.aggregationType), - optionSet: z.object({ id: z.string() }), - commentOptionSet: z.object({ id: z.string() }), - legendSets: z.array(z.object({ id: z.string() })), - aggregationLevels: z.array(z.number()), - attributeValues: z.array( - z.object({ - value: z.string(), - attribute: z.object({ - id: z.string(), - }), - }) - ), - }) - .partial() diff --git a/src/pages/dataElements/form/dataElementSchema.ts b/src/pages/dataElements/form/dataElementSchema.ts new file mode 100644 index 00000000..00bf44b8 --- /dev/null +++ b/src/pages/dataElements/form/dataElementSchema.ts @@ -0,0 +1,33 @@ +import { z } from 'zod' +import { DataElement } from '../../../types/generated' + +export const dataElementSchema = z + .object({ + name: z.string().trim(), + shortName: z.string().trim(), + code: z.string().trim(), + description: z.string().trim(), + formName: z.string().trim(), + url: z.string().trim(), + fieldMask: z.string().trim(), + style: z.object({ + color: z.string().optional(), + icon: z.string().optional(), + }), + domainType: z.union([z.literal('AGGREGATE'), z.literal('TRACKER')]), + valueType: z.nativeEnum(DataElement.valueType), + aggregationType: z.nativeEnum(DataElement.aggregationType), + optionSet: z.object({ id: z.string() }), + commentOptionSet: z.object({ id: z.string() }), + legendSets: z.array(z.object({ id: z.string() })), + aggregationLevels: z.array(z.number()), + attributeValues: z.array( + z.object({ + value: z.string().optional(), + attribute: z.object({ + id: z.string(), + }), + }) + ), + }) + .partial() diff --git a/src/pages/dataElements/form/index.ts b/src/pages/dataElements/form/index.ts index 41f4a9b7..27f586c5 100644 --- a/src/pages/dataElements/form/index.ts +++ b/src/pages/dataElements/form/index.ts @@ -1,3 +1,3 @@ export { DataElementFormFields } from './DataElementFormFields' export type { FormValues } from './types' -export { useValidate } from './useValidate' +export { validate } from './validate' diff --git a/src/pages/dataElements/form/useValidate.ts b/src/pages/dataElements/form/useValidate.ts deleted file mode 100644 index 4c2840b7..00000000 --- a/src/pages/dataElements/form/useValidate.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { setIn } from 'final-form' -import { useMemo } from 'react' -import { createDataElementSchema } from './createDataElementSchema' -import type { FormValues } from './types' - -// @TODO: Figure out if there's a utility for this? I couldn't find one -function segmentsToPath(segments: Array) { - return segments.reduce((path, segment) => { - return typeof segment === 'number' - ? `${path}[${segment}]` - : `${path}.${segment}` - }) as string -} - -export function useValidate() { - const dataElementSchema = useMemo(() => createDataElementSchema(), []) - - return (values: FormValues) => { - const zodResult = dataElementSchema.safeParse(values) - - if (zodResult.success !== false) { - return undefined - } - - const allFormErrors = zodResult.error.issues.reduce( - (formErrors, error) => { - const errorPath = segmentsToPath(error.path) - return setIn(formErrors, errorPath, error.message) - }, - {} - ) - - return allFormErrors - } -} diff --git a/src/pages/dataElements/form/validate.ts b/src/pages/dataElements/form/validate.ts new file mode 100644 index 00000000..c43b370a --- /dev/null +++ b/src/pages/dataElements/form/validate.ts @@ -0,0 +1,27 @@ +import { setIn } from 'final-form' +import { dataElementSchema } from './dataElementSchema' +import type { FormValues } from './types' + +// @TODO: Figure out if there's a utility for this? I couldn't find one +function segmentsToPath(segments: Array) { + return segments.reduce((path, segment) => { + return typeof segment === 'number' + ? `${path}[${segment}]` + : `${path}.${segment}` + }) as string +} + +export function validate(values: FormValues) { + const zodResult = dataElementSchema.safeParse(values) + + if (zodResult.success !== false) { + return undefined + } + + const allFormErrors = zodResult.error.issues.reduce((formErrors, error) => { + const errorPath = segmentsToPath(error.path) + return setIn(formErrors, errorPath, error.message) + }, {}) + + return allFormErrors +}