diff --git a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx
index 3b1aaeb9..d0d749c5 100644
--- a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx
+++ b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx
@@ -58,11 +58,13 @@ interface SearchableSingleSelectPropTypes {
selected?: string
error?: string
showAllOption?: boolean
+ label: string
}
export const SearchableSingleSelect = ({
showAllOption,
error,
+ label,
loading,
onChange,
onFilterChange,
@@ -96,7 +98,6 @@ export const SearchableSingleSelect = ({
const observer = new IntersectionObserver(
(entries) => {
const [{ isIntersecting }] = entries
-
if (isIntersecting) {
onEndReached()
}
@@ -124,7 +125,7 @@ export const SearchableSingleSelect = ({
// any value to the "selected" prop, as otherwise an error will be thrown
selected={hasSelectedInOptionList ? selected : ''}
onChange={onChange}
- placeholder={i18n.t('Category combo')}
+ placeholder={label}
>
diff --git a/src/components/metadataSelects/CategoryComboSelect/CategoryComboSelect.tsx b/src/components/metadataSelects/CategoryComboSelect/CategoryComboSelect.tsx
index be222eec..0e79a2db 100644
--- a/src/components/metadataSelects/CategoryComboSelect/CategoryComboSelect.tsx
+++ b/src/components/metadataSelects/CategoryComboSelect/CategoryComboSelect.tsx
@@ -1,3 +1,4 @@
+import i18n from '@dhis2/d2-i18n'
import React, { useCallback, useRef, useState } from 'react'
import { SelectOption } from '../../../types'
import { SearchableSingleSelect } from '../../SearchableSingleSelect'
@@ -92,6 +93,7 @@ export function CategoryComboSelect({
return (
{
if (selected === selectedOption?.value) {
diff --git a/src/components/sectionList/filters/GenericSelectionFilter.tsx b/src/components/sectionList/filters/GenericSelectionFilter.tsx
new file mode 100644
index 00000000..b21c3c92
--- /dev/null
+++ b/src/components/sectionList/filters/GenericSelectionFilter.tsx
@@ -0,0 +1,117 @@
+import { useDataEngine } from '@dhis2/app-runtime'
+import React, { useState } from 'react'
+import { useInfiniteQuery, QueryFunctionContext } from 'react-query'
+import { useModelSectionHandleOrThrow } from '../../../lib'
+import { Query } from '../../../types'
+import {
+ BaseIdentifiableObject,
+ IdentifiableObject,
+ ModelCollectionResponse,
+} from '../../../types/generated'
+import { SearchableSingleSelect } from '../../SearchableSingleSelect'
+import { useSectionListFilter } from './useSectionListFilter'
+
+type SimpleQuery = {
+ resource: string
+ fields?: string[]
+ order?: string[]
+}
+
+type GenericSelectionFilterProps = {
+ label: string
+ filterKey: string
+ query: SimpleQuery
+}
+type WrapInData = { data: T }
+
+const infiniteQueryFn =
+ (dataEngine: ReturnType) =>
+ ({
+ queryKey: [query],
+ pageParam = 1,
+ signal,
+ }: QueryFunctionContext<[Query], number>) => {
+ const pagedQuery = {
+ data: {
+ ...query.data,
+ params: {
+ ...query.data.params,
+ page: pageParam,
+ },
+ },
+ }
+ return dataEngine.query(pagedQuery, {
+ signal,
+ }) as Promise<
+ WrapInData>
+ >
+ }
+
+export const GenericSelectionFilter = ({
+ filterKey,
+ label,
+ query,
+}: GenericSelectionFilterProps) => {
+ const dataEngine = useDataEngine()
+ const [filter, setFilter] = useSectionListFilter(filterKey)
+ const [searchValue, setSearchValue] = useState('')
+
+ const handleChange = ({ selected }: { selected: string }) => {
+ setFilter(selected)
+ }
+
+ const optionsQuery = {
+ data: {
+ resource: query.resource,
+ params: {
+ pageSize: 20,
+ fields:
+ query.fields?.length && query.fields?.length > 0
+ ? query.fields
+ : ['id', 'displayName'],
+ filter: searchValue
+ ? `displayName:ilike:${searchValue}`
+ : undefined,
+ },
+ },
+ }
+
+ const res = useInfiniteQuery({
+ queryKey: [optionsQuery],
+ queryFn: infiniteQueryFn(dataEngine),
+ getNextPageParam: (lastPage) => {
+ const pager = lastPage?.data?.pager
+ return pager.nextPage ? pager.page + 1 : undefined
+ },
+ getPreviousPageParam: (lastPage) => {
+ const pager = lastPage?.data?.pager
+ return pager.nextPage ? pager.page - 1 : undefined
+ },
+ })
+
+ const displayOptions =
+ res.data?.pages.flatMap((p) =>
+ p.data[query.resource].map(({ id, displayName }) => ({
+ value: id,
+ label: displayName,
+ }))
+ ) ?? []
+
+ return (
+ setSearchValue(value)}
+ loading={false}
+ label={label}
+ error={res.error?.toString()}
+ onRetryClick={() => {
+ res.refetch()
+ }}
+ />
+ )
+}
diff --git a/src/components/sectionList/filters/useSectionListFilter.ts b/src/components/sectionList/filters/useSectionListFilter.ts
index 535e9e98..8da8a8cd 100644
--- a/src/components/sectionList/filters/useSectionListFilter.ts
+++ b/src/components/sectionList/filters/useSectionListFilter.ts
@@ -126,6 +126,9 @@ const parseToGistQueryFilter = (filters: Filters): string[] => {
restFilterGroup = 1
}
Object.entries(restFilters).forEach(([key, value]) => {
+ if (key === 'uid') {
+ key = 'id'
+ }
const group = restFilterGroup ? `${restFilterGroup++}:` : ''
queryFilters.push(`${group}${key}:eq:${value}`)
})
diff --git a/src/constants/sectionListViews.ts b/src/constants/sectionListViews.ts
index ad3643ef..542a263e 100644
--- a/src/constants/sectionListViews.ts
+++ b/src/constants/sectionListViews.ts
@@ -1,6 +1,6 @@
import i18n from '@dhis2/d2-i18n'
import { PublicAccessValue } from '../components/sectionList/modelValue/PublicAccess'
-import { uniqueBy } from '../lib'
+import { uniqueBy } from '../lib/utils/uniqueBy'
import { SectionName } from './sections'
import { getTranslatedProperty } from './translatedModelProperties'
diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx
index d9b3fe48..faa50393 100644
--- a/src/pages/dataElements/List.tsx
+++ b/src/pages/dataElements/List.tsx
@@ -5,6 +5,7 @@ import {
ValueTypeSelectionFilter,
useQueryParamsForModelGist,
} from '../../components'
+import { GenericSelectionFilter } from '../../components/sectionList/filters/GenericSelectionFilter'
import { useModelListView } from '../../components/sectionList/listView'
import { getFieldFilterFromPath, useModelGist } from '../../lib/'
import { DataElement, GistCollectionResponse } from '../../types/models'
@@ -27,6 +28,7 @@ type DataElements = GistCollectionResponse
export const Component = () => {
const { columns, query: listViewQuery } = useModelListView()
const initialParams = useQueryParamsForModelGist()
+
const { refetch, error, data } = useModelGist(
'dataElements/gist',
{
@@ -52,6 +54,11 @@ export const Component = () => {
return (
+
diff --git a/src/types/generated/utility.ts b/src/types/generated/utility.ts
index 76206d0b..8a91db6d 100644
--- a/src/types/generated/utility.ts
+++ b/src/types/generated/utility.ts
@@ -1,10 +1,23 @@
/* GENERATED BY https://github.com/Birkbjo/dhis2-open-api-ts */
-import { IdentifiableObject, GistPager } from './'
+import { IdentifiableObject, GistPager, Pager } from './'
// import { CategoryCombo, DataElement } from "../generated";
type ModelReferenceCollection = Array
type ModelReference = IdentifiableObject | ModelReferenceCollection
+export type ModelCollectionPart<
+ T extends IdentifiableObject,
+ PagedListName extends string = 'result'
+> = {
+ [K in PagedListName]: T[]
+}
+export type ModelCollectionResponse<
+ T extends IdentifiableObject = IdentifiableObject,
+ PagedListName extends string = 'result'
+> = {
+ pager: Pager
+} & ModelCollectionPart
+
type BaseGist = IdentifiableObject & {
apiEndpoints: GistApiEndpoints
}