From a84ee60546f1ce8b15c16b648514f3f13027e582 Mon Sep 17 00:00:00 2001 From: Chisomchima Date: Fri, 24 Jan 2025 09:48:16 +0100 Subject: [PATCH] feat: implement merging of indicators --- src/pages/indicators/Merge.tsx | 90 +++++++++++++ .../merge/IndicatorMergeFormFields.tsx | 119 ++++++++++++++++++ .../indicators/merge/IndicatorMergeSchema.ts | 29 +++++ src/pages/indicators/merge/index.ts | 2 + 4 files changed, 240 insertions(+) create mode 100644 src/pages/indicators/Merge.tsx create mode 100644 src/pages/indicators/merge/IndicatorMergeFormFields.tsx create mode 100644 src/pages/indicators/merge/IndicatorMergeSchema.ts create mode 100644 src/pages/indicators/merge/index.ts diff --git a/src/pages/indicators/Merge.tsx b/src/pages/indicators/Merge.tsx new file mode 100644 index 00000000..4fe50096 --- /dev/null +++ b/src/pages/indicators/Merge.tsx @@ -0,0 +1,90 @@ +import { useDataEngine } from '@dhis2/app-runtime' +import i18n from '@dhis2/d2-i18n' +import React, { useMemo } from 'react' +import { Form } from 'react-final-form' +import { + DefaultMergeFormContents, + MergeComplete, + StyledMergeForm, + Title, +} from '../../components/merge' +import { getDefaults, useLocationWithState } from '../../lib' +import { createFormError } from '../../lib/form/createFormError' +import { IndicatorMergeFormFields } from './merge/IndicatorMergeFormFields' +import { + IndicatorMergeFormValues, + mergeFormSchema, + validate, +} from './merge/IndicatorMergeSchema' + +export const Component = () => { + const location = useLocationWithState<{ selectedModels: Set }>() + + const dataEngine = useDataEngine() + const initialValues = useMemo(() => { + const defaults = { + ...getDefaults(mergeFormSchema), + target: undefined, + sources: Array.from(location.state?.selectedModels || []).map( + (id) => ({ + id, + }) + ), + } + return defaults + }, [location.state?.selectedModels]) + + const onSubmit = async (values: IndicatorMergeFormValues) => { + try { + const data = mergeFormSchema.parse(values) + await dataEngine.mutate({ + resource: 'indicators/merge', + type: 'create', + data, + }) + return undefined + } catch (e) { + console.error(e) + return createFormError(e) + } + } + + return ( +
+ {({ handleSubmit }) => ( + + {i18n.t('Configure indicator merge')} + } + mergeCompleteElement={ + +

+ {i18n.t( + 'The indicator merge operation is complete.' + )} +

+

+ {i18n.t( + 'All selected indicators were merged successfully.' + )} +

+
+ } + > + +
+
+ )} +
+ ) +} diff --git a/src/pages/indicators/merge/IndicatorMergeFormFields.tsx b/src/pages/indicators/merge/IndicatorMergeFormFields.tsx new file mode 100644 index 00000000..c8e46d81 --- /dev/null +++ b/src/pages/indicators/merge/IndicatorMergeFormFields.tsx @@ -0,0 +1,119 @@ +import i18n from '@dhis2/d2-i18n' +import { NoticeBox } from '@dhis2/ui' +import React from 'react' +import { useFormState } from 'react-final-form' +import { StandardFormSectionTitle } from '../../../components' +import { + BaseSourcesField, + BaseTargetField, + MergeSourcesTargetWrapper, + DeleteSourcesFields, + Description, + FormSection, + FormSections, + ConfirmationField, +} from '../../../components/merge' +import { IndicatorMergeFormValues } from './IndicatorMergeSchema' + +export const IndicatorMergeFormFields = () => { + return ( + + + +

+ {i18n.t(`The merge operation will merge the source indicators into + the target indicator. One or many source indicators + can be specified`)} +

+

+ {i18n.t(`Only one target should be specified. The merge operation will + transfer all of the indicator metadata associations to the + source indicator over to the target indicator.`)} +

+
+ + + + +
+ + + {i18n.t('Merge settings')} + + + + i18n.t('Keep {{ count }} source indicators', { + count, + }) + } + getDeleteLabel={(count) => + i18n.t('Delete {{ count }} source indicators', { + count, + }) + } + /> + + + + + +
+ ) +} + +const MergeWarnings = () => { + const { values } = useFormState({ + subscription: { values: true }, + }) + + if (values.sources?.length === 0 || !values.target) { + return null + } + + const sourcesWithDifferentFactors = + values.sources?.filter((s) => s.factor !== values.target.factor) || [] + if (sourcesWithDifferentFactors.length > 0) { + return ( +
+ + {i18n.t( + 'The following source indicator types have different factors than the target indicator type with factor {{ targetFactor }}:', + { targetFactor: values.target.factor } + )} +
    + {sourcesWithDifferentFactors.map((s) => ( +
  • {s.displayName ?? s.id}
  • + ))} +
+ {i18n.t( + 'It is not recommended to merge indicator types with different factors.' + )} +
+
+ ) + } + + return null +} diff --git a/src/pages/indicators/merge/IndicatorMergeSchema.ts b/src/pages/indicators/merge/IndicatorMergeSchema.ts new file mode 100644 index 00000000..38dba029 --- /dev/null +++ b/src/pages/indicators/merge/IndicatorMergeSchema.ts @@ -0,0 +1,29 @@ +import i18n from '@dhis2/d2-i18n' +import { z } from 'zod' +import { mergeFormSchemaBase } from '../../../components/merge' +import { createFormValidate } from '../../../lib' + +const indicatorSchema = z.object({ + id: z.string(), + displayName: z.string(), + name: z.string(), + factor: z.number().optional(), +}) + +export const mergeFormSchema = mergeFormSchemaBase + .extend({ + sources: z + .array(indicatorSchema) + .min(1, i18n.t('At least one source is required')) + .default([]), + target: indicatorSchema, + }) + .transform((data) => ({ + ...data, + sources: data.sources.map((source) => source.id), + target: data.target.id, + })) + +export type IndicatorMergeFormValues = z.input + +export const validate = createFormValidate(mergeFormSchema) diff --git a/src/pages/indicators/merge/index.ts b/src/pages/indicators/merge/index.ts new file mode 100644 index 00000000..222a75c6 --- /dev/null +++ b/src/pages/indicators/merge/index.ts @@ -0,0 +1,2 @@ +export * from './IndicatorMergeFormFields' +export * from './IndicatorMergeSchema'