From 7687c13d99335154880275e7631af9c221d0aceb Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 30 Sep 2024 17:16:21 +0200 Subject: [PATCH 1/4] feat(catCombo): add category combo form and list (#410) * fix: add utility type for picking based on field-filters * refactor: simplify PickWithFieldFilters * fix: simplify createJsonpatchOperation and formcontents * fix: simplify getAllAttributes * fix: fix PickWithFieldFilters * wip: simplify editform * fix: implement basic category form * feat: Edit and New form base * fix: get defaults using zod * refactor: add defaultformcontents for new * feat(form): caregory forms, formbase * chore: fix eslint warnings * fix: cleanup * fix(category-form): add options-transfer, generic ModelTransfer * fix: improve model-transfer * fix: fix lint and build issues * fix(model-transferfield): expose filterPlaceholder * fix(formbase): allow support for no attributes * fix(formcontents): fix new form layout * fix(types): prettify PickWithFieldFilter result-type * fix(list): add filter and columns descriptors to category * fix: test * refactor: add common zod schemas * fix: format * feat(form): add catCombo form * fix: add categoryCombo filters and columns * fix: fix categoryCombo edit save * fix: add missing file - categoryFilter * refactor: use common form schemas * fix: translate validation message * fix: add label for category options --- i18n/en.pot | 88 +++++++++++--- .../attributes/useCustomAttributesQuery.ts | 1 + .../sectionList/filters/DynamicFilters.tsx | 2 + .../filterSelectors/CategoryFilter.tsx | 24 ++++ .../filters/filterSelectors/index.ts | 1 + src/lib/sectionList/filters/filterConfig.tsx | 3 + .../filters/parseFiltersToQueryParams.ts | 1 + .../listViews/sectionListViewsConfig.ts | 20 +++- src/pages/categoryCombos/Edit.tsx | 54 +++++++++ src/pages/categoryCombos/List.tsx | 6 + src/pages/categoryCombos/New.tsx | 24 ++++ .../form/CategoryComboFormFields.tsx | 110 ++++++++++++++++++ .../form/categoryComboSchema.ts | 27 +++++ src/pages/categoryCombos/form/index.ts | 2 + src/pages/categoryCombos/form/types.ts | 0 15 files changed, 345 insertions(+), 18 deletions(-) create mode 100644 src/components/sectionList/filters/filterSelectors/CategoryFilter.tsx create mode 100644 src/pages/categoryCombos/Edit.tsx create mode 100644 src/pages/categoryCombos/List.tsx create mode 100644 src/pages/categoryCombos/New.tsx create mode 100644 src/pages/categoryCombos/form/CategoryComboFormFields.tsx create mode 100644 src/pages/categoryCombos/form/categoryComboSchema.ts create mode 100644 src/pages/categoryCombos/form/index.ts create mode 100644 src/pages/categoryCombos/form/types.ts diff --git a/i18n/en.pot b/i18n/en.pot index 428511c1..8e3d64b6 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-08-28T01:15:41.705Z\n" -"PO-Revision-Date: 2024-08-28T01:15:41.705Z\n" +"POT-Creation-Date: 2024-09-30T15:09:50.864Z\n" +"PO-Revision-Date: 2024-09-30T15:09:50.864Z\n" msgid "schemas" msgstr "schemas" @@ -66,9 +66,6 @@ msgstr "All" msgid "Filter options" msgstr "Filter options" -msgid "Something went wrong when submitting the form" -msgstr "Something went wrong when submitting the form" - msgid "Save and close" msgstr "Save and close" @@ -78,6 +75,15 @@ msgstr "Exit without saving" msgid "Create {{modelName}} " msgstr "Create {{modelName}} " +msgid "Validation errors" +msgstr "Validation errors" + +msgid "Some fields have validation errors. Please fix them before saving." +msgstr "Some fields have validation errors. Please fix them before saving." + +msgid "Something went wrong when submitting the form" +msgstr "Something went wrong when submitting the form" + msgid "" msgstr "" @@ -114,6 +120,21 @@ msgstr "Failed to load {{label}}" msgid "Failed to load" msgstr "Failed to load" +msgid "Download" +msgstr "Download" + +msgid "Merge" +msgstr "Merge" + +msgid "Delete source data element values" +msgstr "Delete source data element values" + +msgid "Last updated" +msgstr "Last updated" + +msgid "Discard" +msgstr "Discard" + msgid "Aggregation level(s)" msgstr "Aggregation level(s)" @@ -213,9 +234,6 @@ msgstr "Created" msgid "Last updated by" msgstr "Last updated by" -msgid "Last updated" -msgstr "Last updated" - msgid "Id" msgstr "Id" @@ -237,9 +255,6 @@ msgstr "Details" msgid "Failed to load details" msgstr "Failed to load details" -msgid "Download" -msgstr "Download" - msgid "Download {{section}}" msgstr "Download {{section}}" @@ -276,6 +291,9 @@ msgstr "Include user sharing settings" msgid "Clear all filters" msgstr "Clear all filters" +msgid "Category" +msgstr "Category" + msgid "Type to filter options" msgstr "Type to filter options" @@ -408,9 +426,6 @@ msgstr "Edit and view" msgid "Search for a user or group" msgstr "Search for a user or group" -msgid "Category" -msgstr "Category" - msgid "Categories" msgstr "Categories" @@ -810,6 +825,9 @@ msgstr "GeoJSON" msgid "Disaggregation" msgstr "Disaggregation" +msgid "Aggregation type" +msgstr "Aggregation type" + msgid "Favorite" msgstr "Favorite" @@ -837,6 +855,21 @@ msgstr "Data dimension type" msgid "This field requires a unique value, please choose another one" msgstr "This field requires a unique value, please choose another one" +msgid "{{label}} (required)" +msgstr "{{label}} (required)" + +msgid "No changes to be saved" +msgstr "No changes to be saved" + +msgid "Saved successfully" +msgstr "Saved successfully" + +msgid "Cannot save empty object" +msgstr "Cannot save empty object" + +msgid "Created successfully" +msgstr "Created successfully" + msgid "Required" msgstr "Required" @@ -876,6 +909,30 @@ msgstr "Filter available category options" msgid "Filter selected category options" msgstr "Filter selected category options" +msgid "Choose how this category combo will be used to capture and analyze data." +msgstr "Choose how this category combo will be used to capture and analyze data." + +msgid "Skip category total in reports" +msgstr "Skip category total in reports" + +msgid "Choose the categories to include in this category combo." +msgstr "Choose the categories to include in this category combo." + +msgid "Available categories" +msgstr "Available categories" + +msgid "Selected categories" +msgstr "Selected categories" + +msgid "Filter available categories" +msgstr "Filter available categories" + +msgid "Filter selected categories" +msgstr "Filter selected categories" + +msgid "At least one category is required" +msgstr "At least one category is required" + msgid "Create data element group" msgstr "Create data element group" @@ -909,9 +966,6 @@ msgstr "The default way to aggregate this data element in analytics." msgid "Disabled for the selected value type." msgstr "Disabled for the selected value type." -msgid "Aggregation type" -msgstr "Aggregation type" - msgid "Color and icon" msgstr "Color and icon" diff --git a/src/components/form/attributes/useCustomAttributesQuery.ts b/src/components/form/attributes/useCustomAttributesQuery.ts index bda6c250..2663b5c9 100644 --- a/src/components/form/attributes/useCustomAttributesQuery.ts +++ b/src/components/form/attributes/useCustomAttributesQuery.ts @@ -40,6 +40,7 @@ export function useCustomAttributesQuery({ enabled = true, }: UseCustomAttributesQueryOptions = {}) { const schemaSection = useSchemaSectionHandleOrThrow() + const customAttributes = useDataQuery( CUSTOM_ATTRIBUTES_QUERY, { diff --git a/src/components/sectionList/filters/DynamicFilters.tsx b/src/components/sectionList/filters/DynamicFilters.tsx index 07479fc7..3dbfded4 100644 --- a/src/components/sectionList/filters/DynamicFilters.tsx +++ b/src/components/sectionList/filters/DynamicFilters.tsx @@ -8,12 +8,14 @@ import { ValueTypeSelectionFilter, PublicAccessFilter, DataDimensionTypeFilter, + Categoryfilter, } from './filterSelectors' import { useFilterKeys } from './useFilterKeys' type FilterKeyToComponentMap = Partial> const filterKeyToComponentMap: FilterKeyToComponentMap = { + category: Categoryfilter, categoryCombo: CategoryComboFilter, dataSet: DataSetFilter, domainType: DomainTypeSelectionFilter, diff --git a/src/components/sectionList/filters/filterSelectors/CategoryFilter.tsx b/src/components/sectionList/filters/filterSelectors/CategoryFilter.tsx new file mode 100644 index 00000000..9b73efff --- /dev/null +++ b/src/components/sectionList/filters/filterSelectors/CategoryFilter.tsx @@ -0,0 +1,24 @@ +import i18n from '@dhis2/d2-i18n' +import React from 'react' +import { useSectionListFilter } from '../../../../lib' +import { createFilterDataQuery } from './createFilterDataQuery' +import { ModelFilterSelect } from './ModelFilter' + +const query = createFilterDataQuery('categories') + +export const Categoryfilter = () => { + const [filter, setFilter] = useSectionListFilter('category') + + const selected = filter?.[0] + + return ( + + setFilter(selected ? [selected] : undefined) + } + /> + ) +} diff --git a/src/components/sectionList/filters/filterSelectors/index.ts b/src/components/sectionList/filters/filterSelectors/index.ts index 55442aca..db287c03 100644 --- a/src/components/sectionList/filters/filterSelectors/index.ts +++ b/src/components/sectionList/filters/filterSelectors/index.ts @@ -4,3 +4,4 @@ export * from './CategoryComboFilter' export * from './IdentifiableFilter' export * from './ConstantSelectionFilter' export * from './PublicAccessFilter' +export * from './CategoryFilter' diff --git a/src/lib/sectionList/filters/filterConfig.tsx b/src/lib/sectionList/filters/filterConfig.tsx index cb2fda52..92232623 100644 --- a/src/lib/sectionList/filters/filterConfig.tsx +++ b/src/lib/sectionList/filters/filterConfig.tsx @@ -13,6 +13,7 @@ export const filterParamsSchema = z [IDENTIFIABLE_FILTER_KEY]: z.string(), aggregationType: z.array(z.nativeEnum(DataElement.aggregationType)), categoryCombo: zodArrayIds, + category: zodArrayIds, dataSet: zodArrayIds, domainType: z.array(z.nativeEnum(DataElement.domainType)), publicAccess: z.array( @@ -31,6 +32,7 @@ export const filterQueryParamType = { domainType: CustomDelimitedArrayParam, valueType: CustomDelimitedArrayParam, dataSet: CustomDelimitedArrayParam, + category: CustomDelimitedArrayParam, categoryCombo: CustomDelimitedArrayParam, publicAccess: CustomDelimitedArrayParam, dataDimensionType: StringParam, @@ -59,4 +61,5 @@ export type ConfigurableFilterKey = Exclude< FilterKey, typeof IDENTIFIABLE_FILTER_KEY > + export type FilterKeys = FilterKey[] diff --git a/src/lib/sectionList/filters/parseFiltersToQueryParams.ts b/src/lib/sectionList/filters/parseFiltersToQueryParams.ts index 16ae3fa0..49fc0bc0 100644 --- a/src/lib/sectionList/filters/parseFiltersToQueryParams.ts +++ b/src/lib/sectionList/filters/parseFiltersToQueryParams.ts @@ -30,6 +30,7 @@ const defaultFilter = ( const filterToQueryParamMap: FilterToQueryParamsMap = { identifiable: (value) => `identifiable:token:${value}`, + category: (value) => inFilter('categories.id', value), categoryCombo: (value, section) => { if (section.name === 'category') { return inFilter('categoryCombos.id', value) diff --git a/src/lib/sectionList/listViews/sectionListViewsConfig.ts b/src/lib/sectionList/listViews/sectionListViewsConfig.ts index 3efa3845..53207a35 100644 --- a/src/lib/sectionList/listViews/sectionListViewsConfig.ts +++ b/src/lib/sectionList/listViews/sectionListViewsConfig.ts @@ -109,10 +109,28 @@ export const modelListViewsConfig = { }, category: { columns: { - default: ['name', 'dataDimensionType', DESCRIPTORS.publicAccess], + default: [ + 'name', + 'dataDimensionType', + DESCRIPTORS.publicAccess, + 'lastUpdated', + ], }, filters: { default: ['dataDimensionType', 'categoryCombo'], }, }, + categoryCombo: { + columns: { + default: [ + 'name', + 'dataDimensionType', + DESCRIPTORS.publicAccess, + 'lastUpdated', + ], + }, + filters: { + default: ['dataDimensionType', 'category'], + }, + }, } satisfies SectionListViewConfig diff --git a/src/pages/categoryCombos/Edit.tsx b/src/pages/categoryCombos/Edit.tsx new file mode 100644 index 00000000..717844ea --- /dev/null +++ b/src/pages/categoryCombos/Edit.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { useQuery } from 'react-query' +import { useParams } from 'react-router-dom' +import { DefaultEditFormContents, FormBase } from '../../components' +import { DEFAULT_FIELD_FILTERS, SECTIONS_MAP, useOnSubmitEdit } from '../../lib' +import { useBoundResourceQueryFn } from '../../lib/query/useBoundQueryFn' +import { CategoryCombo, PickWithFieldFilters } from '../../types/generated' +import { validate, CategoryComboFormFields } from './form' + +const fieldFilters = [ + ...DEFAULT_FIELD_FILTERS, + 'name', + 'code', + 'categories[id,displayName]', + 'skipTotal', + 'dataDimensionType', +] as const + +export type CategoryComboFormValues = PickWithFieldFilters< + CategoryCombo, + typeof fieldFilters +> + +const section = SECTIONS_MAP.categoryCombo + +export const Component = () => { + const queryFn = useBoundResourceQueryFn() + const modelId = useParams().id as string + const query = { + resource: 'categoryCombos', + id: modelId, + params: { + fields: fieldFilters.concat(), + }, + } + const categoryCombo = useQuery({ + queryKey: [query], + queryFn: queryFn, + }) + + return ( + + + + + + ) +} diff --git a/src/pages/categoryCombos/List.tsx b/src/pages/categoryCombos/List.tsx new file mode 100644 index 00000000..7133f024 --- /dev/null +++ b/src/pages/categoryCombos/List.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import { DefaultSectionList } from '../DefaultSectionList' + +export const Component = () => ( + +) diff --git a/src/pages/categoryCombos/New.tsx b/src/pages/categoryCombos/New.tsx new file mode 100644 index 00000000..e222ee3c --- /dev/null +++ b/src/pages/categoryCombos/New.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { FormBase } from '../../components' +import { DefaultNewFormContents } from '../../components/form/DefaultFormContents' +import { SECTIONS_MAP, useOnSubmitNew } from '../../lib' +import { validate } from './form' +import { CategoryComboFormFields } from './form/CategoryComboFormFields' +import { initialValues } from './form/categoryComboSchema' + +const section = SECTIONS_MAP.categoryCombo + +export const Component = () => { + return ( + + + + + + ) +} diff --git a/src/pages/categoryCombos/form/CategoryComboFormFields.tsx b/src/pages/categoryCombos/form/CategoryComboFormFields.tsx new file mode 100644 index 00000000..11f2004b --- /dev/null +++ b/src/pages/categoryCombos/form/CategoryComboFormFields.tsx @@ -0,0 +1,110 @@ +import i18n from '@dhis2/d2-i18n' +import { CheckboxFieldFF, RadioFieldFF } from '@dhis2/ui' +import React from 'react' +import { Field } from 'react-final-form' +import { + StandardFormField, + StandardFormSection, + StandardFormSectionTitle, + StandardFormSectionDescription, + HorizontalFieldGroup, + ModelTransferField, + NameField, + CodeField, +} from '../../../components' +import { SECTIONS_MAP } from '../../../lib' + +const section = SECTIONS_MAP.categoryCombo + +export const CategoryComboFormFields = () => { + return ( + <> + + + {i18n.t('Basic information')} + + + {i18n.t('Set up the basic information for this category.')} + + + + + + + + + + + + + {i18n.t('Data configuration')} + + + {i18n.t( + 'Choose how this category combo will be used to capture and analyze data.' + )} + + + + + name="dataDimensionType" + component={RadioFieldFF} + label={i18n.t('Disaggregation')} + type="radio" + value={'DISAGGREGATION'} + /> + + name="dataDimensionType" + component={RadioFieldFF} + label={i18n.t('Attribute')} + type="radio" + value={'ATTRIBUTE'} + /> + + + + + + + + + + + + + {i18n.t( + 'Choose the categories to include in this category combo.' + )} + + + + + + + + + ) +} diff --git a/src/pages/categoryCombos/form/categoryComboSchema.ts b/src/pages/categoryCombos/form/categoryComboSchema.ts new file mode 100644 index 00000000..95e06d28 --- /dev/null +++ b/src/pages/categoryCombos/form/categoryComboSchema.ts @@ -0,0 +1,27 @@ +import i18n from '@dhis2/d2-i18n' +import { z } from 'zod' +import { getDefaults, modelFormSchemas } from '../../../lib' +import { createFormValidate } from '../../../lib/form/validate' +import { CategoryComboFormValues } from '../Edit' +import { CategoryCombo } from './../../../types/generated/models' + +const { identifiable, withAttributeValues, referenceCollection } = + modelFormSchemas + +export const categoryComboSchema = identifiable + .merge(withAttributeValues) + .extend({ + code: z.string().trim().optional(), + dataDimensionType: z + .nativeEnum(CategoryCombo.dataDimensionType) + .default(CategoryCombo.dataDimensionType.DISAGGREGATION), + skipTotal: z.boolean().default(false), + categories: referenceCollection + .min(1, i18n.t('At least one category is required')) + .default([]), + }) + +export const initialValues: Partial = + getDefaults(categoryComboSchema) + +export const validate = createFormValidate(categoryComboSchema) diff --git a/src/pages/categoryCombos/form/index.ts b/src/pages/categoryCombos/form/index.ts new file mode 100644 index 00000000..d977c120 --- /dev/null +++ b/src/pages/categoryCombos/form/index.ts @@ -0,0 +1,2 @@ +export { categoryComboSchema, validate } from './categoryComboSchema' +export { CategoryComboFormFields } from './CategoryComboFormFields' diff --git a/src/pages/categoryCombos/form/types.ts b/src/pages/categoryCombos/form/types.ts new file mode 100644 index 00000000..e69de29b From 8f5fdca6502d8e177dd0dfa980958ec26a97d832 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 30 Sep 2024 15:19:27 +0000 Subject: [PATCH 2/4] chore(release): cut 0.5.0 [skip release] # [0.5.0](https://github.com/dhis2/maintenance-app-beta/compare/v0.4.0...v0.5.0) (2024-09-30) ### Features * **catCombo:** add category combo form and list ([#410](https://github.com/dhis2/maintenance-app-beta/issues/410)) ([7687c13](https://github.com/dhis2/maintenance-app-beta/commit/7687c13d99335154880275e7631af9c221d0aceb)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cf9530..dacd9ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.5.0](https://github.com/dhis2/maintenance-app-beta/compare/v0.4.0...v0.5.0) (2024-09-30) + + +### Features + +* **catCombo:** add category combo form and list ([#410](https://github.com/dhis2/maintenance-app-beta/issues/410)) ([7687c13](https://github.com/dhis2/maintenance-app-beta/commit/7687c13d99335154880275e7631af9c221d0aceb)) + # [0.4.0](https://github.com/dhis2/maintenance-app-beta/compare/v0.3.1...v0.4.0) (2024-09-28) diff --git a/package.json b/package.json index 475a7c08..b35b2a44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maintenance-app", - "version": "0.4.0", + "version": "0.5.0", "description": "", "license": "BSD-3-Clause", "private": true, From 8d3b87b7c47399bac5a8c4b88a8c0046fd69ca74 Mon Sep 17 00:00:00 2001 From: Flaminia Date: Mon, 30 Sep 2024 08:32:00 -0700 Subject: [PATCH 3/4] feat: add details panel to org list * feat: add details panel to org list * feat: add tests for additionsl org unit list features --- .../detailsPanel/DefaultDetailsPanel.tsx | 6 +- .../sectionList/detailsPanel/DetailsPanel.tsx | 2 +- .../sectionList/download/DownloadDialog.tsx | 2 +- .../download/DownloadDialogContent.tsx | 1 + src/pages/organisationUnits/List.spec.tsx | 137 +++++++++++++++++- .../list/OrganisationUnitList.tsx | 32 +++- .../list/OrganisationUnitListActions.tsx | 17 ++- .../list/OrganisationUnitRow.tsx | 4 + src/types/generated/utility.ts | 2 +- 9 files changed, 193 insertions(+), 10 deletions(-) diff --git a/src/components/sectionList/detailsPanel/DefaultDetailsPanel.tsx b/src/components/sectionList/detailsPanel/DefaultDetailsPanel.tsx index c4939f95..14d19154 100644 --- a/src/components/sectionList/detailsPanel/DefaultDetailsPanel.tsx +++ b/src/components/sectionList/detailsPanel/DefaultDetailsPanel.tsx @@ -89,15 +89,15 @@ const DetailsContent = ({ data }: { data: DetailsResponse }) => { )} {data.code} - {data.createdBy.displayName} + {data.createdBy?.displayName} {data.lastUpdatedBy - ? data.lastUpdatedBy.displayName - : data.createdBy.displayName} + ? data.lastUpdatedBy?.displayName + : data.createdBy?.displayName} diff --git a/src/components/sectionList/detailsPanel/DetailsPanel.tsx b/src/components/sectionList/detailsPanel/DetailsPanel.tsx index 52d63885..e5937a11 100644 --- a/src/components/sectionList/detailsPanel/DetailsPanel.tsx +++ b/src/components/sectionList/detailsPanel/DetailsPanel.tsx @@ -13,7 +13,7 @@ type DetailsPanelProps = { export const DetailsPanel = ({ children, onClose }: DetailsPanelProps) => { return (