-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add data element group sets New and Edit view
- Loading branch information
Showing
29 changed files
with
1,132 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
src/components/metadataFormControls/DataElementGroupsTransfer/DataElementGroupsTransfer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import i18n from '@dhis2/d2-i18n' | ||
import { Transfer } from '@dhis2/ui' | ||
import React, { | ||
ReactElement, | ||
forwardRef, | ||
useCallback, | ||
useImperativeHandle, | ||
useRef, | ||
useState, | ||
} from 'react' | ||
import { SelectOption } from '../../../types' | ||
import { useInitialOptionQuery } from './useInitialOptionQuery' | ||
import { useOptionsQuery } from './useOptionsQuery' | ||
|
||
function computeDisplayOptions({ | ||
selected, | ||
selectedOptions, | ||
options, | ||
}: { | ||
options: SelectOption[] | ||
selected: string[] | ||
selectedOptions: SelectOption[] | ||
}): SelectOption[] { | ||
// This happens only when we haven't fetched the lable for an initially | ||
// selected value. Don't show anything to prevent error that an option is | ||
// missing | ||
if (!selectedOptions.length && selected.length) { | ||
return [] | ||
} | ||
|
||
const missingSelectedOptions = selectedOptions.filter((selectedOption) => { | ||
return !options?.find((option) => option.value === selectedOption.value) | ||
}) | ||
|
||
return [...options, ...missingSelectedOptions] | ||
} | ||
|
||
interface DataElementGroupsSelectProps { | ||
onChange: ({ selected }: { selected: string[] }) => void | ||
selected: string[] | ||
rightHeader?: ReactElement | ||
rightFooter?: ReactElement | ||
leftFooter?: ReactElement | ||
leftHeader?: ReactElement | ||
} | ||
|
||
export const DataElementGroupsTransfer = forwardRef( | ||
function DataElementGroupsSelect( | ||
{ | ||
onChange, | ||
selected, | ||
rightHeader, | ||
rightFooter, | ||
leftFooter, | ||
leftHeader, | ||
}: DataElementGroupsSelectProps, | ||
ref | ||
) { | ||
// Using a ref because we don't want to react to changes. | ||
// We're using this value only when imperatively calling `refetch`, | ||
// nothing that depends on the render-cycle depends on this value | ||
const [searchTerm, setSearchTerm] = useState('') | ||
const pageRef = useRef(0) | ||
|
||
// We need to persist the selected option so we can display an <Option /> | ||
// when the current list doesn't contain the selected option (e.g. when | ||
// the page with the selected option hasn't been reached yet or when | ||
// filtering) | ||
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>( | ||
[] | ||
) | ||
|
||
const optionsQuery = useOptionsQuery() | ||
const initialOptionQuery = useInitialOptionQuery({ | ||
selected, | ||
onComplete: setSelectedOptions, | ||
}) | ||
|
||
const { refetch, data } = optionsQuery | ||
const pager = data?.pager | ||
const page = pager?.page || 0 | ||
const pageCount = pager?.pageCount || 0 | ||
|
||
const loading = | ||
optionsQuery.fetching || | ||
optionsQuery.loading || | ||
initialOptionQuery.loading | ||
const error = | ||
optionsQuery.error || initialOptionQuery.error | ||
? // @TODO: Ask Joe what do do here! | ||
'An error has occurred. Please try again' | ||
: '' | ||
|
||
useImperativeHandle( | ||
ref, | ||
() => ({ | ||
refetch: () => { | ||
pageRef.current = 1 | ||
refetch({ page: pageRef.current, filter: searchTerm }) | ||
}, | ||
}), | ||
[refetch, searchTerm] | ||
) | ||
|
||
const adjustQueryParamsWithChangedFilter = useCallback( | ||
({ value }: { value: string | undefined }) => { | ||
value = value ?? '' | ||
setSearchTerm(value) | ||
pageRef.current = 1 | ||
refetch({ page: pageRef.current, filter: value }) | ||
}, | ||
[refetch] | ||
) | ||
|
||
const incrementPage = useCallback(() => { | ||
if (optionsQuery.loading || page === pageCount) { | ||
return | ||
} | ||
|
||
pageRef.current = page + 1 | ||
refetch({ page: pageRef.current, filter: searchTerm }) | ||
}, [refetch, page, optionsQuery.loading, searchTerm, pageCount]) | ||
|
||
const displayOptions = computeDisplayOptions({ | ||
selected, | ||
selectedOptions, | ||
options: data?.result || [], | ||
}) | ||
|
||
return ( | ||
<Transfer | ||
filterable | ||
filterPlaceholder={i18n.t('Filter data element groups')} | ||
searchTerm={searchTerm} | ||
options={displayOptions} | ||
selected={selected} | ||
loading={loading} | ||
onChange={({ selected }: { selected: string[] }) => { | ||
const nextSelectedOptions = displayOptions.filter( | ||
({ value }) => selected.includes(value) | ||
) | ||
setSelectedOptions(nextSelectedOptions) | ||
onChange({ selected }) | ||
}} | ||
onEndReached={incrementPage} | ||
onFilterChange={adjustQueryParamsWithChangedFilter} | ||
rightHeader={rightHeader} | ||
rightFooter={rightFooter} | ||
leftHeader={leftHeader} | ||
leftFooter={leftFooter} | ||
/> | ||
) | ||
} | ||
) |
1 change: 1 addition & 0 deletions
1
src/components/metadataFormControls/DataElementGroupsTransfer/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { DataElementGroupsTransfer } from './DataElementGroupsTransfer' |
12 changes: 12 additions & 0 deletions
12
src/components/metadataFormControls/DataElementGroupsTransfer/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { | ||
DataElementGroup, | ||
GistCollectionResponse, | ||
} from '../../../types/generated' | ||
|
||
const filterFields = ['id', 'displayName'] as const //(name is translated by default in /gist) | ||
export type FilteredDataElementGroup = Pick< | ||
DataElementGroup, | ||
(typeof filterFields)[number] | ||
> | ||
export type DataElementGroupsQueryResult = | ||
GistCollectionResponse<FilteredDataElementGroup> |
44 changes: 44 additions & 0 deletions
44
src/components/metadataFormControls/DataElementGroupsTransfer/useInitialOptionQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useDataQuery } from '@dhis2/app-runtime' | ||
import { useRef } from 'react' | ||
import { SelectOption } from '../../../types' | ||
import { FilteredDataElementGroup } from './types' | ||
|
||
type InitialDataElementGroupsQueryResult = { | ||
dataElementGroups: { | ||
dataElementGroups: FilteredDataElementGroup[] | ||
} | ||
} | ||
|
||
export function useInitialOptionQuery({ | ||
selected, | ||
onComplete, | ||
}: { | ||
onComplete: (options: SelectOption[]) => void | ||
selected: string[] | ||
}) { | ||
const initialSelected = useRef(selected) | ||
const query = { | ||
dataElementGroups: { | ||
resource: 'dataElementGroups', | ||
params: { | ||
paging: false, | ||
fields: ['id', 'displayName'], | ||
filter: `id:in:[${initialSelected.current.join(',')}]`, | ||
}, | ||
}, | ||
} | ||
|
||
return useDataQuery<InitialDataElementGroupsQueryResult>(query, { | ||
lazy: !initialSelected.current, | ||
variables: { id: selected }, | ||
onComplete: (data) => { | ||
const { dataElementGroups } = data.dataElementGroups | ||
const options = dataElementGroups.map(({ id, displayName }) => ({ | ||
value: id, | ||
label: displayName, | ||
})) | ||
|
||
onComplete(options) | ||
}, | ||
}) | ||
} |
77 changes: 77 additions & 0 deletions
77
src/components/metadataFormControls/DataElementGroupsTransfer/useOptionsQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { useDataQuery } from '@dhis2/app-runtime' | ||
import { useEffect, useMemo, useState } from 'react' | ||
import { SelectOption } from '../../../types' | ||
import { DataElementGroup, Pager } from '../../../types/generated' | ||
|
||
type DataElementGroupsQueryResult = { | ||
dataElementGroups: { | ||
pager: Pager | ||
dataElementGroups: DataElementGroup[] | ||
} | ||
} | ||
|
||
const CATEGORY_COMBOS_QUERY = { | ||
dataElementGroups: { | ||
resource: 'dataElementGroups', | ||
params: (variables: Record<string, string>) => { | ||
const params = { | ||
page: variables.page, | ||
pageSize: 10, | ||
fields: ['id', 'displayName'], | ||
order: ['displayName'], | ||
} | ||
|
||
if (variables.filter) { | ||
return { | ||
...params, | ||
filter: `name:ilike:${variables.filter}`, | ||
} | ||
} | ||
|
||
return params | ||
}, | ||
}, | ||
} | ||
|
||
export function useOptionsQuery() { | ||
const [loadedOptions, setLoadedOptions] = useState<SelectOption[]>([]) | ||
const queryResult = useDataQuery<DataElementGroupsQueryResult>( | ||
CATEGORY_COMBOS_QUERY, | ||
{ | ||
variables: { | ||
page: 1, | ||
filter: '', | ||
}, | ||
} | ||
) | ||
const { data } = queryResult | ||
|
||
// Must be done in `useEffect` and not in `onComplete`, as `onComplete` | ||
// won't get called when useDataQuery has the values in cache already | ||
useEffect(() => { | ||
if (data?.dataElementGroups) { | ||
const { pager, dataElementGroups } = data.dataElementGroups | ||
// We want to add new options to existing ones so we don't have to | ||
// refetch existing options | ||
setLoadedOptions((prevLoadedOptions) => [ | ||
// We only want to add when the current page is > 1 | ||
...(pager.page === 1 ? [] : prevLoadedOptions), | ||
...(dataElementGroups.map((catCombo) => { | ||
const { id, displayName } = catCombo | ||
return { value: id, label: displayName } | ||
}) || []), | ||
]) | ||
} | ||
}, [data]) | ||
|
||
return useMemo( | ||
() => ({ | ||
...queryResult, | ||
data: { | ||
pager: queryResult.data?.dataElementGroups.pager, | ||
result: loadedOptions, | ||
}, | ||
}), | ||
[loadedOptions, queryResult] | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './AggregationLevelMultiSelect' | ||
export * from './CategoryComboSelect' | ||
export * from './DataElementGroupsTransfer' | ||
export * from './DataElementsTransfer' | ||
export * from './LegendSetTransfer' | ||
export * from './OptionSetSelect' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.form { | ||
background: var(--colors-white); | ||
padding: var(--spacers-dp16); | ||
padding-bottom: var(--spacers-dp32); | ||
} | ||
|
||
.formActions { | ||
position: fixed; | ||
left: 0; | ||
bottom: 0; | ||
width: 100vw; | ||
padding: var(--spacers-dp16); | ||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.8); | ||
background: var(--colors-white); | ||
} |
Oops, something went wrong.