diff --git a/src/components/result-view-tab.tsx b/src/components/result-view-tab.tsx index 8427922683..363dfe5bca 100644 --- a/src/components/result-view-tab.tsx +++ b/src/components/result-view-tab.tsx @@ -95,10 +95,11 @@ export const ResultViewTab: FunctionComponent = ({ studyUuid={studyUuid} nodeUuid={currentNode?.id} currentRootNetworkUuid={currentRootNetworkUuid} + openVoltageLevelDiagram={openVoltageLevelDiagram} /> ); - }, [studyUuid, currentNode, currentRootNetworkUuid]); + }, [studyUuid, currentNode, currentRootNetworkUuid, openVoltageLevelDiagram]); const renderSecurityAnalysisResult = useMemo(() => { return ( diff --git a/src/components/results/loadflow/load-flow-result-tab.tsx b/src/components/results/loadflow/load-flow-result-tab.tsx index f667c565c2..1d5ff354d0 100644 --- a/src/components/results/loadflow/load-flow-result-tab.tsx +++ b/src/components/results/loadflow/load-flow-result-tab.tsx @@ -10,7 +10,7 @@ import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; import { FormattedMessage, useIntl } from 'react-intl/lib'; -import { LimitTypes, LoadFlowTabProps } from './load-flow-result.type'; +import { LimitTypes, LoadFlowTabProps, OverloadedEquipment } from './load-flow-result.type'; import { LoadFlowResult } from './load-flow-result'; import { fetchLimitViolations, fetchLoadFlowResult } from '../../../services/study/loadflow'; import RunningStatus from 'components/utils/running-status'; @@ -47,6 +47,10 @@ import { UUID } from 'crypto'; import GlobalFilterSelector from '../common/global-filter/global-filter-selector'; import useGlobalFilters from '../common/global-filter/use-global-filters'; import { useGlobalFilterOptions } from '../common/global-filter/use-global-filter-options'; +import { ICellRendererParams } from 'ag-grid-community'; +import { Button, Tooltip } from '@mui/material'; +import { resultsStyles } from '../common/utils'; +import { useLoadFlowResultColumnActions } from './use-load-flow-result-column-actions'; const styles = { flexWrapper: { @@ -70,6 +74,7 @@ export const LoadFlowResultTab: FunctionComponent = ({ studyUuid, nodeUuid, currentRootNetworkUuid, + openVoltageLevelDiagram, }) => { const intl = useIntl(); @@ -84,7 +89,12 @@ export const LoadFlowResultTab: FunctionComponent = ({ const { countriesFilter, voltageLevelsFilter, propertiesFilter } = useGlobalFilterOptions(); const { globalFilters, handleGlobalFilterChange, getGlobalFilterParameter } = useGlobalFilters({}); - + const { onLinkClick } = useLoadFlowResultColumnActions({ + studyUuid, + nodeUuid, + currentRootNetworkUuid, + openVoltageLevelDiagram, + }); const { loading: filterEnumsLoading, result: filterEnums } = useFetchFiltersEnums(); const getEnumLabel = useCallback( @@ -159,12 +169,44 @@ export const LoadFlowResultTab: FunctionComponent = ({ invalidations: loadflowResultInvalidations, }); + const SubjectIdRenderer = useCallback( + (props: ICellRendererParams) => { + const { value, node, colDef } = props || {}; + const onClick = () => { + const row: OverloadedEquipment = { ...node?.data }; + onLinkClick(row, colDef); + }; + if (value) { + return ( + + + + ); + } + }, + [onLinkClick] + ); + const loadFlowLimitViolationsColumns = useMemo(() => { switch (tabIndex) { case 0: - return loadFlowCurrentViolationsColumnsDefinition(intl, filterEnums, getEnumLabel, tabIndex); + return loadFlowCurrentViolationsColumnsDefinition( + intl, + filterEnums, + getEnumLabel, + tabIndex, + SubjectIdRenderer + ); case 1: - return loadFlowVoltageViolationsColumnsDefinition(intl, filterEnums, getEnumLabel, tabIndex); + return loadFlowVoltageViolationsColumnsDefinition( + intl, + filterEnums, + getEnumLabel, + tabIndex, + SubjectIdRenderer + ); case 2: return loadFlowResultColumnsDefinition( intl, @@ -178,7 +220,7 @@ export const LoadFlowResultTab: FunctionComponent = ({ default: return []; } - }, [tabIndex, intl, filterEnums, getEnumLabel]); + }, [tabIndex, intl, filterEnums, getEnumLabel, SubjectIdRenderer]); const resetResultStates = useCallback(() => { setResult(null); diff --git a/src/components/results/loadflow/load-flow-result-utils.ts b/src/components/results/loadflow/load-flow-result-utils.ts index 831e375975..37e1b3070c 100644 --- a/src/components/results/loadflow/load-flow-result-utils.ts +++ b/src/components/results/loadflow/load-flow-result-utils.ts @@ -36,6 +36,7 @@ import { FilterEnumsType, } from '../../custom-aggrid/custom-aggrid-filters/custom-aggrid-filter.type'; import { convertDuration, formatNAValue } from 'components/custom-aggrid/utils/format-values-utils'; +import { SubjectIdRendererType } from '../securityanalysis/security-analysis.type'; export const convertSide = (side: string | undefined, intl: IntlShape) => { return side === BranchSide.ONE @@ -237,7 +238,8 @@ export const loadFlowCurrentViolationsColumnsDefinition = ( intl: IntlShape, filterEnums: FilterEnumsType, getEnumLabel: (value: string) => string, // Used for translation of enum values in the filter - tabIndex: number + tabIndex: number, + subjectIdRenderer: SubjectIdRendererType ): ColDef[] => { const sortParams: ColumnContext['sortParams'] = { table: LOADFLOW_RESULT_SORT_STORE, @@ -252,6 +254,8 @@ export const loadFlowCurrentViolationsColumnsDefinition = ( headerName: intl.formatMessage({ id: 'OverloadedEquipment' }), colId: 'subjectId', field: 'subjectId', + cellRenderer: subjectIdRenderer, + context: { sortParams, filterComponent: CustomAggridComparatorFilter, @@ -359,7 +363,8 @@ export const loadFlowVoltageViolationsColumnsDefinition = ( intl: IntlShape, filterEnums: FilterEnumsType, getEnumLabel: (value: string) => string, // Used for translation of enum values in the filter - tabIndex: number + tabIndex: number, + subjectIdRenderer: SubjectIdRendererType ): ColDef[] => { const sortParams: ColumnContext['sortParams'] = { table: LOADFLOW_RESULT_SORT_STORE, @@ -384,6 +389,7 @@ export const loadFlowVoltageViolationsColumnsDefinition = ( headerName: intl.formatMessage({ id: 'OverloadedEquipmentVoltageLevel' }), colId: 'subjectId', field: 'subjectId', + cellRenderer: subjectIdRenderer, context: { sortParams, filterComponent: CustomAggridComparatorFilter, diff --git a/src/components/results/loadflow/load-flow-result.type.ts b/src/components/results/loadflow/load-flow-result.type.ts index cb7920aafc..4a279836eb 100644 --- a/src/components/results/loadflow/load-flow-result.type.ts +++ b/src/components/results/loadflow/load-flow-result.type.ts @@ -40,6 +40,7 @@ export interface LoadFlowTabProps { studyUuid: UUID; nodeUuid: UUID; currentRootNetworkUuid: UUID; + openVoltageLevelDiagram: (id: string) => void; } export interface LoadflowResultTap { diff --git a/src/components/results/loadflow/use-load-flow-result-column-actions.ts b/src/components/results/loadflow/use-load-flow-result-column-actions.ts new file mode 100644 index 0000000000..4484c48aca --- /dev/null +++ b/src/components/results/loadflow/use-load-flow-result-column-actions.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useCallback } from 'react'; +import { OverloadedEquipment } from './load-flow-result.type'; +import { ColDef } from 'ag-grid-community'; +import { fetchVoltageLevelIdForLineOrTransformerBySide } from '../../../services/study/network-map'; +import { BranchSide } from '../../utils/constants'; +import { UUID } from 'crypto'; +import { useSnackMessage } from '@gridsuite/commons-ui'; +import { useIntl } from 'react-intl'; + +type UseLoadFlowResultColumnActionsProps = { + studyUuid: UUID; + nodeUuid: UUID; + currentRootNetworkUuid: UUID; + openVoltageLevelDiagram: (id: string) => void; +}; + +export const useLoadFlowResultColumnActions = ({ + studyUuid, + nodeUuid, + currentRootNetworkUuid, + openVoltageLevelDiagram, +}: UseLoadFlowResultColumnActionsProps) => { + const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const getBranchSide = useCallback( + (side: string | undefined) => { + if (side === intl.formatMessage({ id: BranchSide.ONE })) { + return BranchSide.ONE; + } else if (side === intl.formatMessage({ id: BranchSide.TWO })) { + return BranchSide.TWO; + } + return null; + }, + [intl] + ); + + const onLinkClick = useCallback( + (row: OverloadedEquipment, column?: ColDef) => { + if (studyUuid && nodeUuid && currentRootNetworkUuid) { + if (column?.field === 'subjectId') { + let vlId: string | undefined = ''; + const { subjectId, side } = row || {}; + // ideally we would have the type of the network element, but we don't + fetchVoltageLevelIdForLineOrTransformerBySide( + studyUuid, + nodeUuid, + currentRootNetworkUuid, + subjectId ?? '', + getBranchSide(side) ?? BranchSide.ONE + ) + .then((voltageLevelId) => { + if (!voltageLevelId) { + vlId = subjectId; + } else { + vlId = voltageLevelId; + } + }) + .finally(() => { + if (!vlId) { + snackError({ + messageId: 'NetworkEquipmentNotFound', + messageValues: { + equipmentId: row.subjectId || '', + }, + }); + } else if (openVoltageLevelDiagram) { + openVoltageLevelDiagram(vlId); + } + }); + } + } + }, + [studyUuid, nodeUuid, currentRootNetworkUuid, getBranchSide, openVoltageLevelDiagram, snackError] + ); + + return { onLinkClick }; +};