diff --git a/src/app/layout/Layout.module.css b/src/app/layout/Layout.module.css index 06692d4f..0bf1d64f 100644 --- a/src/app/layout/Layout.module.css +++ b/src/app/layout/Layout.module.css @@ -39,6 +39,7 @@ .sidebar.hide { width: 0px; height: 0px; + visibility: hidden; } .pageTitle { diff --git a/src/components/merge/BaseMergeFields.tsx b/src/components/merge/BaseMergeFields.tsx new file mode 100644 index 00000000..57925d2d --- /dev/null +++ b/src/components/merge/BaseMergeFields.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { DisplayableModel } from '../../types/models' +import { + ModelMultiSelectField, + ModelMultiSelectFieldProps, +} from '../metadataFormControls/ModelMultiSelect' +import css from './MergeFields.module.css' +import { IconArrowRight24 } from '@dhis2/ui' +import { useField } from 'react-final-form' + +type Optional = Partial> & Omit + +type BaseSourceFieldProps = Optional< + ModelMultiSelectFieldProps, + 'name' +> + +export const BaseSourcesField = (props: BaseSourceFieldProps) => { + const targetValue = useField('target', { + subscription: { value: true }, + }).input.value + + return ( + + availableSources.filter((s) => s.id !== targetValue?.[0]?.id) + } + {...props} + /> + ) +} + +export const BaseTargetField = (props: BaseSourceFieldProps) => { + const sourcesValues = useField('sources', { + subscription: { value: true }, + }).input.value + + return ( + + availableTargets.filter( + (t) => !sourcesValues.some((s) => s.id === t.id) + ) + } + {...props} + /> + ) +} + +export const MergeSourcesTargetWrapper = ({ + children, +}: React.PropsWithChildren) => { + return ( +
+ {children} + +
+ ) +} diff --git a/src/components/merge/MergeFields.module.css b/src/components/merge/MergeFields.module.css new file mode 100644 index 00000000..3edd28d4 --- /dev/null +++ b/src/components/merge/MergeFields.module.css @@ -0,0 +1,25 @@ +.targetSourceWrapper { + display: grid; + /* grid is used to place the arrow between the children*/ + grid-template-areas: 'source arrow target'; + grid-template-columns: minmax(380px, max-content) 24px minmax( + 380px, + max-content + ); + gap: var(--spacers-dp8); + background-color: var(--colors-grey100); + padding: var(--spacers-dp16); + justify-content: start; + align-items: center; + width: min-content; +} + +.targetSourceWrapper > div { + max-width: 380px; +} + +.targetSourceWrapper > svg { + grid-area: arrow; + color: var(--colors-grey700); + margin-block-start: 19px; /* 19px is the default height of the field label */ +} diff --git a/src/components/merge/index.ts b/src/components/merge/index.ts new file mode 100644 index 00000000..9bdb26cb --- /dev/null +++ b/src/components/merge/index.ts @@ -0,0 +1 @@ +export * from './BaseMergeFields' diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx index 10eeb58f..5f140a70 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx @@ -40,8 +40,6 @@ export const ModelMultiSelect = ({ ...baseModelSingleSelectProps }: ModelMultiSelectProps) => { // keep select in ref, so we dont recompute for inline selects - const selectRef = useRef(select) - select = selectRef.current const [searchTerm, setSearchTerm] = useState('') const searchFilter = `identifiable:token:${searchTerm}` const filter: string[] = searchTerm ? [searchFilter] : [] @@ -66,12 +64,7 @@ export const ModelMultiSelect = ({ selected, }) - const resolvedAvailable = useMemo(() => { - if (select) { - return select(availableData) - } - return availableData - }, [availableData]) + const resolvedAvailable = select ? select(availableData) : availableData const handleFilterChange = useDebouncedCallback(({ value }) => { if (value != undefined) { diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx index 63880fc8..b9468252 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx @@ -15,7 +15,7 @@ type OwnProps = { onChange?: ModelMultiSelectProps['onChange'] } -type ModelMultiSelectFieldProps = Omit< +export type ModelMultiSelectFieldProps = Omit< ModelMultiSelectProps, 'selected' | 'onChange' > & @@ -47,10 +47,10 @@ export function ModelMultiSelectField({ {...modelSingleSelectProps} selected={input.value} - onChange={(selected) => { - input.onChange(selected) + onChange={(payload) => { + input.onChange(payload.selected) input.onBlur() - onChange?.(selected) + onChange?.(payload) }} query={query} /> diff --git a/src/pages/indicatorTypes/merge/IndicatorTypeMerge.tsx b/src/pages/indicatorTypes/merge/IndicatorTypeMerge.tsx index 9b2a8a17..9a5600b2 100644 --- a/src/pages/indicatorTypes/merge/IndicatorTypeMerge.tsx +++ b/src/pages/indicatorTypes/merge/IndicatorTypeMerge.tsx @@ -1,11 +1,29 @@ import { useDataEngine } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' -import { Button, ButtonStrip, CheckboxFieldFF, RadioFieldFF } from '@dhis2/ui' +import { + Button, + ButtonStrip, + CheckboxFieldFF, + FieldGroup, + RadioFieldFF, +} from '@dhis2/ui' import React, { useMemo } from 'react' import { Field, Form } from 'react-final-form' import { z } from 'zod' -import { ModelTransferField } from '../../../components' +import { + ModelTransferField, + StandardFormSection, + StandardFormSectionTitle, +} from '../../../components' import { HorizontalFieldGroup } from '../../../components/form' +import { + BaseSourcesField, + BaseTargetField, + MergeSourcesTargetWrapper, +} from '../../../components/merge' +import { ModelMultiSelectField } from '../../../components/metadataFormControls/ModelMultiSelect' +import { ModelSingleSelect } from '../../../components/metadataFormControls/ModelSingleSelect' +import { ModelFilterSelect } from '../../../components/sectionList/filters/filterSelectors/ModelFilter' import { getDefaults } from '../../../lib' import { mergeFormSchema, validate } from './indicatorTypeMergeSchema' @@ -52,46 +70,60 @@ export const IndicatorTypeMergeForm = ({ padding: '16px', }} > - - 0 - ? `id:!in:[${Array.from( - values.sources.map((s) => - typeof s === 'string' - ? s - : s.id - ) - )}]` + + + }, + }} + /> + + - + + + {i18n.t('Merge settings')} + + + + + component={RadioFieldFF} + name="deleteSources" + label={i18n.t( + 'Keep {{count}} source indicator types', + { count: values.sources.length } + )} + type="radio" + value={'keep'} + /> + + component={RadioFieldFF} + name="deleteSources" + label={i18n.t( + 'Delete {{count}} source indicator types', + { count: values.sources.length } + )} + type="radio" + value={'delete'} + /> + +