Skip to content

Commit 68566e4

Browse files
authored
feat: add category options form and list (#419)
* feat(categoryOptions): add categoryOptions form * feat: add filters to categoryOption list * fix: add form-name field, add availability header * fix: blur datefield on date select * fix: improve getDefaults type * refactor: use common attributeValues field-filter * fix(datefield): useSystemSettings instead of settings * fix(datefield): fix inputWidth * fix: update multi-calendar-dates dep * fix(orgunitfield): make orgUnit model available * fix(categoryOption): fix endDate validation * fix: update ui to alpha * style: sort imports * style: fix lint * style: cleanup * fix: minor cleanup
1 parent 683013a commit 68566e4

File tree

28 files changed

+882
-461
lines changed

28 files changed

+882
-461
lines changed

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
},
4040
"dependencies": {
4141
"@dhis2/app-runtime": "^3.9.3",
42-
"@dhis2/ui": "^9.11.3",
42+
"@dhis2/multi-calendar-dates": "^2.0.0-alpha.5",
43+
"@dhis2/ui": "^10.0.0-alpha.3",
4344
"@tanstack/react-table": "^8.16.0",
4445
"@types/lodash": "^4.14.198",
4546
"lodash": "^4.17.21",
@@ -53,6 +54,8 @@
5354
"zustand": "^4.4.0"
5455
},
5556
"resolutions": {
56-
"eslint": "^8"
57+
"eslint": "^8",
58+
"@dhis2/multi-calendar-dates": "^2.0.0-alpha.5",
59+
"@dhis2/ui": "^10.0.0-alpha.3"
5760
}
5861
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { CalendarInput, CalendarInputProps } from '@dhis2/ui'
2+
import React from 'react'
3+
import { useField } from 'react-final-form'
4+
import { selectedLocale, useSystemSetting } from '../../../lib'
5+
6+
type DateFieldProps = Omit<
7+
CalendarInputProps,
8+
'name' | 'calendar' | 'onDateSelect' | 'date'
9+
> & {
10+
name: string
11+
// this is not exposed in CalendarInputProps - but it should be
12+
label?: string
13+
}
14+
export function DateField({
15+
name,
16+
label,
17+
...calendarInputProps
18+
}: DateFieldProps) {
19+
const calendar = useSystemSetting('keyCalendar')
20+
const locale = selectedLocale
21+
const { meta, input } = useField<string | undefined>(name, {
22+
format: (value) => {
23+
if (value) {
24+
return value.slice(0, 10)
25+
}
26+
return value
27+
},
28+
})
29+
30+
const handleChange: CalendarInputProps['onDateSelect'] = (payload) => {
31+
input.onChange(payload?.calendarDateString)
32+
input.onBlur()
33+
}
34+
35+
return (
36+
<div style={{ width: '400px' }}>
37+
{/* TODO: we can remove style above, once inputWidth for CalendarInput is fixed */}
38+
<CalendarInput
39+
date={input.value}
40+
name={name}
41+
calendar={calendar as CalendarInputProps['calendar']}
42+
onDateSelect={handleChange}
43+
timeZone={'utc'}
44+
locale={locale}
45+
error={meta.touched && meta.invalid && meta.error}
46+
validationText={meta.touched && meta.error}
47+
onBlur={(_, e) => input.onBlur(e)}
48+
clearable
49+
label={label}
50+
{...calendarInputProps}
51+
/>
52+
</div>
53+
)
54+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import i18n from '@dhis2/d2-i18n'
2+
import {
3+
Field,
4+
OrganisationUnitTree,
5+
OrganisationUnitTreeProps,
6+
} from '@dhis2/ui'
7+
import React from 'react'
8+
import { useField } from 'react-final-form'
9+
import { useCurrentUserRootOrgUnits } from '../../../lib/user/currentUserStore'
10+
11+
type OrganisationUnitFieldProps = {
12+
name?: string
13+
}
14+
15+
type OrganisationUnitFormValue = {
16+
id: string
17+
path: string
18+
displayName: string
19+
}
20+
21+
export const OrganisationUnitField = ({ name }: OrganisationUnitFieldProps) => {
22+
const { input, meta } = useField<
23+
OrganisationUnitFormValue[],
24+
HTMLElement,
25+
OrganisationUnitFormValue[]
26+
>(name ?? 'organisationUnits')
27+
28+
const roots = useCurrentUserRootOrgUnits()
29+
30+
const rootIds = roots.map((ou) => ou.id)
31+
32+
const handleChange: OrganisationUnitTreeProps['onChange'] = ({
33+
selected,
34+
displayName,
35+
id,
36+
path,
37+
}) => {
38+
const prevSelected = new Map(input.value.map((ou) => [ou.path, ou]))
39+
const newSelected = selected.map((selectedPath) => {
40+
const prev = prevSelected.get(selectedPath)
41+
return prev ?? { id, path, displayName }
42+
})
43+
44+
input.onChange(newSelected)
45+
input.onBlur()
46+
}
47+
48+
const selectedPaths = input.value?.map((ou) => ou.path) ?? []
49+
50+
return (
51+
<Field
52+
label={i18n.t('Select organisation units')}
53+
error={meta.touched && meta.error}
54+
validationText={meta.touched && meta.error}
55+
>
56+
<OrganisationUnitTree
57+
roots={rootIds}
58+
onChange={handleChange}
59+
selected={selectedPaths}
60+
initiallyExpanded={rootIds}
61+
/>
62+
</Field>
63+
)
64+
}

src/components/form/fields/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { NameField } from './NameField'
33
export { ShortNameField } from './ShortNameField'
44
export { CodeField } from './CodeField'
55
export { DescriptionField } from './DescriptionField'
6+
export { OrganisationUnitField } from './OrganisationUnitField'

src/components/sectionList/filters/DynamicFilters.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
PublicAccessFilter,
1010
DataDimensionTypeFilter,
1111
Categoryfilter,
12+
CategoryOptionGroupFilter,
1213
} from './filterSelectors'
1314
import { useFilterKeys } from './useFilterKeys'
1415

