diff --git a/src/components/app.jsx b/src/components/app.jsx index 4ed4b0617e..5a314cceea 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -73,7 +73,7 @@ import useStudyNavigationSync from 'hooks/use-study-navigation-sync'; import { useOptionalLoadingParameters } from '../hooks/use-optional-loading-parameters'; import { SortWay } from '../types/custom-aggrid-types.ts'; import { useBaseVoltages } from '../hooks/use-base-voltages.ts'; -import { useGlobalFilterOptions } from './results/common/global-filter/use-global-filter-options.ts'; +import { useGlobalFilterOptions } from './results/common/global-filter/hooks/use-global-filter-options.ts'; import { updateComputationColumnFilters, updateComputationGlobalFilters } from './results/common/utils.ts'; import { isEditingGlobalFilter } from '../utils/editing-global-filter-sync.ts'; import { cleanupStaleStudyData } from '../redux/session-storage/local-storage'; diff --git a/src/components/results/common/global-filter/adapter/global-filter-app-data.ts b/src/components/results/common/global-filter/adapter/global-filter-app-data.ts new file mode 100644 index 0000000000..7215f2aefe --- /dev/null +++ b/src/components/results/common/global-filter/adapter/global-filter-app-data.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { fetchStudyMetadata } from '@gridsuite/commons-ui'; + +export async function fetchSubstationPropertiesGlobalFilters(): Promise<{ + substationPropertiesGlobalFilters?: Map; +}> { + const { substationPropertiesGlobalFilters } = await fetchStudyMetadata(); + const definedSubstationPropertiesGlobalFilters: Map = substationPropertiesGlobalFilters + ? new Map(Object.entries(substationPropertiesGlobalFilters)) + : new Map(); + return { + substationPropertiesGlobalFilters: definedSubstationPropertiesGlobalFilters, + }; +} diff --git a/src/components/results/common/global-filter/adapter/global-filter-provider.tsx b/src/components/results/common/global-filter/adapter/global-filter-provider.tsx new file mode 100644 index 0000000000..9592b2054c --- /dev/null +++ b/src/components/results/common/global-filter/adapter/global-filter-provider.tsx @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'; +import { GlobalFilter, RecentGlobalFilter } from '../global-filter.type'; +import { FilterType, isCriteriaFilter } from '../../utils'; +import { + ElementAttributes, + ElementType, + EquipmentType, + fetchDirectoryElementPath, + fetchElementsInfos, + snackWithFallback, + useSnackMessage, +} from '@gridsuite/commons-ui'; +import { computeFullPath } from '../../../../../utils/compute-title'; +import { + addToGlobalFilterOptions, + addToSelectedGlobalFilters, + clearSelectedGlobalFilters as clearSelectedGlobalFiltersAction, + markNotFoundGlobalFiltersAsDeleted, + removeFromGlobalFilterOptions, + removeFromSelectedGlobalFilters, +} from '../../../../../redux/actions'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppDispatch } from '../../../../../redux/store'; +import { HttpStatusCode } from '../../../../../utils/http-status-code'; +import { TableType } from '../../../../../types/custom-aggrid-types'; +import { AppState } from '../../../../../redux/reducer.type'; +import GlobalFilterContextProvider from '../context/global-filter-context-provider'; +import { fetchSubstationPropertiesGlobalFilters } from './global-filter-app-data'; +import { useLocalizedCountries } from '../../../../utils/localized-countries-hook'; +import type { UUID } from 'node:crypto'; + +const EMPTY_ARRAY: RecentGlobalFilter[] = []; + +type FilterUpdateResult = { kind: 'updated' | 'notFound'; filter: GlobalFilter } | { kind: 'unchanged' }; + +type GlobalFilterProviderProps = PropsWithChildren<{ + filterCategories: FilterType[]; + genericFiltersStrictMode: boolean; + filterableEquipmentTypes: string[]; + tableType: TableType; + tableUuid: string; +}>; + +export default function GlobalFilterProvider({ + children, + filterCategories, + genericFiltersStrictMode = false, + filterableEquipmentTypes = [], + tableType, + tableUuid, +}: Readonly) { + const dispatch = useDispatch(); + const { snackError } = useSnackMessage(); + const { translate: translateCountryCode } = useLocalizedCountries(); + const [substationPropertiesGlobalFilters, setSubstationPropertiesGlobalFilters] = useState>(); + + const globalFilterOptions = useSelector((state: AppState) => state.globalFilterOptions); + + const selectedFilterIds = useSelector((state: AppState) => state.tableFilters.globalFilters[tableUuid]?.selected); + + const recentGlobalFilters = useSelector( + (state: AppState) => state.tableFilters.globalFilters[tableUuid]?.recents ?? EMPTY_ARRAY + ); + + const selectedGlobalFilters = useMemo( + () => + selectedFilterIds + ? selectedFilterIds + .map((id) => globalFilterOptions.find((opt) => opt.id === id)) + .filter((filter): filter is GlobalFilter => filter !== undefined) + : [], + [selectedFilterIds, globalFilterOptions] + ); + + useEffect(() => { + fetchSubstationPropertiesGlobalFilters().then(({ substationPropertiesGlobalFilters }) => { + setSubstationPropertiesGlobalFilters(substationPropertiesGlobalFilters); + }); + }, []); + + const updateGenericFilter = useCallback( + async (genericFilter: GlobalFilter): Promise => { + try { + if (!genericFilter.uuid) { + return { kind: 'unchanged' }; + } + const response: ElementAttributes[] = await fetchDirectoryElementPath(genericFilter.uuid); + const parentDirectoriesNames = response.map((parent) => parent.elementName); + const label = + response.find((parent) => parent.type === ElementType.FILTER)?.elementName ?? genericFilter.label; + const path = computeFullPath(parentDirectoriesNames); + if (path !== genericFilter.path || label !== genericFilter.label) { + return { kind: 'updated', filter: { ...genericFilter, path, label } }; + } + return { kind: 'unchanged' }; + } catch (responseError) { + const error = responseError as Error & { status: number }; + if (error.status === HttpStatusCode.NOT_FOUND) { + return { kind: 'notFound', filter: genericFilter }; + } + snackWithFallback(snackError, error, { + headerId: 'ComputationFilterResultsError', + }); + return { kind: 'unchanged' }; + } + }, + [snackError] + ); + + // Check the selected global filters and mark them as deleted if they no longer exist. + useEffect(() => { + const checkSelectedFilters = async () => { + const genericFilters: GlobalFilter[] = selectedGlobalFilters.filter( + (globalFilter) => isCriteriaFilter(globalFilter) && !globalFilter.deleted + ); + const results = await Promise.all(genericFilters.map(updateGenericFilter)); + const updatedFilters: GlobalFilter[] = []; + const notFoundFilters: GlobalFilter[] = []; + for (const result of results) { + if (result.kind === 'updated') updatedFilters.push(result.filter); + else if (result.kind === 'notFound') notFoundFilters.push(result.filter); + } + if (updatedFilters.length) { + dispatch(addToGlobalFilterOptions(updatedFilters)); + } + if (notFoundFilters.length) { + dispatch(markNotFoundGlobalFiltersAsDeleted(notFoundFilters, tableUuid, tableType)); + } + }; + + checkSelectedFilters().catch((error) => console.error(error)); + }, [dispatch, selectedGlobalFilters, tableType, tableUuid, updateGenericFilter]); + + const selectGlobalFilter = useCallback( + (id: string) => { + dispatch(addToSelectedGlobalFilters(tableType, tableUuid, [id])); + }, + [dispatch, tableType, tableUuid] + ); + + const unselectGlobalFilters = useCallback( + (ids: string[]) => { + dispatch(removeFromSelectedGlobalFilters(tableType, tableUuid, ids)); + }, + [dispatch, tableType, tableUuid] + ); + + const clearSelectedGlobalFilters = useCallback(() => { + dispatch(clearSelectedGlobalFiltersAction(tableType, tableUuid)); + }, [dispatch, tableType, tableUuid]); + + const removeGlobalFilterOption = useCallback( + (id: string) => { + dispatch(removeFromGlobalFilterOptions(id)); + }, + [dispatch] + ); + + const addFiltersToGlobalFiltersOptions = useCallback( + async (elementIds: UUID[]) => { + const elements: ElementAttributes[] = await fetchElementsInfos(elementIds); + const newlySelectedFilters: GlobalFilter[] = []; + elements.forEach((element: ElementAttributes) => { + // ignore already selected filters and non-generic filters : + if (!selectedGlobalFilters.find((filter) => filter.uuid && filter.uuid === element.elementUuid)) { + // add the others + const substationOrVoltageLevel = + element.specificMetadata?.equipmentType === EquipmentType.SUBSTATION || + element.specificMetadata?.equipmentType === EquipmentType.VOLTAGE_LEVEL; + newlySelectedFilters.push({ + id: element.elementUuid, + uuid: element.elementUuid, + equipmentType: element.specificMetadata?.equipmentType, + label: element.elementName, + filterType: substationOrVoltageLevel ? FilterType.SUBSTATION_OR_VL : FilterType.GENERIC_FILTER, + filterTypeFromMetadata: element.specificMetadata?.type, + }); + } + }); + + dispatch(addToGlobalFilterOptions(newlySelectedFilters)); + newlySelectedFilters.forEach((filter) => + dispatch(addToSelectedGlobalFilters(tableType, tableUuid, [filter.id])) + ); + }, + [dispatch, selectedGlobalFilters, tableType, tableUuid] + ); + + return ( + + {children} + + ); +} diff --git a/src/components/results/common/global-filter/context/global-filter-context-provider.tsx b/src/components/results/common/global-filter/context/global-filter-context-provider.tsx new file mode 100644 index 0000000000..a0a1240717 --- /dev/null +++ b/src/components/results/common/global-filter/context/global-filter-context-provider.tsx @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useMemo } from 'react'; +import { GlobalFilterContext } from './global-filter-context'; +import { GlobalFilterContextProviderProps, GlobalFilterContextValue } from './global-filter-context.type'; + +export default function GlobalFilterContextProvider({ + children, + globalFilterOptions, + selectedGlobalFilters, + recentGlobalFilters, + substationPropertiesGlobalFilters, + filterCategories, + genericFiltersStrictMode, + filterableEquipmentTypes, + translateCountryCode, + selectGlobalFilter, + unselectGlobalFilters, + clearSelectedGlobalFilters, + removeGlobalFilterOption, + addFiltersToGlobalFiltersOptions, +}: Readonly) { + const value = useMemo( + () => ({ + globalFilterOptions, + selectedGlobalFilters, + recentGlobalFilters, + substationPropertiesGlobalFilters, + filterCategories, + genericFiltersStrictMode, + filterableEquipmentTypes, + translateCountryCode, + selectGlobalFilter, + unselectGlobalFilters, + clearSelectedGlobalFilters, + removeGlobalFilterOption, + addFiltersToGlobalFiltersOptions, + }), + [ + globalFilterOptions, + selectedGlobalFilters, + recentGlobalFilters, + substationPropertiesGlobalFilters, + filterCategories, + genericFiltersStrictMode, + filterableEquipmentTypes, + translateCountryCode, + selectGlobalFilter, + unselectGlobalFilters, + clearSelectedGlobalFilters, + removeGlobalFilterOption, + addFiltersToGlobalFiltersOptions, + ] + ); + + return {children}; +} diff --git a/src/components/results/common/global-filter/context/global-filter-context.ts b/src/components/results/common/global-filter/context/global-filter-context.ts new file mode 100644 index 0000000000..c720ac5924 --- /dev/null +++ b/src/components/results/common/global-filter/context/global-filter-context.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { createContext, useContext } from 'react'; +import { GlobalFilterContextValue } from './global-filter-context.type'; + +export const GlobalFilterContext = createContext(null); + +export function useGlobalFilterContext() { + const context = useContext(GlobalFilterContext); + if (!context) { + throw new Error('useGlobalFilterContext must be used inside GlobalFilterProvider'); + } + return context; +} diff --git a/src/components/results/common/global-filter/context/global-filter-context.type.ts b/src/components/results/common/global-filter/context/global-filter-context.type.ts new file mode 100644 index 0000000000..0b89e62cc3 --- /dev/null +++ b/src/components/results/common/global-filter/context/global-filter-context.type.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { PropsWithChildren } from 'react'; +import { GlobalFilter, RecentGlobalFilter } from '../global-filter.type'; +import type { UUID } from 'node:crypto'; + +export type GlobalFilterContextValue = { + globalFilterOptions: GlobalFilter[]; + selectedGlobalFilters: GlobalFilter[]; + recentGlobalFilters: RecentGlobalFilter[]; + substationPropertiesGlobalFilters?: Map; + filterCategories: string[]; + genericFiltersStrictMode: boolean; + filterableEquipmentTypes: string[]; + translateCountryCode: (countryCode: string) => string; + selectGlobalFilter: (id: string) => void; + unselectGlobalFilters: (ids: string[]) => void; + clearSelectedGlobalFilters: () => void; + removeGlobalFilterOption: (id: string) => void; + addFiltersToGlobalFiltersOptions: (elementIds: UUID[]) => Promise; +}; + +export type GlobalFilterContextProviderProps = PropsWithChildren; diff --git a/src/components/results/common/global-filter/global-filter-context.ts b/src/components/results/common/global-filter/global-filter-context.ts deleted file mode 100644 index 00c76facb2..0000000000 --- a/src/components/results/common/global-filter/global-filter-context.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { createContext } from 'react'; -import { GlobalFilter } from './global-filter-types'; -import { TableType } from '../../../../types/custom-aggrid-types'; -import type { RecentGlobalFilter } from './global-filter-types'; - -export const GlobalFilterContext = createContext<{ - // manage internal states - openedDropdown: boolean; - setOpenedDropdown: (open: boolean) => void; - directoryItemSelectorOpen: boolean; - setDirectoryItemSelectorOpen: (open: boolean) => void; - filterGroupSelected?: string; - setFilterGroupSelected: (selectedFilterGroup: string) => void; - globalFilterOptions: GlobalFilter[]; - selectedGlobalFilters: GlobalFilter[]; - recentGlobalFilters: RecentGlobalFilter[]; - filterCategories: string[]; - genericFiltersStrictMode: boolean; - filterableEquipmentTypes: string[]; - tableType: TableType; - tableUuid: string; -}>({ - openedDropdown: false, - setOpenedDropdown: () => {}, - directoryItemSelectorOpen: false, - setDirectoryItemSelectorOpen: () => {}, - filterGroupSelected: undefined, - setFilterGroupSelected: () => {}, - globalFilterOptions: [], - selectedGlobalFilters: [], - recentGlobalFilters: [], - filterCategories: [], - genericFiltersStrictMode: false, - filterableEquipmentTypes: [], - tableType: TableType.Loadflow, - tableUuid: TableType.Loadflow, -}); diff --git a/src/components/results/common/global-filter/global-filter-provider.tsx b/src/components/results/common/global-filter/global-filter-provider.tsx deleted file mode 100644 index bd1db01fe9..0000000000 --- a/src/components/results/common/global-filter/global-filter-provider.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'; -import { GlobalFilter, RecentGlobalFilter } from './global-filter-types'; -import { FilterType, isCriteriaFilter } from '../utils'; -import { - ElementAttributes, - ElementType, - fetchDirectoryElementPath, - snackWithFallback, - useSnackMessage, -} from '@gridsuite/commons-ui'; -import { computeFullPath } from '../../../../utils/compute-title'; -import { addToGlobalFilterOptions, markNotFoundGlobalFiltersAsDeleted } from '../../../../redux/actions'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppDispatch } from '../../../../redux/store'; -import { GlobalFilterContext } from './global-filter-context'; -import { HttpStatusCode } from '../../../../utils/http-status-code'; -import { TableType } from '../../../../types/custom-aggrid-types'; -import { AppState } from '../../../../redux/reducer.type'; - -const EMPTY_ARRAY: RecentGlobalFilter[] = []; -type FilterUpdateResult = { kind: 'updated' | 'notFound'; filter: GlobalFilter } | { kind: 'unchanged' }; -export default function GlobalFilterProvider({ - children, - filterCategories, - genericFiltersStrictMode = false, - filterableEquipmentTypes = [], - tableType, - tableUuid, -}: PropsWithChildren & { - filterCategories: FilterType[]; - genericFiltersStrictMode: boolean; - filterableEquipmentTypes: string[]; - tableType: TableType; - tableUuid: string; -}) { - const dispatch = useDispatch(); - const { snackError } = useSnackMessage(); - - const globalFilterOptions = useSelector((state: AppState) => state.globalFilterOptions); - - const selectedFilterIds = useSelector((state: AppState) => state.tableFilters.globalFilters[tableUuid]?.selected); - - const recentGlobalFilters = useSelector( - (state: AppState) => state.tableFilters.globalFilters[tableUuid]?.recents ?? EMPTY_ARRAY - ); - - const selectedGlobalFilters = useMemo( - () => - selectedFilterIds - ? selectedFilterIds - .map((id) => globalFilterOptions.find((opt) => opt.id === id)) - .filter((f) => f !== undefined) - : [], - [selectedFilterIds, globalFilterOptions] - ); - - const [openedDropdown, setOpenedDropdown] = useState(false); - const [directoryItemSelectorOpen, setDirectoryItemSelectorOpen] = useState(false); - // maybe a filter type or a recent filter or whatever category - const [filterGroupSelected, setFilterGroupSelected] = useState(FilterType.VOLTAGE_LEVEL); - - const updateGenericFilter = useCallback( - async (genericFilter: GlobalFilter): Promise => { - try { - if (!genericFilter.uuid) { - return { kind: 'unchanged' }; - } - const response: ElementAttributes[] = await fetchDirectoryElementPath(genericFilter.uuid); - const parentDirectoriesNames = response.map((parent) => parent.elementName); - const label = - response.find((parent) => parent.type === ElementType.FILTER)?.elementName ?? genericFilter.label; - const path = computeFullPath(parentDirectoriesNames); - if (path !== genericFilter.path || label !== genericFilter.label) { - return { kind: 'updated', filter: { ...genericFilter, path, label } }; - } - return { kind: 'unchanged' }; - } catch (responseError) { - const error = responseError as Error & { status: number }; - if (error.status === HttpStatusCode.NOT_FOUND) { - return { kind: 'notFound', filter: genericFilter }; - } - snackWithFallback(snackError, error, { - headerId: 'ComputationFilterResultsError', - }); - return { kind: 'unchanged' }; - } - }, - [snackError] - ); - - // Check the selected global filters and mark them as deleted if they no longer exist. - useEffect(() => { - const checkSelectedFilters = async () => { - const genericFilters: GlobalFilter[] = selectedGlobalFilters.filter( - (globalFilter) => isCriteriaFilter(globalFilter) && !globalFilter.deleted - ); - const results = await Promise.all(genericFilters.map(updateGenericFilter)); - const updatedFilters: GlobalFilter[] = []; - const notFoundFilters: GlobalFilter[] = []; - for (const result of results) { - if (result.kind === 'updated') updatedFilters.push(result.filter); - else if (result.kind === 'notFound') notFoundFilters.push(result.filter); - } - if (updatedFilters.length) { - dispatch(addToGlobalFilterOptions(updatedFilters)); - } - if (notFoundFilters.length) { - dispatch(markNotFoundGlobalFiltersAsDeleted(notFoundFilters, tableUuid, tableType)); - } - }; - - checkSelectedFilters().catch((error) => console.error(error)); - }, [dispatch, selectedGlobalFilters, tableType, tableUuid, updateGenericFilter]); - - const value = useMemo( - () => ({ - openedDropdown, - setOpenedDropdown, - directoryItemSelectorOpen, - setDirectoryItemSelectorOpen, - filterGroupSelected, - setFilterGroupSelected, - globalFilterOptions, - selectedGlobalFilters, - recentGlobalFilters, - filterCategories, - genericFiltersStrictMode, - filterableEquipmentTypes, - tableType, - tableUuid, - }), - [ - openedDropdown, - directoryItemSelectorOpen, - filterGroupSelected, - globalFilterOptions, - selectedGlobalFilters, - recentGlobalFilters, - filterCategories, - genericFiltersStrictMode, - filterableEquipmentTypes, - tableType, - tableUuid, - ] - ); - - return {children}; -} diff --git a/src/components/results/common/global-filter/global-filter-selector.tsx b/src/components/results/common/global-filter/global-filter-selector.tsx index a35eab4c3a..9167430c4a 100644 --- a/src/components/results/common/global-filter/global-filter-selector.tsx +++ b/src/components/results/common/global-filter/global-filter-selector.tsx @@ -5,12 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import GlobalFilterProvider from './global-filter-provider'; -import GlobalFilterAutocomplete from './global-filter-autocomplete'; +import GlobalFilterProvider from './adapter/global-filter-provider'; import { FilterType } from '../utils'; import { TableType } from '../../../../types/custom-aggrid-types'; import type { UUID } from 'node:crypto'; import { EquipmentType } from '@gridsuite/commons-ui'; +import GlobalFilter from './ui/global-filter'; export type GlobalFilterSelectorProps = { filterCategories?: FilterType[]; @@ -35,7 +35,7 @@ export default function GlobalFilterSelector({ tableType={tableType} tableUuid={tableUuid ?? tableType} > - + ); } diff --git a/src/components/results/common/global-filter/global-filter-styles.ts b/src/components/results/common/global-filter/global-filter.style.ts similarity index 99% rename from src/components/results/common/global-filter/global-filter-styles.ts rename to src/components/results/common/global-filter/global-filter.style.ts index a131afdb62..4ac99e4c3a 100644 --- a/src/components/results/common/global-filter/global-filter-styles.ts +++ b/src/components/results/common/global-filter/global-filter.style.ts @@ -9,7 +9,7 @@ import { cyan, lime } from '@mui/material/colors'; import { FilterType } from '../utils'; import { mergeSx, type MuiStyles } from '@gridsuite/commons-ui'; import { Theme, SxProps } from '@mui/material'; -import { GlobalFilter } from './global-filter-types'; +import { GlobalFilter } from './global-filter.type'; export const getResultsGlobalFiltersChipStyle = (element: GlobalFilter): SxProps => { return mergeSx( diff --git a/src/components/results/common/global-filter/global-filter-types.ts b/src/components/results/common/global-filter/global-filter.type.ts similarity index 100% rename from src/components/results/common/global-filter/global-filter-types.ts rename to src/components/results/common/global-filter/global-filter.type.ts diff --git a/src/components/results/common/global-filter/use-computation-global-filters.ts b/src/components/results/common/global-filter/hooks/use-computation-global-filters.ts similarity index 86% rename from src/components/results/common/global-filter/use-computation-global-filters.ts rename to src/components/results/common/global-filter/hooks/use-computation-global-filters.ts index a1e1383dd7..f5970cc809 100644 --- a/src/components/results/common/global-filter/use-computation-global-filters.ts +++ b/src/components/results/common/global-filter/hooks/use-computation-global-filters.ts @@ -4,12 +4,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { TableType } from '../../../../types/custom-aggrid-types'; +import { TableType } from '../../../../../types/custom-aggrid-types'; import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../../../../redux/reducer.type'; +import { AppState } from '../../../../../redux/reducer.type'; import { useEffect } from 'react'; import { useSelectedGlobalFilters } from './use-selected-global-filters'; -import { updateComputationGlobalFilters } from '../utils'; +import { updateComputationGlobalFilters } from '../../utils'; // Get the global filters for a given table from the server and store them in the Redux store export function useFetchComputationGlobalFilters(tableType: TableType) { diff --git a/src/components/results/common/global-filter/use-global-filter-options.ts b/src/components/results/common/global-filter/hooks/use-global-filter-options.ts similarity index 87% rename from src/components/results/common/global-filter/use-global-filter-options.ts rename to src/components/results/common/global-filter/hooks/use-global-filter-options.ts index 511375e241..1022bafd09 100644 --- a/src/components/results/common/global-filter/use-global-filter-options.ts +++ b/src/components/results/common/global-filter/hooks/use-global-filter-options.ts @@ -6,17 +6,14 @@ */ import { snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui'; import { useEffect } from 'react'; -import { fetchAllCountries } from '../../../../services/study/network-map'; -import { FilterType } from '../utils'; -import { - addGlobalFilterId, - fetchSubstationPropertiesGlobalFilters, - GlobalFilterWithoutId, -} from './global-filter-utils'; +import { fetchAllCountries } from '../../../../../services/study/network-map'; +import { FilterType } from '../../utils'; +import { addGlobalFilterId, GlobalFilterWithoutId } from '../utils/global-filter-utils'; +import { fetchSubstationPropertiesGlobalFilters } from '../adapter/global-filter-app-data'; import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../../../../redux/reducer.type'; -import { useBaseVoltages } from '../../../../hooks/use-base-voltages'; -import { addToGlobalFilterOptions } from '../../../../redux/actions'; +import { AppState } from '../../../../../redux/reducer.type'; +import { useBaseVoltages } from '../../../../../hooks/use-base-voltages'; +import { addToGlobalFilterOptions } from '../../../../../redux/actions'; /** * Custom hook that manages global filter options for tables. diff --git a/src/components/results/common/global-filter/use-global-filter-results.ts b/src/components/results/common/global-filter/hooks/use-global-filter-results.ts similarity index 86% rename from src/components/results/common/global-filter/use-global-filter-results.ts rename to src/components/results/common/global-filter/hooks/use-global-filter-results.ts index 1a175704c7..21ec70e41c 100644 --- a/src/components/results/common/global-filter/use-global-filter-results.ts +++ b/src/components/results/common/global-filter/hooks/use-global-filter-results.ts @@ -9,12 +9,12 @@ import type { NonEmptyTuple } from 'type-fest'; import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { snackWithFallback, useDebounce, useSnackMessage } from '@gridsuite/commons-ui'; -import type { GlobalFilter } from './global-filter-types'; -import { evaluateGlobalFilter } from '../../../../services/study/filter'; -import type { AppState } from '../../../../redux/reducer.type'; -import type { FilterEquipmentType } from '../../../../types/filter-lib/filter'; -import { isStatusBuilt } from '../../../graph/util/model-functions'; -import { buildValidGlobalFilters } from './build-valid-global-filters'; +import type { GlobalFilter } from '../global-filter.type'; +import { evaluateGlobalFilter } from '../../../../../services/study/filter'; +import type { AppState } from '../../../../../redux/reducer.type'; +import type { FilterEquipmentType } from '../../../../../types/filter-lib/filter'; +import { isStatusBuilt } from '../../../../graph/util/model-functions'; +import { buildValidGlobalFilters } from '../utils/build-valid-global-filters'; /* Because of ESLint react-hooks/rules-of-hooks, nullable value must be managed inside the hook, because * React hooks can't be called conditionally and/or different order. */ diff --git a/src/components/results/common/global-filter/use-selected-global-filters.ts b/src/components/results/common/global-filter/hooks/use-selected-global-filters.ts similarity index 94% rename from src/components/results/common/global-filter/use-selected-global-filters.ts rename to src/components/results/common/global-filter/hooks/use-selected-global-filters.ts index a1973c5184..8a35635aae 100644 --- a/src/components/results/common/global-filter/use-selected-global-filters.ts +++ b/src/components/results/common/global-filter/hooks/use-selected-global-filters.ts @@ -7,8 +7,8 @@ import { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { GlobalFilter } from './global-filter-types'; -import { RootState, store } from '../../../../redux/store'; +import { GlobalFilter } from '../global-filter.type'; +import { RootState, store } from '../../../../../redux/store'; /** * Subscribes to the store and returns the selected global filters for the given table key. diff --git a/src/components/results/common/global-filter/global-filter-paper.tsx b/src/components/results/common/global-filter/ui/global-filter-dropdown-panel.tsx similarity index 74% rename from src/components/results/common/global-filter/global-filter-paper.tsx rename to src/components/results/common/global-filter/ui/global-filter-dropdown-panel.tsx index cee978bd3c..c12ec2d78f 100644 --- a/src/components/results/common/global-filter/global-filter-paper.tsx +++ b/src/components/results/common/global-filter/ui/global-filter-dropdown-panel.tsx @@ -6,35 +6,25 @@ */ import { Box, Button, Grid, ListItemButton, Paper, Typography } from '@mui/material'; -import { GLOBAL_FILTERS_CELL_HEIGHT, IMPORT_FILTER_HEIGHT, resultsGlobalFilterStyles } from './global-filter-styles'; +import { GLOBAL_FILTERS_CELL_HEIGHT, IMPORT_FILTER_HEIGHT, resultsGlobalFilterStyles } from '../global-filter.style'; import { FormattedMessage, useIntl } from 'react-intl'; import FileUploadIcon from '@mui/icons-material/FileUpload'; -import { PropsWithChildren, RefObject, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { PropsWithChildren, RefObject, useCallback, useMemo, useState } from 'react'; import ListItemText from '@mui/material/ListItemText'; import List from '@mui/material/List'; -import { FilterType, isCriteriaFilterType } from '../utils'; -import { GlobalFilter } from './global-filter-types'; -import { fetchSubstationPropertiesGlobalFilters, RECENT_FILTER } from './global-filter-utils'; +import { FilterType, isCriteriaFilterType } from '../../utils'; +import { RECENT_FILTER } from '../utils/global-filter-utils'; import ClickAwayListener from '@mui/material/ClickAwayListener'; import { DirectoryItemSelector, - ElementAttributes, ElementType, EquipmentType, - fetchElementsInfos, mergeSx, TreeViewFinderNodeProps, } from '@gridsuite/commons-ui'; -import { GlobalFilterContext } from './global-filter-context'; import SelectedGlobalFilters from './selected-global-filters'; -import { TextWithInfoIcon } from './text-with-info-icon'; -import { - addToSelectedGlobalFilters, - addToGlobalFilterOptions, - clearSelectedGlobalFilters, -} from '../../../../redux/actions'; -import { useDispatch } from 'react-redux'; -import { AppDispatch } from '../../../../redux/store'; +import { LabelWithInfoTooltip } from './label-with-info-tooltip'; +import { useGlobalFilterContext } from '../context/global-filter-context'; const XS_COLUMN1: number = 3; const XS_COLUMN2: number = 4; @@ -42,32 +32,29 @@ const XS_COLUMN3: number = 5; type GlobalFilterPaperProps = PropsWithChildren<{ autocompleteRef?: RefObject; + setOpenedDropdown: (open: boolean) => void; + filterGroupSelected: string; + setFilterGroupSelected: (selectedFilterGroup: string) => void; }>; -function GlobalFilterPaper({ children, autocompleteRef }: Readonly) { +function GlobalFilterDropdownPanel({ + children, + autocompleteRef, + setOpenedDropdown, + filterGroupSelected, + setFilterGroupSelected, +}: Readonly) { + const intl = useIntl(); + const [directoryItemSelectorOpen, setDirectoryItemSelectorOpen] = useState(false); const { - setOpenedDropdown, - directoryItemSelectorOpen, - setDirectoryItemSelectorOpen, - filterGroupSelected, - setFilterGroupSelected, selectedGlobalFilters, + substationPropertiesGlobalFilters, filterCategories, genericFiltersStrictMode, filterableEquipmentTypes, - tableType, - tableUuid, - } = useContext(GlobalFilterContext); - const intl = useIntl(); - const dispatch = useDispatch(); - const [substationPropertiesFilters, setSubstationPropertiesFilters] = useState>(); - - // fetches substation properties global filters from local config - useEffect(() => { - fetchSubstationPropertiesGlobalFilters().then(({ substationPropertiesGlobalFilters }) => { - setSubstationPropertiesFilters(substationPropertiesGlobalFilters); - }); - }, []); + clearSelectedGlobalFilters, + addFiltersToGlobalFiltersOptions, + } = useGlobalFilterContext(); const filteringOnlySubstations = useMemo(() => { return filterableEquipmentTypes?.length === 1 && filterableEquipmentTypes[0] === EquipmentType.SUBSTATION; @@ -99,7 +86,7 @@ function GlobalFilterPaper({ children, autocompleteRef }: Readonly { const sortedCategories = [ ...standardCategories, - ...(substationPropertiesFilters ? Array.from(substationPropertiesFilters.keys()) : []), + ...(substationPropertiesGlobalFilters ? Array.from(substationPropertiesGlobalFilters.keys()) : []), ]; // criteria filters are always at the end of the menu const substationCategory: string[] = sortedCategories.splice( @@ -117,7 +104,7 @@ function GlobalFilterPaper({ children, autocompleteRef }: Readonly @@ -133,45 +120,17 @@ function GlobalFilterPaper({ children, autocompleteRef }: Readonly { if (!values) { return; } setOpenedDropdown(true); - - const elements: ElementAttributes[] = await fetchElementsInfos(values.map((value) => value.id)); - const newlySelectedFilters: GlobalFilter[] = []; - elements.forEach((element: ElementAttributes) => { - // ignore already selected filters and non-generic filters : - if (!selectedGlobalFilters.find((filter) => filter.uuid && filter.uuid === element.elementUuid)) { - // add the others - const substationOrVoltageLevel = - element.specificMetadata?.equipmentType === EquipmentType.SUBSTATION || - element.specificMetadata?.equipmentType === EquipmentType.VOLTAGE_LEVEL; - newlySelectedFilters.push({ - id: element.elementUuid, - uuid: element.elementUuid, - equipmentType: element.specificMetadata?.equipmentType, - label: element.elementName, - filterType: substationOrVoltageLevel ? FilterType.SUBSTATION_OR_VL : FilterType.GENERIC_FILTER, - filterTypeFromMetadata: element.specificMetadata?.type, - }); - } - }); - - dispatch(addToGlobalFilterOptions(newlySelectedFilters)); - dispatch( - addToSelectedGlobalFilters( - tableType, - tableUuid, - newlySelectedFilters.map((f) => f.id) - ) - ); + await addFiltersToGlobalFiltersOptions(values.map((value) => value.id)); setDirectoryItemSelectorOpen(false); }, - [selectedGlobalFilters, setDirectoryItemSelectorOpen, setOpenedDropdown, dispatch, tableType, tableUuid] + [addFiltersToGlobalFiltersOptions, setDirectoryItemSelectorOpen, setOpenedDropdown] ); /** @@ -209,7 +168,7 @@ function GlobalFilterPaper({ children, autocompleteRef }: Readonly - @@ -219,7 +178,7 @@ function GlobalFilterPaper({ children, autocompleteRef }: Readonly{filtersMsg}