-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add data element group New and Edit views
- Loading branch information
Showing
10 changed files
with
284 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
src/pages/dataElementGroups/edit/createJsonPatchOperations.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
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) | ||
}) | ||
}) | ||
}) |
20 changes: 20 additions & 0 deletions
20
src/pages/dataElementGroups/edit/createJsonPatchOperations.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import get from 'lodash/fp/get' | ||
import { JsonPatchOperation } from '../../../types' | ||
|
||
interface FormatFormValuesArgs<FormValues> { | ||
originalValue: unknown | ||
dirtyFields: { [key in keyof FormValues]?: boolean } | ||
values: FormValues | ||
} | ||
|
||
export function createJsonPatchOperations<FormValues>({ | ||
dirtyFields, | ||
originalValue, | ||
values, | ||
}: FormatFormValuesArgs<FormValues>): JsonPatchOperation[] { | ||
return Object.keys(dirtyFields).map((name) => ({ | ||
op: get(name, originalValue) ? 'replace' : 'add', | ||
path: `/${name.replace(/[.]/g, '/')}`, | ||
value: get(name, values) || '', | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { createJsonPatchOperations } from './createJsonPatchOperations' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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 ( | ||
<FieldRFF | ||
component={InputFieldFF} | ||
dataTest="dataelementsformfields-code" | ||
inputWidth="150px" | ||
name="code" | ||
label={i18n.t('Code')} | ||
validateFields={[]} | ||
validate={validate} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
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 ( | ||
<FieldRFF | ||
component={TextAreaFieldFF} | ||
dataTest="dataelementsformfields-description" | ||
inputWidth="400px" | ||
name="description" | ||
label={i18n.t('Description')} | ||
helpText={i18n.t( | ||
"Explain the purpose of this data element and how it's measured." | ||
)} | ||
validate={validate} | ||
validateFields={[]} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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<string, FormValues>([ | ||
checkIsValueTaken, | ||
checkMaxLength, | ||
required, | ||
]), | ||
[checkIsValueTaken, checkMaxLength] | ||
) | ||
} | ||
|
||
export function NameField() { | ||
const validator = useValidator() | ||
const { meta } = useField('name', { | ||
subscription: { validating: true }, | ||
}) | ||
|
||
return ( | ||
<FieldRFF<string | undefined> | ||
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={[]} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
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<string, FormValues>([ | ||
checkIsValueTaken, | ||
checkMaxLength, | ||
required, | ||
]), | ||
[checkIsValueTaken, checkMaxLength] | ||
) | ||
} | ||
|
||
export function ShortNameField() { | ||
const validator = useValidator() | ||
const { meta } = useField('shortName', { | ||
subscription: { validating: true }, | ||
}) | ||
|
||
return ( | ||
<FieldRFF<string | undefined> | ||
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={[]} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { setIn } from 'final-form' | ||
import { dataElementGroupSchema } from './dataElementGroupSchema' | ||
import type { FormValues } from './types' | ||
|
||
// @TODO: Figure out if there's a utility for this? I couldn't find one | ||
function segmentsToPath(segments: Array<string | number>) { | ||
return segments.reduce((path, segment) => { | ||
return typeof segment === 'number' | ||
? `${path}[${segment}]` | ||
: `${path}.${segment}` | ||
}) as string | ||
} | ||
|
||
export function validate(values: FormValues) { | ||
const zodResult = dataElementGroupSchema.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 | ||
} |