Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dataset setup fields #468

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-12-09T08:26:30.983Z\n"
"PO-Revision-Date: 2024-12-09T08:26:30.983Z\n"
"POT-Creation-Date: 2024-12-11T08:38:02.793Z\n"
"PO-Revision-Date: 2024-12-11T08:38:02.793Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -1234,6 +1234,12 @@ msgstr "Configure data elements"
msgid "Choose what data is collected for this data set."
msgstr "Choose what data is collected for this data set."

msgid "Data set disaggregation"
msgstr "Data set disaggregation"

msgid "Choose an optional category combination to disaggregate the entire data set."
msgstr "Choose an optional category combination to disaggregate the entire data set."

msgid "Configure data entry periods"
msgstr "Configure data entry periods"

Expand Down
4 changes: 3 additions & 1 deletion src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ function createSectionLazyRouteFunction(
return async () => {
try {
return await import(
`../../pages/${section.namePlural}/${componentFileName}`
`../../pages/${
section.routeName || section.namePlural
}/${componentFileName}`
)
} catch (e) {
// means the component is not implemented yet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import i18n from '@dhis2/d2-i18n'
import React, { forwardRef } from 'react'
import { ModelSingleSelect } from '../ModelSingleSelect'
import type { ModelSingleSelectProps } from '../ModelSingleSelect'
import { useInitialOptionQuery } from './useInitialOptionQuery'
import { useOptionsQuery } from './useOptionsQuery'
import { useCategoryCombosQuery } from './useCategoryCombosQuery'
import { useInitialCategoryComboQuery } from './useInitialCategoryComboQuery'

type CategoryComboSelectProps = Omit<
ModelSingleSelectProps,
Expand All @@ -30,8 +30,8 @@ export const CategoryComboSelect = forwardRef(function CategoryComboSelect(
required={required}
invalid={invalid}
disabled={disabled}
useInitialOptionQuery={useInitialOptionQuery}
useOptionsQuery={useOptionsQuery}
useInitialOptionQuery={useInitialCategoryComboQuery}
useOptionsQuery={useCategoryCombosQuery}
placeholder={placeholder}
showAllOption={showAllOption}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const DEFAULT_CATEGORY_SELECT_OPTION = {
label: DEFAULT_CATEGORY_COMBO.displayName,
}

export function useOptionsQuery() {
export function useCategoryCombosQuery() {
const [loadedOptions, setLoadedOptions] = useState<SelectOption[]>([])
// The gist doesn't include the `isDefault` value, need to use `useDataQuery`
const queryResult = useDataQuery<CategoryComboQueryResult>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type InitialCategoryComboQueryResult = {
categoryCombo: FilteredCategoryCombo
}

const INITIAL_OPTION_QUERY = {
const INITIAL_CATEGORY_COMBO_QUERY = {
categoryCombo: {
resource: 'categoryCombos',
id: (variables: Record<string, string>) => variables.id,
Expand All @@ -18,23 +18,26 @@ const INITIAL_OPTION_QUERY = {
},
}

export function useInitialOptionQuery({
export function useInitialCategoryComboQuery({
selected,
onComplete,
}: {
onComplete: (option: SelectOption) => void
selected?: string
}) {
const initialSelected = useRef(selected)
return useDataQuery<InitialCategoryComboQueryResult>(INITIAL_OPTION_QUERY, {
lazy:
!initialSelected.current ||
initialSelected.current === DEFAULT_CATEGORY_COMBO.id,
variables: { id: selected },
onComplete: (data) => {
const categoryCombo = data.categoryCombo
const { id: value, displayName: label } = categoryCombo
onComplete({ value, label })
},
})
return useDataQuery<InitialCategoryComboQueryResult>(
INITIAL_CATEGORY_COMBO_QUERY,
{
lazy:
!initialSelected.current ||
initialSelected.current === DEFAULT_CATEGORY_COMBO.id,
variables: { id: selected },
onComplete: (data) => {
const categoryCombo = data.categoryCombo
const { id: value, displayName: label } = categoryCombo
onComplete({ value, label })
},
}
)
}
3 changes: 2 additions & 1 deletion src/lib/constants/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export const SCHEMA_SECTIONS = {
// @ts-expect-error temporary route for testing
dataSetWIP: {
name: SchemaName.dataSet,
namePlural: 'dataSetsWip',
routeName: 'dataSetsWip',
namePlural: 'dataSets',
title: i18n.t('Data set'),
titlePlural: i18n.t('Data sets'),
parentSectionKey: 'dataSet',
Expand Down
17 changes: 7 additions & 10 deletions src/lib/optionSet/useOptionSetsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type OptionSetQueryResult = {
}
}

const CATEGORY_COMBOS_QUERY = {
const OPTION_SETS_QUERY = {
optionSets: {
resource: 'optionSets',
params: (variables: Record<string, string>) => {
Expand All @@ -35,15 +35,12 @@ const CATEGORY_COMBOS_QUERY = {

export function useOptionSetsQuery() {
const [loadedOptions, setLoadedOptions] = useState<SelectOption[]>([])
const queryResult = useDataQuery<OptionSetQueryResult>(
CATEGORY_COMBOS_QUERY,
{
variables: {
page: 1,
filter: '',
},
}
)
const queryResult = useDataQuery<OptionSetQueryResult>(OPTION_SETS_QUERY, {
variables: {
page: 1,
filter: '',
},
})
const { data } = queryResult

// Must be done in `useEffect` and not in `onComplete`, as `onComplete`
Expand Down
2 changes: 1 addition & 1 deletion src/lib/routeUtils/routePaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getSectionPath = (section: Section | string) => {
if (typeof section === 'string') {
return section
}
return section.namePlural
return section.routeName || section.namePlural
}

export const getSectionNewPath = (section: Section | string) => {
Expand Down
24 changes: 24 additions & 0 deletions src/pages/dataSetsWip/form/CategoryComboField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import i18n from '@dhis2/d2-i18n'
import { Field, InputFieldFF } from '@dhis2/ui'
import React from 'react'
import { Field as FieldRFF, useField } from 'react-final-form'

export function CategoryComboField() {
const validate = () => {}
const { input, meta } = useField('categoryCombo', {
validateFields: [],
validate,
format: (categoryCombo) => categoryCombo && categoryCombo.id,
parse: (id) => ({ id }),
})
return (
<FieldRFF<string | undefined>
component={InputFieldFF}
inputWidth="400px"
label={i18n.t('{{fieldLabel}} (required)', {
fieldLabel: i18n.t('Category combination'),
})}
name="categoryCombo.id"
/>
)
}
23 changes: 20 additions & 3 deletions src/pages/dataSetsWip/form/DataSetFormContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ import {
import {
SECTIONS_MAP,
useSectionedFormContext,
useSelectedSection,
useSyncSelectedSectionWithScroll,
} from '../../../lib'
import { ColorAndIconField } from '../../dataElements/fields'
import { CategoryComboField } from './CategoryComboField'
import { DataSetFormDescriptor } from './formDescriptor'
import { PeriodTypeField } from './PeriodTypeField'

const section = SECTIONS_MAP.dataSet

export const DataSetFormContents = () => {
const descriptor = useSectionedFormContext<typeof DataSetFormDescriptor>()
useSyncSelectedSectionWithScroll()
const [selectedSection] = useSelectedSection()
return (
<>
<SectionedFormSections>
Expand All @@ -41,7 +42,12 @@ export const DataSetFormContents = () => {
)}
</StandardFormSectionDescription>
<DefaultIdentifiableFields />
<DescriptionField schemaSection={section} />
<StandardFormField>
<DescriptionField schemaSection={section} />
</StandardFormField>
<StandardFormField>
<ColorAndIconField />
</StandardFormField>
</SectionedFormSection>
<SectionedFormSection name={descriptor.getSection('data').name}>
<StandardFormSectionTitle>
Expand All @@ -60,6 +66,16 @@ export const DataSetFormContents = () => {
}}
/>
</StandardFormField>
<div style={{ height: 24 }} />
<StandardFormSectionTitle>
{i18n.t('Data set disaggregation')}
</StandardFormSectionTitle>
<StandardFormSectionDescription>
{i18n.t(
'Choose an optional category combination to disaggregate the entire data set.'
)}
</StandardFormSectionDescription>
<CategoryComboField />
</SectionedFormSection>
<SectionedFormSection
name={descriptor.getSection('periods').name}
Expand All @@ -72,6 +88,7 @@ export const DataSetFormContents = () => {
'Choose for what time periods data can be entered for this data set'
)}
</StandardFormSectionDescription>
<PeriodTypeField />
</SectionedFormSection>
<SectionedFormSection
name={descriptor.getSection('validation').name}
Expand Down
16 changes: 16 additions & 0 deletions src/pages/dataSetsWip/form/New.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { generateDefaultAddFormTests } from '../../defaultFormTests'

generateDefaultAddFormTests({ componentName: 'Data set' })

xdescribe('Data set add form additional tests', () => {
it('contain all needed field', () => {})
it('should not submit when required values are missing', () => {})
it('should submit the data and return to the list view on success', () => {})
it('should show an error if name field is too long', () => {})
it('should show an error if short name field is too long', () => {})
it('should show an error if code field is too long', () => {})
it('should show an error if description field is too long', () => {})
it('should show an error if name field is already in use', () => {})
it('should show an error if short name field is already in use', () => {})
it('should show an error if code field is already in use', () => {})
})
47 changes: 47 additions & 0 deletions src/pages/dataSetsWip/form/PeriodTypeField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import i18n from '@dhis2/d2-i18n'
import { InputFieldFF } from '@dhis2/ui'
import React from 'react'
import { Field as FieldRFF, useField } from 'react-final-form'

export function PeriodTypeField() {
const validate = () => {}
const { input, meta } = useField('categoryCombo', {
validateFields: [],
validate,
format: (categoryCombo) => categoryCombo && categoryCombo.id,
parse: (id) => ({ id }),
})
return (
<FieldRFF<string | undefined>
component={InputFieldFF}
inputWidth="400px"
label={i18n.t('Period type')}
name="periodType"
/>

// <Field
// required
// name="periodType"
// label={i18n.t('{{fieldLabel}} (required)', {
// fieldLabel: i18n.t('Period type'),
// })}
// error={meta.touched && !!meta.error}
// validationText={meta.touched ? meta.error : undefined}
// >
//
// <ModelSingleSelect
// required
// useInitialOptionQuery={useInitialCategoryComboQuery}
// useOptionsQuery={useCategoryCombosQuery}
// invalid={meta.touched && !!meta.error}
// selected={input.value}
// onChange={({ selected }) => {
// input.onChange(selected)
// input.onBlur()
// }}
// onBlur={input.onBlur}
// onFocus={input.onFocus}
// />
// </Field>
)
}
13 changes: 10 additions & 3 deletions src/pages/dataSetsWip/form/dataSetFormSchema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { z } from 'zod'
import { getDefaults, modelFormSchemas } from '../../../lib'
import {
DEFAULT_CATEGORY_COMBO,
getDefaults,
modelFormSchemas,
} from '../../../lib'
import { createFormValidate } from '../../../lib/form/validate'

const { withAttributeValues, identifiable, style, referenceCollection } =
Expand All @@ -10,10 +14,13 @@ export const dataSetFormSchema = identifiable
.extend({
id: z.string().optional(),
code: z.string().trim().optional(),
description: z.string().trim().optional(),
description: z.string().trim().max(2000).optional(),
style,
dataElements: referenceCollection.default([]),
// categoryCombo: z.object({ id: z.string() }),
categoryCombo: z
.object({ id: z.string() })
.default({ id: DEFAULT_CATEGORY_COMBO.id }),
periodType: z.string().default('Monthly'),
})

export const initialValues = getDefaults(dataSetFormSchema)
Expand Down
1 change: 1 addition & 0 deletions src/types/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface SectionBase {
namePlural: string
titlePlural: string
title: string
routeName?: string
}

// SchemaSection is a section that can be mapped directly to a schema by the name
Expand Down
Loading