Skip to content

Commit

Permalink
feat: generic selectionlist filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Birkbjo committed Sep 27, 2023
1 parent 2d07750 commit 099993e
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ interface SearchableSingleSelectPropTypes {
selected?: string
error?: string
showAllOption?: boolean
label: string
}

export const SearchableSingleSelect = ({
showAllOption,
error,
label,
loading,
onChange,
onFilterChange,
Expand Down Expand Up @@ -96,7 +98,6 @@ export const SearchableSingleSelect = ({
const observer = new IntersectionObserver(
(entries) => {
const [{ isIntersecting }] = entries

if (isIntersecting) {
onEndReached()
}
Expand Down Expand Up @@ -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}
>
<div className={classes.searchField}>
<div className={classes.searchInput}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -92,6 +93,7 @@ export function CategoryComboSelect({

return (
<SearchableSingleSelect
label={i18n.t('Category combo')}
showAllOption={showAllOption}
onChange={({ selected }) => {
if (selected === selectedOption?.value) {
Expand Down
117 changes: 117 additions & 0 deletions src/components/sectionList/filters/GenericSelectionFilter.tsx
Original file line number Diff line number Diff line change
@@ -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'

Check warning on line 4 in src/components/sectionList/filters/GenericSelectionFilter.tsx

View workflow job for this annotation

GitHub Actions / lint

'useModelSectionHandleOrThrow' is defined but never used
import { Query } from '../../../types'
import {
BaseIdentifiableObject,
IdentifiableObject,

Check warning on line 8 in src/components/sectionList/filters/GenericSelectionFilter.tsx

View workflow job for this annotation

GitHub Actions / lint

'IdentifiableObject' is defined but never used
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<T> = { data: T }

const infiniteQueryFn =
(dataEngine: ReturnType<typeof useDataEngine>) =>
({
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<ModelCollectionResponse<BaseIdentifiableObject, string>>
>
}

export const GenericSelectionFilter = ({
filterKey,
label,
query,
}: GenericSelectionFilterProps) => {
const dataEngine = useDataEngine()
const [filter, setFilter] = useSectionListFilter(filterKey)
const [searchValue, setSearchValue] = useState<string>('')

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 (
<SearchableSingleSelect
showAllOption={true}
onChange={handleChange}
onEndReached={res.fetchNextPage}
options={displayOptions}
selected={filter}
showEndLoader={!res.isFetching && !!res.hasNextPage}
onFilterChange={({ value }) => setSearchValue(value)}
loading={false}
label={label}
error={res.error?.toString()}
onRetryClick={() => {
res.refetch()
}}
/>
)
}
3 changes: 3 additions & 0 deletions src/components/sectionList/filters/useSectionListFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
})
Expand Down
2 changes: 1 addition & 1 deletion src/constants/sectionListViews.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
7 changes: 7 additions & 0 deletions src/pages/dataElements/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -27,6 +28,7 @@ type DataElements = GistCollectionResponse<FilteredDataElement>
export const Component = () => {
const { columns, query: listViewQuery } = useModelListView()
const initialParams = useQueryParamsForModelGist()

const { refetch, error, data } = useModelGist<DataElements>(
'dataElements/gist',
{
Expand All @@ -52,6 +54,11 @@ export const Component = () => {

return (
<div>
<GenericSelectionFilter
label="Data elements"
filterKey="uid"
query={{ resource: 'dataElements' }}
/>
<SectionListWrapper
filterElement={
<>
Expand Down
15 changes: 14 additions & 1 deletion src/types/generated/utility.ts
Original file line number Diff line number Diff line change
@@ -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<T = IdentifiableObject> = Array<T>
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<T, PagedListName>

type BaseGist<T> = IdentifiableObject & {
apiEndpoints: GistApiEndpoints<T>
}
Expand Down

0 comments on commit 099993e

Please sign in to comment.