Skip to content

Commit

Permalink
fix: add source-target fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Birkbjo committed Dec 2, 2024
1 parent f46d74d commit 5e921b1
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 61 deletions.
1 change: 1 addition & 0 deletions src/app/layout/Layout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
.sidebar.hide {
width: 0px;
height: 0px;
visibility: hidden;
}

.pageTitle {
Expand Down
66 changes: 66 additions & 0 deletions src/components/merge/BaseMergeFields.tsx
Original file line number Diff line number Diff line change
@@ -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'

Check failure on line 8 in src/components/merge/BaseMergeFields.tsx

View workflow job for this annotation

GitHub Actions / lint

`@dhis2/ui` import should occur before import of `react`
import { useField } from 'react-final-form'

Check failure on line 9 in src/components/merge/BaseMergeFields.tsx

View workflow job for this annotation

GitHub Actions / lint

`react-final-form` import should occur before import of `../../types/models`

type Optional<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>

type BaseSourceFieldProps = Optional<
ModelMultiSelectFieldProps<DisplayableModel>,
'name'
>

export const BaseSourcesField = (props: BaseSourceFieldProps) => {
const targetValue = useField<DisplayableModel[]>('target', {
subscription: { value: true },
}).input.value

return (
<ModelMultiSelectField
name="sources"
label="Sources"
maxHeight="150px"
// use select to filter out the target from available sources
select={(availableSources) =>
availableSources.filter((s) => s.id !== targetValue?.[0]?.id)
}
{...props}
/>
)
}

export const BaseTargetField = (props: BaseSourceFieldProps) => {
const sourcesValues = useField<DisplayableModel[]>('sources', {
subscription: { value: true },
}).input.value

return (
<ModelMultiSelectField
name="target"
label="Target"
maxHeight="150px"
select={(availableTargets) =>
availableTargets.filter(
(t) => !sourcesValues.some((s) => s.id === t.id)
)
}
{...props}
/>
)
}

export const MergeSourcesTargetWrapper = ({
children,
}: React.PropsWithChildren) => {
return (
<div className={css.targetSourceWrapper}>
{children}
<IconArrowRight24 />
</div>
)
}
25 changes: 25 additions & 0 deletions src/components/merge/MergeFields.module.css
Original file line number Diff line number Diff line change
@@ -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 */
}
1 change: 1 addition & 0 deletions src/components/merge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './BaseMergeFields'
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ export const ModelMultiSelect = <TModel extends DisplayableModel>({
...baseModelSingleSelectProps
}: ModelMultiSelectProps<TModel>) => {
// 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] : []
Expand All @@ -66,12 +64,7 @@ export const ModelMultiSelect = <TModel extends DisplayableModel>({
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type OwnProps<TModel extends DisplayableModel> = {
onChange?: ModelMultiSelectProps<TModel>['onChange']
}

type ModelMultiSelectFieldProps<TModel extends DisplayableModel> = Omit<
export type ModelMultiSelectFieldProps<TModel extends DisplayableModel> = Omit<
ModelMultiSelectProps<TModel>,
'selected' | 'onChange'
> &
Expand Down Expand Up @@ -47,10 +47,10 @@ export function ModelMultiSelectField<TModel extends DisplayableModel>({
<ModelMultiSelect<TModel>
{...modelSingleSelectProps}
selected={input.value}
onChange={(selected) => {
input.onChange(selected)
onChange={(payload) => {
input.onChange(payload.selected)
input.onBlur()
onChange?.(selected)
onChange?.(payload)
}}
query={query}
/>
Expand Down
112 changes: 72 additions & 40 deletions src/pages/indicatorTypes/merge/IndicatorTypeMerge.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -52,46 +70,60 @@ export const IndicatorTypeMergeForm = ({
padding: '16px',
}}
>
<ModelTransferField
name="sources"
query={{
resource: 'indicatorTypes',
params: {
fields: 'id,displayName',
filter: values.target
? `id:!in:[${values.target}]`
: [],
},
}}
label="Source indicator types"
/>
<ModelTransferField
name="target"
label="Target indicator type"
query={{
resource: 'indicatorTypes',
params: {
fields: 'id,displayName',
filter:
values.sources.length > 0
? `id:!in:[${Array.from(
values.sources.map((s) =>
typeof s === 'string'
? s
: s.id
)
)}]`
<MergeSourcesTargetWrapper>
<BaseSourcesField
query={{
resource: 'indicatorTypes',
params: {
fields: 'id,displayName',
filter: values.target
? `id:!in:[${values.target}]`
: [],
},
}}
/>
},
}}
/>
<BaseTargetField
query={{
resource: 'indicatorTypes',
params: {
fields: 'id,displayName',
},
}}
/>
</MergeSourcesTargetWrapper>

<Field
component={CheckboxFieldFF}
name="deleteSources"
label={i18n.t('Delete source indicator types')}
type="checkbox"
/>
<StandardFormSection>
<StandardFormSectionTitle>
{i18n.t('Merge settings')}
</StandardFormSectionTitle>

<FieldGroup
label={i18n.t(
'What should happen to the source indicator types after the merge is complete?'
)}
>
<Field<string | undefined>
component={RadioFieldFF}
name="deleteSources"
label={i18n.t(
'Keep {{count}} source indicator types',
{ count: values.sources.length }
)}
type="radio"
value={'keep'}
/>
<Field<string | undefined>
component={RadioFieldFF}
name="deleteSources"
label={i18n.t(
'Delete {{count}} source indicator types',
{ count: values.sources.length }
)}
type="radio"
value={'delete'}
/>
</FieldGroup>
</StandardFormSection>
<ButtonStrip>
<Button primary type="submit">
{i18n.t('Merge')}
Expand Down
14 changes: 5 additions & 9 deletions src/pages/indicatorTypes/merge/indicatorTypeMergeSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ import { createFormValidate, modelFormSchemas } from '../../../lib'

const { referenceCollection } = modelFormSchemas

export const mergeFormSchema = z
.object({
sources: referenceCollection.default([]),
target: referenceCollection.length(1).default([]),
deleteSources: z.boolean().default(false),
})
.refine((data) => {
return 'error!'
})
export const mergeFormSchema = z.object({
sources: referenceCollection.default([]),
target: referenceCollection.length(1).default([]),
deleteSources: z.enum(['keep', 'delete']).default('keep'),
})

export const validate = createFormValidate(mergeFormSchema)

0 comments on commit 5e921b1

Please sign in to comment.