From 7062ab899628a840d58d38e8e19326ed092384bf Mon Sep 17 00:00:00 2001 From: Ghazoua Rehili Date: Tue, 11 Feb 2025 15:01:43 +0100 Subject: [PATCH 1/3] add some check on sjb field (#2576) Signed-off-by: Rehili Ghazwa --- .../dialogs/connectivity/connectivity-form.jsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/dialogs/connectivity/connectivity-form.jsx b/src/components/dialogs/connectivity/connectivity-form.jsx index 8bf48c112e..77d300fbdc 100644 --- a/src/components/dialogs/connectivity/connectivity-form.jsx +++ b/src/components/dialogs/connectivity/connectivity-form.jsx @@ -81,7 +81,6 @@ export const ConnectivityForm = ({ return; } if (watchVoltageLevelId) { - setValue(`${id}.${BUS_OR_BUSBAR_SECTION}`, null); const existingVoltageLevelOption = voltageLevelOptions.find((option) => option.id === watchVoltageLevelId); if (existingVoltageLevelOption) { fetchBusesOrBusbarSectionsForVoltageLevel( @@ -95,9 +94,6 @@ export const ConnectivityForm = ({ } else { setBusOrBusbarSectionOptions([]); } - } else { - setBusOrBusbarSectionOptions([]); - setValue(`${id}.${BUS_OR_BUSBAR_SECTION}`, null); } }, [ watchVoltageLevelId, @@ -105,7 +101,6 @@ export const ConnectivityForm = ({ currentNodeUuid, currentRootNetworkUuid, voltageLevelOptions, - setValue, id, isEquipmentModification, ]); @@ -121,7 +116,9 @@ export const ConnectivityForm = ({ const handleChange = useCallback(() => { onVoltageLevelChangeCallback?.(); - }, [onVoltageLevelChangeCallback]); + setBusOrBusbarSectionOptions([]); + setValue(`${id}.${BUS_OR_BUSBAR_SECTION}`, null); + }, [id, onVoltageLevelChangeCallback, setValue]); useEffect(() => { if (isEquipmentModification) { @@ -150,6 +147,7 @@ export const ConnectivityForm = ({ onChangeCallback={handleChange} allowNewValue forcePopupIcon + selectOnFocus name={`${id}.${VOLTAGE_LEVEL}`} label={voltageLevelSelectLabel} options={voltageLevelOptions} From 9eca3d609230b7b3895ba984a9d1544c139b8596 Mon Sep 17 00:00:00 2001 From: Abdelsalem <46495975+AbdelHedhili@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:32:08 +0100 Subject: [PATCH 2/3] refacto spreadsheet redux storage (#2574) Signed-off-by: Abdelsalem --- .../spreadsheet/config/spreadsheet.type.ts | 6 + src/components/spreadsheet/table-wrapper.tsx | 52 +++--- .../spreadsheet/use-spreadsheet-equipments.ts | 139 ++++++++-------- src/redux/actions.ts | 25 +-- src/redux/reducer.ts | 153 ++++++++++-------- 5 files changed, 192 insertions(+), 183 deletions(-) diff --git a/src/components/spreadsheet/config/spreadsheet.type.ts b/src/components/spreadsheet/config/spreadsheet.type.ts index c48ef91658..3d27099108 100644 --- a/src/components/spreadsheet/config/spreadsheet.type.ts +++ b/src/components/spreadsheet/config/spreadsheet.type.ts @@ -8,6 +8,7 @@ import type { UUID } from 'crypto'; import type { EQUIPMENT_TYPES } from '../../utils/equipment-types'; import type { CustomAggridFilterParams, CustomColDef } from '../../custom-aggrid/custom-aggrid-header.type'; +import { Identifiable } from '@gridsuite/commons-ui'; export type EquipmentFetcher = ( studyUuid: UUID, @@ -31,4 +32,9 @@ export interface SpreadsheetTabDefinition[]; } +export type SpreadsheetEquipmentsByNodes = { + nodesId: string[]; + equipmentsByNodeId: Record; +}; + export type ColumnState = { colId: string; visible: boolean }; diff --git a/src/components/spreadsheet/table-wrapper.tsx b/src/components/spreadsheet/table-wrapper.tsx index 6f9c0d3b33..0fb534ef87 100644 --- a/src/components/spreadsheet/table-wrapper.tsx +++ b/src/components/spreadsheet/table-wrapper.tsx @@ -108,10 +108,8 @@ export const TableWrapper: FunctionComponent = ({ () => new Set(lockedColumns[tabIndex] ? JSON.parse(lockedColumns[tabIndex]) : []), [lockedColumns, tabIndex] ); + const nodesAliases = useSelector((state: AppState) => state.customColumnsNodesAliases); const tablesDefinitions = useSelector((state: AppState) => state.tables.definitions); - const additionalEquipmentsByNodesForCustomColumns = useSelector( - (state: AppState) => state.additionalEquipmentsByNodesForCustomColumns - ); const developerMode = useSelector((state: AppState) => state[PARAM_DEVELOPER_MODE]); const [manualTabSwitch, setManualTabSwitch] = useState(true); @@ -207,45 +205,32 @@ export const TableWrapper: FunctionComponent = ({ }, [errorMessage, snackError]); useEffect(() => { - if (disabled || !equipments) { + if (disabled || equipments.nodesId.find((nodeId) => nodeId === currentNode.id) === undefined) { return; } - - let equipmentsWithCustomColumnInfo = [...equipments]; + let localRowData: Identifiable[] = []; if (tableDefinition.type) { - Object.entries(additionalEquipmentsByNodesForCustomColumns).forEach(([nodeAlias, equipments]) => { - const equipmentsToAdd = equipments[tableDefinition.type]; - if (equipmentsToAdd) { - equipmentsToAdd.forEach((equipmentToAdd) => { - let matchingEquipmentIndex = equipmentsWithCustomColumnInfo.findIndex( - (equipmentWithCustomColumnInfo) => equipmentWithCustomColumnInfo.id === equipmentToAdd.id - ); - let matchingEquipment = equipmentsWithCustomColumnInfo[matchingEquipmentIndex]; - if (matchingEquipment) { - let equipmentWithAddedInfo: RecursiveIdentifiable = { ...matchingEquipment }; - equipmentWithAddedInfo[nodeAlias] = equipmentToAdd; - equipmentsWithCustomColumnInfo[matchingEquipmentIndex] = equipmentWithAddedInfo; - } - }); - } + equipments.equipmentsByNodeId[currentNode.id].forEach((equipment) => { + let equipmentToAdd: RecursiveIdentifiable = { ...equipment }; + Object.entries(equipments.equipmentsByNodeId).forEach(([nodeId, equipments]) => { + let matchingEquipment = equipments.find((eq) => eq.id === equipment.id); + let nodeAlias = nodesAliases.find((value) => value.id === nodeId); + if (nodeAlias !== undefined && matchingEquipment !== undefined) { + equipmentToAdd[nodeAlias.alias] = matchingEquipment; + } + }); + localRowData.push(equipmentToAdd); }); } - setRowData(equipmentsWithCustomColumnInfo); // To handle cases where a "customSpreadsheet" tab is opened. // This ensures that the grid correctly displays data specific to the custom tab. if (gridRef.current?.api) { - gridRef.current.api.setGridOption('rowData', equipmentsWithCustomColumnInfo); + gridRef.current.api.setGridOption('rowData', localRowData); updateSortConfig(); } - }, [ - tabIndex, - disabled, - equipments, - additionalEquipmentsByNodesForCustomColumns, - tableDefinition.type, - updateSortConfig, - ]); + setRowData(localRowData); + }, [tabIndex, disabled, equipments, tableDefinition.type, nodesAliases, currentNode.id, updateSortConfig]); const handleSwitchTab = useCallback( (value: number) => { @@ -382,7 +367,10 @@ export const TableWrapper: FunctionComponent = ({ currentNode={currentNode} rowData={rowData} columnData={mergedColumnData} - fetched={!!equipments || !!errorMessage} + fetched={ + equipments.nodesId.find((nodeId) => nodeId === currentNode.id) !== undefined || + !!errorMessage + } handleColumnDrag={handleColumnDrag} handleRowDataUpdated={handleRowDataUpdated} shouldHidePinnedHeaderRightBorder={isLockedColumnNamesEmpty} diff --git a/src/components/spreadsheet/use-spreadsheet-equipments.ts b/src/components/spreadsheet/use-spreadsheet-equipments.ts index c418dcbe51..7939f710b0 100644 --- a/src/components/spreadsheet/use-spreadsheet-equipments.ts +++ b/src/components/spreadsheet/use-spreadsheet-equipments.ts @@ -7,12 +7,11 @@ import { Identifiable } from '@gridsuite/commons-ui'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; +import { UUID } from 'crypto'; import { useGetStudyImpacts } from 'hooks/use-get-study-impacts'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { - addAdditionalEquipmentsByNodesForCustomColumns, - AdditionalNodeData, deleteEquipments, EquipmentToDelete, loadEquipments, @@ -26,28 +25,18 @@ import type { SpreadsheetEquipmentType } from './config/spreadsheet.type'; import { fetchAllEquipments } from 'services/study/network-map'; import { getFetcher } from './config/equipment/common-config'; import { isNodeBuilt } from 'components/graph/util/model-functions'; +import { SpreadsheetEquipmentsByNodes } from './config/spreadsheet.type'; type FormatFetchedEquipments = (equipments: Identifiable[]) => Identifiable[]; -const filterUndefined = ( - res: AdditionalNodeData | undefined -): res is { - alias: string; - identifiables: Identifiable[]; -} => { - return res !== undefined; -}; - export const useSpreadsheetEquipments = ( type: SpreadsheetEquipmentType, formatFetchedEquipments: FormatFetchedEquipments ) => { const dispatch = useDispatch(); const allEquipments = useSelector((state: AppState) => state.spreadsheetNetwork); - const equipments = allEquipments[type]; - const allAdditionalEquipments = useSelector((state: AppState) => state.additionalEquipmentsByNodesForCustomColumns); + const equipments = useMemo(() => allEquipments[type], [allEquipments, type]); const customColumnsDefinitions = useSelector((state: AppState) => state.tables.allCustomColumnsDefinitions); - const customColumnsNodesAliases = useSelector((state: AppState) => state.customColumnsNodesAliases); const studyUuid = useSelector((state: AppState) => state.studyUuid); const isNetworkModificationTreeModelUpToDate = useSelector( (state: AppState) => state.isNetworkModificationTreeModelUpToDate @@ -56,6 +45,25 @@ export const useSpreadsheetEquipments = ( const currentNode = useSelector((state: AppState) => state.currentTreeNode); const [errorMessage, setErrorMessage] = useState(); const [isFetching, setIsFetching] = useState(false); + const nodesAliases = useSelector((state: AppState) => state.customColumnsNodesAliases); + + const nodesIdToFetch = useMemo(() => { + let nodesIdToFetch = new Set(); + // We check if we have the data for the currentNode and if we don't we save the fact that we need to fetch it + if (equipments.nodesId.find((nodeId) => nodeId === currentNode?.id) === undefined) { + nodesIdToFetch.add(currentNode?.id as string); + } + //Then we do the same for the other nodes we need the data of (the ones defined in aliases) + nodesAliases.forEach((nodeAlias) => { + if (equipments.nodesId.find((nodeId) => nodeId === nodeAlias.id) === undefined) { + nodesIdToFetch.add(nodeAlias.id); + } + }); + return nodesIdToFetch; + }, [currentNode?.id, equipments.nodesId, nodesAliases]); + + const shouldFetchEquipments = useMemo(() => nodesIdToFetch.size > 0, [nodesIdToFetch]); + const { impactedSubstationsIds, deletedEquipments, @@ -65,16 +73,33 @@ export const useSpreadsheetEquipments = ( resetImpactedElementTypes, } = useGetStudyImpacts(); - const shouldFetchEquipments = !equipments; + useEffect(() => { + const currentNodeId = currentNode?.id as UUID; + + let unwantedFetchedNodes = new Set(); + Object.values(allEquipments).forEach((value) => { + unwantedFetchedNodes = new Set([...unwantedFetchedNodes, ...value.nodesId]); + }); + const usedNodesId = new Set(nodesAliases.map((nodeAlias) => nodeAlias.id)); + usedNodesId.add(currentNodeId); + usedNodesId.forEach((nodeId) => unwantedFetchedNodes.delete(nodeId)); + if (unwantedFetchedNodes.size !== 0) { + dispatch(removeNodeData(Array.from(unwantedFetchedNodes))); + } + }, [dispatch, nodesAliases, currentNode, allEquipments]); useEffect(() => { // updating data related to impacted elements + const nodeId = currentNode?.id as UUID; // If we dont have any data in the spreadsheet, we don't need to update the equipments - const hasSpreadsheedData = () => { - return Object.values(allEquipments).some((value) => Array.isArray(value) && value.length > 0); + const hasSpreadsheetData = () => { + return Object.values(allEquipments) + .map((value) => value.equipmentsByNodeId[nodeId]) + .filter((value) => value !== undefined) + .some((value) => value.length > 0); }; - if (!hasSpreadsheedData()) { + if (!hasSpreadsheetData()) { resetImpactedSubstationsIds(); resetDeletedEquipments(); resetImpactedElementTypes(); @@ -97,11 +122,9 @@ export const useSpreadsheetEquipments = ( } if (impactedSubstationsIds.length > 0 && studyUuid && currentRootNetworkUuid && currentNode?.id) { // The formatting of the fetched equipments is done in the reducer - fetchAllEquipments(studyUuid, currentNode.id, currentRootNetworkUuid, impactedSubstationsIds).then( - (values) => { - dispatch(updateEquipments(values)); - } - ); + fetchAllEquipments(studyUuid, nodeId, currentRootNetworkUuid, impactedSubstationsIds).then((values) => { + dispatch(updateEquipments(values, nodeId)); + }); resetImpactedSubstationsIds(); } if (deletedEquipments.length > 0) { @@ -122,8 +145,9 @@ export const useSpreadsheetEquipments = ( const equipmentsToDeleteArray: EquipmentToDelete[] = equipmentsToDelete.map((equipment) => ({ equipmentType: equipment.equipmentType as SpreadsheetEquipmentType, equipmentId: equipment.equipmentId, + nodeId: nodeId, })); - dispatch(deleteEquipments(equipmentsToDeleteArray)); + dispatch(deleteEquipments(equipmentsToDeleteArray, nodeId)); } resetDeletedEquipments(); @@ -153,19 +177,29 @@ export const useSpreadsheetEquipments = ( ) { setErrorMessage(null); setIsFetching(true); - getFetcher(type)(studyUuid, currentNode?.id, currentRootNetworkUuid) - .then((results) => { + let fetchers: Promise[] = []; + let spreadsheetEquipmentsByNodes: SpreadsheetEquipmentsByNodes = { + nodesId: [], + equipmentsByNodeId: {}, + }; + + nodesIdToFetch.forEach((nodeId) => { + const fetcherPromises = getFetcher(type)(studyUuid, nodeId as UUID, currentRootNetworkUuid, []); + fetchers.push(fetcherPromises); + fetcherPromises.then((results) => { let fetchedEquipments = results.flat(); + spreadsheetEquipmentsByNodes.nodesId.push(nodeId); if (formatFetchedEquipments) { fetchedEquipments = formatFetchedEquipments(fetchedEquipments); + spreadsheetEquipmentsByNodes.equipmentsByNodeId[nodeId] = fetchedEquipments; } - dispatch(loadEquipments(type, fetchedEquipments)); - setIsFetching(false); - }) - .catch((err) => { - setErrorMessage(err); - setIsFetching(false); }); + }); + + Promise.all(fetchers).then(() => { + dispatch(loadEquipments(type, spreadsheetEquipmentsByNodes)); + setIsFetching(false); + }); } }, [ shouldFetchEquipments, @@ -176,46 +210,9 @@ export const useSpreadsheetEquipments = ( dispatch, formatFetchedEquipments, customColumnsDefinitions, + nodesIdToFetch, type, ]); - useEffect(() => { - if (studyUuid && currentRootNetworkUuid) { - // Clean nodes that are not loaded anymore - const unwantedFetchedNodes = new Set(Object.keys(allAdditionalEquipments)); - const usedNodes = new Set(customColumnsNodesAliases); - usedNodes.forEach((node) => unwantedFetchedNodes.delete(node.alias)); - if (unwantedFetchedNodes.size !== 0) { - dispatch(removeNodeData(Array.from(unwantedFetchedNodes))); - } - - // Fetch new nodes for the current type if required - const fetchedEquipments = customColumnsNodesAliases.map(async (aliasInfo) => { - if (allAdditionalEquipments[aliasInfo.alias]?.[type] !== undefined) { - return undefined; - } - const identifiableList = await getFetcher(type)(studyUuid, aliasInfo.id, currentRootNetworkUuid); - return { - alias: aliasInfo.alias, - identifiables: formatFetchedEquipments(identifiableList.flat()), - } satisfies AdditionalNodeData; - }); - Promise.all(fetchedEquipments).then((results) => { - const filteredResults = results.filter(filterUndefined); - if (filteredResults.length !== 0) { - dispatch(addAdditionalEquipmentsByNodesForCustomColumns(type, filteredResults)); - } - }); - } - }, [ - dispatch, - studyUuid, - currentRootNetworkUuid, - formatFetchedEquipments, - customColumnsNodesAliases, - type, - allAdditionalEquipments, - ]); - return { equipments, errorMessage, isFetching }; }; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 045b33cfb4..7649cb59e4 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -52,6 +52,7 @@ import { ColumnWithFormula } from 'types/custom-columns.types'; import { NetworkModificationNodeData, RootNodeData } from '../components/graph/tree-node.type'; import GSMapEquipments from 'components/network/gs-map-equipments'; import { + SpreadsheetEquipmentsByNodes, ColumnState, SpreadsheetEquipmentType, SpreadsheetTabDefinition, @@ -138,7 +139,6 @@ export type AppActions = | UpdateCustomColumnsDefinitionsAction | RemoveCustomColumnsDefinitionsAction | UpdateCustomColumnsNodesAliasesAction - | AddEquipmentsByNodesForCustomColumnsAction | UpdateNetworkVisualizationParametersAction | StateEstimationResultFilterAction | SaveSpreadSheetGsFilterAction; @@ -146,17 +146,17 @@ export type AppActions = export const LOAD_EQUIPMENTS = 'LOAD_EQUIPMENTS'; export type LoadEquipmentsAction = Readonly> & { equipmentType: SpreadsheetEquipmentType; - equipments: Identifiable[]; + spreadsheetEquipmentByNodes: SpreadsheetEquipmentsByNodes; }; export function loadEquipments( equipmentType: SpreadsheetEquipmentType, - equipments: Identifiable[] + spreadsheetEquipmentByNodes: SpreadsheetEquipmentsByNodes ): LoadEquipmentsAction { return { type: LOAD_EQUIPMENTS, equipmentType: equipmentType, - equipments: equipments, + spreadsheetEquipmentByNodes: spreadsheetEquipmentByNodes, }; } @@ -187,13 +187,13 @@ export function addAdditionalEquipmentsByNodesForCustomColumns( export const REMOVE_NODE_DATA = 'REMOVE_NODE_DATA'; export type RemoveNodeDataAction = Readonly> & { - aliases: string[]; + nodesIdToRemove: string[]; }; -export function removeNodeData(aliases: string[]): RemoveNodeDataAction { +export function removeNodeData(nodesIdToRemove: string[]): RemoveNodeDataAction { return { type: REMOVE_NODE_DATA, - aliases, + nodesIdToRemove, }; } @@ -212,12 +212,17 @@ export function updateCustomColumnsNodesAliases(nodesAliases: NodeAlias[]): Upda export const UPDATE_EQUIPMENTS = 'UPDATE_EQUIPMENTS'; export type UpdateEquipmentsAction = Readonly> & { equipments: Record; + nodeId: UUID; }; -export function updateEquipments(equipments: Record): UpdateEquipmentsAction { +export function updateEquipments( + equipments: Record, + nodeId: UUID +): UpdateEquipmentsAction { return { type: UPDATE_EQUIPMENTS, equipments: equipments, + nodeId: nodeId, }; } @@ -228,12 +233,14 @@ export type EquipmentToDelete = { export const DELETE_EQUIPMENTS = 'DELETE_EQUIPMENTS'; export type DeleteEquipmentsAction = Readonly> & { equipments: EquipmentToDelete[]; + nodeId: UUID; }; -export function deleteEquipments(equipments: EquipmentToDelete[]): DeleteEquipmentsAction { +export function deleteEquipments(equipments: EquipmentToDelete[], nodeId: UUID): DeleteEquipmentsAction { return { type: DELETE_EQUIPMENTS, equipments, + nodeId, }; } diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index 71bac78673..68e7e66854 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -29,12 +29,10 @@ import { } from '@gridsuite/commons-ui'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import { - ADD_ADDITIONAL_EQUIPMENTS_BY_NODES_FOR_CUSTOM_COLUMNS, ADD_FILTER_FOR_NEW_SPREADSHEET, ADD_NOTIFICATION, ADD_SORT_FOR_NEW_SPREADSHEET, ADD_TO_RECENT_GLOBAL_FILTERS, - AddEquipmentsByNodesForCustomColumnsAction, AddFilterForNewSpreadsheetAction, AddNotificationAction, AddSortForNewSpreadsheetAction, @@ -289,6 +287,7 @@ import { COMPUTING_AND_NETWORK_MODIFICATION_TYPE } from '../utils/report/report. import { BUILD_STATUS } from '../components/network/constants'; import GSMapEquipments from 'components/network/gs-map-equipments'; import { + SpreadsheetEquipmentsByNodes, ColumnState, SpreadsheetEquipmentType, SpreadsheetTabDefinition, @@ -505,7 +504,6 @@ export interface AppState extends CommonStoreState { reloadMap: boolean; isMapEquipmentsInitialized: boolean; spreadsheetNetwork: SpreadsheetNetworkState; - additionalEquipmentsByNodesForCustomColumns: AdditionalEquipmentsByNodesForCustomColumnsState; gsFilterSpreadsheetState: GsFilterSpreadsheetState; customColumnsNodesAliases: NodeAlias[]; networkVisualizationsParameters: NetworkVisualizationParameters; @@ -578,32 +576,32 @@ const initialLogsFilterState: LogsFilterState = { [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.NON_EVACUATED_ENERGY_ANALYSIS]: [], }; -export type SpreadsheetNetworkState = Record; +const emptySpreadsheetEquipmentsByNodes: SpreadsheetEquipmentsByNodes = { + nodesId: [], + equipmentsByNodeId: {}, +}; + +export type SpreadsheetNetworkState = Record; const initialSpreadsheetNetworkState: SpreadsheetNetworkState = { - [EQUIPMENT_TYPES.SUBSTATION]: null, - [EQUIPMENT_TYPES.VOLTAGE_LEVEL]: null, - [EQUIPMENT_TYPES.LINE]: null, - [EQUIPMENT_TYPES.TWO_WINDINGS_TRANSFORMER]: null, - [EQUIPMENT_TYPES.THREE_WINDINGS_TRANSFORMER]: null, - [EQUIPMENT_TYPES.GENERATOR]: null, - [EQUIPMENT_TYPES.LOAD]: null, - [EQUIPMENT_TYPES.BATTERY]: null, - [EQUIPMENT_TYPES.DANGLING_LINE]: null, - [EQUIPMENT_TYPES.TIE_LINE]: null, - [EQUIPMENT_TYPES.HVDC_LINE]: null, - [EQUIPMENT_TYPES.LCC_CONVERTER_STATION]: null, - [EQUIPMENT_TYPES.VSC_CONVERTER_STATION]: null, - [EQUIPMENT_TYPES.SHUNT_COMPENSATOR]: null, - [EQUIPMENT_TYPES.STATIC_VAR_COMPENSATOR]: null, - [EQUIPMENT_TYPES.BUS]: null, - [EQUIPMENT_TYPES.BUSBAR_SECTION]: null, + [EQUIPMENT_TYPES.SUBSTATION]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.VOLTAGE_LEVEL]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.LINE]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.TWO_WINDINGS_TRANSFORMER]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.THREE_WINDINGS_TRANSFORMER]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.GENERATOR]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.LOAD]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.BATTERY]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.DANGLING_LINE]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.TIE_LINE]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.HVDC_LINE]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.LCC_CONVERTER_STATION]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.VSC_CONVERTER_STATION]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.SHUNT_COMPENSATOR]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.STATIC_VAR_COMPENSATOR]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.BUS]: emptySpreadsheetEquipmentsByNodes, + [EQUIPMENT_TYPES.BUSBAR_SECTION]: emptySpreadsheetEquipmentsByNodes, }; -export type AdditionalEquipmentsByNodesForCustomColumnsState = Record< - string, - Record ->; -const initialAdditionalEquipmentsByNodesForCustomColumns: AdditionalEquipmentsByNodesForCustomColumnsState = {}; const initialCustomColumnsNodesAliases: NodeAlias[] = []; export type GsFilterSpreadsheetState = Record; @@ -666,7 +664,6 @@ const initialState: AppState = { networkAreaDiagramDepth: 0, networkAreaDiagramNbVoltageLevels: 0, spreadsheetNetwork: { ...initialSpreadsheetNetworkState }, - additionalEquipmentsByNodesForCustomColumns: initialAdditionalEquipmentsByNodesForCustomColumns, gsFilterSpreadsheetState: initialGsFilterSpreadsheet, customColumnsNodesAliases: initialCustomColumnsNodesAliases, computingStatus: { @@ -1502,35 +1499,40 @@ export const reducer = createReducer(initialState, (builder) => { ); builder.addCase(LOAD_EQUIPMENTS, (state, action: LoadEquipmentsAction) => { - state.spreadsheetNetwork[action.equipmentType] = action.equipments; + Object.entries(action.spreadsheetEquipmentByNodes.equipmentsByNodeId).forEach(([nodeId, equipments]) => { + state.spreadsheetNetwork[action.equipmentType].equipmentsByNodeId[nodeId] = equipments; + }); + //to remove duplicate + state.spreadsheetNetwork[action.equipmentType].nodesId = [ + ...new Set([ + ...state.spreadsheetNetwork[action.equipmentType].nodesId, + ...action.spreadsheetEquipmentByNodes.nodesId, + ]), + ]; }); - builder.addCase( - ADD_ADDITIONAL_EQUIPMENTS_BY_NODES_FOR_CUSTOM_COLUMNS, - (state, action: AddEquipmentsByNodesForCustomColumnsAction) => { - const additionalData = action.data.reduce((acc, item) => { - acc[item.alias] = { - ...state.additionalEquipmentsByNodesForCustomColumns[item.alias], - [action.equipmentType]: item.identifiables, + builder.addCase(REMOVE_NODE_DATA, (state, action: RemoveNodeDataAction) => { + state.spreadsheetNetwork = Object.entries(state.spreadsheetNetwork).reduce( + (newRecord, [equipmentType, equipmentData]) => { + const { nodesId, equipmentsByNodeId } = equipmentData; + + // Filter out node IDs that should be removed + const updatedNodesId = nodesId.filter((nodeId) => !action.nodesIdToRemove.includes(nodeId)); + + // Remove entries in equipmentsByNodeId where the key is in nodeIdsToRemove + const updatedEquipmentsByNodeId = Object.fromEntries( + Object.entries(equipmentsByNodeId).filter(([nodeId]) => !action.nodesIdToRemove.includes(nodeId)) + ); + + newRecord[equipmentType as SpreadsheetEquipmentType] = { + nodesId: updatedNodesId, + equipmentsByNodeId: updatedEquipmentsByNodeId, }; - return acc; - }, {} as AdditionalEquipmentsByNodesForCustomColumnsState); - state.additionalEquipmentsByNodesForCustomColumns = { - ...state.additionalEquipmentsByNodesForCustomColumns, - ...additionalData, - }; - } - ); - builder.addCase(REMOVE_NODE_DATA, (state, action: RemoveNodeDataAction) => { - state.additionalEquipmentsByNodesForCustomColumns = Object.keys( - state.additionalEquipmentsByNodesForCustomColumns - ) - .filter((alias) => !action.aliases.includes(alias)) - .reduce((acc, alias) => { - acc[alias] = state.additionalEquipmentsByNodesForCustomColumns[alias]; - return acc; - }, {} as AdditionalEquipmentsByNodesForCustomColumnsState); + return newRecord; + }, + {} as Record + ); }); builder.addCase(UPDATE_CUSTOM_COLUMNS_NODES_ALIASES, (state, action: UpdateCustomColumnsNodesAliasesAction) => { @@ -1549,9 +1551,9 @@ export const reducer = createReducer(initialState, (builder) => { Identifiable[] ][]) { const equipmentType = getEquipmentTypeFromUpdateType(updateType); - const currentEquipment: Identifiable[] | null = + const currentEquipment: Identifiable[] | undefined = // @ts-expect-error TODO manage undefined value case - state.spreadsheetNetwork[equipmentType]; + state.spreadsheetNetwork[equipmentType]?.equipmentsByNodeId[action.nodeId]; // Format the updated equipments to match the table format const formattedEquipments = formatFetchedEquipments( @@ -1565,17 +1567,27 @@ export const reducer = createReducer(initialState, (builder) => { //since substations data contains voltage level ones, they have to be treated separately if (equipmentType === EQUIPMENT_TYPES.SUBSTATION) { const [updatedSubstations, updatedVoltageLevels] = updateSubstationsAndVoltageLevels( - state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION] as Substation[], - // @ts-expect-error TODO manage null value case - state.spreadsheetNetwork[EQUIPMENT_TYPES.VOLTAGE_LEVEL], + state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION].equipmentsByNodeId[ + action.nodeId + ] as Substation[], + state.spreadsheetNetwork[EQUIPMENT_TYPES.VOLTAGE_LEVEL].equipmentsByNodeId[action.nodeId], formattedEquipments ); - state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION] = updatedSubstations; - state.spreadsheetNetwork[EQUIPMENT_TYPES.VOLTAGE_LEVEL] = updatedVoltageLevels; + if (updatedSubstations != null) { + state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION].equipmentsByNodeId[action.nodeId] = + updatedSubstations; + } + if (updatedVoltageLevels != null) { + state.spreadsheetNetwork[EQUIPMENT_TYPES.VOLTAGE_LEVEL].equipmentsByNodeId[action.nodeId] = + updatedVoltageLevels; + } } else { // @ts-expect-error TODO manage undefined value case - state.spreadsheetNetwork[equipmentType] = updateEquipments(currentEquipment, formattedEquipments); + state.spreadsheetNetwork[equipmentType].equipmentsByNodeId[action.nodeId] = updateEquipments( + currentEquipment, + formattedEquipments + ); } } } @@ -1583,22 +1595,21 @@ export const reducer = createReducer(initialState, (builder) => { builder.addCase(DELETE_EQUIPMENTS, (state, action: DeleteEquipmentsAction) => { action.equipments.forEach(({ equipmentType: equipmentToDeleteType, equipmentId: equipmentToDeleteId }) => { - const currentEquipments = state.spreadsheetNetwork[equipmentToDeleteType]; - if (currentEquipments != null) { + const currentEquipments = + state.spreadsheetNetwork[equipmentToDeleteType]?.equipmentsByNodeId[action.nodeId]; + if (currentEquipments !== undefined) { // in case of voltage level deletion, we need to update the linked substation which contains a list of its voltage levels if (equipmentToDeleteType === EQUIPMENT_TYPES.VOLTAGE_LEVEL) { - const currentSubstations = state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION] as - | Substation[] - | null; + const currentSubstations = state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION].equipmentsByNodeId[ + action.nodeId + ] as Substation[] | null; if (currentSubstations != null) { - state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION] = updateSubstationAfterVLDeletion( - currentSubstations, - equipmentToDeleteId - ); + state.spreadsheetNetwork[EQUIPMENT_TYPES.SUBSTATION].equipmentsByNodeId[action.nodeId] = + updateSubstationAfterVLDeletion(currentSubstations, equipmentToDeleteId); } } - state.spreadsheetNetwork[equipmentToDeleteType] = deleteEquipment( + state.spreadsheetNetwork[equipmentToDeleteType].equipmentsByNodeId[action.nodeId] = deleteEquipment( currentEquipments, equipmentToDeleteId ); @@ -1613,7 +1624,7 @@ export const reducer = createReducer(initialState, (builder) => { }); builder.addCase(RESET_EQUIPMENTS_BY_TYPES, (state, action: ResetEquipmentsByTypesAction) => { action.equipmentTypes.forEach((equipmentType) => { - state.spreadsheetNetwork[equipmentType] = null; + state.spreadsheetNetwork[equipmentType] = emptySpreadsheetEquipmentsByNodes; }); }); From 6f8d8e8bd5f75efeb7833edb73698df50897143b Mon Sep 17 00:00:00 2001 From: Ghazoua Rehili Date: Wed, 12 Feb 2025 10:28:28 +0100 Subject: [PATCH 3/3] excluse disconnector breaker from delete equipment type list (#2577) Signed-off-by: Rehili Ghazwa --- .../equipment-deletion/equipment-deletion-form.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-form.jsx b/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-form.jsx index 162818b4e2..eb9895c545 100644 --- a/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-form.jsx +++ b/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-form.jsx @@ -55,6 +55,8 @@ const DeleteEquipmentForm = ({ studyUuid, currentNode, currentRootNetworkUuid, e EQUIPMENT_TYPES.BUS, EQUIPMENT_TYPES.BUSBAR_SECTION, EQUIPMENT_TYPES.TIE_LINE, + EQUIPMENT_TYPES.BREAKER, + EQUIPMENT_TYPES.DISCONNECTOR, ]); return Object.values(EQUIPMENT_TYPES).filter((equipmentType) => !equipmentTypesToExclude.has(equipmentType)); }, []);