From ae75f5cabd46053de78f031a96b11313cbfd127e Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 26 Jan 2024 17:51:37 +0100 Subject: [PATCH 01/22] refactor(filters): dynamic filter components --- .../sectionList/SectionListWrapper.tsx | 3 +- .../sectionList/filters/DynamicFilters.tsx | 31 +++++++++++++++ .../filters/FilterWrapper.module.css | 6 +++ .../sectionList/filters/FilterWrapper.tsx | 11 +++--- .../{ => filterSelectors}/ConstantFilters.tsx | 13 ++++++- .../ConstantSelectionFilter.tsx | 5 +-- .../{ => filterSelectors}/Filters.module.css | 0 .../IdentifiableFilter.tsx | 2 +- .../filters/filterSelectors/index.ts | 1 + src/components/sectionList/filters/index.ts | 6 +-- .../sectionList/filters/useFilterKeys.tsx | 38 +++++++++++++++++++ src/lib/constants/index.ts | 1 - src/lib/sectionList/filters/filterConfig.tsx | 2 +- .../filters/useSectionListFilters.ts | 2 +- src/lib/sectionList/index.ts | 1 + .../listViews}/index.ts | 0 .../listViews}/sectionListViewsConfig.ts | 2 +- .../listViews}/viewConfigResolver.ts | 12 +++--- 18 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 src/components/sectionList/filters/DynamicFilters.tsx create mode 100644 src/components/sectionList/filters/FilterWrapper.module.css rename src/components/sectionList/filters/{ => filterSelectors}/ConstantFilters.tsx (62%) rename src/components/sectionList/filters/{ => filterSelectors}/ConstantSelectionFilter.tsx (91%) rename src/components/sectionList/filters/{ => filterSelectors}/Filters.module.css (100%) rename src/components/sectionList/filters/{ => filterSelectors}/IdentifiableFilter.tsx (97%) create mode 100644 src/components/sectionList/filters/filterSelectors/index.ts create mode 100644 src/components/sectionList/filters/useFilterKeys.tsx rename src/lib/{constants/sectionListView => sectionList/listViews}/index.ts (100%) rename src/lib/{constants/sectionListView => sectionList/listViews}/sectionListViewsConfig.ts (97%) rename src/lib/{constants/sectionListView => sectionList/listViews}/viewConfigResolver.ts (92%) diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 2dacbcea..b958f000 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -28,7 +28,6 @@ export const SectionListWrapper = ({ error, pager, }: SectionListWrapperProps) => { - data const { columns: headerColumns } = useModelListView() const schema = useSchemaFromHandle() const [selectedModels, setSelectedModels] = useState>(new Set()) @@ -78,7 +77,7 @@ export const SectionListWrapper = ({ return (
- {filterElement} +
> + +const filterKeyToComponentMap: FilterKeyToComponentMap = { + domainType: DomainTypeSelectionFilter, + valueType: ValueTypeSelectionFilter, + aggregationType: AggregationTypeFilter, +} + +export const DynamicFilters = () => { + const filterKeys = useFilterKeys() + + return ( + <> + {filterKeys.map((filterKey) => { + const FilterComponent = filterKeyToComponentMap[filterKey] + return FilterComponent ? ( + + ) : null + })} + + ) +} diff --git a/src/components/sectionList/filters/FilterWrapper.module.css b/src/components/sectionList/filters/FilterWrapper.module.css new file mode 100644 index 00000000..46b41f9e --- /dev/null +++ b/src/components/sectionList/filters/FilterWrapper.module.css @@ -0,0 +1,6 @@ +.filterWrapper { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} diff --git a/src/components/sectionList/filters/FilterWrapper.tsx b/src/components/sectionList/filters/FilterWrapper.tsx index 79a3fdf5..1c79c88c 100644 --- a/src/components/sectionList/filters/FilterWrapper.tsx +++ b/src/components/sectionList/filters/FilterWrapper.tsx @@ -2,12 +2,11 @@ import i18n from '@dhis2/d2-i18n' import { Button } from '@dhis2/ui' import React from 'react' import { useSectionListFilters } from './../../../lib' -import css from './Filters.module.css' -import { IdentifiableFilter } from './IdentifiableFilter' +import { DynamicFilters } from './DynamicFilters' +import { IdentifiableFilter } from './filterSelectors/IdentifiableFilter' +import css from './FilterWrapper.module.css' -type FilterWrapperProps = React.PropsWithChildren - -export const FilterWrapper = ({ children }: FilterWrapperProps) => { +export const FilterWrapper = () => { const [, setFilters] = useSectionListFilters() const handleClear = () => { @@ -17,7 +16,7 @@ export const FilterWrapper = ({ children }: FilterWrapperProps) => { return (
- {children} + diff --git a/src/components/sectionList/filters/ConstantFilters.tsx b/src/components/sectionList/filters/filterSelectors/ConstantFilters.tsx similarity index 62% rename from src/components/sectionList/filters/ConstantFilters.tsx rename to src/components/sectionList/filters/filterSelectors/ConstantFilters.tsx index 635b0340..202ee16a 100644 --- a/src/components/sectionList/filters/ConstantFilters.tsx +++ b/src/components/sectionList/filters/filterSelectors/ConstantFilters.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { DOMAIN_TYPE, VALUE_TYPE } from '../../../lib' +import { AGGREGATION_TYPE, DOMAIN_TYPE, VALUE_TYPE } from '../../../../lib' import { ConstantSelectionFilter } from './ConstantSelectionFilter' export const DomainTypeSelectionFilter = () => { @@ -22,3 +22,14 @@ export const ValueTypeSelectionFilter = () => { /> ) } + +export const AggregationTypeFilter = () => { + return ( + + ) +} diff --git a/src/components/sectionList/filters/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx similarity index 91% rename from src/components/sectionList/filters/ConstantSelectionFilter.tsx rename to src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx index c518f14f..47b90117 100644 --- a/src/components/sectionList/filters/ConstantSelectionFilter.tsx +++ b/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx @@ -1,8 +1,8 @@ import i18n from '@dhis2/d2-i18n' import { SingleSelect, SingleSelectOption } from '@dhis2/ui' import React from 'react' -import { FilterKey, useSectionListFilter } from '../../../lib' -import { SelectOnChangeObject } from '../../../types' +import { FilterKey, useSectionListFilter } from '../../../../lib' +import { SelectOnChangeObject } from '../../../../types' import css from './Filters.module.css' type ConstantSelectionFilterProps = { @@ -19,7 +19,6 @@ export const ConstantSelectionFilter = ({ filterable, }: ConstantSelectionFilterProps) => { const [filter, setFilter] = useSectionListFilter(filterKey) - return ( { diff --git a/src/components/sectionList/filters/filterSelectors/index.ts b/src/components/sectionList/filters/filterSelectors/index.ts new file mode 100644 index 00000000..d34cadc2 --- /dev/null +++ b/src/components/sectionList/filters/filterSelectors/index.ts @@ -0,0 +1 @@ +export * from './ConstantFilters' diff --git a/src/components/sectionList/filters/index.ts b/src/components/sectionList/filters/index.ts index 40486a57..bfb9c6bb 100644 --- a/src/components/sectionList/filters/index.ts +++ b/src/components/sectionList/filters/index.ts @@ -1,3 +1,3 @@ -export * from './ConstantSelectionFilter' -export * from './IdentifiableFilter' -export * from './ConstantFilters' +export * from './filterSelectors/ConstantSelectionFilter' +export * from './filterSelectors/IdentifiableFilter' +export * from './filterSelectors/ConstantFilters' diff --git a/src/components/sectionList/filters/useFilterKeys.tsx b/src/components/sectionList/filters/useFilterKeys.tsx new file mode 100644 index 00000000..affa8033 --- /dev/null +++ b/src/components/sectionList/filters/useFilterKeys.tsx @@ -0,0 +1,38 @@ +import { useMemo } from 'react' +import { + useSectionListFilters, + ParsedFilterParams, + ConfigurableFilterKey, + IDENTIFIABLE_KEY, +} from '../../../lib' +import { useModelListView } from '../listView' + +export type FiltersWithValue = { + [FilterKey in ConfigurableFilterKey]: { + value: ParsedFilterParams[FilterKey] + label: string + filterKey: FilterKey + } +} + +export const useFilterKeys = () => { + const [filters] = useSectionListFilters() + const views = useModelListView() + const viewFilters = views.filters + // combine filters and views, since filters in URL might not be selected for view + // but we should show them when they have a value + const filterKeys = useMemo(() => { + const viewFilterKeys = viewFilters.map(({ filterKey }) => filterKey) + const selectedFiltersNotInView = Object.entries(filters) + .filter( + ([filterKey, value]) => + value !== undefined && + filterKey !== IDENTIFIABLE_KEY && + !viewFilterKeys.includes(filterKey as ConfigurableFilterKey) + ) + .map(([filterKey]) => filterKey) as ConfigurableFilterKey[] + + return viewFilterKeys.concat(selectedFiltersNotInView) + }, [filters, viewFilters]) + return filterKeys +} diff --git a/src/lib/constants/index.ts b/src/lib/constants/index.ts index c8301647..61de1c84 100644 --- a/src/lib/constants/index.ts +++ b/src/lib/constants/index.ts @@ -1,6 +1,5 @@ export * from './sections' export * from './translatedModelConstants' export * from './translatedModelProperties' -export * from './sectionListView' export const IDENTIFIABLE_KEY = 'identifiable' diff --git a/src/lib/sectionList/filters/filterConfig.tsx b/src/lib/sectionList/filters/filterConfig.tsx index 0fae40c3..158ccadb 100644 --- a/src/lib/sectionList/filters/filterConfig.tsx +++ b/src/lib/sectionList/filters/filterConfig.tsx @@ -15,7 +15,7 @@ export const filterParamsSchema = z categoryCombo: zodArrayIds, dataSet: zodArrayIds, domainType: z.array(z.nativeEnum(DataElement.domainType)), - valueType: z.array(z.string()), + valueType: z.array(z.nativeEnum(DataElement.valueType)), }) .partial() diff --git a/src/lib/sectionList/filters/useSectionListFilters.ts b/src/lib/sectionList/filters/useSectionListFilters.ts index 087888b8..e029f6d1 100644 --- a/src/lib/sectionList/filters/useSectionListFilters.ts +++ b/src/lib/sectionList/filters/useSectionListFilters.ts @@ -14,7 +14,7 @@ import { export const useSectionListFilters = () => { const [filters, setFilterParams] = useQueryParams(filterQueryParamType) const [, setPagingParams] = usePaginationQueryParams() - + console.log({ filters }) const parsedFilters = useMemo(() => { const parsed = filterParamsSchema.safeParse(filters) if (parsed.success) { diff --git a/src/lib/sectionList/index.ts b/src/lib/sectionList/index.ts index ab050cdf..180856e0 100644 --- a/src/lib/sectionList/index.ts +++ b/src/lib/sectionList/index.ts @@ -5,3 +5,4 @@ export { useUpdatePaginationParams, } from './usePaginationParams' export * from './useParamsForDataQuery' +export * from './listViews' diff --git a/src/lib/constants/sectionListView/index.ts b/src/lib/sectionList/listViews/index.ts similarity index 100% rename from src/lib/constants/sectionListView/index.ts rename to src/lib/sectionList/listViews/index.ts diff --git a/src/lib/constants/sectionListView/sectionListViewsConfig.ts b/src/lib/sectionList/listViews/sectionListViewsConfig.ts similarity index 97% rename from src/lib/constants/sectionListView/sectionListViewsConfig.ts rename to src/lib/sectionList/listViews/sectionListViewsConfig.ts index 2042180c..90a6f529 100644 --- a/src/lib/constants/sectionListView/sectionListViewsConfig.ts +++ b/src/lib/sectionList/listViews/sectionListViewsConfig.ts @@ -1,5 +1,5 @@ import i18n from '@dhis2/d2-i18n' -import type { ConfigurableFilterKey } from '../../sectionList/filters/' +import type { ConfigurableFilterKey } from '../filters' export interface ModelPropertyDescriptor { label: string diff --git a/src/lib/constants/sectionListView/viewConfigResolver.ts b/src/lib/sectionList/listViews/viewConfigResolver.ts similarity index 92% rename from src/lib/constants/sectionListView/viewConfigResolver.ts rename to src/lib/sectionList/listViews/viewConfigResolver.ts index c7434499..740d93ae 100644 --- a/src/lib/constants/sectionListView/viewConfigResolver.ts +++ b/src/lib/sectionList/listViews/viewConfigResolver.ts @@ -1,5 +1,5 @@ +import { getTranslatedProperty } from '../../constants/translatedModelProperties' import { uniqueBy } from '../../utils' -import { getTranslatedProperty } from '../translatedModelProperties' import { defaultModelViewConfig, modelListViewsConfig, @@ -23,7 +23,7 @@ interface ResolvedSectionListView { [key: string]: ResolvedViewConfig } -const toModelPropertyDescriptor = ( +export const toModelPropertyDescriptor = ( propertyConfig: ModelPropertyConfig ): ModelPropertyDescriptor => { if (typeof propertyConfig === 'string') { @@ -35,7 +35,9 @@ const toModelPropertyDescriptor = ( return propertyConfig } -const toFilterDescriptor = (propertyConfig: FilterConfig): FilterDescriptor => { +export const toFilterDescriptor = ( + propertyConfig: FilterConfig +): FilterDescriptor => { if (typeof propertyConfig === 'string') { return { label: getTranslatedProperty(propertyConfig), @@ -111,7 +113,7 @@ const resolveListViewsConfig = () => { return merged } -const mergedModelViewsConfig = resolveListViewsConfig() +const resolvedModelViewsConfig = resolveListViewsConfig() const resolvedDefaultConfig = { columns: resolveColumnConfig(defaultModelViewConfig.columns), filters: resolveFilterConfig(defaultModelViewConfig.filters), @@ -120,7 +122,7 @@ const resolvedDefaultConfig = { export const getViewConfigForSection = ( sectionName: string ): ResolvedViewConfig => { - const resolvedConfig = mergedModelViewsConfig[sectionName] + const resolvedConfig = resolvedModelViewsConfig[sectionName] if (resolvedConfig) { return resolvedConfig } From 3242a4fde32eac4c7ff8666c6fea739768077305 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Fri, 26 Jan 2024 17:52:35 +0100 Subject: [PATCH 02/22] fix: cleanup filter props in DE-List --- src/components/sectionList/SectionListWrapper.tsx | 2 -- src/pages/dataElements/List.tsx | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index b958f000..1f1c9052 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -16,14 +16,12 @@ import { SectionListRow } from './SectionListRow' import { SectionListTitle } from './SectionListTitle' type SectionListWrapperProps = { - filterElement?: React.ReactElement data: ModelCollection | undefined pager: Pager | undefined error: FetchError | undefined } export const SectionListWrapper = ({ - filterElement, data, error, pager, diff --git a/src/pages/dataElements/List.tsx b/src/pages/dataElements/List.tsx index fcaf2b3f..1feb859c 100644 --- a/src/pages/dataElements/List.tsx +++ b/src/pages/dataElements/List.tsx @@ -1,10 +1,6 @@ import { useDataQuery } from '@dhis2/app-runtime' import React, { useEffect } from 'react' -import { - SectionListWrapper, - DomainTypeSelectionFilter, - ValueTypeSelectionFilter, -} from '../../components' +import { SectionListWrapper } from '../../components' import { useModelListView } from '../../components/sectionList/listView' import { useSchemaFromHandle, useParamsForDataQuery } from '../../lib/' import { getFieldFilter } from '../../lib/models/path' @@ -62,12 +58,6 @@ export const Component = () => { return (
- - - - } error={error} data={data?.result.dataElements} pager={data?.result.pager} From 5c7acb40e08c7ea8021ebc076aa27979f28f117d Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 29 Jan 2024 00:49:05 +0100 Subject: [PATCH 03/22] feat(filters): add filters for dataSet and catCombo --- .../SearchableSingleSelect.tsx | 6 +- .../SearchableSingleSelect/index.ts | 3 +- .../sectionList/filters/DynamicFilters.tsx | 5 +- .../filterSelectors/CategoryComboFilter.tsx | 24 +++ .../ConstantSelectionFilter.tsx | 1 + .../filters/filterSelectors/DataSetFilter.tsx | 24 +++ .../filters/filterSelectors/ModelFilter.tsx | 177 ++++++++++++++++++ .../filterSelectors/createdFilterDataQuery.ts | 13 ++ .../filters/filterSelectors/index.ts | 2 + .../sectionList/filters/useFilterKeys.tsx | 3 +- .../sectionList/listView/useModelListView.tsx | 2 +- src/lib/sectionList/filters/index.ts | 1 + .../filters/parseFiltersToQueryParams.ts | 4 +- .../filters/useFilterQueryParams.ts | 2 +- .../listViews/sectionListViewsConfig.ts | 4 +- src/lib/sectionList/useParamsForDataQuery.ts | 11 +- 16 files changed, 261 insertions(+), 21 deletions(-) create mode 100644 src/components/sectionList/filters/filterSelectors/CategoryComboFilter.tsx create mode 100644 src/components/sectionList/filters/filterSelectors/DataSetFilter.tsx create mode 100644 src/components/sectionList/filters/filterSelectors/ModelFilter.tsx create mode 100644 src/components/sectionList/filters/filterSelectors/createdFilterDataQuery.ts diff --git a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx index 3a6c9013..26894a26 100644 --- a/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx +++ b/src/components/SearchableSingleSelect/SearchableSingleSelect.tsx @@ -6,11 +6,10 @@ import { SingleSelectOption, } from '@dhis2/ui' import React, { forwardRef, useEffect, useState } from 'react' -// import { useDebouncedCallback } from 'use-debounce' import { useDebouncedState } from '../../lib' import classes from './SearchableSingleSelect.module.css' -interface Option { +export interface Option { value: string label: string } @@ -55,6 +54,7 @@ interface SearchableSingleSelectPropTypes { onRetryClick: () => void options: Option[] placeholder: string + prefix?: string showEndLoader: boolean loading: boolean selected?: string @@ -70,6 +70,7 @@ export const SearchableSingleSelect = ({ error, loading, placeholder, + prefix, onBlur, onChange, onEndReached, @@ -126,6 +127,7 @@ export const SearchableSingleSelect = ({ error={invalid} onChange={onChange} placeholder={placeholder} + prefix={prefix} onBlur={onBlur} onFocus={onFocus} > diff --git a/src/components/SearchableSingleSelect/index.ts b/src/components/SearchableSingleSelect/index.ts index 7f9aa7b6..889bd39d 100644 --- a/src/components/SearchableSingleSelect/index.ts +++ b/src/components/SearchableSingleSelect/index.ts @@ -1 +1,2 @@ -export { SearchableSingleSelect } from './SearchableSingleSelect' +export * from './SearchableSingleSelect' +//asf diff --git a/src/components/sectionList/filters/DynamicFilters.tsx b/src/components/sectionList/filters/DynamicFilters.tsx index 0ce82eb9..20c7e0cd 100644 --- a/src/components/sectionList/filters/DynamicFilters.tsx +++ b/src/components/sectionList/filters/DynamicFilters.tsx @@ -2,6 +2,8 @@ import React from 'react' import { ConfigurableFilterKey } from './../../../lib' import { AggregationTypeFilter, + CategoryComboFilter, + DataSetFilter, DomainTypeSelectionFilter, ValueTypeSelectionFilter, } from './filterSelectors' @@ -10,6 +12,8 @@ import { useFilterKeys } from './useFilterKeys' type FilterKeyToComponentMap = Partial> const filterKeyToComponentMap: FilterKeyToComponentMap = { + categoryCombo: CategoryComboFilter, + dataSet: DataSetFilter, domainType: DomainTypeSelectionFilter, valueType: ValueTypeSelectionFilter, aggregationType: AggregationTypeFilter, @@ -17,7 +21,6 @@ const filterKeyToComponentMap: FilterKeyToComponentMap = { export const DynamicFilters = () => { const filterKeys = useFilterKeys() - return ( <> {filterKeys.map((filterKey) => { diff --git a/src/components/sectionList/filters/filterSelectors/CategoryComboFilter.tsx b/src/components/sectionList/filters/filterSelectors/CategoryComboFilter.tsx new file mode 100644 index 00000000..67a2e91c --- /dev/null +++ b/src/components/sectionList/filters/filterSelectors/CategoryComboFilter.tsx @@ -0,0 +1,24 @@ +import i18n from '@dhis2/d2-i18n' +import React from 'react' +import { useSectionListFilter } from '../../../../lib' +import { createFilterDataQuery } from './createdFilterDataQuery' +import { ModelFilterSelect } from './ModelFilter' + +const query = createFilterDataQuery('categoryCombos') + +export const CategoryComboFilter = () => { + const [filter, setFilter] = useSectionListFilter('categoryCombo') + + const selected = filter?.[0] + + return ( + + setFilter(selected ? [selected] : undefined) + } + /> + ) +} diff --git a/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx b/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx index 47b90117..2c29b768 100644 --- a/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx +++ b/src/components/sectionList/filters/filterSelectors/ConstantSelectionFilter.tsx @@ -28,6 +28,7 @@ export const ConstantSelectionFilter = ({ selected={Array.isArray(filter) ? filter[0] : filter} placeholder={label} dense + prefix={label} filterable={filterable} filterPlaceholder={i18n.t('Type to filter options')} noMatchText={i18n.t('No matches')} diff --git a/src/components/sectionList/filters/filterSelectors/DataSetFilter.tsx b/src/components/sectionList/filters/filterSelectors/DataSetFilter.tsx new file mode 100644 index 00000000..c8d570b2 --- /dev/null +++ b/src/components/sectionList/filters/filterSelectors/DataSetFilter.tsx @@ -0,0 +1,24 @@ +import i18n from '@dhis2/d2-i18n' +import React from 'react' +import { useSectionListFilter } from '../../../../lib' +import { createFilterDataQuery } from './createdFilterDataQuery' +import { ModelFilterSelect } from './ModelFilter' + +const query = createFilterDataQuery('dataSets') + +export const DataSetFilter = () => { + const [filter, setFilter] = useSectionListFilter('dataSet') + + const selected = filter?.[0] + + return ( + + setFilter(selected ? [selected] : undefined) + } + /> + ) +} diff --git a/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx b/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx new file mode 100644 index 00000000..eff0b634 --- /dev/null +++ b/src/components/sectionList/filters/filterSelectors/ModelFilter.tsx @@ -0,0 +1,177 @@ +import { useDataQuery } from '@dhis2/app-runtime' +import React, { useCallback, useRef, useState } from 'react' +import type { Query } from '../../../../types' +import { Pager } from '../../../../types/generated' +import { Option, SearchableSingleSelect } from '../../../SearchableSingleSelect' + +function computeDisplayOptions({ + selected, + selectedOption, + options, +}: { + options: OptionResult[] + selected?: string + required?: boolean + selectedOption?: OptionResult +}): Option[] { + // This happens only when we haven't fetched the label for an initially + // selected value. Don't show anything to prevent error that an option is + // missing + if (!selectedOption && selected) { + return [] + } + + const optionsContainSelected = options?.find(({ id }) => id === selected) + + const withSelectedOption = + selectedOption && !optionsContainSelected + ? [...options, selectedOption] + : options + + return withSelectedOption.map((option) => ({ + value: option.id, + label: option.displayName, + })) +} + +type ModelQuery = { + result: Query[keyof Query] +} + +type OptionResult = { + id: string + displayName: string +} + +type OptionsResult = { + result: { + pager: Pager + } & { [key: string]: OptionResult[] } +} + +type PagedResult = { pager: Pager } & OptionsResult + +const createInitialOptionQuery = ( + resource: string, + selected?: string +): ModelQuery => ({ + result: { + resource: resource, + id: selected, + params: (params) => ({ + ...params, + fields: ['id', 'displayName'], + }), + }, +}) + +export interface ModelSingleSelectProps { + onChange: ({ selected }: { selected: string | undefined }) => void + selected?: string + placeholder: string + query: Query +} + +export const ModelFilterSelect = ({ + onChange, + selected, + query, + placeholder, +}: ModelSingleSelectProps) => { + // 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 filterRef = useRef() + const pageRef = useRef(0) + + const [initialQuery] = useState(() => + createInitialOptionQuery(query.result.resource, selected) + ) + + const initialOptionResult = useDataQuery(initialQuery, { + // run only when we have an initial selected value + lazy: initialQuery.result.id === undefined, + onComplete: (data) => { + setSelectedOption(data) + }, + }) + + // We need to persist the selected option so we can display an