Skip to content

Commit d1252f5

Browse files
authored
Loadflow modification dialog (#3136)
Signed-off-by: LE SAULNIER Kevin <[email protected]>
1 parent d9fd02c commit d1252f5

File tree

7 files changed

+269
-26
lines changed

7 files changed

+269
-26
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { CheckCircleOutlined } from '@mui/icons-material';
9+
import { Alert, Button, Theme } from '@mui/material';
10+
import { useState } from 'react';
11+
import { FormattedMessage } from 'react-intl';
12+
import { LoadflowModifications } from './loadflow-modifications';
13+
14+
const styles = {
15+
loadFlowModif: (theme: Theme) => ({
16+
display: 'flex',
17+
alignItems: 'center',
18+
marginTop: theme.spacing(3),
19+
marginBottom: theme.spacing(2),
20+
marginRight: theme.spacing(2),
21+
}),
22+
icon: (theme: Theme) => ({
23+
marginRight: theme.spacing(1),
24+
fontSize: theme.spacing(2.75),
25+
}),
26+
};
27+
28+
export const LoadflowModificationAlert = () => {
29+
const [isModificationDialogOpen, setIsModificationDialogOpen] = useState(false);
30+
31+
const handleDetailsClick = () => {
32+
setIsModificationDialogOpen(true);
33+
};
34+
35+
const handleLoadflowModificationsClose = () => {
36+
setIsModificationDialogOpen(false);
37+
};
38+
39+
return (
40+
<>
41+
<Alert
42+
sx={styles.loadFlowModif}
43+
icon={<CheckCircleOutlined sx={styles.icon} />}
44+
action={
45+
<Button onClick={handleDetailsClick}>
46+
<FormattedMessage id="loadFlowModificationDetails" />
47+
</Button>
48+
}
49+
severity="success"
50+
>
51+
<FormattedMessage id="loadFlowModification" />
52+
</Alert>
53+
{isModificationDialogOpen && (
54+
<LoadflowModifications open={isModificationDialogOpen} onClose={handleLoadflowModificationsClose} />
55+
)}
56+
</>
57+
);
58+
};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { Box, Button, Dialog, DialogContent, DialogProps, DialogTitle, Tab, Tabs } from '@mui/material';
9+
import { FunctionComponent, useCallback, useMemo, useState } from 'react';
10+
import { useIntl } from 'react-intl';
11+
import { useLoadflowModifications } from './use-loadflow-modifications';
12+
import { CustomAGGrid } from '@gridsuite/commons-ui';
13+
import { AGGRID_LOCALES } from 'translations/not-intl/aggrid-locales';
14+
import { GridReadyEvent, RowDataUpdatedEvent, ValueFormatterParams } from 'ag-grid-community';
15+
import { SortWay } from 'types/custom-aggrid-types';
16+
17+
const styles = {
18+
container: {
19+
display: 'flex',
20+
position: 'relative',
21+
},
22+
tabs: {
23+
position: 'relative',
24+
top: 0,
25+
left: 0,
26+
},
27+
};
28+
29+
interface LoadflowModificationsProps extends DialogProps {
30+
onClose: () => void;
31+
}
32+
33+
export const LoadflowModifications: FunctionComponent<LoadflowModificationsProps> = ({ open, onClose }) => {
34+
const intl = useIntl();
35+
const [tabIndex, setTabIndex] = useState(0);
36+
const [data, isLoading] = useLoadflowModifications();
37+
38+
const makeAggridColumnDef = useCallback(
39+
(field: string, translationId: string) => ({
40+
headerName: intl.formatMessage({ id: translationId }),
41+
field: field,
42+
colId: field,
43+
headerComponentParams: { displayName: intl.formatMessage({ id: translationId }) },
44+
}),
45+
[intl]
46+
);
47+
48+
const twtColumnDefs = useMemo(() => {
49+
return [
50+
{ ...makeAggridColumnDef('twoWindingsTransformerId', 'Id'), sort: SortWay.ASC },
51+
makeAggridColumnDef('initialTapPosition', 'loadflowModificationsTapIn'),
52+
makeAggridColumnDef('solvedTapPosition', 'loadflowModificationsTapOut'),
53+
{
54+
...makeAggridColumnDef('type', 'Type'),
55+
valueFormatter: (params: ValueFormatterParams) => intl.formatMessage({ id: params.value }),
56+
},
57+
];
58+
}, [intl, makeAggridColumnDef]);
59+
60+
const scColumnDefs = useMemo(() => {
61+
return [
62+
{ ...makeAggridColumnDef('shuntCompensatorId', 'Id'), sort: SortWay.ASC },
63+
makeAggridColumnDef('initialSectionCount', 'loadflowModificationsSectionCountIn'),
64+
makeAggridColumnDef('solvedSectionCount', 'loadflowModificationsSectionCountOut'),
65+
];
66+
}, [makeAggridColumnDef]);
67+
68+
const onGridReady = useCallback(({ api }: GridReadyEvent) => {
69+
api?.sizeColumnsToFit();
70+
}, []);
71+
72+
const onRowDataUpdated = useCallback((params: RowDataUpdatedEvent) => {
73+
params.api.sizeColumnsToFit();
74+
}, []);
75+
76+
const displayedData = useMemo(() => {
77+
return tabIndex === 0 ? data?.twoWindingsTransformerModifications : data?.shuntCompensatorModifications;
78+
}, [data?.shuntCompensatorModifications, data?.twoWindingsTransformerModifications, tabIndex]);
79+
80+
const displayedDataColDef = useMemo(() => {
81+
return tabIndex === 0 ? twtColumnDefs : scColumnDefs;
82+
}, [scColumnDefs, tabIndex, twtColumnDefs]);
83+
84+
const defaultColDef = {
85+
resizable: false,
86+
suppressMovable: true,
87+
};
88+
89+
return (
90+
<Dialog
91+
PaperProps={{
92+
sx: {
93+
height: '90vh',
94+
},
95+
}}
96+
fullWidth
97+
maxWidth="md"
98+
open={true}
99+
>
100+
<DialogTitle>
101+
{intl.formatMessage({
102+
id: 'loadflowModifications',
103+
})}
104+
</DialogTitle>
105+
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
106+
<Box sx={styles.container}>
107+
<Box sx={styles.tabs}>
108+
<Tabs value={tabIndex} onChange={(_event, newTabIndex) => setTabIndex(newTabIndex)}>
109+
<Tab label={intl.formatMessage({ id: 'Transformers' })} />
110+
<Tab label={intl.formatMessage({ id: 'SHUNT_COMPENSATOR' })} />
111+
</Tabs>
112+
</Box>
113+
</Box>
114+
<Box mt={1} style={{ flexGrow: 1 }}>
115+
<CustomAGGrid
116+
defaultColDef={defaultColDef}
117+
rowData={displayedData}
118+
columnDefs={displayedDataColDef}
119+
rowSelection="single"
120+
overrideLocales={AGGRID_LOCALES}
121+
loading={isLoading}
122+
onGridReady={onGridReady}
123+
onRowDataUpdated={onRowDataUpdated}
124+
/>
125+
</Box>
126+
<Button onClick={onClose}>Fermer</Button>
127+
</DialogContent>
128+
</Dialog>
129+
);
130+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { useEffect, useState } from 'react';
9+
import { useSelector } from 'react-redux';
10+
import { AppState } from 'redux/reducer';
11+
import { fetchLoadFlowModifications } from 'services/study/loadflow';
12+
13+
export const useLoadflowModifications = () => {
14+
const studyUuid = useSelector((appState: AppState) => appState.studyUuid);
15+
const currentNode = useSelector((appState: AppState) => appState.currentTreeNode);
16+
const currentRootNetworkUuid = useSelector((appState: AppState) => appState.currentRootNetworkUuid);
17+
const [data, setData] = useState<{
18+
twoWindingsTransformerModifications: {
19+
twoWindingsTransformerId: string;
20+
tapPositionIn: number;
21+
tapPositionOut: number;
22+
type: string;
23+
}[];
24+
shuntCompensatorModifications: {
25+
shuntCompensatorId: string;
26+
sectionCountIn: number;
27+
sectionCountOut: number;
28+
}[];
29+
}>();
30+
const [isLoading, setIsLoading] = useState(false);
31+
32+
useEffect(() => {
33+
if (!studyUuid || !currentNode || !currentRootNetworkUuid) {
34+
return;
35+
}
36+
setIsLoading(true);
37+
fetchLoadFlowModifications(studyUuid, currentNode.id, currentRootNetworkUuid)
38+
.then((modifications) => {
39+
setData(modifications);
40+
})
41+
.catch((err) => console.error(err))
42+
.finally(() => setIsLoading(false));
43+
}, [studyUuid, currentNode, currentRootNetworkUuid]);
44+
45+
return [data, isLoading] as const;
46+
};

src/components/graph/menus/network-modifications/node-editor.tsx

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import { ComputingType } from '@gridsuite/commons-ui';
1111
import { useDispatch, useSelector } from 'react-redux';
1212

1313
import { setToggleOptions } from '../../../../redux/actions';
14-
import { Alert, Box } from '@mui/material';
14+
import { Box } from '@mui/material';
1515
import { AppState } from '../../../../redux/reducer';
16-
import { CheckCircleOutlined } from '@mui/icons-material';
17-
import { FormattedMessage } from 'react-intl';
1816
import RunningStatus from 'components/utils/running-status';
1917
import { NodeEditorHeader } from './node-editor-header';
2018
import { isSecurityModificationNode } from '../../tree-node.type';
2119
import { StudyDisplayMode } from '../../../network-modification.type';
20+
import { LoadflowModificationAlert } from './loadflow-modifications/loadflow-modification-alert';
2221

2322
const styles = {
2423
paper: (theme: Theme) => ({
@@ -28,17 +27,6 @@ const styles = {
2827
elevation: 3,
2928
background: theme.palette.background.paper,
3029
}),
31-
loadFlowModif: (theme: Theme) => ({
32-
display: 'flex',
33-
alignItems: 'center',
34-
marginTop: theme.spacing(3),
35-
marginBottom: theme.spacing(2),
36-
marginRight: theme.spacing(2),
37-
}),
38-
icon: (theme: Theme) => ({
39-
marginRight: theme.spacing(1),
40-
fontSize: theme.spacing(2.75),
41-
}),
4230
};
4331

4432
const NodeEditor = () => {
@@ -52,21 +40,14 @@ const NodeEditor = () => {
5240
dispatch(setToggleOptions(toggleOptions.filter((option) => option !== StudyDisplayMode.MODIFICATIONS)));
5341
};
5442

55-
const renderLoadFlowModificationTable = () => {
56-
return (
57-
<Alert sx={styles.loadFlowModif} icon={<CheckCircleOutlined sx={styles.icon} />} severity="success">
58-
<FormattedMessage id="loadFlowModification" />
59-
</Alert>
60-
);
61-
};
6243
return (
6344
<Box sx={styles.paper}>
6445
<NodeEditorHeader onClose={closeModificationsDrawer} />
6546

6647
<NetworkModificationNodeEditor />
67-
{loadFlowStatus === RunningStatus.SUCCEED &&
68-
isSecurityModificationNode(currentTreeNode) &&
69-
renderLoadFlowModificationTable()}
48+
{loadFlowStatus === RunningStatus.SUCCEED && isSecurityModificationNode(currentTreeNode) && (
49+
<LoadflowModificationAlert />
50+
)}
7051
</Box>
7152
);
7253
};

src/services/study/loadflow.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ export function fetchLoadFlowResult(
155155
return backendFetchJson(urlWithParams);
156156
}
157157

158+
export function fetchLoadFlowModifications(studyUuid: UUID, currentNodeUuid: UUID, currentRootNetworkUuid: UUID) {
159+
console.info(
160+
`Fetching loadflow modifications on study '${studyUuid}', on root network '${currentRootNetworkUuid}' and node '${currentNodeUuid}' ...`
161+
);
162+
163+
const url =
164+
getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, currentNodeUuid, currentRootNetworkUuid) +
165+
'/loadflow/modifications';
166+
console.debug(url);
167+
return backendFetchJson(url);
168+
}
169+
158170
export function fetchLimitViolations(
159171
studyUuid: UUID,
160172
currentNodeUuid: UUID,

src/translations/en.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,5 +1590,13 @@
15901590
"allBusbarSections": "All",
15911591
"allOptionHelperText": "Busbars have different sections (number or index)",
15921592
"enableNavigationSync" : "Enable multi-tab tree navigation",
1593-
"disableNavigationSync" : "Disable multi-tab tree navigation"
1593+
"disableNavigationSync" : "Disable multi-tab tree navigation",
1594+
"loadflowModificationsTapIn": "Initial tap",
1595+
"loadflowModificationsTapOut": "Final tap",
1596+
"loadflowModificationsSectionCountIn": "Initial section",
1597+
"loadflowModificationsSectionCountOut": "Final section",
1598+
"loadflowModifications": "Loadflow modification",
1599+
"loadFlowModificationDetails":"Details",
1600+
"PHASE_TAP": "Phase",
1601+
"RATIO_TAP": "Ratio"
15941602
}

src/translations/fr.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1586,5 +1586,13 @@
15861586
"allBusbarSections": "Tous",
15871587
"allOptionHelperText": "Tous les jeux de barres n'ont pas les memes sections (index et nombre)",
15881588
"enableNavigationSync": "Activer la navigation dans l'arbre en multi-onglets",
1589-
"disableNavigationSync": "Désactiver la navigation dans l'arbre en multi-onglets"
1589+
"disableNavigationSync": "Désactiver la navigation dans l'arbre en multi-onglets",
1590+
"loadflowModificationsTapIn": "Prise en entrée",
1591+
"loadflowModificationsTapOut": "Prise en sortie",
1592+
"loadflowModificationsSectionCountIn": "Gradin en entrée",
1593+
"loadflowModificationsSectionCountOut": "Gradin en sortie",
1594+
"loadflowModifications": "Modification calcul de répartition",
1595+
"loadFlowModificationDetails": "Détails",
1596+
"PHASE_TAP": "Déphaseur",
1597+
"RATIO_TAP": "Régleur"
15901598
}

0 commit comments

Comments
 (0)