From 0035e151c76167d7a56a15088f64df3e7067f82a Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 19 May 2026 13:37:32 +0200 Subject: [PATCH 1/3] nmk power cut off view --- .../security-analysis-result-nmk.tsx | 22 +++-- .../security-analysis-result-tab.tsx | 89 ++++++++++++++----- .../security-analysis-result-utils.ts | 60 +++++++++++++ .../security-analysis.type.ts | 1 + .../use-security-analysis-column-defs.tsx | 21 ++++- src/translations/en.json | 7 +- src/translations/fr.json | 5 +- 7 files changed, 167 insertions(+), 38 deletions(-) diff --git a/src/components/results/securityanalysis/security-analysis-result-nmk.tsx b/src/components/results/securityanalysis/security-analysis-result-nmk.tsx index b7e97a1d30..b27e0cec6c 100644 --- a/src/components/results/securityanalysis/security-analysis-result-nmk.tsx +++ b/src/components/results/securityanalysis/security-analysis-result-nmk.tsx @@ -13,6 +13,7 @@ import { SecurityAnalysisResultNmkProps, } from './security-analysis.type'; import { + flattenNmKPowerCutOff, flattenNmKResultsConstraints, flattenNmKResultsContingencies, handlePostSortRows, @@ -37,6 +38,7 @@ export const SecurityAnalysisResultNmk: FunctionComponent { @@ -45,23 +47,27 @@ export const SecurityAnalysisResultNmk: FunctionComponent - isFromContingency - ? flattenNmKResultsContingencies(intl, content as ConstraintsFromContingencyItem[]) - : flattenNmKResultsConstraints(intl, content as ContingenciesFromConstraintItem[]), - [content, intl, isFromContingency] - ); + const rows = useMemo(() => { + if (isPowerCutOffView) { + return flattenNmKPowerCutOff(content as ConstraintsFromContingencyItem[]); + } + return isFromContingency + ? flattenNmKResultsContingencies(intl, content as ConstraintsFromContingencyItem[]) + : flattenNmKResultsConstraints(intl, content as ContingenciesFromConstraintItem[]); + }, [content, intl, isFromContingency, isPowerCutOffView]); const getRowStyle = useCallback( (params: RowClassParams) => { + if (isPowerCutOffView) { + return undefined; + } if ((isFromContingency && params?.data?.contingencyId) || (!isFromContingency && params?.data?.subjectId)) { return { backgroundColor: theme.selectedRow.background, }; } }, - [isFromContingency, theme.selectedRow.background] + [isFromContingency, isPowerCutOffView, theme.selectedRow.background] ); const agGridProps = { diff --git a/src/components/results/securityanalysis/security-analysis-result-tab.tsx b/src/components/results/securityanalysis/security-analysis-result-tab.tsx index 68a03c394e..dcf866cd10 100644 --- a/src/components/results/securityanalysis/security-analysis-result-tab.tsx +++ b/src/components/results/securityanalysis/security-analysis-result-tab.tsx @@ -9,7 +9,7 @@ import { FunctionComponent, SyntheticEvent, useCallback, useEffect, useMemo, use import { useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from 'react-intl'; import { AppState } from '../../../redux/reducer.type'; -import { Box, LinearProgress, MenuItem, Select, Tab, Tabs } from '@mui/material'; +import { Badge, Box, LinearProgress, MenuItem, Select, SelectChangeEvent, Tab, Tabs } from '@mui/material'; import { downloadSecurityAnalysisResultZippedCsv, fetchSecurityAnalysisResult, @@ -21,7 +21,13 @@ import { ComputingType, EquipmentType, GsLangUser, type MuiStyles, PARAM_DEVELOP import { SecurityAnalysisResultN } from './security-analysis-result-n'; import { SecurityAnalysisResultNmk } from './security-analysis-result-nmk'; import { ComputationReportViewer } from '../common/computation-report-viewer'; -import { RESULT_TYPE, SecurityAnalysisQueryParams, SecurityAnalysisTabProps } from './security-analysis.type'; +import { + ConstraintsFromContingencyItem, + ContingenciesFromConstraintItem, + RESULT_TYPE, + SecurityAnalysisQueryParams, + SecurityAnalysisTabProps, +} from './security-analysis.type'; import { convertFilterValues, getStoreFields, @@ -75,7 +81,8 @@ const styles = { const N_RESULTS_TAB_INDEX = 0; const NMK_RESULTS_TAB_INDEX = 1; const LOGS_TAB_INDEX = 2; - +const POWER_CUT_OFF_VIEW = 'constraints-from-power-cut-off' as const; +type NmkView = NMK_TYPE | typeof POWER_CUT_OFF_VIEW; export const SecurityAnalysisResultTab: FunctionComponent = ({ studyUuid, nodeUuid, @@ -86,9 +93,11 @@ export const SecurityAnalysisResultTab: FunctionComponent(null); tabIndexRef.current = tabIndex; - const [nmkType, setNmkType] = useState(NMK_TYPE.CONSTRAINTS_FROM_CONTINGENCIES); const [count, setCount] = useState(0); + const [nmkView, setNmkView] = useState(NMK_TYPE.CONSTRAINTS_FROM_CONTINGENCIES); + const nmkType: NMK_TYPE = nmkView === POWER_CUT_OFF_VIEW ? NMK_TYPE.CONSTRAINTS_FROM_CONTINGENCIES : nmkView; + const isPowerCutOffView = nmkView === POWER_CUT_OFF_VIEW; useEffect(() => { if (!isDeveloperMode && tabIndexRef.current === N_RESULTS_TAB_INDEX) { // handle tabIndex when dev mode is disabled @@ -185,14 +194,10 @@ export const SecurityAnalysisResultTab: FunctionComponent { + const handleChangeNmkType = (event: SelectChangeEvent) => { dispatchPagination({ page: 0, rowsPerPage }); resetResultStates(); - setNmkType( - nmkType === NMK_TYPE.CONSTRAINTS_FROM_CONTINGENCIES - ? NMK_TYPE.CONTINGENCIES_FROM_CONSTRAINTS - : NMK_TYPE.CONSTRAINTS_FROM_CONTINGENCIES - ); + setNmkView(event.target.value as NmkView); }; const handleTabChange = (event: SyntheticEvent, newTabIndex: number) => { @@ -229,7 +234,7 @@ export const SecurityAnalysisResultTab: FunctionComponent columnDefs.map((cDef) => cDef.headerName ?? ''), [columnDefs]); const downloadZipResult = useCallback( @@ -279,6 +284,30 @@ export const SecurityAnalysisResultTab: FunctionComponent { + const content = result?.content; + if (!content) { + return false; + } + const hasCutOff = ( + cr?: { + disconnectedLoadActivePower?: number | null; + disconnectedGenerationActivePower?: number | null; + } | null + ) => cr?.disconnectedLoadActivePower != null || cr?.disconnectedGenerationActivePower != null; + + if (resultType === RESULT_TYPE.NMK_CONTINGENCIES) { + return (content as ConstraintsFromContingencyItem[]).some((item) => + hasCutOff(item.contingency?.connectivityResult) + ); + } + if (resultType === RESULT_TYPE.NMK_LIMIT_VIOLATIONS) { + return (content as ContingenciesFromConstraintItem[]).some((item) => + item.contingencies?.some((c) => hasCutOff(c.contingency?.connectivityResult)) + ); + } + return false; + }, [result, resultType]); return ( <> @@ -307,20 +336,31 @@ export const SecurityAnalysisResultTab: FunctionComponent {tabIndex === NMK_RESULTS_TAB_INDEX && ( - + + )} {(tabIndex === NMK_RESULTS_TAB_INDEX || (tabIndex === N_RESULTS_TAB_INDEX && isDeveloperMode)) && ( { return ''; } }; + +export const flattenNmKPowerCutOff = (result: ConstraintsFromContingencyItem[] | null) => { + if (!result) { + return undefined; + } + return result.map(({ contingency }) => { + const { contingencyId, status, elements = [], connectivityResult } = contingency || {}; + return { + contingencyId, + contingencyEquipmentsIds: elements.map((e) => e.id), + status, + disconnectedLoadActivePower: connectivityResult?.disconnectedLoadActivePower, + disconnectedGenerationActivePower: connectivityResult?.disconnectedGenerationActivePower, + }; + }); +}; + +export const securityAnalysisTableNmKPowerCutOffColumnsDefinition = ( + intl: IntlShape, + filterEnums: FilterEnumsType, + getEnumLabel: (value: string) => string, + tabIndex: number +): ColDef[] => { + const { sortParams, filterParams } = createTableParams(tabIndex); + + return [ + makeAgGridCustomHeaderColumn({ + ...makeAgGridStringColumn('Contingency', 'contingencyId', intl, filterParams, sortParams), + valueGetter: contingencyGetterValues, + cellRenderer: ContingencyCellRenderer, + }), + createEnumColumn( + 'status', + 'ComputationStatus', + filterEnums['status'] ?? [], + getEnumLabel, + intl, + sortParams, + filterParams + ), + makeAgGridCustomHeaderColumn( + makeAgGridFloatColumn( + 'disconnectedLoadActivePower', + 'disconnectedLoadActivePower', + intl, + filterParams, + sortParams + ) + ), + makeAgGridCustomHeaderColumn( + makeAgGridFloatColumn( + 'disconnectedGenerationActivePower', + 'disconnectedGenerationActivePower', + intl, + filterParams, + sortParams + ) + ), + ]; +}; diff --git a/src/components/results/securityanalysis/security-analysis.type.ts b/src/components/results/securityanalysis/security-analysis.type.ts index 28335f9b7f..d8f1ae2a61 100644 --- a/src/components/results/securityanalysis/security-analysis.type.ts +++ b/src/components/results/securityanalysis/security-analysis.type.ts @@ -129,6 +129,7 @@ export interface SecurityAnalysisResultNmkProps { columnDefs: ColDef[]; isLoadingResult: boolean; isFromContingency: boolean; + isPowerCutOffView: boolean; paginationProps: TablePaginationProps; computationSubType: string; } 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 2d5701abb0..d663b89be2 100644 --- a/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx +++ b/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx @@ -17,6 +17,7 @@ import { securityAnalysisTableNColumnsDefinition, securityAnalysisTableNmKConstraintsColumnsDefinition, securityAnalysisTableNmKContingenciesColumnsDefinition, + securityAnalysisTableNmKPowerCutOffColumnsDefinition, } from './security-analysis-result-utils'; import { useSelector } from 'react-redux'; import { AppState } from 'redux/reducer.type'; @@ -33,13 +34,15 @@ export interface SecurityAnalysisFilterEnumsType { type UseSecurityAnalysisColumnsDefsProps = ( filterEnums: SecurityAnalysisFilterEnumsType, resultType: RESULT_TYPE, - tabIndex: number + tabIndex: number, + isPowerCutOffView: boolean ) => ColDef[]; export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps = ( filterEnums, resultType, - tabIndex + tabIndex, + isPowerCutOffView ) => { const intl = useIntl(); const { snackError } = useSnackMessage(); @@ -132,6 +135,9 @@ export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps ); const columnDefs = useMemo(() => { + if (isPowerCutOffView) { + return securityAnalysisTableNmKPowerCutOffColumnsDefinition(intl, filterEnums.nmk, getEnumLabel, tabIndex); + } switch (resultType) { case RESULT_TYPE.NMK_CONTINGENCIES: return securityAnalysisTableNmKContingenciesColumnsDefinition( @@ -152,7 +158,16 @@ export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps case RESULT_TYPE.N: return securityAnalysisTableNColumnsDefinition(intl, filterEnums.n, getEnumLabel, tabIndex); } - }, [resultType, intl, SubjectIdRenderer, filterEnums.nmk, filterEnums.n, getEnumLabel, tabIndex]); + }, [ + isPowerCutOffView, + resultType, + intl, + filterEnums.nmk, + filterEnums.n, + getEnumLabel, + tabIndex, + SubjectIdRenderer, + ]); return columnDefs; }; diff --git a/src/translations/en.json b/src/translations/en.json index 99619bfd91..ca60601d2e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1547,5 +1547,8 @@ "deactivateModification": "Deactivate network modification", "deselectModification": "Deselect modification", "selectModification": "Select modification", - "moveModification": "Move modification" -} + "moveModification": "Move modification", + "disconnectedLoadActivePower": "Load cut off (MW)", + "disconnectedGenerationActivePower": "Power cut off (MW)", + "ConstraintsFromPowerCutOff": "Power cut off" +} diff --git a/src/translations/fr.json b/src/translations/fr.json index a07fdd1d5b..d89920f714 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -1541,5 +1541,8 @@ "deactivateModification": "Désactiver la modification réseau", "deselectModification": "Désélectionner la modification", "selectModification": "Sélectionner la modification", - "moveModification": "Déplacer la modification" + "moveModification": "Déplacer la modification", + "disconnectedLoadActivePower": "Consommation coupée (MW)", + "disconnectedGenerationActivePower": "Puissance coupée (MW)", + "ConstraintsFromPowerCutOff": "Puissances coupées" } From 6e73cb5017a630a1523c336ea5f77cebfe3e7f31 Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 19 May 2026 13:56:24 +0200 Subject: [PATCH 2/3] fix type --- .../results/securityanalysis/security-analysis.type.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/results/securityanalysis/security-analysis.type.ts b/src/components/results/securityanalysis/security-analysis.type.ts index d8f1ae2a61..e7016faacc 100644 --- a/src/components/results/securityanalysis/security-analysis.type.ts +++ b/src/components/results/securityanalysis/security-analysis.type.ts @@ -46,6 +46,7 @@ export interface ContingencyItem { status?: string; contingencyId?: string; elements?: Element[]; + connectivityResult?: ConnectivityResult; } export interface Contingency { @@ -53,6 +54,11 @@ export interface Contingency { limitViolation?: LimitViolation; } +export interface ConnectivityResult { + disconnectedLoadActivePower: number; + disconnectedGenerationActivePower: number; +} + export interface SecurityAnalysisNmkTableRow { subjectId?: string; locationId?: string; From 50a5dfbc5e94825982ca9d511b9987dc7cc7158b Mon Sep 17 00:00:00 2001 From: Rehili Ghazwa Date: Tue, 19 May 2026 15:50:18 +0200 Subject: [PATCH 3/3] code review rabbit --- .../security-analysis-result-tab.tsx | 5 ++++- .../security-analysis-result-utils.ts | 12 +++++++++++- .../securityanalysis/security-analysis.type.ts | 1 + .../use-security-analysis-column-defs.tsx | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/results/securityanalysis/security-analysis-result-tab.tsx b/src/components/results/securityanalysis/security-analysis-result-tab.tsx index dcf866cd10..0544d616e6 100644 --- a/src/components/results/securityanalysis/security-analysis-result-tab.tsx +++ b/src/components/results/securityanalysis/security-analysis-result-tab.tsx @@ -164,8 +164,11 @@ export const SecurityAnalysisResultTab: FunctionComponent { diff --git a/src/components/results/securityanalysis/security-analysis-result-utils.ts b/src/components/results/securityanalysis/security-analysis-result-utils.ts index 129f0fdffb..9d6d52a36f 100644 --- a/src/components/results/securityanalysis/security-analysis-result-utils.ts +++ b/src/components/results/securityanalysis/security-analysis-result-utils.ts @@ -745,7 +745,10 @@ export enum NMK_TYPE { CONTINGENCIES_FROM_CONSTRAINTS = 'contingencies-from-constraints', } -export const mappingColumnToField = (resultType: RESULT_TYPE) => { +export const mappingColumnToField = (resultType: RESULT_TYPE, isPowerCutOffView = false) => { + if (resultType === RESULT_TYPE.NMK_CONTINGENCIES && isPowerCutOffView) { + return FROM_COLUMN_TO_FIELD_NMK_POWER_CUT_OFF; + } switch (resultType) { case RESULT_TYPE.N: return FROM_COLUMN_TO_FIELD_N; @@ -802,6 +805,13 @@ export const flattenNmKPowerCutOff = (result: ConstraintsFromContingencyItem[] | }); }; +export const FROM_COLUMN_TO_FIELD_NMK_POWER_CUT_OFF: Record = { + contingencyId: 'contingencyId', + status: 'status', + disconnectedLoadActivePower: 'connectivityResult.disconnectedLoadActivePower', + disconnectedGenerationActivePower: 'connectivityResult.disconnectedGenerationActivePower', +}; + export const securityAnalysisTableNmKPowerCutOffColumnsDefinition = ( intl: IntlShape, filterEnums: FilterEnumsType, diff --git a/src/components/results/securityanalysis/security-analysis.type.ts b/src/components/results/securityanalysis/security-analysis.type.ts index e7016faacc..8f06c8b626 100644 --- a/src/components/results/securityanalysis/security-analysis.type.ts +++ b/src/components/results/securityanalysis/security-analysis.type.ts @@ -108,6 +108,7 @@ export type SecurityAnalysisQueryParams = { sort?: SortConfig[]; page?: number; size?: number; + isPowerCutOffView?: boolean; }; export type SubjectIdRendererType = (cellData: ICellRendererParams) => React.JSX.Element | undefined; 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 d663b89be2..3f35a45524 100644 --- a/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx +++ b/src/components/results/securityanalysis/use-security-analysis-column-defs.tsx @@ -135,7 +135,7 @@ export const useSecurityAnalysisColumnsDefs: UseSecurityAnalysisColumnsDefsProps ); const columnDefs = useMemo(() => { - if (isPowerCutOffView) { + if (isPowerCutOffView && resultType === RESULT_TYPE.NMK_CONTINGENCIES) { return securityAnalysisTableNmKPowerCutOffColumnsDefinition(intl, filterEnums.nmk, getEnumLabel, tabIndex); } switch (resultType) {