diff --git a/package-lock.json b/package-lock.json index 71be1f268f..046c62a7f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,6 +100,7 @@ "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "babel-jest": "^29.7.0", "eslint": "^8.57.0", "eslint-config-airbnb": "^19.0.4", @@ -16527,6 +16528,28 @@ "node": ">=12" } }, + "node_modules/@testing-library/react/node_modules/@types/react": { + "version": "17.0.80", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", + "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react-dom": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "^17" + } + }, "node_modules/@testing-library/react/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -17180,25 +17203,13 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", - "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "devOptional": true, "license": "MIT", "dependencies": { - "@types/react": "^17" - } - }, - "node_modules/@types/react-dom/node_modules/@types/react": { - "version": "17.0.80", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", - "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" + "@types/react": "*" } }, "node_modules/@types/react-transition-group": { @@ -17221,7 +17232,7 @@ "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/send": { diff --git a/package.json b/package.json index e5b5cccdd6..e95ff2f50e 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "babel-jest": "^29.7.0", "eslint": "^8.57.0", "eslint-config-airbnb": "^19.0.4", diff --git a/src/DataRequests/DataRequestsTable.jsx b/src/DataRequests/DataRequestsTable.jsx index 171f2b7ed5..b2ac163f3d 100644 --- a/src/DataRequests/DataRequestsTable.jsx +++ b/src/DataRequests/DataRequestsTable.jsx @@ -25,7 +25,7 @@ const tableHeader = [ /** @param {ResearcherInfo} researcher */ function parseResearcherInfo(researcher) { - return researcher ? ( + return researcher.first_name && researcher.last_name && researcher.institution ? ( {researcher.first_name} {researcher.last_name}
({researcher.institution}) diff --git a/src/GuppyComponents/ConnectedFilter/index.jsx b/src/GuppyComponents/ConnectedFilter/index.jsx index 1a7c994d89..364345511a 100644 --- a/src/GuppyComponents/ConnectedFilter/index.jsx +++ b/src/GuppyComponents/ConnectedFilter/index.jsx @@ -14,6 +14,7 @@ import { /** @typedef {import('../types').GuppyConfig} GuppyConfig */ /** @typedef {import('../types').SimpleAggsData} SimpleAggsData */ /** @typedef {import('../types').StandardFilterState} StandardFilterState */ +/** @typedef {import('../types').FilterState} FilterState */ /** * @typedef {Object} ConnectedFilterProps @@ -26,12 +27,13 @@ import { * @property {boolean} [hidden] * @property {boolean} [hideZero] * @property {SimpleAggsData} [initialTabsOptions] - * @property {(anchorValue: string) => void} onAnchorValueChange + * @property {(anchorValue: string, currentFilterState: FilterState) => void} onAnchorValueChange * @property {FilterChangeHandler} onFilterChange * @property {(x: string[]) => void} [onPatientIdsChange] * @property {string[]} [patientIds] * @property {SimpleAggsData} tabsOptions * @property {Array} dictionaryEntries + * @property {Object} filterUIState */ /** @param {ConnectedFilterProps} props */ @@ -50,14 +52,16 @@ function ConnectedFilter({ onPatientIdsChange, patientIds, tabsOptions, - dictionaryEntries + dictionaryEntries, + filterUIState, }) { if ( hidden || filterConfig.tabs === undefined || filterConfig.tabs.length === 0 - ) + ) { return null; + } const processedTabsOptions = sortTabsOptions( updateCountsInInitialTabsOptions(initialTabsOptions, tabsOptions, filter) @@ -104,6 +108,7 @@ function ConnectedFilter({ patientIds={patientIds} hideZero={hideZero} tabs={filterTabs} + filterUIState={filterUIState} /> ); } @@ -152,7 +157,8 @@ ConnectedFilter.propTypes = { onPatientIdsChange: PropTypes.func, patientIds: PropTypes.arrayOf(PropTypes.string), tabsOptions: PropTypes.object.isRequired, - dictionaryEntries: PropTypes.array.isRequired + dictionaryEntries: PropTypes.array.isRequired, + filterUIState: PropTypes.object.isRequired, }; export default ConnectedFilter; diff --git a/src/GuppyComponents/GuppyWrapper/index.jsx b/src/GuppyComponents/GuppyWrapper/index.jsx index 875fca9161..f365e6b2d7 100644 --- a/src/GuppyComponents/GuppyWrapper/index.jsx +++ b/src/GuppyComponents/GuppyWrapper/index.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { queryGuppyForAggregationChartData, @@ -27,6 +27,7 @@ import { /** @typedef {import('../types').GuppyData} GuppyData */ /** @typedef {import('../types').OptionFilter} OptionFilter */ /** @typedef {import('../types').SimpleAggsData} SimpleAggsData */ +/** @typedef {import('../types').FilterChangeHandler} FilterChangeHandler */ /** * @typedef {Object} GuppyWrapperProps @@ -36,9 +37,9 @@ import { * @property {FilterState} explorerFilter * @property {FilterConfig} filterConfig * @property {GuppyConfig} guppyConfig - * @property {(x: FilterState) => void} onFilterChange * @property {string[]} patientIds * @property {string[]} rawDataFields + * @property {boolean} isInitialQuery */ /** @@ -64,9 +65,9 @@ function GuppyWrapper({ explorerFilter = {}, filterConfig, guppyConfig, - onFilterChange = () => {}, patientIds, rawDataFields: rawDataFieldsConfig = [], + isInitialQuery, }) { /** @type {[GuppyWrapperState, React.Dispatch>]} */ const [state, setState] = useState({ @@ -185,7 +186,7 @@ function GuppyWrapper({ anchorValue, filterTabs, gqlFilter: getGQLFilter(augmentFilter(filter)), - isInitialQuery: state.initialTabsOptions === undefined, + isInitialQuery, signal: controller.current.signal, }).then(({ data, errors }) => { if (data === undefined) @@ -449,7 +450,7 @@ function GuppyWrapper({ /** * @param {string} anchorValue */ - function handleAnchorValueChange(anchorValue) { + function handleAnchorValueChange(anchorValue, currentFilterState) { if (anchorValue in anchoredTabsOptionsCache) { setState((prevState) => ({ ...prevState, @@ -465,7 +466,7 @@ function GuppyWrapper({ setState((prevState) => ({ ...prevState, anchorValue })); fetchAggsOptionsDataFromGuppy({ anchorValue, - filter: filterState, + filter: mergeFilters(currentFilterState, adminAppliedPreFilters), filterTabs: filterConfig.tabs.filter(({ title }) => filterConfig.anchor.tabs.includes(title) ), @@ -484,11 +485,6 @@ function GuppyWrapper({ } } - /** @param {FilterState} filter */ - function handleFilterChange(filter) { - onFilterChange?.(mergeFilters(filter, adminAppliedPreFilters)); - } - return children({ ...state, filter: filterState, @@ -497,8 +493,7 @@ function GuppyWrapper({ downloadRawDataByTypeAndFilter, fetchAndUpdateRawData, getTotalCountsByTypeAndFilter, - onAnchorValueChange: handleAnchorValueChange, - onFilterChange: handleFilterChange, + onAnchorValueChange: useCallback(handleAnchorValueChange, [adminAppliedPreFilters]), }); } @@ -527,9 +522,9 @@ GuppyWrapper.propTypes = { aggFields: PropTypes.array, dataType: PropTypes.string.isRequired, }).isRequired, - onFilterChange: PropTypes.func, patientIds: PropTypes.arrayOf(PropTypes.string), rawDataFields: PropTypes.arrayOf(PropTypes.string), + isInitialQuery: PropTypes.bool }; export default GuppyWrapper; diff --git a/src/GuppyComponents/Utils/filters.js b/src/GuppyComponents/Utils/filters.js index 6facbc8dd4..1791ec9fb8 100644 --- a/src/GuppyComponents/Utils/filters.js +++ b/src/GuppyComponents/Utils/filters.js @@ -6,6 +6,7 @@ import { queryGuppyForRawData } from './queries'; /** @typedef {import('../types').AggsCount} AggsCount */ /** @typedef {import('../types').AggsData} AggsData */ /** @typedef {import('../types').ComposedFilterState} ComposedFilterState */ +/** @typedef {import('../types').ComposedFilterStateWithRef} ComposedFilterStateWithRef */ /** @typedef {import('../types').FilterState} FilterState */ /** @typedef {import('../types').FilterConfig} FilterConfig */ /** @typedef {import('../types').GqlFilter} GqlFilter */ @@ -16,7 +17,7 @@ import { queryGuppyForRawData } from './queries'; /** @typedef {{ [x: string]: { selectedValues?: string[] } }} AdminAppliedPreFilter */ /** - * @param {ComposedFilterState} userFilter + * @param {ComposedFilterState | ComposedFilterStateWithRef} userFilter * @param {AdminAppliedPreFilter} adminAppliedPreFilter */ function mergeToComposedFilterState(userFilter, adminAppliedPreFilter) { diff --git a/src/GuppyComponents/Utils/queries.js b/src/GuppyComponents/Utils/queries.js index d4f20a8a53..8ae9bf7ad9 100644 --- a/src/GuppyComponents/Utils/queries.js +++ b/src/GuppyComponents/Utils/queries.js @@ -3,6 +3,7 @@ import flat from 'flat'; import papaparse from 'papaparse'; import { FILE_DELIMITERS, FILTER_TYPE, GUPPY_URL } from './const'; import { headers } from '../../localconf'; +import { fetchWithClientCache } from '../../utils.fetch'; /** @typedef {import("../types").AnchorConfig} AnchorConfig */ /** @typedef {import("../types").AnchoredFilterState} AnchoredFilterState */ @@ -21,6 +22,7 @@ const graphqlEndpoint = `${GUPPY_URL}/graphql`; const downloadEndpoint = `${GUPPY_URL}/download`; const statusEndpoint = `${GUPPY_URL}/_status`; + /** * Converts JSON to a specified file format. * Defaultes to JSON if file format is not supported. @@ -123,7 +125,7 @@ export function queryGuppyForAggregationChartData({ }` ).replace(/\s+/g, ' '); - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -165,7 +167,7 @@ export function queryGuppyForAggregationCountData({ type, gqlFilter, signal }) { }` ).replace(/\s+/g, ' '); - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -348,7 +350,7 @@ export function queryGuppyForAggregationOptionsData({ }); const variables = { ...gqlFilterByGroup }; - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -360,7 +362,7 @@ export function queryGuppyForAggregationOptionsData({ } export function queryGuppyForStatus() { - return fetch(statusEndpoint, { + return fetchWithClientCache(statusEndpoint, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -434,7 +436,7 @@ export function queryGuppyForSubAggregationData({ }` ).replace(/\s+/g, ' '); - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -533,7 +535,7 @@ export function queryGuppyForRawData({ ${aggregationFragment} }`.replace(/\s+/g, ' '); - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -803,7 +805,7 @@ export function queryGuppyForTotalCounts({ type, filter }) { }` ).replace(/\s+/g, ' '); - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -826,7 +828,7 @@ export function queryGuppyForTotalCounts({ type, filter }) { * @param {string} args.type */ export function getAllFieldsFromGuppy({ type }) { - return fetch(graphqlEndpoint, { + return fetchWithClientCache(graphqlEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/GuppyComponents/types.d.ts b/src/GuppyComponents/types.d.ts index fe5bd3991e..5e1dd6da60 100644 --- a/src/GuppyComponents/types.d.ts +++ b/src/GuppyComponents/types.d.ts @@ -38,7 +38,21 @@ export type ComposedFilterState = { value?: (ComposedFilterState | StandardFilterState)[]; }; -export type FilterState = ComposedFilterState | StandardFilterState; +export type RefFilterState = { + __type: 'REF'; + value: { + id: string; + label: string; + }; +}; + +export interface ComposedFilterStateWithRef extends ComposedFilterState { + refIds?: string[]; + value?: (ComposedFilterStateWithRef | StandardFilterState | RefFilterState)[]; +} + +export type FilterState = ComposedFilterState | StandardFilterState | ComposedFilterStateWithRef | EmptyFilter; + export type GqlInFilter = { IN: { @@ -183,7 +197,7 @@ export type AggsData = { | SimpleAggsData; }; -export type FilterChangeHandler = (filter: FilterState) => void; +export type FilterChangeHandler = (filter: FilterState, skipExplorerUpdate?: boolean) => void; export type GuppyData = { accessibleCount: number; @@ -217,6 +231,5 @@ export type GuppyData = { size: number; sort: GqlSort; }) => Promise; - onAnchorValueChange: (anchorValue: string) => void; - onFilterChange: FilterChangeHandler; + onAnchorValueChange: (anchorValue: string, currentFilterState: FilterState) => void; }; diff --git a/src/GuppyDataExplorer/Explorer.css b/src/GuppyDataExplorer/Explorer.css index 1e8d3a4fe7..20506e63fe 100644 --- a/src/GuppyDataExplorer/Explorer.css +++ b/src/GuppyDataExplorer/Explorer.css @@ -9,28 +9,71 @@ .explorer__main { padding: 1rem; + width: calc(100% - 350px); } .explorer__sidebar { + width: 350px; display: flex; + padding: 1rem; + border-right: 1px solid var(--g3-color__smoke); flex-direction: column; - justify-content: space-between; +} + +@media screen and (max-width: 1090px) { + .explorer__sidebar { + display: none; + } + + .explorer__main { + width: 100%; + } } .explorer__filter { padding: 1rem 1rem 2rem; } +.explorer__side-bar-footer { + position: sticky; + bottom: 0; + background-color: var(--pcdc-color__primary); +} + .explorer__version-info-area { - margin: 0 auto; - padding: 3rem 0 2rem; - width: fit-content; + color: var(--g3-color__white); + padding: 1rem; } .explorer__version-info > span { font-style: italic; } +.explorer__version-info > a { + color: var(--g3-color__white); + text-decoration: underline; + + &:hover { + color: var(--g3-color__silver); + } +} + +.explorer__version-info-text-button { + text-wrap: nowrap; + text-decoration: underline; + background: none; + border: none; + padding: none; + width: initial; + height: initial; + font-style: italic; + color: var(--g3-color__white); + + &:hover { + color: var(--g3-color__silver); + } +} + /* Medium screen and less */ @media screen and (max-width: 1090px) { .explorer__sidebar { diff --git a/src/GuppyDataExplorer/ExplorerFilter/ExplorerFilter.css b/src/GuppyDataExplorer/ExplorerFilter/ExplorerFilter.css index c9723c55a4..be44216758 100644 --- a/src/GuppyDataExplorer/ExplorerFilter/ExplorerFilter.css +++ b/src/GuppyDataExplorer/ExplorerFilter/ExplorerFilter.css @@ -6,28 +6,40 @@ .explorer-filter__title { display: inline; margin-bottom: 1rem; + max-width: 200px; + -webkit-line-clamp: 2; + /* autoprefixer: off */ + -webkit-box-orient: vertical; + /* autoprefixer: on */ + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; } -.explorer-filter__unselect-button { - height: auto; - width: auto; +.explorer-filter__text-button { + text-wrap: nowrap; + text-decoration: underline; background: none; border: none; padding: none; - color: var(--g3-color__gray); + color: var(--pcdc-color__primary); font-weight: var(--g3-font__semi-bold-weight); - margin-left: 10px; } -.explorer-filter__unselect-button:hover { - color: var(--g3-color__bg-coal); +.explorer-filter__text-button:hover { + color: var(--pcdc-color__primary-light); } -.explorer-filter__unselect-button::before { - border-left: 1px solid var(--g3-color__lightgray); - content: ''; - height: 1.5rem; - margin-right: 10px; +.explorer-filter__text-button:disabled { + color: var(--g3-color__smoke); +} + +.explorer-filter__query-container { + overflow: hidden; + padding: 1rem; + border-radius: 5px; + background-color: var(--g3-color__silver); + margin-bottom: 1rem; } .explorer-filter__combine-mode { @@ -58,3 +70,54 @@ .explorer-filter__combine-mode input[type='radio'] { display: none; } + +.explorer-filter__combine-filter-select { + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 1rem; +} + +.explorer-filter__combine-filter-select .react-aria-Select { + width: 200px; + margin-right: 8px; +} + +.explorer__combined-filter ul > li { + width: 100%; + flex-direction: column; + display: flex; + align-items: center; + margin-bottom: 1rem; +} + +.explorer-filter__combine-filter-delete { + width: 45px; +} + +.explorer__combined-filter .pill-container { + margin-left: calc(-45px - 8px); +} + +.explorer-filter__combined-filter-container { + align-items: center; + display: flex; + flex-direction: column; +} + +.explorer-filter__combined-filter-add { + margin-left: calc(-45px - 8px); +} + +.explorer-filter__combined-filter-title { + margin-left: calc(-45px - 8px); + margin-bottom: 16px; +} + +.explorer-filter__combined-filter-fields { + display: flex; + width: 100%; + flex-direction: column; + align-items: start; +} diff --git a/src/GuppyDataExplorer/ExplorerFilter/index.jsx b/src/GuppyDataExplorer/ExplorerFilter/index.jsx index 3b36846716..f57fad7182 100644 --- a/src/GuppyDataExplorer/ExplorerFilter/index.jsx +++ b/src/GuppyDataExplorer/ExplorerFilter/index.jsx @@ -1,54 +1,304 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import PropTypes from 'prop-types'; import ConnectedFilter from '../../GuppyComponents/ConnectedFilter'; import { updatePatientIds } from '../../redux/explorer/slice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; +import React, { useState, useEffect } from 'react'; +import { + checkIfFilterEmpty, + FILTER_TYPE, + pluckFromAnchorFilter, + pluckFromFilter, +} from '../ExplorerFilterSetWorkspace/utils'; import './ExplorerFilter.css'; +import FilterDisplay from '../../components/FilterDisplay'; +import Select from '../../components/Select'; +import Pill from '../../components/Pill'; +import FilterSetLabel from '../ExplorerFilterSetWorkspace/FilterSetLabel'; +import { Popover, Dialog, DialogTrigger, Button } from 'react-aria-components'; /** @typedef {import('../../redux/types').RootState} RootState */ /** @typedef {import('../../GuppyComponents/types').StandardFilterState} StandardFilterState */ /** @typedef {import('../types').GuppyData} GuppyData */ +/** @typedef {import('../types').FilterChangeHandler} FilterChangeHandler */ +/** @typedef {import('../types').SavedExplorerFilterSet} SavedExplorerFilterSet */ +/** @typedef {import('../types').ExplorerFilterSet} ExplorerFilterSet */ +/** @typedef {import('../types').UnsavedExplorerFilterSet} UnsavedExplorerFilterSet */ +/** @typedef {import('../types').ComposedFilterStateWithRef} ComposedFilterStateWithRef */ +/** @typedef {import('../types').RefFilterState} RefFilterState */ +/** @typedef {import('../../components/Select').SelectItem} SelectItem */ + +function getSelectedFilterSets(combinedFilter) { + let refs = combinedFilter.refIds ?? []; + + if (combinedFilter.value.some((val) => val.__type !== 'REF') && refs.length === 0) { + return ['']; + } else if (refs.length === 0) { + return ['', '']; + } + + return [...refs]; +} + +/** + * @typedef {Object} CombinedExplorerFilterProps + * @property {string} className + * @property {any} workspace + * @property {string} [title] + * @property {ComposedFilterStateWithRef} combinedFilter + * @property {FilterChangeHandler} onFilterChange + */ +/** @param {CombinedExplorerFilterProps} props */ +function _CombinedExplorerFilter({ workspace, title = 'Filter', combinedFilter, onFilterChange, className }) { + const combineFilterSetOptions = Object.entries(workspace.all) + .filter(([tabId, filterSet]) => { + return filterSet.filter.__type !== FILTER_TYPE.COMPOSED; + }) + .map(([tabId, filterSet], i) => { + return { + id: tabId, + display: , + text: filterSet.name + }; + }); + const [selectedFilterSets, setSelectedFilterSets] = useState(getSelectedFilterSets(combinedFilter)); + const [combineMode, setCombineMode] = useState('AND'); + const combinedQueries = combinedFilter.value.filter(query => query.__type === 'STANDARD'); + const { + config: { filterConfig, }, + } = useAppSelector((state) => state.explorer); + + useEffect(() => { + setSelectedFilterSets(getSelectedFilterSets(combinedFilter)); + setCombineMode(combinedFilter.__combineMode); + }, [combinedFilter]); -/** @param {{ className: string }} props */ -export function DisabledExplorerFilter({ className }) { return (
-

Filters

+

{title}

+
+
+

Combine Filters

+
    + {combinedQueries.map((/** @type {StandardFilterState} */query, index, arr) => { + let key = index.toString() + JSON.stringify(query.value); + return
  • +
    + { + if (selectedFilterSets.indexOf(option.id) === index) { + return false; + } + return selectedFilterSets.includes(option.id); + }).map(option => option.id) + } + onChange={(/** @type {SelectItem} */item) => { + let newSelected = [...selectedFilterSets]; + newSelected[index] = item?.id?.toString() ?? ''; + /** @type {(ComposedFilterStateWithRef | StandardFilterState | RefFilterState)[]} */ + let newSelectedValues = newSelected + .filter(id => id !== '') + .map(id => { + let option = combineFilterSetOptions.find(option => option.id === id) + return { + __type: 'REF', + value: { + id, label: option.text + } + }; + }); + let newValues = combinedFilter.value.filter(query => query.__type === 'STANDARD').concat(newSelectedValues); + setSelectedFilterSets(newSelected); + onFilterChange({ + ...combinedFilter, + /** @type {(ComposedFilterStateWithRef | StandardFilterState | RefFilterState)[]} */ + value: newValues, + refIds: newSelected + }); + }} + /> + :