From eb2f87891910199f88a649171be209402234314b Mon Sep 17 00:00:00 2001 From: Mohammer5 Date: Tue, 5 Mar 2024 20:26:11 +0800 Subject: [PATCH] refactor(de group sets): align form code with de group form code --- i18n/en.pot | 28 +- src/pages/dataElementGroupSets/Edit.tsx | 252 ++++++++---------- src/pages/dataElementGroupSets/New.tsx | 79 ++++-- .../edit/createJsonPatchOperations.spec.ts | 49 ---- .../edit/createJsonPatchOperations.ts | 20 -- src/pages/dataElementGroupSets/edit/index.ts | 1 - .../dataElementGroupSets/fields/CodeField.tsx | 25 -- .../fields/DescriptionField.tsx | 28 -- .../dataElementGroupSets/fields/NameField.tsx | 64 ----- .../fields/ShortNameField.tsx | 62 ----- .../dataElementGroupSets/fields/index.ts | 4 - .../form/DataElementGroupSetFormFields.tsx | 45 ++-- src/pages/dataElementGroups/Edit.tsx | 2 - 13 files changed, 204 insertions(+), 455 deletions(-) delete mode 100644 src/pages/dataElementGroupSets/edit/createJsonPatchOperations.spec.ts delete mode 100644 src/pages/dataElementGroupSets/edit/createJsonPatchOperations.ts delete mode 100644 src/pages/dataElementGroupSets/edit/index.ts delete mode 100644 src/pages/dataElementGroupSets/fields/CodeField.tsx delete mode 100644 src/pages/dataElementGroupSets/fields/DescriptionField.tsx delete mode 100644 src/pages/dataElementGroupSets/fields/NameField.tsx delete mode 100644 src/pages/dataElementGroupSets/fields/ShortNameField.tsx diff --git a/i18n/en.pot b/i18n/en.pot index 3a9ba8b9..dfa1c42e 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-03-05T09:22:49.833Z\n" -"PO-Revision-Date: 2024-03-05T09:22:49.833Z\n" +"POT-Creation-Date: 2024-03-05T12:19:26.068Z\n" +"PO-Revision-Date: 2024-03-05T12:19:26.068Z\n" msgid "schemas" msgstr "schemas" @@ -657,6 +657,9 @@ msgstr "This field requires a unique value, please choose another one" msgid "Required" msgstr "Required" +msgid "Custom attributes" +msgstr "Custom attributes" + msgid "Exit without saving" msgstr "Exit without saving" @@ -678,32 +681,29 @@ msgstr "Refresh list" msgid "Add new" msgstr "Add new" -msgid "Explain the purpose of this data element and how it's measured." -msgstr "Explain the purpose of this data element and how it's measured." - -msgid "A data element name should be concise and easy to recognize." -msgstr "A data element name should be concise and easy to recognize." - msgid "Basic information" msgstr "Basic information" msgid "Set up the information for this data element group" msgstr "Set up the information for this data element group" +msgid "Explain the purpose of this data element group." +msgstr "Explain the purpose of this data element group." + msgid "@TODO" msgstr "@TODO" -msgid "Custom attributes" -msgstr "Custom attributes" +msgid "Custom fields for your DHIS2 instance" +msgstr "Custom fields for your DHIS2 instance" msgid "Selected data elements" msgstr "Selected data elements" -msgid "Explain the purpose of this data element group." -msgstr "Explain the purpose of this data element group." +msgid "Explain the purpose of this data element and how it's measured." +msgstr "Explain the purpose of this data element and how it's measured." -msgid "Custom fields for your DHIS2 instance" -msgstr "Custom fields for your DHIS2 instance" +msgid "A data element name should be concise and easy to recognize." +msgstr "A data element name should be concise and easy to recognize." msgid "Create data element" msgstr "Create data element" diff --git a/src/pages/dataElementGroupSets/Edit.tsx b/src/pages/dataElementGroupSets/Edit.tsx index 7a8d3700..0a16ea61 100644 --- a/src/pages/dataElementGroupSets/Edit.tsx +++ b/src/pages/dataElementGroupSets/Edit.tsx @@ -1,20 +1,23 @@ -import { useDataEngine, useDataQuery } from '@dhis2/app-runtime' +import { useDataQuery } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' -import { NoticeBox } from '@dhis2/ui' -import { FORM_ERROR, FormApi } from 'final-form' -import React, { useEffect, useRef } from 'react' +import { FormApi } from 'final-form' +import React from 'react' import { withTypes } from 'react-final-form' import { useNavigate, useParams } from 'react-router-dom' +import { Loader } from '../../components' import { - Loader, - StandardFormActions, - StandardFormSection, -} from '../../components' -import { SCHEMA_SECTIONS, getSectionPath, validate } from '../../lib' -import { JsonPatchOperation } from '../../types' -import { DataElementGroupSet } from '../../types/generated' -import { createJsonPatchOperations } from './edit/' -import classes from './Edit.module.css' + DefaultFormContents, + useCustomAttributesQuery, +} from '../../components/form' +import { + SCHEMA_SECTIONS, + getSectionPath, + usePatchModel, + validate, +} from '../../lib' +import { createJsonPatchOperations } from '../../lib/form/createJsonPatchOperations' +import { getAllAttributeValues } from '../../lib/models/attributes' +import { Attribute, DataElementGroupSet } from '../../types/generated' import { DataElementGroupSetFormFields, dataElementGroupSetSchema, @@ -29,172 +32,137 @@ type DataElementGroupSetQueryResponse = { dataElementGroupSet: DataElementGroupSet } -const listPath = `/${getSectionPath(SCHEMA_SECTIONS.dataElementGroupSet)}` +const section = SCHEMA_SECTIONS.dataElementGroupSet -function useDataElementGroupSetQuery(id: string) { - const DATA_ELEMENT_QUERY = { - dataElementGroupSet: { - resource: `dataElementGroupSets/${id}`, - params: { - fields: ['*', 'attributeValues[*]'], - }, +const query = { + dataElementGroupSet: { + resource: `dataElementGroupSets`, + id: ({ id }: Record) => id, + params: { + fields: ['*', 'attributeValues[*]'], }, - } + }, +} - return useDataQuery(DATA_ELEMENT_QUERY, { +function useDataElementGroupSetQuery(id: string) { + return useDataQuery(query, { variables: { id }, }) } -function usePatchDirtyFields() { - const dataEngine = useDataEngine() +function computeInitialValues({ + dataElementGroupSet, + customAttributes, +}: { + dataElementGroupSet: DataElementGroupSet + customAttributes: Attribute[] +}) { + if (!dataElementGroupSet) { + return {} + } - return async ({ - values, - dirtyFields, + // We want to have an array in the state with all attributes, not just the + // ones that are set + const attributeValues = getAllAttributeValues( dataElementGroupSet, - }: { - values: FormValues - dirtyFields: { [name: string]: boolean } - dataElementGroupSet: DataElementGroupSet - }) => { - const jsonPatchPayload = createJsonPatchOperations({ - values, - dirtyFields, - originalValue: dataElementGroupSet, - }) + customAttributes + ) - // We want the promise so we know when submitting is done. The promise - // returned by the mutation function of useDataMutation will never - // resolve - const ADD_NEW_DATA_ELEMENT_MUTATION = { - resource: 'dataElementGroupSets', - id: values.id, - type: 'json-patch', - data: ({ operations }: { operations: JsonPatchOperation[] }) => - operations, - } as const - - try { - await dataEngine.mutate(ADD_NEW_DATA_ELEMENT_MUTATION, { - variables: { operations: jsonPatchPayload }, - }) - } catch (e) { - return { [FORM_ERROR]: (e as Error | string).toString() } - } + return { + id: dataElementGroupSet.id, + name: dataElementGroupSet.name, + shortName: dataElementGroupSet.shortName, + code: dataElementGroupSet.code, + description: dataElementGroupSet.description, + compulsory: dataElementGroupSet.compulsory, + dataDimension: dataElementGroupSet.dataDimension, + dataElementGroups: dataElementGroupSet.dataElementGroups || [], + attributeValues, } } export const Component = () => { - const navigate = useNavigate() const params = useParams() const dataElementGroupSetId = params.id as string const dataElementGroupSetQuery = useDataElementGroupSetQuery( dataElementGroupSetId ) - const patchDirtyFields = usePatchDirtyFields() - - async function onSubmit(values: FormValues, form: FinalFormFormApi) { - const errors = await patchDirtyFields({ - values, - dirtyFields: form.getState().dirtyFields, - dataElementGroupSet: dataElementGroupSetQuery.data - ?.dataElementGroupSet as DataElementGroupSet, - }) + const attributesQuery = useCustomAttributesQuery() - if (errors) { - return errors - } - - navigate(listPath) - } - - const dataElementGroupSet = dataElementGroupSetQuery.data - ?.dataElementGroupSet as DataElementGroupSet - const initialValues = dataElementGroupSet - ? { - id: dataElementGroupSetId, - name: dataElementGroupSet.name, - shortName: dataElementGroupSet.shortName, - code: dataElementGroupSet.code, - description: dataElementGroupSet.description, - compulsory: dataElementGroupSet.compulsory, - dataDimension: dataElementGroupSet.dataDimension, - dataElementGroups: dataElementGroupSet.dataElementGroups || [], - } - : {} + const dataElementGroupSet = + dataElementGroupSetQuery.data?.dataElementGroupSet return ( -
{ - return validate(dataElementGroupSetSchema, values) - }} - initialValues={initialValues} + - {({ handleSubmit, submitting, submitError }) => ( - - - - )} - + +
) } -function FormContents({ - submitError, - submitting, +function DataElementGroupSetForm({ + dataElementGroupSet, + attributes, }: { - submitting: boolean - submitError?: string + dataElementGroupSet: DataElementGroupSet + attributes: Attribute[] }) { - const formErrorRef = useRef(null) const navigate = useNavigate() + const patchDirtyFields = usePatchModel( + dataElementGroupSet.id, + section.namePlural + ) + + async function onSubmit(values: FormValues, form: FinalFormFormApi) { + const jsonPatchOperations = createJsonPatchOperations({ + values, + dirtyFields: form.getState().dirtyFields, + originalValue: dataElementGroupSet, + }) + const errors = await patchDirtyFields(jsonPatchOperations) - useEffect(() => { - if (submitError) { - formErrorRef.current?.scrollIntoView({ behavior: 'smooth' }) + if (errors) { + return errors } - }, [submitError]) + + navigate(getSectionPath(section)) + } return ( - <> - {submitError && ( - -
- - {submitError} - -
-
+
{ + return validate(dataElementGroupSetSchema, values) + }} + initialValues={computeInitialValues({ + dataElementGroupSet, + customAttributes: attributes, + })} + > + {({ handleSubmit, submitting, submitError }) => ( + + + + +
)} - -
- -
- -
- navigate(listPath)} - /> -
- + ) } diff --git a/src/pages/dataElementGroupSets/New.tsx b/src/pages/dataElementGroupSets/New.tsx index d9002490..215e57d1 100644 --- a/src/pages/dataElementGroupSets/New.tsx +++ b/src/pages/dataElementGroupSets/New.tsx @@ -2,11 +2,17 @@ import { useDataEngine } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { NoticeBox } from '@dhis2/ui' import { FORM_ERROR } from 'final-form' -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useMemo, useRef } from 'react' import { Form } from 'react-final-form' import { useNavigate } from 'react-router-dom' -import { StandardFormActions, StandardFormSection } from '../../components' +import { + Loader, + StandardFormActions, + StandardFormSection, +} from '../../components' +import { useCustomAttributesQuery } from '../../components/form' import { SCHEMA_SECTIONS, getSectionPath, validate } from '../../lib' +import { Attribute } from '../../types/generated' import { DataElementGroupSetFormFields, dataElementGroupSetSchema, @@ -16,14 +22,26 @@ import classes from './New.module.css' const listPath = `/${getSectionPath(SCHEMA_SECTIONS.dataElementGroupSet)}` -const initialValues = { - name: '', - shortName: '', - code: '', - description: '', - compulsory: false, - dataDimension: false, - dataElementGroups: [], +function useInitialValues(customAttributes: Attribute[]) { + const attributeValues = useMemo( + () => + customAttributes.map((attribute) => ({ + attribute, + value: '', + })), + [customAttributes] + ) + + return { + name: '', + shortName: '', + code: '', + description: '', + compulsory: false, + dataDimension: false, + dataElementGroups: [], + attributeValues, + } } const ADD_NEW_DATA_ELEMENT_GROUP_MUTATION = { @@ -35,6 +53,8 @@ const ADD_NEW_DATA_ELEMENT_GROUP_MUTATION = { export function Component() { const dataEngine = useDataEngine() const navigate = useNavigate() + const customAttributesQuery = useCustomAttributesQuery() + const initialValues = useInitialValues(customAttributesQuery.data) const onSubmit = async (payload: FormValues) => { try { @@ -52,24 +72,29 @@ export function Component() { } return ( -
{ - return validate(dataElementGroupSetSchema, values) - }} - initialValues={initialValues} + - {({ handleSubmit, submitting, submitError }) => ( - - navigate(listPath)} - /> - - )} - +
{ + return validate(dataElementGroupSetSchema, values) + }} + initialValues={initialValues} + > + {({ handleSubmit, submitting, submitError }) => ( + + navigate(listPath)} + /> + + )} + +
) } diff --git a/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.spec.ts b/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.spec.ts deleted file mode 100644 index 422cf33c..00000000 --- a/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { createJsonPatchOperations } from './createJsonPatchOperations' - -describe('createJsonPatchOperations', () => { - describe('createJsonPatchOperations', () => { - it('should return an empty array if no dirty fields', () => { - const actual = createJsonPatchOperations({ - dirtyFields: {}, - originalValue: { id: 'foo' }, - values: {}, - }) - expect(actual).toEqual([]) - }) - - it('should return a json-patch payload for a single field', () => { - const actual = createJsonPatchOperations({ - dirtyFields: { name: true }, - originalValue: { - id: 'foo', - name: 'bar', - }, - values: { name: 'baz' }, - }) - const expected = [ - { - op: 'replace', - path: '/name', - value: 'baz', - }, - ] - expect(actual).toEqual(expected) - }) - - it('should return a json-patch payload with add if value does not exist in originalValue', () => { - const actual = createJsonPatchOperations({ - dirtyFields: { name: true }, - originalValue: { id: 'foo' }, - values: { name: 'baz' }, - }) - const expected = [ - { - op: 'add', - path: '/name', - value: 'baz', - }, - ] - expect(actual).toEqual(expected) - }) - }) -}) diff --git a/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.ts b/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.ts deleted file mode 100644 index bfdb1dea..00000000 --- a/src/pages/dataElementGroupSets/edit/createJsonPatchOperations.ts +++ /dev/null @@ -1,20 +0,0 @@ -import get from 'lodash/fp/get' -import { JsonPatchOperation } from '../../../types' - -interface FormatFormValuesArgs { - originalValue: unknown - dirtyFields: { [key in keyof FormValues]?: boolean } - values: FormValues -} - -export function createJsonPatchOperations({ - dirtyFields, - originalValue, - values, -}: FormatFormValuesArgs): JsonPatchOperation[] { - return Object.keys(dirtyFields).map((name) => ({ - op: get(name, originalValue) ? 'replace' : 'add', - path: `/${name.replace(/[.]/g, '/')}`, - value: get(name, values) || '', - })) -} diff --git a/src/pages/dataElementGroupSets/edit/index.ts b/src/pages/dataElementGroupSets/edit/index.ts deleted file mode 100644 index 0069ca37..00000000 --- a/src/pages/dataElementGroupSets/edit/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { createJsonPatchOperations } from './createJsonPatchOperations' diff --git a/src/pages/dataElementGroupSets/fields/CodeField.tsx b/src/pages/dataElementGroupSets/fields/CodeField.tsx deleted file mode 100644 index fedc4eb8..00000000 --- a/src/pages/dataElementGroupSets/fields/CodeField.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { InputFieldFF } from '@dhis2/ui' -import React from 'react' -import { Field as FieldRFF } from 'react-final-form' -import { useCheckMaxLengthFromSchema } from '../../../lib' -import type { SchemaName } from '../../../types' - -export function CodeField() { - const validate = useCheckMaxLengthFromSchema( - 'dataElement' as SchemaName, - 'code' - ) - - return ( - - ) -} diff --git a/src/pages/dataElementGroupSets/fields/DescriptionField.tsx b/src/pages/dataElementGroupSets/fields/DescriptionField.tsx deleted file mode 100644 index c11298b8..00000000 --- a/src/pages/dataElementGroupSets/fields/DescriptionField.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { TextAreaFieldFF } from '@dhis2/ui' -import React from 'react' -import { Field as FieldRFF } from 'react-final-form' -import { useCheckMaxLengthFromSchema } from '../../../lib' -import type { SchemaName } from '../../../types' - -export function DescriptionField() { - const validate = useCheckMaxLengthFromSchema( - 'dataElement' as SchemaName, - 'formName' - ) - - return ( - - ) -} diff --git a/src/pages/dataElementGroupSets/fields/NameField.tsx b/src/pages/dataElementGroupSets/fields/NameField.tsx deleted file mode 100644 index b6c38724..00000000 --- a/src/pages/dataElementGroupSets/fields/NameField.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { InputFieldFF } from '@dhis2/ui' -import React, { useMemo } from 'react' -import { Field as FieldRFF, useField } from 'react-final-form' -import { useParams } from 'react-router-dom' -import { - composeAsyncValidators, - required, - useCheckMaxLengthFromSchema, - useIsFieldValueUnique, -} from '../../../lib' -import { SchemaName } from '../../../types' -import type { FormValues } from '../form' - -function useValidator() { - const params = useParams() - const dataElementId = params.id as string - const checkIsValueTaken = useIsFieldValueUnique({ - model: 'dataElements', - field: 'name', - id: dataElementId, - }) - - const checkMaxLength = useCheckMaxLengthFromSchema( - SchemaName.dataElement, - 'name' - ) - - return useMemo( - () => - composeAsyncValidators([ - checkIsValueTaken, - checkMaxLength, - required, - ]), - [checkIsValueTaken, checkMaxLength] - ) -} - -export function NameField() { - const validator = useValidator() - const { meta } = useField('name', { - subscription: { validating: true }, - }) - - return ( - - loading={meta.validating} - component={InputFieldFF} - dataTest="dataelementsformfields-name" - required - inputWidth="400px" - label={i18n.t('{{fieldLabel}} (required)', { - fieldLabel: i18n.t('Name'), - })} - name="name" - helpText={i18n.t( - 'A data element name should be concise and easy to recognize.' - )} - validate={(name?: string) => validator(name)} - validateFields={[]} - /> - ) -} diff --git a/src/pages/dataElementGroupSets/fields/ShortNameField.tsx b/src/pages/dataElementGroupSets/fields/ShortNameField.tsx deleted file mode 100644 index 7e1845d5..00000000 --- a/src/pages/dataElementGroupSets/fields/ShortNameField.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import i18n from '@dhis2/d2-i18n' -import { InputFieldFF } from '@dhis2/ui' -import React, { useMemo } from 'react' -import { Field as FieldRFF, useField } from 'react-final-form' -import { useParams } from 'react-router-dom' -import { - composeAsyncValidators, - required, - useCheckMaxLengthFromSchema, - useIsFieldValueUnique, -} from '../../../lib' -import type { SchemaName } from '../../../types' -import type { FormValues } from '../form' - -function useValidator() { - const params = useParams() - const dataElementId = params.id as string - const checkIsValueTaken = useIsFieldValueUnique({ - model: 'dataElements', - field: 'name', - id: dataElementId, - }) - - const checkMaxLength = useCheckMaxLengthFromSchema( - 'dataElement' as SchemaName, - 'formName' - ) - - return useMemo( - () => - composeAsyncValidators([ - checkIsValueTaken, - checkMaxLength, - required, - ]), - [checkIsValueTaken, checkMaxLength] - ) -} - -export function ShortNameField() { - const validator = useValidator() - const { meta } = useField('shortName', { - subscription: { validating: true }, - }) - - return ( - - loading={meta.validating} - component={InputFieldFF} - dataTest="dataelementsformfields-shortname" - required - inputWidth="400px" - label={i18n.t('{{fieldLabel}} (required)', { - fieldLabel: i18n.t('Short name'), - })} - name="shortName" - helpText={i18n.t('Often used in reports where space is limited')} - validate={(name?: string) => validator(name)} - validateFields={[]} - /> - ) -} diff --git a/src/pages/dataElementGroupSets/fields/index.ts b/src/pages/dataElementGroupSets/fields/index.ts index a25ce40d..1100ca53 100644 --- a/src/pages/dataElementGroupSets/fields/index.ts +++ b/src/pages/dataElementGroupSets/fields/index.ts @@ -1,7 +1,3 @@ -export { CodeField } from './CodeField' export { CompulsoryField } from './CompulsoryField' export { DataDimensionField } from './DataDimensionField' export { DataElementGroupsField } from './DataElementGroupsField' -export { DescriptionField } from './DescriptionField' -export { NameField } from './NameField' -export { ShortNameField } from './ShortNameField' diff --git a/src/pages/dataElementGroupSets/form/DataElementGroupSetFormFields.tsx b/src/pages/dataElementGroupSets/form/DataElementGroupSetFormFields.tsx index af1911e6..85884b1f 100644 --- a/src/pages/dataElementGroupSets/form/DataElementGroupSetFormFields.tsx +++ b/src/pages/dataElementGroupSets/form/DataElementGroupSetFormFields.tsx @@ -1,21 +1,25 @@ import i18n from '@dhis2/d2-i18n' import React from 'react' import { + CustomAttributes, StandardFormSection, StandardFormSectionTitle, StandardFormSectionDescription, StandardFormField, } from '../../../components' import { - CodeField, + DefaultIdentifiableFields, + DescriptionField, +} from '../../../components/form' +import { SCHEMA_SECTIONS } from '../../../lib' +import { CompulsoryField, DataDimensionField, DataElementGroupsField, - DescriptionField, - NameField, - ShortNameField, } from '../fields' +const schemaSection = SCHEMA_SECTIONS.dataElementGroupSet + export function DataElementGroupSetFormFields() { return ( <> @@ -30,20 +34,15 @@ export function DataElementGroupSetFormFields() { )} - - - + - - - - - - - - - + @@ -57,7 +56,7 @@ export function DataElementGroupSetFormFields() { - {i18n.t('Data elements')} + {i18n.t('Data element groups')} @@ -68,6 +67,18 @@ export function DataElementGroupSetFormFields() { + + + + {i18n.t('Custom attributes')} + + + + {i18n.t('Custom fields for your DHIS2 instance')} + + + + ) } diff --git a/src/pages/dataElementGroups/Edit.tsx b/src/pages/dataElementGroups/Edit.tsx index 5cc5a026..052e8352 100644 --- a/src/pages/dataElementGroups/Edit.tsx +++ b/src/pages/dataElementGroups/Edit.tsx @@ -64,8 +64,6 @@ function computeInitialValues({ dataElementGroup, customAttributes ) - console.log('> customAttributes', customAttributes) - console.log('> attributeValues', attributeValues) return { id: dataElementGroup.id,