-
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 New and Edit views
- Loading branch information
Showing
28 changed files
with
1,094 additions
and
40 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
150 changes: 150 additions & 0 deletions
150
src/components/metadataFormControls/DataElementsTransfer/DataElementsTransfer.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,150 @@ | ||
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 DataElementsSelectProps { | ||
onChange: ({ selected }: { selected: string[] }) => void | ||
selected: string[] | ||
rightHeader?: ReactElement | ||
rightFooter?: ReactElement | ||
leftFooter?: ReactElement | ||
leftHeader?: ReactElement | ||
} | ||
|
||
export const DataElementsTransfer = forwardRef(function DataElementsSelect( | ||
{ | ||
onChange, | ||
selected, | ||
rightHeader, | ||
rightFooter, | ||
leftFooter, | ||
leftHeader, | ||
}: DataElementsSelectProps, | ||
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 elements')} | ||
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/DataElementsTransfer/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 { DataElementsTransfer } from './DataElementsTransfer' |
9 changes: 9 additions & 0 deletions
9
src/components/metadataFormControls/DataElementsTransfer/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,9 @@ | ||
import { DataElement, GistCollectionResponse } from '../../../types/generated' | ||
|
||
const filterFields = ['id', 'displayName'] as const //(name is translated by default in /gist) | ||
export type FilteredDataElement = Pick< | ||
DataElement, | ||
(typeof filterFields)[number] | ||
> | ||
export type DataElementsQueryResult = | ||
GistCollectionResponse<FilteredDataElement> |
44 changes: 44 additions & 0 deletions
44
src/components/metadataFormControls/DataElementsTransfer/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 { FilteredDataElement } from './types' | ||
|
||
type InitialDataElementsQueryResult = { | ||
dataElements: { | ||
dataElements: FilteredDataElement[] | ||
} | ||
} | ||
|
||
export function useInitialOptionQuery({ | ||
selected, | ||
onComplete, | ||
}: { | ||
onComplete: (options: SelectOption[]) => void | ||
selected: string[] | ||
}) { | ||
const initialSelected = useRef(selected) | ||
const query = { | ||
dataElements: { | ||
resource: 'dataElements', | ||
params: { | ||
paging: false, | ||
fields: ['id', 'displayName'], | ||
filter: `id:in:[${initialSelected.current.join(',')}]`, | ||
}, | ||
}, | ||
} | ||
|
||
return useDataQuery<InitialDataElementsQueryResult>(query, { | ||
lazy: !initialSelected.current, | ||
variables: { id: selected }, | ||
onComplete: (data) => { | ||
const { dataElements } = data.dataElements | ||
const options = dataElements.map(({ id, displayName }) => ({ | ||
value: id, | ||
label: displayName, | ||
})) | ||
|
||
onComplete(options) | ||
}, | ||
}) | ||
} |
Oops, something went wrong.