diff --git a/src/components/app.jsx b/src/components/app.jsx index 1fba6048b1..38eef41b48 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -36,7 +36,6 @@ import { StudyContainer } from './study-container'; import { fetchDefaultParametersValues, fetchIdpSettings } from '../services/utils'; import { getOptionalServices } from '../services/study/index'; import { - addFilterForNewSpreadsheet, addSortForNewSpreadsheet, initOrUpdateGlobalFilters, initTableDefinitions, @@ -49,8 +48,10 @@ import { setOptionalServices, setParamsLoaded, setUpdateNetworkVisualizationParameters, + updateColumnFiltersAction, updateTableColumns, } from '../redux/actions'; +import { TableType } from '../types/custom-aggrid-types'; import { getNetworkVisualizationParameters, getSpreadsheetConfigCollection } from '../services/study/study-config'; import { isComputationResultColumnFilterUpdatedNotification, @@ -208,7 +209,7 @@ const App = () => { const formattedGlobalFilters = model.globalFilters ?? []; dispatch(renameTableDefinition(tabUuid, model.name)); dispatch(updateTableColumns(tabUuid, formattedColumns)); - dispatch(addFilterForNewSpreadsheet(tabUuid, columnsFilters)); + dispatch(updateColumnFiltersAction(TableType.Spreadsheet, tabUuid, columnsFilters)); dispatch(initOrUpdateGlobalFilters(tabUuid, formattedGlobalFilters)); dispatch( addSortForNewSpreadsheet(tabUuid, [ diff --git a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx index baa6db73bf..9533e99e89 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx +++ b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-autocomplete-filter.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent, SyntheticEvent, useCallback, useEffect, useState } from 'react'; import { Autocomplete, TextField } from '@mui/material'; import { useIntl } from 'react-intl'; -import { useCustomAggridFilter } from './hooks/use-custom-aggrid-filter'; +import { useCustomAggridColumnFilter } from './hooks/use-custom-aggrid-column-filter'; import { isNonEmptyStringOrArray } from '../../../utils/types-utils'; import { CustomAggridFilterParams, FILTER_TEXT_COMPARATORS } from '../../../types/custom-aggrid-types'; @@ -24,7 +24,7 @@ export const CustomAggridAutocompleteFilter: FunctionComponent { const intl = useIntl(); - const { selectedFilterData, handleChangeFilterValue } = useCustomAggridFilter(api, colId, filterParams); + const { selectedFilterData, handleChangeFilterValue } = useCustomAggridColumnFilter(colId, filterParams); const [computedFilterOptions, setComputedFilterOptions] = useState(options ?? []); const getUniqueValues = useCallback(() => { diff --git a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-boolean-filter.tsx b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-boolean-filter.tsx index e82de3ffc5..abad07e5c5 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-boolean-filter.tsx +++ b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-boolean-filter.tsx @@ -10,7 +10,7 @@ import { IconButton, MenuItem, Select } from '@mui/material'; import ClearIcon from '@mui/icons-material/Clear'; import { useIntl } from 'react-intl'; import { SelectChangeEvent } from '@mui/material/Select/SelectInput'; -import { useCustomAggridFilter } from './hooks/use-custom-aggrid-filter'; +import { useCustomAggridColumnFilter } from './hooks/use-custom-aggrid-column-filter'; import { isNonEmptyStringOrArray } from '../../../utils/types-utils'; import { mergeSx, type MuiStyles } from '@gridsuite/commons-ui'; import { BooleanFilterValue } from './utils/aggrid-filters-utils'; @@ -28,14 +28,10 @@ const styles = { }, } as const satisfies MuiStyles; -export const CustomAggridBooleanFilter: FunctionComponent = ({ - api, - colId, - filterParams, -}) => { +export const CustomAggridBooleanFilter: FunctionComponent = ({ colId, filterParams }) => { const intl = useIntl(); - const { selectedFilterData, handleChangeFilterValue } = useCustomAggridFilter(api, colId, filterParams); + const { selectedFilterData, handleChangeFilterValue } = useCustomAggridColumnFilter(colId, filterParams); const handleValueChange = (event: SelectChangeEvent) => { const newValue = event.target.value; diff --git a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter.tsx b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter.tsx index 41d074e86a..a036222baf 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter.tsx +++ b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter.tsx @@ -12,7 +12,7 @@ import { useCustomAggridComparatorFilter } from './hooks/use-custom-aggrid-compa import { CustomAggridFilterParams, FILTER_TEXT_COMPARATORS } from '../../../types/custom-aggrid-types'; -export const CustomAggridComparatorFilter = ({ api, colId, filterParams }: CustomAggridFilterParams) => { +export const CustomAggridComparatorFilter = ({ colId, filterParams }: CustomAggridFilterParams) => { const { selectedFilterData, selectedFilterComparator, @@ -21,7 +21,7 @@ export const CustomAggridComparatorFilter = ({ api, colId, filterParams }: Custo handleFilterComparatorChange, handleFilterTextChange, handleClearFilter, - } = useCustomAggridComparatorFilter(api, colId, filterParams); + } = useCustomAggridComparatorFilter(colId, filterParams); const { comparators = [], // used for text filter as a UI type (examples: contains, startsWith..) diff --git a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-duration-filter.tsx b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-duration-filter.tsx index ee6728c89a..1abbf88a0e 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-duration-filter.tsx +++ b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-duration-filter.tsx @@ -11,7 +11,7 @@ import ClearIcon from '@mui/icons-material/Clear'; import { type MuiStyles } from '@gridsuite/commons-ui'; import { CustomAggridComparatorSelector } from './custom-aggrid-comparator-selector'; import { SelectChangeEvent } from '@mui/material/Select/SelectInput'; -import { useCustomAggridFilter } from './hooks/use-custom-aggrid-filter'; +import { useCustomAggridColumnFilter } from './hooks/use-custom-aggrid-column-filter'; import { CustomAggridFilterParams } from '../../../types/custom-aggrid-types'; @@ -37,11 +37,11 @@ const styles = { }, } as const satisfies MuiStyles; -const CustomAggridDurationFilter: FunctionComponent = ({ api, colId, filterParams }) => { +const CustomAggridDurationFilter: FunctionComponent = ({ colId, filterParams }) => { const intl = useIntl(); const { selectedFilterData, selectedFilterComparator, handleChangeFilterValue, handleChangeComparator } = - useCustomAggridFilter(api, colId, filterParams); + useCustomAggridColumnFilter(colId, filterParams); const { comparators = [], // used for text filter as a UI type (examples: contains, startsWith..) diff --git a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.tsx b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.tsx index d5819c8516..4c31c16874 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.tsx +++ b/src/components/custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.tsx @@ -8,7 +8,7 @@ import React, { ComponentType, MouseEvent, useMemo, useState } from 'react'; import { Popover } from '@mui/material'; import { type MuiStyles } from '@gridsuite/commons-ui'; import { CustomFilterIcon } from './custom-filter-icon'; -import { useCustomAggridFilter } from './hooks/use-custom-aggrid-filter'; +import { useCustomAggridColumnFilter } from './hooks/use-custom-aggrid-column-filter'; import { CustomAggridAutocompleteFilterParams } from './custom-aggrid-autocomplete-filter'; import { CustomAggridFilterParams, FILTER_TEXT_COMPARATORS } from '../../../types/custom-aggrid-types'; @@ -39,8 +39,7 @@ export const CustomAggridFilter = ({ }: CustomAggridFilterWrapperParams) => { const [filterAnchorElement, setFilterAnchorElement] = useState(null); - const { selectedFilterData, selectedFilterComparator } = useCustomAggridFilter( - filterComponentParams.api, + const { selectedFilterData, selectedFilterComparator } = useCustomAggridColumnFilter( filterComponentParams.colId, filterComponentParams.filterParams ); diff --git a/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-filter.ts b/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts similarity index 68% rename from src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-filter.ts rename to src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts index 71454211f3..dbf3a6aa80 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-filter.ts +++ b/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-column-filter.ts @@ -4,10 +4,8 @@ * 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 { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { debounce } from '@mui/material'; -import { GridApi } from 'ag-grid-community'; -import { useFilterSelector } from '../../../../hooks/use-filter-selector'; import { computeTolerance } from '../utils/filter-tolerance-utils'; import { FILTER_DATA_TYPES, @@ -15,9 +13,15 @@ import { FilterConfig, FilterData, FilterParams, + TableType, } from '../../../../types/custom-aggrid-types'; -import { updateAgGridFilters } from '../utils/aggrid-filters-utils'; import { useSelector } from 'react-redux'; +import { snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui'; +import { AppState } from '../../../../redux/reducer.type'; +import { getColumnFiltersFromState } from '../../../../redux/selectors/filter-selectors'; +import { persistComputationColumnFilters } from '../../../results/common/column-filter/update-computation-columns-filters'; +import { persistSpreadsheetColumnFilters } from '../../../spreadsheet-view/columns/persist-spreadsheet-column-filters'; +import type { UUID } from 'node:crypto'; const removeElementFromArrayWithFieldValue = (filtersArrayToRemoveFieldValueFrom: FilterConfig[], field: string) => { return filtersArrayToRemoveFieldValueFrom.filter((f) => f.column !== field); @@ -38,10 +42,11 @@ const changeValueFromArrayWithFieldValue = ( } }; -export const useCustomAggridFilter = ( - api: GridApi, +const EMPTY_ARRAY: FilterConfig[] = []; + +export const useCustomAggridColumnFilter = ( colId: string, - { type, tab, dataType, comparators = [], debounceMs = 1000, updateFilterCallback }: FilterParams + { type, tab, dataType, comparators = [], debounceMs = 1000 }: FilterParams ) => { const [selectedFilterComparator, setSelectedFilterComparator] = useState(''); const [selectedFilterData, setSelectedFilterData] = useState(); @@ -49,13 +54,27 @@ export const useCustomAggridFilter = ( // Track if user is currently editing to prevent external sync from overriding input const isEditingRef = useRef(false); - const editingTimeoutRef = useRef | null>(null); - const { filters, dispatchFilters } = useFilterSelector(type, tab); - const studyUuid = useSelector((state: any) => state.studyUuid); + const { snackError } = useSnackMessage(); + const filters = useSelector( + (state) => getColumnFiltersFromState(state, type, tab) ?? EMPTY_ARRAY + ); + const studyUuid = useSelector((state) => state.studyUuid); + const tableDefinitions = useSelector( + (state) => state.tables.definitions + ); + const colDef = useMemo( + () => tableDefinitions.find((t) => t.uuid === tab)?.columns?.find((col) => col.id === colId), + [tableDefinitions, tab, colId] + ); + // data flow is: update backend database -> notification -> update redux state -> useEffect -> update hook state const updateFilter = useCallback( - (colId: string, data: FilterData): void => { + (data: FilterData): void => { + if (!studyUuid || type === TableType.Logs) { + return; + } + const newFilter = { column: colId, dataType: data.dataType, @@ -77,43 +96,35 @@ export const useCustomAggridFilter = ( } else { updatedFilters = changeValueFromArrayWithFieldValue(filters, colId, newFilter); } - updateAgGridFilters(api, updatedFilters); - updateFilterCallback?.(api, updatedFilters, colId, studyUuid, type, tab); - dispatchFilters(updatedFilters); + + const onError = (error: unknown) => snackWithFallback(snackError, error); + + if (type === TableType.Spreadsheet) { + persistSpreadsheetColumnFilters(studyUuid, tab as UUID, updatedFilters, colDef, onError); + } else { + persistComputationColumnFilters(updatedFilters, colId, studyUuid, type, tab, onError); + } }, - [updateFilterCallback, api, dispatchFilters, filters, studyUuid, type, tab] + [filters, studyUuid, type, tab, colId, colDef, snackError] ); // We intentionally exclude `updateFilter` from dependencies. // `updateFilter` depends on `filters`, which changes on every filter update. // Including it would recreate the debounced function on each change, // canceling any pending debounced call and preventing updates from being sent. + // PS: it only works because most of the props never change... // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedUpdateFilter = useCallback( debounce((data) => { - updateFilter(colId, data); + updateFilter(data); isEditingRef.current = false; }, debounceMs), - [colId, debounceMs] + [debounceMs] ); - // Cleanup debounce on unmount - useEffect(() => { - return () => { - debouncedUpdateFilter.clear(); - if (editingTimeoutRef.current) { - clearTimeout(editingTimeoutRef.current); - } - }; - }, [debouncedUpdateFilter]); - const handleChangeFilterValue = useCallback( (filterData: FilterData) => { isEditingRef.current = true; - if (editingTimeoutRef.current) clearTimeout(editingTimeoutRef.current); - editingTimeoutRef.current = setTimeout(() => { - isEditingRef.current = false; - }, debounceMs); setSelectedFilterData(filterData.value); setTolerance(filterData.tolerance); @@ -124,23 +135,24 @@ export const useCustomAggridFilter = ( tolerance: filterData.tolerance, }); }, - [dataType, debouncedUpdateFilter, selectedFilterComparator, debounceMs] + [dataType, debouncedUpdateFilter, selectedFilterComparator] ); const handleChangeComparator = useCallback( (newType: string) => { + isEditingRef.current = true; setSelectedFilterComparator(newType); const filterWithoutValue = newType === FILTER_TEXT_COMPARATORS.IS_EMPTY || newType === FILTER_TEXT_COMPARATORS.IS_NOT_EMPTY; if (filterWithoutValue) { - updateFilter(colId, { + debouncedUpdateFilter({ value: true, type: newType, dataType, tolerance: tolerance, }); } else if (selectedFilterData && selectedFilterData !== true) { - updateFilter(colId, { + debouncedUpdateFilter({ value: selectedFilterData, type: newType, dataType, @@ -149,22 +161,15 @@ export const useCustomAggridFilter = ( } else { // We switch from IS_EMPTY or IS_NOT_EMPTY comparator to a comparator with a value setSelectedFilterData(undefined); - const updatedFilters = removeElementFromArrayWithFieldValue(filters, colId); - updateFilterCallback?.(api, updatedFilters); - dispatchFilters(updatedFilters); + debouncedUpdateFilter({ + value: undefined, + type: newType, + dataType, + tolerance: tolerance, + }); } }, - [ - colId, - dataType, - selectedFilterData, - tolerance, - updateFilter, - filters, - updateFilterCallback, - api, - dispatchFilters, - ] + [dataType, selectedFilterData, tolerance, debouncedUpdateFilter] ); useEffect(() => { diff --git a/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-comparator-filter.ts b/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-comparator-filter.ts index c4188e00d8..d865029733 100644 --- a/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-comparator-filter.ts +++ b/src/components/custom-aggrid/custom-aggrid-filters/hooks/use-custom-aggrid-comparator-filter.ts @@ -8,18 +8,17 @@ import { ChangeEvent, useMemo } from 'react'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { SelectChangeEvent } from '@mui/material/Select/SelectInput'; import { countDecimalPlacesFromString } from '../../../../utils/rounding'; -import { useCustomAggridFilter } from './use-custom-aggrid-filter'; -import { GridApi } from 'ag-grid-community'; +import { useCustomAggridColumnFilter } from './use-custom-aggrid-column-filter'; import { computeTolerance } from '../utils/filter-tolerance-utils'; import { FILTER_DATA_TYPES, FILTER_TEXT_COMPARATORS, FilterParams } from '../../../../types/custom-aggrid-types'; -export const useCustomAggridComparatorFilter = (api: GridApi, colId: string, filterParams: FilterParams) => { +export const useCustomAggridComparatorFilter = (colId: string, filterParams: FilterParams) => { const { dataType = FILTER_DATA_TYPES.TEXT, comparators = [] } = filterParams; const isNumberInput = dataType === FILTER_DATA_TYPES.NUMBER; const { selectedFilterData, selectedFilterComparator, handleChangeFilterValue, handleChangeComparator } = - useCustomAggridFilter(api, colId, filterParams); + useCustomAggridColumnFilter(colId, filterParams); const { snackWarning } = useSnackMessage(); diff --git a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx index 8c0243096c..86fdf4bbf8 100644 --- a/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx +++ b/src/components/grid-layout/cards/diagrams/singleLineDiagram/single-line-diagram-content.tsx @@ -43,7 +43,8 @@ import { updateSwitchState } from '../../../../../services/study/network-modific import { BusMenu } from 'components/menus/bus-menu'; import { startShortCircuitAnalysis } from '../../../../../services/study/short-circuit-analysis'; import { useOneBusShortcircuitAnalysisLoader } from './hooks/use-one-bus-shortcircuit-analysis-loader'; -import { setComputationStarting, setComputingStatus, setLogsFilter } from '../../../../../redux/actions'; +import { setComputationStarting, setComputingStatus, updateColumnFiltersAction } from '../../../../../redux/actions'; +import { TableType } from '../../../../../types/custom-aggrid-types'; import { AppState } from 'redux/reducer.type'; import type { UUID } from 'node:crypto'; import { useParameterState } from 'components/dialogs/parameters/use-parameters-state'; @@ -243,7 +244,7 @@ const SingleLineDiagramContent = memo(function SingleLineDiagramContent(props: S .finally(() => { dispatch(setComputationStarting(false)); // we clear the computation logs filter when a new computation is started - dispatch(setLogsFilter(ComputingType.SHORT_CIRCUIT_ONE_BUS, [])); + dispatch(updateColumnFiltersAction(TableType.Logs, ComputingType.SHORT_CIRCUIT_ONE_BUS, [])); }); }, [ diff --git a/src/components/report-viewer/log-table.tsx b/src/components/report-viewer/log-table.tsx index 7f1d4da64b..0034d502c1 100644 --- a/src/components/report-viewer/log-table.tsx +++ b/src/components/report-viewer/log-table.tsx @@ -8,10 +8,10 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { CustomAGGrid, MessageLogCellRenderer, type MuiStyles, type SxStyle } from '@gridsuite/commons-ui'; import { alpha, useTheme } from '@mui/material/styles'; -import { setLogsFilter } from '../../redux/actions'; +import { updateColumnFiltersAction } from '../../redux/actions'; import { makeAgGridCustomHeaderColumn } from 'components/custom-aggrid/utils/custom-aggrid-header-utils'; import { useReportFetcher } from 'hooks/use-report-fetcher'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { getDefaultSeverityFilter, REPORT_SEVERITY } from '../../utils/report/report-severity'; import { QuickSearch } from './QuickSearch'; import { Box, Chip, Theme } from '@mui/material'; @@ -29,15 +29,16 @@ import { COMPUTING_AND_NETWORK_MODIFICATION_TYPE } from 'utils/report/report.con import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { CustomAggridComparatorFilter } from '../custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter'; -import { useFilterSelector } from '../../hooks/use-filter-selector'; import { FILTER_DATA_TYPES, FILTER_TEXT_COMPARATORS, FilterConfig, TableType } from '../../types/custom-aggrid-types'; +import { AppState } from '../../redux/reducer.type'; +import { getColumnFiltersFromState } from '../../redux/selectors/filter-selectors'; import { AGGRID_LOCALES } from '../../translations/not-intl/aggrid-locales'; import CustomTablePagination from 'components/utils/custom-table-pagination'; import { reportStyles } from './report.styles'; import { useLogsPagination } from './use-logs-pagination'; import { useStableComputedArray } from '../../hooks/use-stable-computed-array'; -const getColumnFilterValue = (array: FilterConfig[] | null, columnName: string): any => { +const getColumnFilterValue = (array: FilterConfig[] | null | undefined, columnName: string): any => { return array?.find((item) => item.column === columnName)?.value ?? null; }; @@ -104,7 +105,9 @@ const LogTable = ({ const [, , , fetchLogs, fetchLogMatches] = useReportFetcher( reportType as keyof typeof COMPUTING_AND_NETWORK_MODIFICATION_TYPE ); - const { filters } = useFilterSelector(TableType.Logs, reportType); + const filters = useSelector((state) => + getColumnFiltersFromState(state, TableType.Logs, reportType) + ); const { pagination, setPagination } = useLogsPagination(reportType); const [selectedRowIndex, setSelectedRowIndex] = useState(-1); @@ -180,7 +183,7 @@ const LogTable = ({ if (needsInitialization) { dispatch( - setLogsFilter(reportType, [ + updateColumnFiltersAction(TableType.Logs, reportType, [ { column: 'severity', dataType: FILTER_DATA_TYPES.TEXT, @@ -372,7 +375,7 @@ const LogTable = ({ : [...severityFilter, severity]; dispatch( - setLogsFilter(reportType, [ + updateColumnFiltersAction(TableType.Logs, reportType, [ { column: 'severity', dataType: FILTER_DATA_TYPES.TEXT, diff --git a/src/components/results/common/column-filter/update-computation-columns-filters.ts b/src/components/results/common/column-filter/update-computation-columns-filters.ts index 4d21b1d0fe..08cc93f73b 100644 --- a/src/components/results/common/column-filter/update-computation-columns-filters.ts +++ b/src/components/results/common/column-filter/update-computation-columns-filters.ts @@ -6,23 +6,20 @@ */ import { FilterConfig, TableType } from '../../../../types/custom-aggrid-types'; import { updateComputationResultFiltersColumn } from '../../../../services/study/study-config'; -import { GridApi } from 'ag-grid-community'; import { UUID } from 'node:crypto'; -export const updateComputationColumnsFilters = ( - agGridApi?: GridApi, +export const persistComputationColumnFilters = ( filters?: FilterConfig[], colId?: string, studyUuid?: UUID, tableType?: TableType, filterSubType?: string, - onBeforePersist?: () => void + onError?: (error: unknown) => void ) => { - if (!agGridApi || !studyUuid || !colId || !filterSubType || !tableType) { + if (!studyUuid || !colId || !filterSubType || !tableType) { return; } const filter = filters?.find((f) => f.column === colId); - onBeforePersist?.(); const columnFilterInfos = { columnId: colId, columnFilterInfos: filter @@ -34,5 +31,7 @@ export const updateComputationColumnsFilters = ( } : null, }; - updateComputationResultFiltersColumn(studyUuid, tableType, filterSubType, columnFilterInfos).then(); + updateComputationResultFiltersColumn(studyUuid, tableType, filterSubType, columnFilterInfos).catch((error) => { + onError?.(error); + }); }; diff --git a/src/components/results/common/column-filter/use-computation-column-filters.ts b/src/components/results/common/column-filter/use-computation-column-filters.ts index fde480b7b7..4b81fa16e7 100644 --- a/src/components/results/common/column-filter/use-computation-column-filters.ts +++ b/src/components/results/common/column-filter/use-computation-column-filters.ts @@ -11,16 +11,21 @@ import { useEffect } from 'react'; import { updateComputationColumnFilters } from '../utils'; const EMPTY_ARRAY: FilterConfig[] = []; + +// This hook has two purposes : +// 1. Initialize the column filters for a given table by fetching the server and storing them in the Redux store +// 2. Return the up-to-date column filters for this table from the Redux store export function useComputationColumnFilters(tableType: TableType, computationSubType: string) { const dispatch = useDispatch(); const studyUuid = useSelector((state: AppState) => state.studyUuid); + // Fetch from server and update Redux store useEffect(() => { if (studyUuid) { updateComputationColumnFilters(dispatch, studyUuid, tableType, computationSubType); } }, [dispatch, studyUuid, tableType, computationSubType]); const filters = useSelector( - (state) => state.tableFilters.columnsFilters?.[tableType]?.[computationSubType]?.columns ?? EMPTY_ARRAY + (state) => state.tableFilters.columnsFilters?.[tableType]?.[computationSubType] ?? EMPTY_ARRAY ); return { filters, diff --git a/src/components/results/common/use-ag-grid-initial-column-filters.ts b/src/components/results/common/use-ag-grid-initial-column-filters.ts index 934048cf59..62a95d2886 100644 --- a/src/components/results/common/use-ag-grid-initial-column-filters.ts +++ b/src/components/results/common/use-ag-grid-initial-column-filters.ts @@ -31,7 +31,7 @@ export const useAgGridInitialColumnFilters = ( const api = params.api; if (!api) return; const { tableFilters } = store.getState(); - const filters = tableFilters.columnsFilters?.[tableType]?.[computationSubType]?.columns; + const filters = tableFilters.columnsFilters?.[tableType]?.[computationSubType]; updateAgGridFilters(api, filters); requestAnimationFrame(() => { api.sizeColumnsToFit(); diff --git a/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx b/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx index d1bd377f86..36b0db8f02 100644 --- a/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx +++ b/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx @@ -29,7 +29,6 @@ import { dynamicSimulationResultInvalidations } from '../../computing-status/use import { useNodeData } from 'components/use-node-data'; import { AGGRID_LOCALES } from '../../../translations/not-intl/aggrid-locales'; import { fetchDynamicSimulationResultTimeline } from '../../../services/study/dynamic-simulation'; -import { updateComputationColumnsFilters } from '../common/column-filter/update-computation-columns-filters'; import { useAgGridInitialColumnFilters } from '../common/use-ag-grid-initial-column-filters'; const styles = { @@ -91,7 +90,6 @@ const DynamicSimulationResultTimeline = memo( filterParams: { type: TableType.DynamicSimulation, tab: TIMELINE, - updateFilterCallback: updateComputationColumnsFilters, dataType: FILTER_DATA_TYPES.NUMBER, comparators: Object.values(FILTER_NUMBER_COMPARATORS), }, @@ -116,7 +114,6 @@ const DynamicSimulationResultTimeline = memo( filterParams: { type: TableType.DynamicSimulation, tab: TIMELINE, - updateFilterCallback: updateComputationColumnsFilters, dataType: FILTER_DATA_TYPES.TEXT, comparators: [FILTER_TEXT_COMPARATORS.STARTS_WITH, FILTER_TEXT_COMPARATORS.CONTAINS], }, @@ -140,7 +137,6 @@ const DynamicSimulationResultTimeline = memo( filterParams: { type: TableType.DynamicSimulation, tab: TIMELINE, - updateFilterCallback: updateComputationColumnsFilters, dataType: FILTER_DATA_TYPES.TEXT, comparators: [FILTER_TEXT_COMPARATORS.STARTS_WITH, FILTER_TEXT_COMPARATORS.CONTAINS], }, diff --git a/src/components/results/loadflow/load-flow-result-utils.ts b/src/components/results/loadflow/load-flow-result-utils.ts index 947a184ac6..7240bf77b8 100644 --- a/src/components/results/loadflow/load-flow-result-utils.ts +++ b/src/components/results/loadflow/load-flow-result-utils.ts @@ -45,7 +45,6 @@ import { } from '../../../types/custom-aggrid-types'; import { convertDuration, formatNAValue } from 'components/custom-aggrid/utils/format-values-utils'; import { SubjectIdRendererType } from '../securityanalysis/security-analysis.type'; -import { updateComputationColumnsFilters } from '../common/column-filter/update-computation-columns-filters'; import { createEnumColumn } from '../common/column-filter/utilis'; export const FROM_COLUMN_TO_FIELD_LIMIT_VIOLATION_RESULT: Record = { @@ -206,7 +205,6 @@ interface TableParams { filterParams: { type: TableType; tab: string; - updateFilterCallback: typeof updateComputationColumnsFilters; }; } @@ -220,7 +218,6 @@ const createTableParams = (tabIndex: number): TableParams => { filterParams: { type: TableType.Loadflow, tab, - updateFilterCallback: updateComputationColumnsFilters, }, }; }; diff --git a/src/components/results/pccmin/pcc-min-export-button.tsx b/src/components/results/pccmin/pcc-min-export-button.tsx index e2b4e248d1..ea485c98c7 100644 --- a/src/components/results/pccmin/pcc-min-export-button.tsx +++ b/src/components/results/pccmin/pcc-min-export-button.tsx @@ -11,8 +11,8 @@ import { downloadZipFile } from '../../../services/utils'; import type { UUID } from 'node:crypto'; import { AppState } from 'redux/reducer.type'; import { useSelector } from 'react-redux'; -import { useFilterSelector } from '../../../hooks/use-filter-selector'; import { TableType } from '../../../types/custom-aggrid-types'; +import { getColumnFiltersFromStore } from '../../../redux/selectors/filter-store-selectors'; import { PCCMIN_ANALYSIS_RESULT_SORT_STORE, PCCMIN_RESULT } from 'utils/store-sort-filter-fields'; import { mapFieldsToColumnsFilter } from 'utils/aggrid-headers-utils'; import { exportPccMinResultsAsCsv } from 'services/study/pcc-min'; @@ -37,7 +37,6 @@ export const PccMinExportButton: FunctionComponent = (p const [isCsvExportLoading, setIsCsvExportLoading] = useState(false); const [isCsvExportSuccessful, setIsCsvExportSuccessful] = useState(false); - const { filters } = useFilterSelector(TableType.PccMin, PCCMIN_RESULT); const sortConfig = useSelector( (state: AppState) => state.tableSort[PCCMIN_ANALYSIS_RESULT_SORT_STORE][PCCMIN_RESULT] ); @@ -59,6 +58,7 @@ export const PccMinExportButton: FunctionComponent = (p const exportCsv = useCallback(() => { setIsCsvExportLoading(true); setIsCsvExportSuccessful(false); + const filters = getColumnFiltersFromStore(TableType.PccMin, PCCMIN_RESULT); const filter = filters ? mapFieldsToColumnsFilter(filters, FROM_COLUMN_TO_FIELD_PCC_MIN) : null; const globalFilters = buildValidGlobalFilters(getSelectedGlobalFilters(TableType.PccMin)); exportPccMinResultsAsCsv( @@ -85,7 +85,7 @@ export const PccMinExportButton: FunctionComponent = (p setIsCsvExportSuccessful(false); }) .finally(() => setIsCsvExportLoading(false)); - }, [filters, studyUuid, nodeUuid, currentRootNetworkUuid, sortConfig, csvHeaders, language, snackError]); + }, [studyUuid, nodeUuid, currentRootNetworkUuid, sortConfig, csvHeaders, language, snackError]); return ( = ({ result, isFetching, - goToFirstPage, setCsvHeaders, setIsCsvButtonDisabled, }) => { const intl = useIntl(); const pccMinStatus = useSelector((state: AppState) => state.computingStatus[ComputingType.PCC_MIN]); const filters = useSelector( - (state: AppState) => state.tableFilters.columnsFilters?.[TableType.PccMin]?.[PCCMIN_RESULT]?.columns + (state: AppState) => state.tableFilters.columnsFilters?.[TableType.PccMin]?.[PCCMIN_RESULT] ); const gridRef = useRef(null); const { openSLD } = useWorkspacePanelActions(); @@ -67,10 +66,7 @@ const PccMinResultTable: FunctionComponent = ({ [openSLD] ); - const columns = useMemo( - () => getPccMinColumns(intl, voltageLevelIdRenderer, goToFirstPage), - [goToFirstPage, intl, voltageLevelIdRenderer] - ); + const columns = useMemo(() => getPccMinColumns(intl, voltageLevelIdRenderer), [intl, voltageLevelIdRenderer]); const statusMessage = useIntlResultStatusMessages(intl, true, filters?.length > 0); diff --git a/src/components/results/pccmin/pcc-min-result.tsx b/src/components/results/pccmin/pcc-min-result.tsx index e3164f8b2e..0456a595ae 100644 --- a/src/components/results/pccmin/pcc-min-result.tsx +++ b/src/components/results/pccmin/pcc-min-result.tsx @@ -95,10 +95,6 @@ export const PccMinResult: FunctionComponent = ({ [dispatchPagination] ); - const goToFirstPage = useCallback(() => { - dispatchPagination({ ...pagination, page: 0 }); - }, [pagination, dispatchPagination]); - useEffect(() => { if (pccMinStatus !== RunningStatus.SUCCEED) { return; @@ -174,7 +170,6 @@ export const PccMinResult: FunctionComponent = ({ isFetching={isFetching} setCsvHeaders={setCsvHeaders} setIsCsvButtonDisabled={setIsCsvButtonDisabled} - goToFirstPage={goToFirstPage} /> ; export interface PccMinResultTableProps { result: SinglePccMinResultInfos[] | undefined; isFetching: boolean; - goToFirstPage: () => void; setCsvHeaders: (newHeaders: string[]) => void; setIsCsvButtonDisabled: (newIsCsv: boolean) => void; } @@ -69,8 +61,7 @@ export const FROM_COLUMN_TO_FIELD_PCC_MIN: Record = { }; export const getPccMinColumns = ( intl: IntlShape, - voltageLevelIdRenderer: (cellData: ICellRendererParams) => React.JSX.Element | undefined, - goToFirstPage: () => void + voltageLevelIdRenderer: (cellData: ICellRendererParams) => React.JSX.Element | undefined ) => { const sortParams: ColumnContext['sortParams'] = { table: PCCMIN_ANALYSIS_RESULT_SORT_STORE, @@ -80,23 +71,6 @@ export const getPccMinColumns = ( const pccMinFilterParams = { type: TableType.PccMin, tab: PCCMIN_RESULT, - updateFilterCallback: ( - agGridApi?: GridApi, - filters?: FilterConfig[], - colId?: string, - studyUuid?: UUID, - filterType?: TableType, - filterSubType?: string - ) => - updateComputationColumnsFilters( - agGridApi, - filters, - colId, - studyUuid, - filterType, - filterSubType, - goToFirstPage - ), }; const createFilterContext = ( diff --git a/src/components/results/securityanalysis/security-analysis-result-tab.tsx b/src/components/results/securityanalysis/security-analysis-result-tab.tsx index e21ff8f4d2..16657af499 100644 --- a/src/components/results/securityanalysis/security-analysis-result-tab.tsx +++ b/src/components/results/securityanalysis/security-analysis-result-tab.tsx @@ -122,10 +122,6 @@ export const SecurityAnalysisResultTab: FunctionComponent { - dispatchPagination({ ...pagination, page: 0 }); - }, [pagination, dispatchPagination]); - const resetPaginationIfNKResults = useCallback(() => { if (tabIndex === NMK_RESULTS_TAB_INDEX) { dispatchPagination({ page: 0, rowsPerPage }); @@ -234,7 +230,7 @@ export const SecurityAnalysisResultTab: FunctionComponent columnDefs.map((cDef) => cDef.headerName ?? ''), [columnDefs]); const downloadZipResult = useCallback( diff --git a/src/components/results/securityanalysis/security-analysis-result-utils.ts b/src/components/results/securityanalysis/security-analysis-result-utils.ts index f9f7269b97..66c27a0458 100644 --- a/src/components/results/securityanalysis/security-analysis-result-utils.ts +++ b/src/components/results/securityanalysis/security-analysis-result-utils.ts @@ -16,7 +16,7 @@ import { SubjectIdRendererType, } from './security-analysis.type'; import { IntlShape } from 'react-intl'; -import { ColDef, GridApi, PostSortRowsParams, ValueFormatterParams, ValueGetterParams } from 'ag-grid-community'; +import { ColDef, PostSortRowsParams, ValueFormatterParams, ValueGetterParams } from 'ag-grid-community'; import { ComputingType, ContingencyCellRenderer } from '@gridsuite/commons-ui'; import { makeAgGridCustomHeaderColumn } from '../../custom-aggrid/utils/custom-aggrid-header-utils'; import { translateLimitNameBackToFront, translateLimitNameFrontToBack } from '../common/utils'; @@ -45,8 +45,6 @@ import { } from '../../../types/custom-aggrid-types'; import { convertDuration, formatNAValue } from '../../custom-aggrid/utils/format-values-utils'; import { MAX_INT32 } from 'services/utils'; -import { updateComputationColumnsFilters } from '../common/column-filter/update-computation-columns-filters'; -import type { UUID } from 'node:crypto'; import { createEnumColumn } from '../common/column-filter/utilis'; interface TableParams { @@ -54,11 +52,10 @@ interface TableParams { filterParams: { type: TableType; tab: string; - updateFilterCallback: typeof updateComputationColumnsFilters; }; } -const createTableParams = (tabIndex: number, goToFirstPage: () => void): TableParams => { +const createTableParams = (tabIndex: number): TableParams => { const tab = getStoreFields(tabIndex); return { sortParams: { @@ -68,23 +65,6 @@ const createTableParams = (tabIndex: number, goToFirstPage: () => void): TablePa filterParams: { type: TableType.SecurityAnalysis, tab, - updateFilterCallback: ( - agGridApi?: GridApi, - filters?: FilterConfig[], - colId?: string, - studyUuid?: UUID, - filterType?: TableType, - filterSubType?: string - ) => - updateComputationColumnsFilters( - agGridApi, - filters, - colId, - studyUuid, - filterType, - filterSubType, - goToFirstPage - ), }, }; }; @@ -187,7 +167,6 @@ export const flattenNmKResultsConstraints = (intl: IntlShape, result: Contingenc interface AgGridFilterParams { type: TableType; tab: string; - updateFilterCallback: () => void; } const makeAgGridStringColumn = ( @@ -283,10 +262,9 @@ export const securityAnalysisTableNColumnsDefinition = ( intl: IntlShape, filterEnums: FilterEnumsType, getEnumLabel: (value: string) => string, // Used for translation of enum values in the filter - tabIndex: number, - goToFirstPage: () => void + tabIndex: number ): ColDef[] => { - const { sortParams, filterParams } = createTableParams(tabIndex, goToFirstPage); + const { sortParams, filterParams } = createTableParams(tabIndex); return [ makeAgGridCustomHeaderColumn(makeAgGridStringColumn('Equipment', 'subjectId', intl, filterParams, sortParams)), @@ -349,10 +327,9 @@ export const securityAnalysisTableNmKContingenciesColumnsDefinition = ( subjectIdRenderer: SubjectIdRendererType, filterEnums: FilterEnumsType, getEnumLabel: (value: string) => string, // Used for translation of enum values in the filter - tabIndex: number, - goToFirstPage: () => void + tabIndex: number ): ColDef[] => { - const { sortParams, filterParams } = createTableParams(tabIndex, goToFirstPage); + const { sortParams, filterParams } = createTableParams(tabIndex); return [ makeAgGridCustomHeaderColumn({ @@ -492,10 +469,9 @@ export const securityAnalysisTableNmKConstraintsColumnsDefinition = ( subjectIdRenderer: SubjectIdRendererType, filterEnums: FilterEnumsType, getEnumLabel: (value: string) => string, // Used for translation of enum values in the filter - tabIndex: number, - goToFirstPage: () => void + tabIndex: number ): ColDef[] => { - const { sortParams, filterParams } = createTableParams(tabIndex, goToFirstPage); + const { sortParams, filterParams } = createTableParams(tabIndex); return [ makeAgGridCustomHeaderColumn({ diff --git a/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx b/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx index b1b955cd2b..2d5701abb0 100644 --- a/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx +++ b/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx @@ -33,15 +33,13 @@ export interface SecurityAnalysisFilterEnumsType { type UseSecurityAnalysisColumnsDefsProps = ( filterEnums: SecurityAnalysisFilterEnumsType, resultType: RESULT_TYPE, - tabIndex: number, - goToFirstPage: () => void + tabIndex: number ) => ColDef[]; export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps = ( filterEnums, resultType, - tabIndex, - goToFirstPage + tabIndex ) => { const intl = useIntl(); const { snackError } = useSnackMessage(); @@ -141,8 +139,7 @@ export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps SubjectIdRenderer, filterEnums.nmk, getEnumLabel, - tabIndex, - goToFirstPage + tabIndex ); case RESULT_TYPE.NMK_LIMIT_VIOLATIONS: return securityAnalysisTableNmKConstraintsColumnsDefinition( @@ -150,19 +147,12 @@ export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps SubjectIdRenderer, filterEnums.nmk, getEnumLabel, - tabIndex, - goToFirstPage + tabIndex ); case RESULT_TYPE.N: - return securityAnalysisTableNColumnsDefinition( - intl, - filterEnums.n, - getEnumLabel, - tabIndex, - goToFirstPage - ); + return securityAnalysisTableNColumnsDefinition(intl, filterEnums.n, getEnumLabel, tabIndex); } - }, [resultType, intl, SubjectIdRenderer, filterEnums.nmk, filterEnums.n, getEnumLabel, tabIndex, goToFirstPage]); + }, [resultType, intl, SubjectIdRenderer, filterEnums.nmk, filterEnums.n, getEnumLabel, tabIndex]); return columnDefs; }; diff --git a/src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx b/src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx index 265e7b577d..b6770d342f 100644 --- a/src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx +++ b/src/components/results/sensitivity-analysis/paged-sensitivity-analysis-result.tsx @@ -119,10 +119,6 @@ function PagedSensitivityAnalysisResult({ [dispatchPagination] ); - const goToFirstPage = useCallback(() => { - dispatchPagination({ ...pagination, page: 0 }); - }, [pagination, dispatchPagination]); - const fetchFilterOptions = useCallback(() => { const selector = { tabSelection: SensitivityResultTabs[nOrNkIndex].id, @@ -218,7 +214,6 @@ function PagedSensitivityAnalysisResult({ result={result?.sensitivities ?? []} nOrNkIndex={nOrNkIndex} sensiKind={sensiKind} - goToFirstPage={goToFirstPage} filtersDef={filtersDef} isLoading={isLoading} setCsvHeaders={setCsvHeaders} diff --git a/src/components/results/sensitivity-analysis/sensitivity-analysis-export-button.tsx b/src/components/results/sensitivity-analysis/sensitivity-analysis-export-button.tsx index 0b265104f0..1dad49d24f 100644 --- a/src/components/results/sensitivity-analysis/sensitivity-analysis-export-button.tsx +++ b/src/components/results/sensitivity-analysis/sensitivity-analysis-export-button.tsx @@ -25,8 +25,8 @@ import { SensiKind } from './sensitivity-analysis-result.type'; import { SortWay, TableType } from '../../../types/custom-aggrid-types'; import { SENSITIVITY_ANALYSIS_RESULT_SORT_STORE } from 'utils/store-sort-filter-fields'; import { PARAM_COMPUTED_LANGUAGE } from '../../../utils/config-params'; -import { useFilterSelector } from '../../../hooks/use-filter-selector'; import { buildValidGlobalFilters } from '../common/global-filter/build-valid-global-filters'; +import { getColumnFiltersFromStore } from '../../../redux/selectors/filter-store-selectors'; import { getSelectedGlobalFilters } from '../common/global-filter/use-selected-global-filters'; @@ -49,7 +49,6 @@ export const SensitivityExportButton: FunctionComponent state[PARAM_COMPUTED_LANGUAGE]); const appTabIndex = useSelector((state: AppState) => state.appTabIndex); - const { filters } = useFilterSelector(TableType.SensitivityAnalysis, mappingTabs(sensiKind, nOrNkIndex)); const sortConfig = useSelector( (state: AppState) => state.tableSort[SENSITIVITY_ANALYSIS_RESULT_SORT_STORE][mappingTabs(sensiKind, nOrNkIndex)] ); @@ -68,6 +67,7 @@ export const SensitivityExportButton: FunctionComponent { setIsCsvExportLoading(true); setIsCsvExportSuccessful(false); + const filters = getColumnFiltersFromStore(TableType.SensitivityAnalysis, mappingTabs(sensiKind, nOrNkIndex)); const mappedFilters = filters?.map((elem) => { const keyMap = nOrNkIndex === 0 ? DATA_KEY_TO_FILTER_KEY_N : DATA_KEY_TO_FILTER_KEY_NK; const newColumn = keyMap[elem.column as keyof typeof keyMap]; @@ -119,7 +119,6 @@ export const SensitivityExportButton: FunctionComponent setIsCsvExportLoading(false)); }, [ - filters, sortConfig, nOrNkIndex, sensiKind, diff --git a/src/components/results/sensitivity-analysis/sensitivity-analysis-result.tsx b/src/components/results/sensitivity-analysis/sensitivity-analysis-result.tsx index 468daf297d..faa37792df 100644 --- a/src/components/results/sensitivity-analysis/sensitivity-analysis-result.tsx +++ b/src/components/results/sensitivity-analysis/sensitivity-analysis-result.tsx @@ -21,19 +21,16 @@ import { FILTER_DATA_TYPES, FILTER_NUMBER_COMPARATORS, FILTER_TEXT_COMPARATORS, - FilterConfig, TableType, } from '../../../types/custom-aggrid-types'; import { makeAgGridCustomHeaderColumn } from '../../custom-aggrid/utils/custom-aggrid-header-utils'; import { SensiKind, SENSITIVITY_AT_NODE, SENSITIVITY_IN_DELTA_MW } from './sensitivity-analysis-result.type'; import { AppState } from '../../../redux/reducer.type'; -import { GridApi, GridColumnsChangedEvent, RowDataUpdatedEvent } from 'ag-grid-community'; +import { GridColumnsChangedEvent, RowDataUpdatedEvent } from 'ag-grid-community'; import { Sensitivity } from '../../../services/study/sensitivity-analysis.type'; import { AGGRID_LOCALES } from '../../../translations/not-intl/aggrid-locales'; import { CustomAggridComparatorFilter } from '../../custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter'; import { getColumnHeaderDisplayNames } from 'components/utils/column-constant'; -import { updateComputationColumnsFilters } from '../common/column-filter/update-computation-columns-filters'; -import type { UUID } from 'node:crypto'; import { useAgGridInitialColumnFilters } from '../common/use-ag-grid-initial-column-filters'; function makeRows(resultRecord: Sensitivity[]) { @@ -51,7 +48,6 @@ type SensitivityAnalysisResultProps = CustomAGGridProps & { sensiKind: SensiKind; filtersDef: { field: string; options: string[] }[]; isLoading: boolean; - goToFirstPage: () => void; setCsvHeaders: (newHeaders: string[]) => void; setIsCsvButtonDisabled: (newIsCsv: boolean) => void; computationSubType: string; @@ -62,7 +58,6 @@ function SensitivityAnalysisResult({ nOrNkIndex = 0, sensiKind = SENSITIVITY_IN_DELTA_MW, filtersDef, - goToFirstPage, isLoading, setCsvHeaders, setIsCsvButtonDisabled, @@ -110,23 +105,6 @@ function SensitivityAnalysisResult({ : [FILTER_TEXT_COMPARATORS.STARTS_WITH, FILTER_TEXT_COMPARATORS.CONTAINS], type: TableType.SensitivityAnalysis, tab: mappingTabs(sensiKind, nOrNkIndex), - updateFilterCallback: ( - agGridApi?: GridApi, - filters?: FilterConfig[], - colId?: string, - studyUuid?: UUID, - filterType?: TableType, - filterSubType?: string - ) => - updateComputationColumnsFilters( - agGridApi, - filters, - colId, - studyUuid, - filterType, - filterSubType, - goToFirstPage - ), }, }, }, @@ -136,7 +114,7 @@ function SensitivityAnalysisResult({ pinned: pinned, }); }, - [intl, nOrNkIndex, sensiKind, goToFirstPage] + [intl, nOrNkIndex, sensiKind] ); const columnsDefs = useMemo(() => { diff --git a/src/components/results/shortcircuit/shortcircuit-analysis-export-button.tsx b/src/components/results/shortcircuit/shortcircuit-analysis-export-button.tsx index 7ed32fed10..63699a1920 100644 --- a/src/components/results/shortcircuit/shortcircuit-analysis-export-button.tsx +++ b/src/components/results/shortcircuit/shortcircuit-analysis-export-button.tsx @@ -19,8 +19,8 @@ import { BranchSide } from 'components/utils/constants'; import { AppState } from 'redux/reducer.type'; import { useSelector } from 'react-redux'; import { PARAM_COMPUTED_LANGUAGE } from '../../../utils/config-params'; -import { useFilterSelector } from '../../../hooks/use-filter-selector'; import { TableType } from '../../../types/custom-aggrid-types'; +import { getColumnFiltersFromStore } from '../../../redux/selectors/filter-store-selectors'; import { convertFilterValues, FROM_COLUMN_TO_FIELD, @@ -53,7 +53,6 @@ export const ShortCircuitExportButton: FunctionComponent state[PARAM_COMPUTED_LANGUAGE]); const appTabIndex = useSelector((state: AppState) => state.appTabIndex); - const { filters } = useFilterSelector(TableType.ShortcircuitAnalysis, mappingTabs(analysisType)); const sortConfig = useSelector( (state: AppState) => state.tableSort[SHORTCIRCUIT_ANALYSIS_RESULT_SORT_STORE][mappingTabs(analysisType)] ); @@ -105,6 +104,7 @@ export const ShortCircuitExportButton: FunctionComponent setIsCsvExportLoading(false)); }, [ sortConfig, - filters, analysisType, csvHeader, enumValueTranslations, diff --git a/src/components/results/shortcircuit/shortcircuit-analysis-result-table.tsx b/src/components/results/shortcircuit/shortcircuit-analysis-result-table.tsx index 0dcdde4438..90a3f7b69e 100644 --- a/src/components/results/shortcircuit/shortcircuit-analysis-result-table.tsx +++ b/src/components/results/shortcircuit/shortcircuit-analysis-result-table.tsx @@ -10,7 +10,6 @@ import { useIntl } from 'react-intl'; import { Box, Button, useTheme } from '@mui/material'; import { SCAFaultResult, SCAFeederResult, ShortCircuitAnalysisType } from './shortcircuit-analysis-result.type'; import { - GridApi, GridReadyEvent, ICellRendererParams, RowClassParams, @@ -35,7 +34,6 @@ import { SHORTCIRCUIT_ANALYSIS_RESULT_SORT_STORE } from '../../../utils/store-so import { ColumnContext, FILTER_DATA_TYPES, - FilterConfig, FilterEnumsType, numericFilterParams, TableType, @@ -46,8 +44,6 @@ import { resultsStyles } from '../common/utils'; import { AGGRID_LOCALES } from '../../../translations/not-intl/aggrid-locales'; import { useWorkspacePanelActions } from 'components/workspace/hooks/use-workspace-panel-actions'; import { PanelType } from '../../workspace/types/workspace.types'; -import { updateComputationColumnsFilters } from '../common/column-filter/update-computation-columns-filters'; -import type { UUID } from 'node:crypto'; import { useAgGridInitialColumnFilters } from '../common/use-ag-grid-initial-column-filters'; import { createMultiEnumFilterParams } from '../common/column-filter/utilis'; @@ -58,7 +54,6 @@ interface ShortCircuitAnalysisResultProps { filterEnums: FilterEnumsType; onGridColumnsChanged: (params: GridReadyEvent) => void; onRowDataUpdated: (event: RowDataUpdatedEvent) => void; - goToFirstPage: () => void; computationSubType: string; } @@ -102,7 +97,6 @@ const ShortCircuitAnalysisResultTable: FunctionComponent { const intl = useIntl(); @@ -110,8 +104,7 @@ const ShortCircuitAnalysisResultTable: FunctionComponent - state.tableFilters.columnsFilters?.[TableType.ShortcircuitAnalysis]?.[computationSubType]?.columns + (state: AppState) => state.tableFilters.columnsFilters?.[TableType.ShortcircuitAnalysis]?.[computationSubType] ); const voltageLevelIdRenderer = useCallback( (props: ICellRendererParams) => { @@ -158,23 +151,6 @@ const ShortCircuitAnalysisResultTable: FunctionComponent - updateComputationColumnsFilters( - agGridApi, - filters, - colId, - studyUuid, - filterType, - filterSubType, - goToFirstPage - ), }; const inputFilterParams = ( @@ -359,7 +335,7 @@ const ShortCircuitAnalysisResultTable: FunctionComponent diff --git a/src/components/results/shortcircuit/shortcircuit-analysis-result.tsx b/src/components/results/shortcircuit/shortcircuit-analysis-result.tsx index d664cf0adc..ac625281ae 100644 --- a/src/components/results/shortcircuit/shortcircuit-analysis-result.tsx +++ b/src/components/results/shortcircuit/shortcircuit-analysis-result.tsx @@ -108,10 +108,6 @@ export const ShortCircuitAnalysisResult: FunctionComponent { - dispatchPagination({ ...pagination, page: 0 }); - }, [pagination, dispatchPagination]); - // Effects useEffect(() => { if (analysisStatus !== RunningStatus.SUCCEED) { @@ -242,7 +238,6 @@ export const ShortCircuitAnalysisResult: FunctionComponent; tableGlobalFilters: Record; tablesSorts: TableSortConfig; } diff --git a/src/components/spreadsheet-view/columns/column-creation-dialog.tsx b/src/components/spreadsheet-view/columns/column-creation-dialog.tsx index f65a6dbe33..872d9ec9c9 100644 --- a/src/components/spreadsheet-view/columns/column-creation-dialog.tsx +++ b/src/components/spreadsheet-view/columns/column-creation-dialog.tsx @@ -29,8 +29,8 @@ import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch } from 'redux/store'; import { setUpdateColumnsDefinitions } from 'redux/actions'; import { hasCyclicDependencies, Item } from './utils/cyclic-dependencies'; -import { useFilterSelector } from 'hooks/use-filter-selector'; -import { COLUMN_TYPES, TableType } from 'types/custom-aggrid-types'; +import { COLUMN_TYPES, FilterConfig, TableType } from 'types/custom-aggrid-types'; +import { getColumnFiltersFromState } from 'redux/selectors/filter-selectors'; import type { UUID } from 'node:crypto'; import { ColumnDefinition, SpreadsheetTabDefinition } from '../types/spreadsheet.type'; import { @@ -50,6 +50,7 @@ import { FloatingPopoverTreeviewWrapper } from './floating-treeview-list/floatin import { isFormulaContentSizeOk } from './utils/formula-validator'; import { MAX_FORMULA_CHARACTERS } from '../constants'; import InfoIcon from '@mui/icons-material/Info'; +import { persistSpreadsheetColumnFilters } from './persist-spreadsheet-column-filters'; export type ColumnCreationDialogProps = { open: UseStateBooleanReturn; @@ -225,7 +226,17 @@ export default function ColumnCreationDialog({ /> ); - const { filters, dispatchFilters } = useFilterSelector(TableType.Spreadsheet, spreadsheetConfigUuid); + const filters = useSelector((state) => + getColumnFiltersFromState(state, TableType.Spreadsheet, spreadsheetConfigUuid) + ); + + const persistFilters = useCallback( + (studyUuid: UUID, newFilters: FilterConfig[]) => { + const onError = (error: unknown) => snackWithFallback(snackError, error); + persistSpreadsheetColumnFilters(studyUuid, spreadsheetConfigUuid, newFilters, columnDefinition, onError); + }, + [spreadsheetConfigUuid, columnDefinition, snackError] + ); const validateParams = ( columnsDefinitions: ColumnDefinition[], @@ -285,10 +296,11 @@ export default function ColumnCreationDialog({ const existingColumn = columnsDefinitions?.find((column) => column.uuid === colUuid); let isUpdate = false; + // If we update the column, we remove its filters if (existingColumn) { isUpdate = true; - const updatedFilters = filters?.filter((filter) => filter.column !== existingColumn.id); - dispatchFilters(updatedFilters); + const updatedFilters = filters?.filter((filter) => filter.column !== existingColumn.id) ?? []; + persistFilters(studyUuid, updatedFilters); } const formattedParams = { @@ -340,7 +352,7 @@ export default function ColumnCreationDialog({ reset, open, filters, - dispatchFilters, + persistFilters, dispatch, tableDefinition, snackError, diff --git a/src/components/spreadsheet-view/columns/common-column-definitions.ts b/src/components/spreadsheet-view/columns/common-column-definitions.ts index 6395bce1ae..da8baba5e7 100644 --- a/src/components/spreadsheet-view/columns/common-column-definitions.ts +++ b/src/components/spreadsheet-view/columns/common-column-definitions.ts @@ -23,7 +23,6 @@ import { CustomColDef, FILTER_DATA_TYPES, FILTER_TEXT_COMPARATORS, - FilterConfig, SortConfig, SPREADSHEET_FILTER_NUMBER_COMPARATORS, TableType, @@ -32,26 +31,8 @@ import { CustomAggridAutocompleteFilter } from 'components/custom-aggrid/custom- import type { UUID } from 'node:crypto'; import { isCalculationRow } from '../utils/calculation-utils'; import { ROW_INDEX_COLUMN_ID } from '../constants'; -import { updateSpreadsheetColumn, updateSpreadsheetSort } from 'services/study/study-config'; +import { updateSpreadsheetSort } from 'services/study/study-config'; import { ColumnDefinition } from '../types/spreadsheet.type'; -import { mapColDefToDto } from '../add-spreadsheet/dialogs/add-spreadsheet-utils'; - -const updateAndPersistFilters = ( - colDef: ColumnDefinition, - tab: string, - snackError: (snackInputs: SnackInputs) => void, - api: GridApi, - filters: FilterConfig[] -) => { - const studyUuid = api.getGridOption('context')?.studyUuid; - if (studyUuid) { - const filter = filters?.find((f) => f.column === colDef.id); - const columnDto = mapColDefToDto(colDef, filter); - updateSpreadsheetColumn(studyUuid, tab as UUID, colDef.uuid, columnDto).catch((error) => { - snackWithFallback(snackError, error); - }); - } -}; const persistSort = ( tab: string, @@ -87,7 +68,6 @@ export const textColumnDefinition = ( filterParams: { type: TableType.Spreadsheet, tab, - updateFilterCallback: updateAndPersistFilters.bind(null, colDef, tab, snackError), dataType: FILTER_DATA_TYPES.TEXT, comparators: [ FILTER_TEXT_COMPARATORS.STARTS_WITH, @@ -140,7 +120,6 @@ export const enumColumnDefinition = ( filterParams: { type: TableType.Spreadsheet, tab, - updateFilterCallback: updateAndPersistFilters.bind(null, colDef, tab, snackError), dataType: FILTER_DATA_TYPES.TEXT, debounceMs: 200, }, @@ -173,7 +152,6 @@ export const numberColumnDefinition = ( filterParams: { type: TableType.Spreadsheet, tab, - updateFilterCallback: updateAndPersistFilters.bind(null, colDef, tab, snackError), dataType: FILTER_DATA_TYPES.NUMBER, comparators: Object.values(SPREADSHEET_FILTER_NUMBER_COMPARATORS), debounceMs: 500, @@ -232,7 +210,6 @@ export const booleanColumnDefinition = ( type: TableType.Spreadsheet, tab, dataType: FILTER_DATA_TYPES.BOOLEAN, - updateFilterCallback: updateAndPersistFilters.bind(null, colDef, tab, snackError), debounceMs: 50, }, }, diff --git a/src/components/spreadsheet-view/columns/persist-spreadsheet-column-filters.ts b/src/components/spreadsheet-view/columns/persist-spreadsheet-column-filters.ts new file mode 100644 index 0000000000..e7d09016d1 --- /dev/null +++ b/src/components/spreadsheet-view/columns/persist-spreadsheet-column-filters.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 type { UUID } from 'node:crypto'; +import { FilterConfig } from '../../../types/custom-aggrid-types'; +import { ColumnDefinition } from '../types/spreadsheet.type'; +import { updateSpreadsheetColumn } from 'services/study/study-config'; +import { mapColDefToDto } from '../add-spreadsheet/dialogs/add-spreadsheet-utils'; + +export const persistSpreadsheetColumnFilters = ( + studyUuid: UUID, + tabUuid: UUID, + filters: FilterConfig[], + colDef?: ColumnDefinition, + onError?: (error: unknown) => void +) => { + if (!colDef) { + return; + } + const colFilter = filters.find((f) => f.column === colDef.id); + const columnDto = mapColDefToDto(colDef, colFilter); + updateSpreadsheetColumn(studyUuid, tabUuid, colDef.uuid, columnDto).catch((error) => { + onError?.(error); + }); +}; diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx index ae218412e7..52a333be2a 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-content/spreadsheet-content.tsx @@ -15,7 +15,7 @@ import { Alert, Box } from '@mui/material'; import { useEquipmentModification } from './hooks/use-equipment-modification'; import { FormattedMessage } from 'react-intl'; import { useSpreadsheetGlobalFilter } from './hooks/use-spreadsheet-gs-filter'; -import { CustomColDef, TableType } from 'types/custom-aggrid-types'; +import { CustomColDef, FilterConfig, TableType } from 'types/custom-aggrid-types'; import { useGridCalculations } from 'components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-grid-calculations'; import { useColumnManagement } from './hooks/use-column-management'; import { PanelType } from 'components/workspace/types/workspace.types'; @@ -28,8 +28,8 @@ import type { RootState } from '../../../../redux/store'; import { selectPanelTargetEquipment } from '../../../../redux/slices/workspace-selectors'; import type { UUID } from 'node:crypto'; import { useWorkspacePanelActions } from '../../../workspace/hooks/use-workspace-panel-actions'; -import { useFilterSelector } from '../../../../hooks/use-filter-selector'; import { updateAgGridFilters } from '../../../custom-aggrid/custom-aggrid-filters/utils/aggrid-filters-utils'; +import { getColumnFiltersFromState } from '../../../../redux/selectors/filter-selectors'; const styles = { table: (theme) => ({ @@ -166,7 +166,9 @@ export const SpreadsheetContent = memo( } }, [transformedRowData, gridRef, isGridReady]); - const { filters } = useFilterSelector(TableType.Spreadsheet, tableDefinition?.uuid); + const filters = useSelector((state) => + getColumnFiltersFromState(state, TableType.Spreadsheet, tableDefinition?.uuid) + ); useEffect(() => { const api = gridRef.current?.api; diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/row-counter/use-filtered-row-counter.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/row-counter/use-filtered-row-counter.tsx index 32187d2ab3..f4aa0c0214 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/row-counter/use-filtered-row-counter.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/row-counter/use-filtered-row-counter.tsx @@ -12,6 +12,7 @@ import { useIntl } from 'react-intl'; import { debounce } from '@mui/material'; import { useSelector } from 'react-redux'; import { AppState } from '../../../../../redux/reducer.type'; +import { TableType } from '../../../../../types/custom-aggrid-types'; import { type FilterChangedEvent, type ModelUpdatedEvent, type RowDataUpdatedEvent } from 'ag-grid-community'; import { useSelectedGlobalFilters } from '../../../../results/common/global-filter/use-selected-global-filters'; import { isCriteriaFilterType } from '../../../../results/common/utils'; @@ -47,7 +48,7 @@ export function useFilteredRowCounterInfo({ const globalFilterSpreadsheetState = useSelectedGlobalFilters(tableDefinition.uuid); const spreadsheetColumnsFiltersState = useSelector( - (state: AppState) => state.spreadsheetFilter[tableDefinition?.uuid] + (state: AppState) => state.tableFilters.columnsFilters[TableType.Spreadsheet]?.[tableDefinition?.uuid] ); // Update is debounced to avoid displayed row count falsely set to 0 because of AG Grid internal behaviour which briefly set row count to 0 in between filters diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-collection-dialog.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-collection-dialog.tsx index 66b262bd0d..cc1c279297 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-collection-dialog.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-collection-dialog.tsx @@ -40,10 +40,10 @@ import { } from '../../../types/spreadsheet.type'; import { v4 as uuid4 } from 'uuid'; import { saveSpreadsheetCollection, updateSpreadsheetCollection } from '../../../../../services/explore'; -import { SPREADSHEET_SORT_STORE, SPREADSHEET_STORE_FIELD } from 'utils/store-sort-filter-fields'; +import { SPREADSHEET_SORT_STORE } from 'utils/store-sort-filter-fields'; +import { SortConfig, TableType } from '../../../../../types/custom-aggrid-types'; import { GlobalFilter } from '../../../../results/common/global-filter/global-filter-types'; import { useNodeAliases } from '../../../hooks/use-node-aliases'; -import { SortConfig } from '../../../../../types/custom-aggrid-types'; interface SaveSpreadsheetCollectionDialogProps { open: UseStateBooleanReturn; @@ -81,7 +81,9 @@ export const SaveSpreadsheetCollectionDialog: FunctionComponent state.tables.definitions); - const tablesFilters = useSelector((state: AppState) => state[SPREADSHEET_STORE_FIELD]); + const tablesFilters = useSelector( + (state: AppState) => state.tableFilters.columnsFilters[TableType.Spreadsheet] ?? {} + ); const tablesGlobalFilterIds = useSelector((state: AppState) => state.tableFilters.globalFilters); const globalFilterOptions = useSelector((state: AppState) => state.globalFilterOptions); const sortConfig = useSelector((state: AppState) => state.tableSort[SPREADSHEET_SORT_STORE]); diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-dialog.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-dialog.tsx index 060dda6ffa..a56a545c98 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-dialog.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/save/save-spreadsheet-dialog.tsx @@ -19,7 +19,8 @@ import { useSelector } from 'react-redux'; import { AppState } from '../../../../../redux/reducer.type'; import { v4 as uuid4 } from 'uuid'; import { ColumnDefinitionDto, SpreadsheetConfig, SpreadsheetTabDefinition } from '../../../types/spreadsheet.type'; -import { SPREADSHEET_SORT_STORE, SPREADSHEET_STORE_FIELD } from 'utils/store-sort-filter-fields'; +import { SPREADSHEET_SORT_STORE } from 'utils/store-sort-filter-fields'; +import { TableType } from '../../../../../types/custom-aggrid-types'; import { useNodeAliases } from '../../../hooks/use-node-aliases'; import { SaveSpreadsheetModelDialog } from './save-spreadsheet-model-dialog'; @@ -33,7 +34,9 @@ export type SaveSpreadsheetDialogProps = { export default function SaveSpreadsheetDialog({ tableDefinition, open }: Readonly) { const { snackInfo, snackError } = useSnackMessage(); const { nodeAliases } = useNodeAliases(); - const tableFilters = useSelector((state: AppState) => state[SPREADSHEET_STORE_FIELD][tableDefinition.uuid]); + const tableFilters = useSelector( + (state: AppState) => state.tableFilters.columnsFilters[TableType.Spreadsheet]?.[tableDefinition.uuid] + ); const sortConfig = useSelector((state: AppState) => state.tableSort[SPREADSHEET_SORT_STORE][tableDefinition.uuid]); const studyUuid = useSelector((state: AppState) => state.studyUuid); diff --git a/src/hooks/use-filter-selector.ts b/src/hooks/use-filter-selector.ts deleted file mode 100644 index 0e0cd3bf27..0000000000 --- a/src/hooks/use-filter-selector.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2024, 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 { UnknownAction } from 'redux'; -import { LOGS_STORE_FIELD, SPREADSHEET_STORE_FIELD } from '../utils/store-sort-filter-fields'; -import { setLogsFilter, setSpreadsheetFilter, updateColumnFiltersAction } from '../redux/actions'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../redux/reducer.type'; -import { FilterConfig, TableType } from '../types/custom-aggrid-types'; -import { useCallback } from 'react'; - -const FILTER_ACTIONS: Partial< - Record< - TableType, - { filterType: string; filterStoreAction: (filterTab: any, filter: FilterConfig[]) => UnknownAction } - > -> = { - [TableType.Spreadsheet]: { - filterType: SPREADSHEET_STORE_FIELD, - filterStoreAction: setSpreadsheetFilter, - }, - [TableType.Logs]: { - filterType: LOGS_STORE_FIELD, - filterStoreAction: setLogsFilter, - }, -}; - -const EMPTY_ARRAY: FilterConfig[] = []; - -const getFilterFromState = (state: AppState, storeField: string, filterTab: string): FilterConfig[] => { - return (state as Record)[storeField]?.[filterTab] || []; -}; - -export const useFilterSelector = (filterType: TableType, filterTab: string) => { - const filterAction = FILTER_ACTIONS[filterType]; - - const selectFilters = useCallback( - (state: AppState): FilterConfig[] => { - if (filterAction) { - return getFilterFromState(state, filterAction.filterType, filterTab); - } - const columnsFilter = state.tableFilters.columnsFilters?.[filterType]?.[filterTab]; - if (Array.isArray(columnsFilter)) return columnsFilter; - return columnsFilter?.columns ?? EMPTY_ARRAY; - }, - [filterAction, filterType, filterTab] - ); - const filters = useSelector(selectFilters); - const dispatch = useDispatch(); - const dispatchFilters = useCallback( - (newFilters: FilterConfig[]) => { - const action = - filterAction?.filterStoreAction ?? - ((tab: string, filters: FilterConfig[]) => updateColumnFiltersAction(filterType, tab, filters)); - dispatch(action(filterTab, newFilters)); - }, - [dispatch, filterAction, filterType, filterTab] - ); - - return { filters, dispatchFilters }; -}; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index e15f7dd656..7c3dec529a 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -24,12 +24,10 @@ import type { UnknownArray } from 'type-fest'; import type NetworkModificationTreeModel from '../components/graph/network-modification-tree-model'; import type { MapHvdcLine, MapLine, MapSubstation, MapTieLine } from '@powsybl/network-viewer'; import type { - AppState, ComputingStatusParameters, CopiedNetworkModifications, NodeSelectionForCopy, OneBusShortCircuitAnalysisDiagram, - SpreadsheetFilterState, } from './reducer.type'; import type { TableSortConfig, TableSortKeysType } from '../types/custom-aggrid-types'; import { @@ -48,12 +46,10 @@ import type { IOptionalService } from '../components/utils/optional-services'; import type { GlobalFilter } from '../components/results/common/global-filter/global-filter-types'; import { LOGS_PAGINATION_STORE_FIELD, - LOGS_STORE_FIELD, PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD, SECURITY_ANALYSIS_PAGINATION_STORE_FIELD, SENSITIVITY_ANALYSIS_PAGINATION_STORE_FIELD, SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD, - SPREADSHEET_STORE_FIELD, } from '../utils/store-sort-filter-fields'; import { CurrentTreeNode, NetworkModificationNodeData, RootNodeData } from '../components/graph/tree-node.type'; import type GSMapEquipments from 'components/network/gs-map-equipments'; @@ -119,13 +115,10 @@ export type AppActions = | AddToGlobalFilterOptionsAction | RemoveFromGlobalFilterOptionsAction | SetLastCompletedComputationAction - | SpreadsheetFilterAction | UpdateSpreadsheetPartialDataAction - | LogsFilterAction | UpdateColumnsDefinitionsAction | RemoveColumnDefinitionAction | UpdateNetworkVisualizationParametersAction - | AddFilterForNewSpreadsheetAction | InitOrUpdateGlobalFilterAction | RemoveTableDefinitionAction | SetCalculationSelectionsAction @@ -1098,12 +1091,6 @@ export function setPccminAnalysisResultPagination( }; } -export const SPREADSHEET_FILTER = 'SPREADSHEET_FILTER'; -export type SpreadsheetFilterAction = Readonly> & { - filterTab: keyof AppState[typeof SPREADSHEET_STORE_FIELD]; - [SPREADSHEET_STORE_FIELD]: FilterConfig[]; -}; - export const UPDATE_COLUMN_FILTERS = 'UPDATE_COLUMN_FILTERS'; export const ADD_GLOBAL_FILTERS = 'ADD_GLOBAL_FILTERS'; export const CLEAR_GLOBAL_FILTERS = 'CLEAR_GLOBAL_FILTERS'; @@ -1175,23 +1162,6 @@ export const removeFromSelectedGlobalFilters = ( filterIds, }); -export const LOGS_FILTER = 'LOGS_FILTER'; -export type LogsFilterAction = Readonly> & { - filterTab: keyof AppState[typeof LOGS_STORE_FIELD]; - [LOGS_STORE_FIELD]: FilterConfig[]; -}; - -export function setLogsFilter( - filterTab: keyof AppState[typeof LOGS_STORE_FIELD], - logsFilter: FilterConfig[] -): LogsFilterAction { - return { - type: LOGS_FILTER, - filterTab: filterTab, - [LOGS_STORE_FIELD]: logsFilter, - }; -} - export const RESET_LOGS_FILTER = 'RESET_LOGS_FILTER'; export type ResetLogsFilterAction = Readonly>; @@ -1257,17 +1227,6 @@ export function setTableSort(table: TableSortKeysType, tab: string, sort: SortCo }; } -export function setSpreadsheetFilter( - filterTab: keyof AppState[typeof SPREADSHEET_STORE_FIELD], - spreadsheetFilter: FilterConfig[] -): SpreadsheetFilterAction { - return { - type: SPREADSHEET_FILTER, - filterTab: filterTab, - [SPREADSHEET_STORE_FIELD]: spreadsheetFilter, - }; -} - export const UPDATE_COLUMNS_DEFINITION = 'UPDATE_COLUMNS_DEFINITION'; export type UpdateColumnsDefinitionsAction = Readonly> & { colData: TableValue; @@ -1377,7 +1336,7 @@ export type InitTableDefinitionsAction = { type: typeof INIT_TABLE_DEFINITIONS; collectionUuid: UUID; tableDefinitions: SpreadsheetTabDefinition[]; - tablesFilters?: SpreadsheetFilterState; + tablesFilters?: Record; globalFilters?: Record; tablesSorts?: TableSortConfig; }; @@ -1385,7 +1344,7 @@ export type InitTableDefinitionsAction = { export const initTableDefinitions = ( collectionUuid: UUID, tableDefinitions: SpreadsheetTabDefinition[], - tablesFilters: SpreadsheetFilterState = {}, + tablesFilters: Record = {}, globalFilters: Record = {}, tablesSorts: TableSortConfig = {} ): InitTableDefinitionsAction => ({ @@ -1408,21 +1367,6 @@ export const reorderTableDefinitions = (definitions: SpreadsheetTabDefinition[]) definitions, }); -export const ADD_FILTER_FOR_NEW_SPREADSHEET = 'ADD_FILTER_FOR_NEW_SPREADSHEET'; - -export type AddFilterForNewSpreadsheetAction = { - type: typeof ADD_FILTER_FOR_NEW_SPREADSHEET; - payload: { tabUuid: UUID; value: FilterConfig[] }; -}; - -export const addFilterForNewSpreadsheet = (tabUuid: UUID, value: FilterConfig[]): AddFilterForNewSpreadsheetAction => ({ - type: ADD_FILTER_FOR_NEW_SPREADSHEET, - payload: { - tabUuid, - value, - }, -}); - export const ADD_SORT_FOR_NEW_SPREADSHEET = 'ADD_SORT_FOR_NEW_SPREADSHEET'; export type AddSortForNewSpreadsheetAction = { diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index 3c69eca6bd..316265e55d 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -28,13 +28,11 @@ import { } from '@gridsuite/commons-ui'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import { - ADD_FILTER_FOR_NEW_SPREADSHEET, ADD_GLOBAL_FILTERS, ADD_NOTIFICATION, ADD_SORT_FOR_NEW_SPREADSHEET, ADD_SPREADSHEET_LOADED_NODES_IDS, ADD_TO_GLOBAL_FILTER_OPTIONS, - type AddFilterForNewSpreadsheetAction, AddGlobalFiltersAction, type AddNotificationAction, type AddSortForNewSpreadsheetAction, @@ -73,9 +71,7 @@ import { LOAD_NETWORK_MODIFICATION_TREE_SUCCESS, type LoadEquipmentsAction, type LoadNetworkModificationTreeSuccessAction, - LOGS_FILTER, LOGS_RESULT_PAGINATION, - type LogsFilterAction, LogsResultPaginationAction, MAP_DATA_LOADING, MAP_EQUIPMENTS_CREATED, @@ -196,8 +192,6 @@ import { SetSpreadsheetFetchingAction, SHORTCIRCUIT_ANALYSIS_RESULT_PAGINATION, ShortcircuitAnalysisResultPaginationAction, - SPREADSHEET_FILTER, - type SpreadsheetFilterAction, STORE_NAD_VIEW_BOX, StoreNadViewBoxAction, TABLE_SORT, @@ -242,7 +236,6 @@ import { LOADFLOW_RESULT_SORT_STORE, LOADFLOW_VOLTAGE_LIMIT_VIOLATION, LOGS_PAGINATION_STORE_FIELD, - LOGS_STORE_FIELD, ONE_BUS, PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD, PCCMIN_ANALYSIS_RESULT_SORT_STORE, @@ -262,7 +255,6 @@ import { SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD, SHORTCIRCUIT_ANALYSIS_RESULT_SORT_STORE, SPREADSHEET_SORT_STORE, - SPREADSHEET_STORE_FIELD, STATEESTIMATION_QUALITY_CRITERION, STATEESTIMATION_QUALITY_PER_REGION, STATEESTIMATION_RESULT_SORT_STORE, @@ -281,14 +273,20 @@ import { type SpreadsheetTabDefinition, } from '../components/spreadsheet-view/types/spreadsheet.type'; import { + FilterConfig, LogsPaginationConfig, PaginationConfig, PCCMIN_ANALYSIS_TABS, + PccminTab, SECURITY_ANALYSIS_TABS, + SecurityAnalysisTab, SENSITIVITY_ANALYSIS_TABS, + SensitivityAnalysisTab, SHORTCIRCUIT_ANALYSIS_TABS, + ShortcircuitAnalysisTab, SortWay, TableSortConfig, + TableType, } from '../types/custom-aggrid-types'; import { NodeInsertModes, RootNetworkIndexationStatus } from 'types/notification-types'; import { mapSpreadsheetEquipments } from '../utils/spreadsheet-equipments-mapper'; @@ -303,7 +301,6 @@ import { type AppState, type Bus, EquipmentUpdateType, - type LogsFilterState, type LogsPaginationState, type SpreadsheetNetworkState, type Substation, @@ -375,7 +372,7 @@ export const DEFAULT_LOGS_PAGINATION: LogsPaginationConfig = { export type Actions = AppActions | AuthenticationActions; -const initialLogsFilterState: LogsFilterState = { +const initialLogsFilterState: Record = { [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.NETWORK_MODIFICATION]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.LOAD_FLOW]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.SECURITY_ANALYSIS]: [], @@ -571,10 +568,6 @@ const initialState: AppState = { [PCCMIN_RESULT]: { ...DEFAULT_PAGINATION }, }, - // Spreadsheet filters - [SPREADSHEET_STORE_FIELD]: {}, - - [LOGS_STORE_FIELD]: { ...initialLogsFilterState }, [LOGS_PAGINATION_STORE_FIELD]: { ...initialLogsPaginationState }, [TABLE_SORT_STORE]: { @@ -647,7 +640,9 @@ const initialState: AppState = { }, }, tableFilters: { - columnsFilters: {}, + columnsFilters: { + [TableType.Logs]: { ...initialLogsFilterState }, + }, globalFilters: {}, }, // Hack to avoid reload Geo Data when switching display mode to TREE then back to MAP or HYBRID @@ -793,7 +788,7 @@ export const reducer = createReducer(initialState, (builder) => { } } state.tables.addedTable = null; - state[SPREADSHEET_STORE_FIELD] = Object.values(action.tableDefinitions) + state.tableFilters.columnsFilters[TableType.Spreadsheet] = Object.values(action.tableDefinitions) .map((tabDef) => tabDef.uuid) .reduce( (acc, tabUuid) => ({ @@ -850,8 +845,8 @@ export const reducer = createReducer(initialState, (builder) => { // Replace the definitions array with the new one state.tables.definitions = newDefinitions; - if (state[SPREADSHEET_STORE_FIELD]) { - delete state[SPREADSHEET_STORE_FIELD][removedTable.uuid]; + if (state.tableFilters.columnsFilters[TableType.Spreadsheet]) { + delete state.tableFilters.columnsFilters[TableType.Spreadsheet][removedTable.uuid]; } if (state[TABLE_SORT_STORE][SPREADSHEET_SORT_STORE]) { @@ -1510,21 +1505,8 @@ export const reducer = createReducer(initialState, (builder) => { } }); - builder.addCase(SPREADSHEET_FILTER, (state, action: SpreadsheetFilterAction) => { - state[SPREADSHEET_STORE_FIELD][action.filterTab] = action[SPREADSHEET_STORE_FIELD]; - }); - - builder.addCase(ADD_FILTER_FOR_NEW_SPREADSHEET, (state, action: AddFilterForNewSpreadsheetAction) => { - const { tabUuid, value } = action.payload; - state[SPREADSHEET_STORE_FIELD][tabUuid] = value; - }); - - builder.addCase(LOGS_FILTER, (state, action: LogsFilterAction) => { - state[LOGS_STORE_FIELD][action.filterTab] = action[LOGS_STORE_FIELD]; - }); - builder.addCase(RESET_LOGS_FILTER, (state, _action: ResetLogsFilterAction) => { - state[LOGS_STORE_FIELD] = { + state.tableFilters.columnsFilters[TableType.Logs] = { ...initialLogsFilterState, }; }); @@ -1582,7 +1564,7 @@ export const reducer = createReducer(initialState, (builder) => { const { uuid, value } = action.definition; const tableDefinition = state.tables.definitions.find((tabDef) => tabDef.uuid === uuid); const tableSort = state.tableSort[SPREADSHEET_SORT_STORE]; - const tableFilter = state[SPREADSHEET_STORE_FIELD]; + const tableFilter = state.tableFilters.columnsFilters[TableType.Spreadsheet]; if (tableDefinition) { tableDefinition.columns = tableDefinition.columns.filter((col) => col.id !== value); @@ -1591,7 +1573,7 @@ export const reducer = createReducer(initialState, (builder) => { if (tableDefinition && tableSort[tableDefinition.name]) { tableSort[tableDefinition.name] = tableSort[tableDefinition.name].filter((sort) => sort.colId !== value); } - if (tableDefinition && tableFilter[tableDefinition.uuid]) { + if (tableDefinition && tableFilter?.[tableDefinition.uuid]) { tableFilter[tableDefinition.uuid] = tableFilter[tableDefinition.uuid].filter( (filter) => filter.column !== value ); @@ -1658,10 +1640,23 @@ export const reducer = createReducer(initialState, (builder) => { builder.addCase(UPDATE_COLUMN_FILTERS, (state, action: UpdateColumnFiltersAction) => { const { filterType, filterSubType, filters } = action; state.tableFilters.columnsFilters[filterType] ??= {}; - state.tableFilters.columnsFilters[filterType][filterSubType] ??= { - columns: [], - }; - state.tableFilters.columnsFilters[filterType][filterSubType].columns = filters; + state.tableFilters.columnsFilters[filterType][filterSubType] = filters; + + // Reset pagination to page 0 when column filters change + switch (filterType) { + case TableType.SecurityAnalysis: + state[SECURITY_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType as SecurityAnalysisTab].page = 0; + break; + case TableType.SensitivityAnalysis: + state[SENSITIVITY_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType as SensitivityAnalysisTab].page = 0; + break; + case TableType.ShortcircuitAnalysis: + state[SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType as ShortcircuitAnalysisTab].page = 0; + break; + case TableType.PccMin: + state[PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD][filterSubType as PccminTab].page = 0; + break; + } }); builder.addCase(ADD_GLOBAL_FILTERS, (state, action: AddGlobalFiltersAction) => { diff --git a/src/redux/reducer.type.ts b/src/redux/reducer.type.ts index 03cf0f0997..b2c98190e5 100644 --- a/src/redux/reducer.type.ts +++ b/src/redux/reducer.type.ts @@ -58,12 +58,10 @@ import type { NodeAlias } from '../components/spreadsheet-view/types/node-alias. import type NetworkModificationTreeModel from '../components/graph/network-modification-tree-model'; import { LOGS_PAGINATION_STORE_FIELD, - LOGS_STORE_FIELD, PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD, SECURITY_ANALYSIS_PAGINATION_STORE_FIELD, SENSITIVITY_ANALYSIS_PAGINATION_STORE_FIELD, SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD, - SPREADSHEET_STORE_FIELD, } from '../utils/store-sort-filter-fields'; import { PARAM_COMPUTED_LANGUAGE, PARAM_LIMIT_REDUCTION, PARAM_USE_NAME, PARAMS_LOADED } from '../utils/config-params'; import { VOLTAGE_LEVEL_ID } from '../components/utils/field-constants'; @@ -135,24 +133,16 @@ export type SpreadsheetNetworkState = { equipments: Record; }; -export type SpreadsheetFilterState = Record; - -export type ComputationResultColumnFilter = { - columns: FilterConfig[]; -}; - export type GlobalFiltersState = { selected: string[]; recents: RecentGlobalFilter[]; // sorted by unselectedDate descending (most recent first), max 10 }; export type TableFiltersState = { - columnsFilters: Record>; + columnsFilters: Record>; globalFilters: Record; }; -export type LogsFilterState = Record; - export type LogsPaginationState = Record; // ——— Diagrams ——— @@ -269,9 +259,6 @@ export interface AppState extends CommonStoreState, AppConfigState { [SHORTCIRCUIT_ANALYSIS_PAGINATION_STORE_FIELD]: Record; [PCCMIN_ANALYSIS_PAGINATION_STORE_FIELD]: Record; - [SPREADSHEET_STORE_FIELD]: SpreadsheetFilterState; - - [LOGS_STORE_FIELD]: LogsFilterState; [LOGS_PAGINATION_STORE_FIELD]: LogsPaginationState; calculationSelections: Record; diff --git a/src/redux/selectors/filter-selectors.ts b/src/redux/selectors/filter-selectors.ts new file mode 100644 index 0000000000..ebc04bc698 --- /dev/null +++ b/src/redux/selectors/filter-selectors.ts @@ -0,0 +1,17 @@ +/** + * 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 { AppState } from '../reducer.type'; +import { FilterConfig, TableType } from '../../types/custom-aggrid-types'; + +export const getColumnFiltersFromState = ( + state: AppState, + filterType: TableType, + filterTab: string +): FilterConfig[] | undefined => { + return state.tableFilters.columnsFilters?.[filterType]?.[filterTab]; +}; diff --git a/src/redux/selectors/filter-store-selectors.ts b/src/redux/selectors/filter-store-selectors.ts new file mode 100644 index 0000000000..f9ce12588d --- /dev/null +++ b/src/redux/selectors/filter-store-selectors.ts @@ -0,0 +1,18 @@ +/** + * 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 { store } from '../store'; +import { FilterConfig, TableType } from '../../types/custom-aggrid-types'; +import { getColumnFiltersFromState } from './filter-selectors'; + +/** + * Get column filters directly from Redux store without using hooks. + * This is useful in callbacks to avoid re-creating callbacks on every filter change. + */ +export const getColumnFiltersFromStore = (filterType: TableType, filterTab: string): FilterConfig[] | undefined => { + return getColumnFiltersFromState(store.getState(), filterType, filterTab); +}; diff --git a/src/services/study/sensitivity-analysis.ts b/src/services/study/sensitivity-analysis.ts index 1d5b468905..23ba74142c 100644 --- a/src/services/study/sensitivity-analysis.ts +++ b/src/services/study/sensitivity-analysis.ts @@ -153,7 +153,7 @@ export function exportSensitivityResultsAsCsv( currentRootNetworkUuid: UUID, csvConfig: CsvConfig, selector: any, - filters: FilterConfig[], + filters: FilterConfig[] | undefined, globalFilters: GlobalFilters | undefined ) { console.info( diff --git a/src/types/custom-aggrid-types.ts b/src/types/custom-aggrid-types.ts index 3a48ae7c84..c250fd0e49 100644 --- a/src/types/custom-aggrid-types.ts +++ b/src/types/custom-aggrid-types.ts @@ -92,15 +92,6 @@ export type FilterParams = { dataType?: string; comparators?: string[]; debounceMs?: number; - updateFilterCallback?: ( - agGridApi?: GridApi, - filters?: FilterConfig[], - colId?: string, - studyUuid?: UUID, - filterType?: TableType, - filterSubType?: string, - onBeforePersist?: () => void - ) => void; }; export type PaginationConfig = { diff --git a/src/utils/store-sort-filter-fields.ts b/src/utils/store-sort-filter-fields.ts index 8898cf7904..709e71e064 100644 --- a/src/utils/store-sort-filter-fields.ts +++ b/src/utils/store-sort-filter-fields.ts @@ -38,11 +38,9 @@ export const DYNAMIC_SIMULATION_RESULT_SORT_STORE = 'dynamicSimulationResult'; export const TIMELINE = 'timeline'; //spreadsheet store fields -export const SPREADSHEET_STORE_FIELD = 'spreadsheetFilter'; export const SPREADSHEET_SORT_STORE = 'spreadsheet'; //logs store fields -export const LOGS_STORE_FIELD = 'logsFilter'; export const LOGS_PAGINATION_STORE_FIELD = 'logsPagination'; //table sort store field