@@ -17,6 +18,7 @@ type FilterKeyToComponentMap = Partial<Record<ConfigurableFilterKey, React.FC>>
1718
const filterKeyToComponentMap: FilterKeyToComponentMap = {
1819
category: Categoryfilter,
1920
categoryCombo: CategoryComboFilter,
21+
categoryOptionGroup: CategoryOptionGroupFilter,
2022
dataSet: DataSetFilter,
2123
domainType: DomainTypeSelectionFilter,
2224
valueType: ValueTypeSelectionFilter,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import i18n from '@dhis2/d2-i18n'
2+
import React from 'react'
3+
import { useSectionListFilter } from '../../../../lib'
4+
import { createFilterDataQuery } from './createFilterDataQuery'
5+
import { ModelFilterSelect } from './ModelFilter'
6+
7+
const query = createFilterDataQuery('categoryOptionGroups')
8+
9+
export const CategoryOptionGroupFilter = () => {
10+
const [filter, setFilter] = useSectionListFilter('categoryOptionGroup')
11+
12+
const selected = filter?.[0]
13+
14+
return (
15+
<ModelFilterSelect
16+
placeholder={i18n.t('Category option group')}
17+
query={query}
18+
selected={selected}
19+
onChange={({ selected }) =>
20+
setFilter(selected ? [selected] : undefined)
21+
}
22+
/>
23+
)
24+
}

src/components/sectionList/filters/filterSelectors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './IdentifiableFilter'
55
export * from './ConstantSelectionFilter'
66
export * from './PublicAccessFilter'
77
export * from './CategoryFilter'
8+
export * from './CategoryOptionGroupFilter'

src/lib/form/label.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import i18n from '@dhis2/d2-i18n'
2+
3+
/**
4+
* Utility function to get the required label.
5+
*
6+
* Note that label should already be translated
7+
*/
8+
export const getRequiredLabel = (label: string) => {
9+
return i18n.t('{{label}} (required)', { label })
10+
}

src/lib/form/validate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ export function validate<FormValues>(
1919
return allFormErrors
2020
}
2121

22-
export function createFormValidate<FormValues>(zodSchema: z.AnyZodObject) {
23-
return (values: FormValues) => validate(zodSchema, values)
22+
export function createFormValidate(zodSchema: z.ZodTypeAny) {
23+
return <FormValues>(values: FormValues) => validate(zodSchema, values)
2424
}

src/lib/localStorage/index.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)