diff --git a/i18n/en.pot b/i18n/en.pot index 07b91e70..27592463 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-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" @@ -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" diff --git a/src/app/routes/Router.tsx b/src/app/routes/Router.tsx index 6d3df47c..cf1c28b9 100644 --- a/src/app/routes/Router.tsx +++ b/src/app/routes/Router.tsx @@ -74,7 +74,9 @@ function createSectionLazyRouteFunction( return async () => { try { return await import( - `../../pages/${section.routeName || section.namePlural}/${componentFileName}` + `../../pages/${ + section.routeName || section.namePlural + }/${componentFileName}` ) } catch (e) { // means the component is not implemented yet diff --git a/src/components/metadataFormControls/CategoryComboSelect/CategoryComboSelect.tsx b/src/components/metadataFormControls/CategoryComboSelect/CategoryComboSelect.tsx index f2665b02..1923e294 100644 --- a/src/components/metadataFormControls/CategoryComboSelect/CategoryComboSelect.tsx +++ b/src/components/metadataFormControls/CategoryComboSelect/CategoryComboSelect.tsx @@ -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 { useInitialCategoryComboQuery } from './useInitialCategoryComboQuery' +import { useCategoryCombosQuery } from './useCategoryCombosQuery' type CategoryComboSelectProps = Omit< ModelSingleSelectProps, @@ -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} diff --git a/src/components/metadataFormControls/CategoryComboSelect/useOptionsQuery.ts b/src/components/metadataFormControls/CategoryComboSelect/useCategoryCombosQuery.ts similarity index 98% rename from src/components/metadataFormControls/CategoryComboSelect/useOptionsQuery.ts rename to src/components/metadataFormControls/CategoryComboSelect/useCategoryCombosQuery.ts index 506b22d7..d3b3d077 100644 --- a/src/components/metadataFormControls/CategoryComboSelect/useOptionsQuery.ts +++ b/src/components/metadataFormControls/CategoryComboSelect/useCategoryCombosQuery.ts @@ -44,7 +44,7 @@ const DEFAULT_CATEGORY_SELECT_OPTION = { label: DEFAULT_CATEGORY_COMBO.displayName, } -export function useOptionsQuery() { +export function useCategoryCombosQuery({ filter } = {}) { const [loadedOptions, setLoadedOptions] = useState([]) // The gist doesn't include the `isDefault` value, need to use `useDataQuery` const queryResult = useDataQuery( diff --git a/src/components/metadataFormControls/CategoryComboSelect/useInitialCategoryComboQuery.ts b/src/components/metadataFormControls/CategoryComboSelect/useInitialCategoryComboQuery.ts new file mode 100644 index 00000000..2413772d --- /dev/null +++ b/src/components/metadataFormControls/CategoryComboSelect/useInitialCategoryComboQuery.ts @@ -0,0 +1,56 @@ +import { useDataQuery } from '@dhis2/app-runtime' +import { useRef } from 'react' +import { DEFAULT_CATEGORY_COMBO } from '../../../lib' +import { SelectOption } from '../../../types' +import { FilteredCategoryCombo } from './types' + +type InitialCategoryComboQueryResult = { + categoryCombo: FilteredCategoryCombo +} + +const INITIAL_CATEGORY_COMBO_QUERY = { + categoryCombo: { + resource: 'categoryCombos', + id: (variables: Record) => variables.id, + params: (variables: Record) => { + const params = { + fields: ['id', 'displayName'], + } + + if (variables.filter?.dataDimensionType) { + params.filter = [ + ...params.filter, + `dataDimensionType:eq:${variables.filter.dataDimensionType}`, + ] + } + + return params + }, + }, +} + +export function useInitialCategoryComboQuery({ + selected, + onComplete, + filter, +}: { + onComplete: (option: SelectOption) => void + selected?: string + filter?: Record +}) { + const initialSelected = useRef(selected) + return useDataQuery( + INITIAL_CATEGORY_COMBO_QUERY, + { + lazy: + !initialSelected.current || + initialSelected.current === DEFAULT_CATEGORY_COMBO.id, + variables: { id: selected, filter }, + onComplete: (data) => { + const categoryCombo = data.categoryCombo + const { id: value, displayName: label } = categoryCombo + onComplete({ value, label }) + }, + } + ) +} diff --git a/src/components/metadataFormControls/CategoryComboSelect/useInitialOptionQuery.ts b/src/components/metadataFormControls/CategoryComboSelect/useInitialOptionQuery.ts deleted file mode 100644 index b9795def..00000000 --- a/src/components/metadataFormControls/CategoryComboSelect/useInitialOptionQuery.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useDataQuery } from '@dhis2/app-runtime' -import { useRef } from 'react' -import { DEFAULT_CATEGORY_COMBO } from '../../../lib' -import { SelectOption } from '../../../types' -import { FilteredCategoryCombo } from './types' - -type InitialCategoryComboQueryResult = { - categoryCombo: FilteredCategoryCombo -} - -const INITIAL_OPTION_QUERY = { - categoryCombo: { - resource: 'categoryCombos', - id: (variables: Record) => variables.id, - params: { - fields: ['id', 'displayName'], - }, - }, -} - -export function useInitialOptionQuery({ - selected, - onComplete, -}: { - onComplete: (option: SelectOption) => void - selected?: string -}) { - const initialSelected = useRef(selected) - return useDataQuery(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 }) - }, - }) -} diff --git a/src/lib/optionSet/useOptionSetsQuery.ts b/src/lib/optionSet/useOptionSetsQuery.ts index a719d2ed..57858069 100644 --- a/src/lib/optionSet/useOptionSetsQuery.ts +++ b/src/lib/optionSet/useOptionSetsQuery.ts @@ -10,7 +10,7 @@ type OptionSetQueryResult = { } } -const CATEGORY_COMBOS_QUERY = { +const OPTION_SETS_QUERY = { optionSets: { resource: 'optionSets', params: (variables: Record) => { @@ -35,15 +35,12 @@ const CATEGORY_COMBOS_QUERY = { export function useOptionSetsQuery() { const [loadedOptions, setLoadedOptions] = useState([]) - const queryResult = useDataQuery( - CATEGORY_COMBOS_QUERY, - { - variables: { - page: 1, - filter: '', - }, - } - ) + const queryResult = useDataQuery(OPTION_SETS_QUERY, { + variables: { + page: 1, + filter: '', + }, + }) const { data } = queryResult // Must be done in `useEffect` and not in `onComplete`, as `onComplete` diff --git a/src/lib/routeUtils/routePaths.ts b/src/lib/routeUtils/routePaths.ts index 75543861..b1f1a466 100644 --- a/src/lib/routeUtils/routePaths.ts +++ b/src/lib/routeUtils/routePaths.ts @@ -14,7 +14,6 @@ export const getSectionPath = (section: Section | string) => { return section } return section.routeName || section.namePlural - } export const getSectionNewPath = (section: Section | string) => { diff --git a/src/pages/dataSetsWip/form/CategoryComboField.tsx b/src/pages/dataSetsWip/form/CategoryComboField.tsx new file mode 100644 index 00000000..b1dde505 --- /dev/null +++ b/src/pages/dataSetsWip/form/CategoryComboField.tsx @@ -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 ( + + component={InputFieldFF} + inputWidth="400px" + label={i18n.t('{{fieldLabel}} (required)', { + fieldLabel: i18n.t('Category combination'), + })} + name="categoryCombo.id" + /> + ) +} diff --git a/src/pages/dataSetsWip/form/DataSetFormContents.tsx b/src/pages/dataSetsWip/form/DataSetFormContents.tsx index d4fe1660..1b183cb0 100644 --- a/src/pages/dataSetsWip/form/DataSetFormContents.tsx +++ b/src/pages/dataSetsWip/form/DataSetFormContents.tsx @@ -19,13 +19,15 @@ import { useSyncSelectedSectionWithScroll, } from '../../../lib' import { DataSetFormDescriptor } from './formDescriptor' +import { CategoryComboField } from './CategoryComboField' +import { PeriodTypeField } from './PeriodTypeField' +import { ColorAndIconField } from '../../dataElements/fields' const section = SECTIONS_MAP.dataSet export const DataSetFormContents = () => { const descriptor = useSectionedFormContext() useSyncSelectedSectionWithScroll() - const [selectedSection] = useSelectedSection() return ( <> @@ -41,7 +43,12 @@ export const DataSetFormContents = () => { )} - + + + + + + @@ -60,6 +67,16 @@ export const DataSetFormContents = () => { }} /> +
+ + {i18n.t('Data set disaggregation')} + + + {i18n.t( + 'Choose an optional category combination to disaggregate the entire data set.' + )} + + { 'Choose for what time periods data can be entered for this data set' )} + {} + const { input, meta } = useField('categoryCombo', { + validateFields: [], + validate, + format: (categoryCombo) => categoryCombo && categoryCombo.id, + parse: (id) => ({ id }), + }) + return ( + + component={InputFieldFF} + inputWidth="400px" + label={i18n.t('Period type')} + name="periodType" + /> + + // + // + // { + // input.onChange(selected) + // input.onBlur() + // }} + // onBlur={input.onBlur} + // onFocus={input.onFocus} + // /> + // + ) +} diff --git a/src/pages/dataSetsWip/form/dataSetFormSchema.ts b/src/pages/dataSetsWip/form/dataSetFormSchema.ts index 0f8dd16f..b87838ee 100644 --- a/src/pages/dataSetsWip/form/dataSetFormSchema.ts +++ b/src/pages/dataSetsWip/form/dataSetFormSchema.ts @@ -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 } = @@ -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) diff --git a/src/types/section.ts b/src/types/section.ts index d4c3c290..6bdfcaa6 100644 --- a/src/types/section.ts +++ b/src/types/section.ts @@ -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 @@ -22,7 +23,6 @@ export type NonSchemaSection = SectionBase & { export type OverviewSection = SectionBase & { componentName: string - routeName?: string } export type ModelSection = SchemaSection | NonSchemaSection