From 4b916b6ae6aad23aa639ec58e97cb26e803b4f8f Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 18 Nov 2024 14:40:54 +0100 Subject: [PATCH 1/2] feat(ModelMultiSelect): add modelmultiselect component --- .../SearchableMultiSelect.module.css | 68 ++++++++ .../SearchableMultiSelect.tsx | 160 ++++++++++++++++++ src/components/SearchableMultiSelect/index.ts | 1 + .../ModelMultiSelect/BaseModelMultiSelect.tsx | 83 +++++++++ .../ModelMultiSelect/ModelMultiSelect.tsx | 137 +++++++++++++++ .../ModelMultiSelectField.tsx | 59 +++++++ .../ModelMultiSelect/index.ts | 1 + .../useRefreshMultiSelect.tsx | 20 +++ src/types/query.ts | 2 +- 9 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 src/components/SearchableMultiSelect/SearchableMultiSelect.module.css create mode 100644 src/components/SearchableMultiSelect/SearchableMultiSelect.tsx create mode 100644 src/components/SearchableMultiSelect/index.ts create mode 100644 src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx create mode 100644 src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx create mode 100644 src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx create mode 100644 src/components/metadataFormControls/ModelMultiSelect/index.ts create mode 100644 src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx diff --git a/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css b/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css new file mode 100644 index 00000000..d78ac769 --- /dev/null +++ b/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css @@ -0,0 +1,68 @@ +.invisibleOption { + display: none; +} + +.loader { + height: 80px; + display: flex; + align-items: center; + justify-content: center; + padding-top: 20px; + overflow: hidden; +} + +.error { + display: flex; + justify-content: center; + font-size: 14px; + padding: var(--spacers-dp12) 16px; +} + +.errorInnerWrapper { + display: flex; + flex-direction: column; + justify-content: center; +} + +.loadingErrorLabel { + color: var(--theme-error); +} + +.errorRetryButton { + background: none; + padding: 0; + border: 0; + outline: 0; + text-decoration: underline; + cursor: pointer; +} + +.searchField { + display: flex; + gap: var(--spacers-dp8); + position: sticky; + top: 0; + padding: var(--spacers-dp16); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.4); + background: var(--colors-white); +} + +.searchInput { + flex-grow: 1; +} + +.clearButton { + font-size: 14px; + flex-grow: 0; + background: none; + padding: 0; + border: 0; + outline: 0; + text-decoration: underline; + cursor: pointer; +} + +.clearButton:hover { + color: var(--theme-valid); + text-decoration: none; +} diff --git a/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx b/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx new file mode 100644 index 00000000..8c4c108a --- /dev/null +++ b/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx @@ -0,0 +1,160 @@ +import i18n from '@dhis2/d2-i18n' +import { + CircularLoader, + Input, + MultiSelect, + MultiSelectOption, + MultiSelectProps, +} from '@dhis2/ui' +import React, { forwardRef, useEffect, useState } from 'react' +import { useDebouncedState } from '../../lib' +import classes from './SearchableMultiSelect.module.css' + +export interface Option { + value: string + label: string +} + +type OwnProps = { + onEndReached?: () => void + onFilterChange: ({ value }: { value: string }) => void + onRetryClick: () => void + options: Option[] + showEndLoader?: boolean + error?: string +} + +export type SearchableMultiSelectPropTypes = Omit< + MultiSelectProps, + keyof OwnProps +> & + OwnProps +export const SearchableMultiSelect = ({ + disabled, + error, + dense, + loading, + placeholder, + prefix, + onBlur, + onChange, + onEndReached, + onFilterChange, + onFocus, + onRetryClick, + options, + selected, + showEndLoader, +}: SearchableMultiSelectPropTypes) => { + const [loadingSpinnerRef, setLoadingSpinnerRef] = useState() + + const { liveValue: filter, setValue: setFilterValue } = + useDebouncedState({ + initialValue: '', + onSetDebouncedValue: (value: string) => onFilterChange({ value }), + }) + + useEffect(() => { + // We don't want to wait for intersections when loading as that can + // cause buggy behavior + if (loadingSpinnerRef && !loading) { + const observer = new IntersectionObserver( + (entries) => { + const [{ isIntersecting }] = entries + + if (isIntersecting) { + onEndReached?.() + } + }, + { threshold: 0.8 } + ) + + observer.observe(loadingSpinnerRef) + return () => observer.disconnect() + } + }, [loadingSpinnerRef, loading, onEndReached]) + + return ( + 1} + > +
+
+ setFilterValue(value ?? '')} + placeholder={i18n.t('Filter options')} + /> +
+ +
+ + {options.map(({ value, label }) => ( + + ))} + + {!error && !loading && showEndLoader && ( + { + if (!!ref && ref !== loadingSpinnerRef) { + setLoadingSpinnerRef(ref) + } + }} + /> + )} + + {!error && loading && } + + {error && } +
+ ) +} + +const Loader = forwardRef(function Loader(_, ref) { + return ( +
+ +
+ ) +}) + +function Error({ + msg, + onRetryClick, +}: { + msg: string + onRetryClick: () => void +}) { + return ( +
+
+ {msg} + +
+
+ ) +} diff --git a/src/components/SearchableMultiSelect/index.ts b/src/components/SearchableMultiSelect/index.ts new file mode 100644 index 00000000..216835ed --- /dev/null +++ b/src/components/SearchableMultiSelect/index.ts @@ -0,0 +1 @@ +export * from './SearchableMultiSelect' diff --git a/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx new file mode 100644 index 00000000..eddb113d --- /dev/null +++ b/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx @@ -0,0 +1,83 @@ +import React, { useCallback, useMemo } from 'react' +import { DisplayableModel } from '../../../types/models' +import { + SearchableMultiSelect, + SearchableMultiSelectPropTypes, +} from '../../SearchableMultiSelect' + +const toDisplayOption = (model: DisplayableModel) => ({ + value: model.id, + label: model.displayName, +}) + +type OwnProps = { + selected: TModel[] + available: TModel[] + onChange: ({ selected }: { selected: TModel[] }) => void + noValueOption?: { value: string; label: string } +} + +export type BaseModelMultiSelectProps = Omit< + SearchableMultiSelectPropTypes, + keyof OwnProps | 'options' | 'selected' +> & + OwnProps + +/* Simple wrapper component handle generic models with MultiSelect-component. */ +export const BaseModelMultiSelect = ({ + available, + selected, + onChange, + noValueOption, + ...searchableMultiSelectProps +}: BaseModelMultiSelectProps) => { + const { allModelsMap, allSingleSelectOptions, selectedOptions } = + useMemo(() => { + const allModelsMap = new Map(available.map((o) => [o.id, o])) + // due to pagination, the selected models might not be in the available list, so add them + selected.forEach((s) => { + if (!allModelsMap.get(s.id)) { + allModelsMap.set(s.id, s) + } + }) + + const allSingleSelectOptions = Array.from(allModelsMap).map( + ([, value]) => toDisplayOption(value) + ) + + const selectedOptions = selected.map((s) => s.id) + if (noValueOption) { + allSingleSelectOptions.unshift(noValueOption) + } + + return { + allModelsMap, + allSingleSelectOptions, + selectedOptions, + } + }, [available, selected, noValueOption]) + + const handleOnChange = useCallback( + ({ selected }: { selected: string[] }) => { + if (!selected) { + return + } + + const selectedModels = selected + .map((s) => allModelsMap.get(s)) + .filter((s) => !!s) + + onChange({ selected: selectedModels }) + }, + [onChange, allModelsMap] + ) + + return ( + + ) +} diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx new file mode 100644 index 00000000..547e2f6d --- /dev/null +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx @@ -0,0 +1,137 @@ +import React, { useMemo, useRef, useState } from 'react' +import { useInfiniteQuery, useQuery } from 'react-query' +import { useDebouncedCallback } from 'use-debounce' +import { useBoundResourceQueryFn } from '../../../lib/query/useBoundQueryFn' +import { PlainResourceQuery } from '../../../types' +import { PagedResponse } from '../../../types/generated' +import { DisplayableModel } from '../../../types/models' +import { + BaseModelMultiSelect, + BaseModelMultiSelectProps, +} from './BaseModelMultiSelect' + +type Response = PagedResponse + +const defaultQuery = { + params: { + order: 'displayName:asc', + fields: ['id', 'displayName'], + pageSize: 10, + }, +} satisfies Omit + +export type ModelMultiSelectProps = Omit< + BaseModelMultiSelectProps, + | 'available' + | 'onFilterChange' + | 'onRetryClick' + | 'onEndReached' + | 'showEndLoader' + | 'loading' + | 'error' + | 'selected' +> & { + query: Omit + onFilterChange?: (value: string) => void + select?: (value: TModel[]) => TModel[] + selected: TModel[] | string[] | undefined +} + +export const ModelMultiSelect = ({ + selected = [], + query, + select, + ...baseModelSingleSelectProps +}: ModelMultiSelectProps) => { + const queryFn = useBoundResourceQueryFn() + // keep select in ref, so we dont recompute for inline selects + const selectRef = useRef(select) + const [searchTerm, setSearchTerm] = useState('') + const searchFilter = `identifiable:token:${searchTerm}` + const filter: string[] = searchTerm ? [searchFilter] : [] + const params = query.params + + const queryObject = { + ...query, + params: { + ...defaultQuery.params, + ...params, + filter: filter.concat(params?.filter || []), + }, + } + const modelName = query.resource + + const queryResult = useInfiniteQuery({ + queryKey: [queryObject] as const, + queryFn: queryFn>, + keepPreviousData: true, + getNextPageParam: (lastPage) => + lastPage.pager.nextPage ? lastPage.pager.page + 1 : undefined, + getPreviousPageParam: (firstPage) => + firstPage.pager.prevPage ? firstPage.pager.page - 1 : undefined, + }) + + const allDataMap = useMemo(() => { + const flatData = + queryResult.data?.pages.flatMap((page) => page[modelName]) ?? [] + if (selectRef.current) { + return selectRef.current(flatData) + } + return flatData + }, [queryResult.data, modelName]) + + const selectedWithoutData = selected.filter( + (s) => allDataMap.find((d) => d.id === s) === undefined + ) + + const selectedQuery = useQuery({ + queryKey: [ + { + resource: modelName, + params: { filter: [`id:in:[${selected?.join()}]`] }, + }, + ] as const, + queryFn: queryFn>, + enabled: + typeof selected?.[0] === 'string' && selectedWithoutData.length > 0, + }) + + const resolvedSelected = useMemo(() => { + if (selectedQuery.data) { + return selectedQuery.data[modelName] + } + if (selectedWithoutData.length === 0) { + return selected.map( + (s) => allDataMap.find((d) => d.id === s) as TModel + ) + } + return selected as TModel[] + }, [ + selectedQuery.data, + selected, + modelName, + allDataMap, + selectedWithoutData.length, + ]) + console.log({ resolvedSelected, selected }) + const handleFilterChange = useDebouncedCallback(({ value }) => { + if (value != undefined) { + setSearchTerm(value) + } + baseModelSingleSelectProps.onFilterChange?.(value) + }, 250) + + return ( + + ) +} diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx new file mode 100644 index 00000000..f10d8cb1 --- /dev/null +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx @@ -0,0 +1,59 @@ +import { Field } from '@dhis2/ui' +import React from 'react' +import { useField } from 'react-final-form' +import { PlainResourceQuery } from '../../../types' +import { DisplayableModel } from '../../../types/models' +import { ModelMultiSelectProps, ModelMultiSelect } from './ModelMultiSelect' + +type OwnProps = { + name: string + query: PlainResourceQuery + label?: string + placeholder?: string + helpText?: string + required?: boolean + onChange?: ModelMultiSelectProps['onChange'] +} + +type ModelMultiSelectFieldProps = Omit< + ModelMultiSelectProps, + 'selected' | 'onChange' +> & + OwnProps + +export function ModelMultiSelectField({ + name, + query, + label, + helpText, + required, + onChange, + ...modelSingleSelectProps +}: ModelMultiSelectFieldProps) { + const { input, meta } = useField(name, { + validateFields: [], + }) + + return ( + + + {...modelSingleSelectProps} + selected={input.value} + onChange={(selected) => { + input.onChange(selected) + input.onBlur() + onChange?.(selected) + }} + query={query} + /> + + ) +} diff --git a/src/components/metadataFormControls/ModelMultiSelect/index.ts b/src/components/metadataFormControls/ModelMultiSelect/index.ts new file mode 100644 index 00000000..41803719 --- /dev/null +++ b/src/components/metadataFormControls/ModelMultiSelect/index.ts @@ -0,0 +1 @@ +export * from './ModelMultiSelect' diff --git a/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx new file mode 100644 index 00000000..d1b53384 --- /dev/null +++ b/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx @@ -0,0 +1,20 @@ +import { useCallback } from 'react' +import { useQueryClient, InvalidateQueryFilters } from 'react-query' +import { PlainResourceQuery } from '../../../types' + +export const useRefreshModelSingleSelect = ( + query: Omit +) => { + const queryClient = useQueryClient() + + return useCallback( + (invalidateFilters?: InvalidateQueryFilters) => { + console.log('invalidate', query) + queryClient.invalidateQueries({ + queryKey: [query], + // ...invalidateFilters, + }) + }, + [queryClient, query] + ) +} diff --git a/src/types/query.ts b/src/types/query.ts index 4cf166b4..26887911 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -10,7 +10,7 @@ type QueryParams = { pageSize?: number page?: number fields?: string | string[] - filter: string | string[] + filter?: string | string[] [key: string]: unknown } From 4bc8ec3fd01baad2dbd30e6a2f5ec3025df275e0 Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Mon, 18 Nov 2024 17:32:17 +0100 Subject: [PATCH 2/2] feat: support string ids for modelmulti-select --- .../SearchableMultiSelect.module.css | 3 + .../SearchableMultiSelect.tsx | 10 +-- .../ModelMultiSelect/BaseModelMultiSelect.tsx | 1 - .../ModelMultiSelect/ModelMultiSelect.tsx | 87 +++++-------------- .../ModelMultiSelectField.tsx | 2 +- .../ModelMultiSelect/index.ts | 3 + .../useModelMultiSelectQuery.tsx | 86 ++++++++++++++++++ .../useRefreshMultiSelect.tsx | 5 +- src/types/query.ts | 3 +- 9 files changed, 122 insertions(+), 78 deletions(-) create mode 100644 src/components/metadataFormControls/ModelMultiSelect/useModelMultiSelectQuery.tsx diff --git a/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css b/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css index d78ac769..e691512c 100644 --- a/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css +++ b/src/components/SearchableMultiSelect/SearchableMultiSelect.module.css @@ -37,6 +37,9 @@ cursor: pointer; } +.multiSelect { + min-width: 150px; +} .searchField { display: flex; gap: var(--spacers-dp8); diff --git a/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx b/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx index 8c4c108a..e5943849 100644 --- a/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx +++ b/src/components/SearchableMultiSelect/SearchableMultiSelect.tsx @@ -76,6 +76,7 @@ export const SearchableMultiSelect = ({ return ( setFilterValue(value ?? '')} placeholder={i18n.t('Filter options')} + type="search" /> - {options.map(({ value, label }) => ( diff --git a/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx index eddb113d..8b7d50cb 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/BaseModelMultiSelect.tsx @@ -62,7 +62,6 @@ export const BaseModelMultiSelect = ({ if (!selected) { return } - const selectedModels = selected .map((s) => allModelsMap.get(s)) .filter((s) => !!s) diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx index 547e2f6d..10eeb58f 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelect.tsx @@ -1,16 +1,12 @@ import React, { useMemo, useRef, useState } from 'react' -import { useInfiniteQuery, useQuery } from 'react-query' import { useDebouncedCallback } from 'use-debounce' -import { useBoundResourceQueryFn } from '../../../lib/query/useBoundQueryFn' import { PlainResourceQuery } from '../../../types' -import { PagedResponse } from '../../../types/generated' import { DisplayableModel } from '../../../types/models' import { BaseModelMultiSelect, BaseModelMultiSelectProps, } from './BaseModelMultiSelect' - -type Response = PagedResponse +import { useModelMultiSelectQuery } from './useModelMultiSelectQuery' const defaultQuery = { params: { @@ -43,9 +39,9 @@ export const ModelMultiSelect = ({ select, ...baseModelSingleSelectProps }: ModelMultiSelectProps) => { - const queryFn = useBoundResourceQueryFn() // keep select in ref, so we dont recompute for inline selects const selectRef = useRef(select) + select = selectRef.current const [searchTerm, setSearchTerm] = useState('') const searchFilter = `identifiable:token:${searchTerm}` const filter: string[] = searchTerm ? [searchFilter] : [] @@ -59,61 +55,24 @@ export const ModelMultiSelect = ({ filter: filter.concat(params?.filter || []), }, } - const modelName = query.resource - - const queryResult = useInfiniteQuery({ - queryKey: [queryObject] as const, - queryFn: queryFn>, - keepPreviousData: true, - getNextPageParam: (lastPage) => - lastPage.pager.nextPage ? lastPage.pager.page + 1 : undefined, - getPreviousPageParam: (firstPage) => - firstPage.pager.prevPage ? firstPage.pager.page - 1 : undefined, + const { + selected: selectedData, + available: availableData, + isLoading, + error, + availableQuery, + } = useModelMultiSelectQuery({ + query: queryObject, + selected, }) - const allDataMap = useMemo(() => { - const flatData = - queryResult.data?.pages.flatMap((page) => page[modelName]) ?? [] - if (selectRef.current) { - return selectRef.current(flatData) + const resolvedAvailable = useMemo(() => { + if (select) { + return select(availableData) } - return flatData - }, [queryResult.data, modelName]) - - const selectedWithoutData = selected.filter( - (s) => allDataMap.find((d) => d.id === s) === undefined - ) + return availableData + }, [availableData]) - const selectedQuery = useQuery({ - queryKey: [ - { - resource: modelName, - params: { filter: [`id:in:[${selected?.join()}]`] }, - }, - ] as const, - queryFn: queryFn>, - enabled: - typeof selected?.[0] === 'string' && selectedWithoutData.length > 0, - }) - - const resolvedSelected = useMemo(() => { - if (selectedQuery.data) { - return selectedQuery.data[modelName] - } - if (selectedWithoutData.length === 0) { - return selected.map( - (s) => allDataMap.find((d) => d.id === s) as TModel - ) - } - return selected as TModel[] - }, [ - selectedQuery.data, - selected, - modelName, - allDataMap, - selectedWithoutData.length, - ]) - console.log({ resolvedSelected, selected }) const handleFilterChange = useDebouncedCallback(({ value }) => { if (value != undefined) { setSearchTerm(value) @@ -124,14 +83,14 @@ export const ModelMultiSelect = ({ return ( !isLoading && availableQuery.fetchNextPage()} + loading={isLoading} + error={error?.toString()} /> ) } diff --git a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx index f10d8cb1..63880fc8 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/ModelMultiSelectField.tsx @@ -36,7 +36,7 @@ export function ModelMultiSelectField({ return ( = PagedResponse + +const defaultQuery = { + params: { + order: 'displayName:asc', + fields: ['id', 'displayName'], + pageSize: 2, + }, +} satisfies Omit + +type UseModelMultiSelectQueryOptions = { + query: Omit + selected: TModel[] | string[] +} +export const useModelMultiSelectQuery = ({ + query, + selected, +}: UseModelMultiSelectQueryOptions) => { + const queryFn = useBoundResourceQueryFn() + const modelName = query.resource + const queryResult = useInfiniteQuery({ + queryKey: [query], + queryFn: queryFn>, + keepPreviousData: true, + getNextPageParam: (lastPage) => + lastPage.pager.nextPage ? lastPage.pager.page + 1 : undefined, + getPreviousPageParam: (firstPage) => + firstPage.pager.prevPage ? firstPage.pager.page - 1 : undefined, + }) + const flatData = useMemo( + () => queryResult.data?.pages.flatMap((page) => page[modelName]) ?? [], + [queryResult.data, modelName] + ) + // in case selected are string (eg. a modelId) and are not in available data- resolve ids to a displayable model + const selectedWithoutData = selected.filter( + (s) => + typeof s === 'string' && + flatData.length > 0 && + !flatData.find((d) => d.id === s) + ) + + const selectedQuery = useQuery({ + queryKey: [ + { + resource: modelName, + params: { + filter: [`id:in:[${selectedWithoutData?.join()}]`], + order: defaultQuery.params.order, + fields: defaultQuery.params.fields, + paging: false, // this should be OK since selected should be a limited list... + }, + }, + ] as const, + queryFn: queryFn>, + enabled: selectedWithoutData.length > 0, + }) + const resolvedSelected = selected.map((s) => { + if (typeof s === 'string') { + return ( + flatData.find((d) => d.id === s) || + selectedQuery.data?.[modelName].find((d) => d.id === s) || + ({ + id: s, + displayName: s, + } as TModel) + ) + } + return s + }) + + return { + selected: resolvedSelected, + available: flatData, + isLoading: queryResult.isLoading || selectedQuery.isLoading, + error: queryResult.error || selectedQuery.error, + availableQuery: queryResult, + selectedQuery, + } +} diff --git a/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx b/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx index d1b53384..eb550acb 100644 --- a/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx +++ b/src/components/metadataFormControls/ModelMultiSelect/useRefreshMultiSelect.tsx @@ -2,17 +2,16 @@ import { useCallback } from 'react' import { useQueryClient, InvalidateQueryFilters } from 'react-query' import { PlainResourceQuery } from '../../../types' -export const useRefreshModelSingleSelect = ( +export const useRefreshModelMultiSelect = ( query: Omit ) => { const queryClient = useQueryClient() return useCallback( (invalidateFilters?: InvalidateQueryFilters) => { - console.log('invalidate', query) queryClient.invalidateQueries({ queryKey: [query], - // ...invalidateFilters, + ...invalidateFilters, }) }, [queryClient, query] diff --git a/src/types/query.ts b/src/types/query.ts index 26887911..19a60a27 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -11,7 +11,8 @@ type QueryParams = { page?: number fields?: string | string[] filter?: string | string[] - [key: string]: unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any } export type PlainResourceQuery = {