Skip to content

Commit

Permalink
feat: add data element group New and Edit views
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammer5 committed Mar 5, 2024
1 parent 40deb7f commit ebb78c1
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 3 deletions.
10 changes: 8 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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:21:50.129Z\n"
"PO-Revision-Date: 2024-03-05T09:21:50.129Z\n"
"POT-Creation-Date: 2024-03-05T09:22:03.031Z\n"
"PO-Revision-Date: 2024-03-05T09:22:03.031Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -672,6 +672,12 @@ 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"

Expand Down
1 change: 0 additions & 1 deletion src/pages/dataElementGroups/Edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { withTypes } from 'react-final-form'
import { useNavigate, useParams } from 'react-router-dom'
import { Loader } from '../../components'
import {
CustomAttributes,
DefaultFormContents,
useCustomAttributesQuery,
} from '../../components/form'
Expand Down
49 changes: 49 additions & 0 deletions src/pages/dataElementGroups/edit/createJsonPatchOperations.spec.ts
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 src/pages/dataElementGroups/edit/createJsonPatchOperations.ts
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) || '',
}))
}
1 change: 1 addition & 0 deletions src/pages/dataElementGroups/edit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createJsonPatchOperations } from './createJsonPatchOperations'
25 changes: 25 additions & 0 deletions src/pages/dataElementGroups/fields/CodeField.tsx
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}
/>
)
}
28 changes: 28 additions & 0 deletions src/pages/dataElementGroups/fields/DescriptionField.tsx
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={[]}
/>
)
}
64 changes: 64 additions & 0 deletions src/pages/dataElementGroups/fields/NameField.tsx
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={[]}
/>
)
}
62 changes: 62 additions & 0 deletions src/pages/dataElementGroups/fields/ShortNameField.tsx
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={[]}
/>
)
}
27 changes: 27 additions & 0 deletions src/pages/dataElementGroups/form/validate.ts
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
}

0 comments on commit ebb78c1

Please sign in to comment.