Skip to content

Commit 4f733d9

Browse files
authored
New user interface for limit sets creation (#2969)
Signed-off-by: basseche <[email protected]>
1 parent ef5ad65 commit 4f733d9

21 files changed

+732
-675
lines changed

src/components/dialogs/commons/grid-section.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { Box, Grid, SxProps, Theme, Tooltip } from '@mui/material';
88
import { FormattedMessage, useIntl } from 'react-intl';
99
import { InfoOutlined } from '@mui/icons-material';
10+
import { mergeSx } from '@gridsuite/commons-ui';
1011

1112
export interface GridSectionProps {
1213
title: string;
@@ -15,22 +16,30 @@ export interface GridSectionProps {
1516
customStyle?: SxProps<Theme>;
1617
tooltipEnabled?: boolean;
1718
tooltipMessage?: string;
19+
isLiteralText?: boolean;
1820
}
19-
2021
export default function GridSection({
2122
title,
2223
heading = 3,
2324
size = 12,
2425
customStyle,
2526
tooltipEnabled = false,
2627
tooltipMessage,
28+
isLiteralText,
2729
}: Readonly<GridSectionProps>) {
2830
const intl = useIntl();
2931
return (
3032
<Grid container spacing={2}>
3133
<Grid item xs={size}>
32-
<Box sx={customStyle} component={`h${heading}`}>
33-
<FormattedMessage id={title} />
34+
<Box
35+
sx={mergeSx(customStyle, {
36+
display: 'flex',
37+
alignItems: 'center',
38+
justifyContent: 'space-between',
39+
})}
40+
component={`h${heading}`}
41+
>
42+
{isLiteralText ? title : <FormattedMessage id={title} />}
3443
{tooltipEnabled && (
3544
<Tooltip sx={{ paddingLeft: 1 }} title={intl.formatMessage({ id: tooltipMessage })}>
3645
<InfoOutlined color="info" fontSize="medium" />

src/components/dialogs/limits/limits-groups-contextual-menu.tsx renamed to src/components/dialogs/limits/creation/limits-groups-contextual-menu.tsx

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,104 +7,89 @@
77

88
import {
99
ID,
10-
OPERATIONAL_LIMITS_GROUPS_1,
11-
OPERATIONAL_LIMITS_GROUPS_2,
10+
OPERATIONAL_LIMITS_GROUPS,
1211
SELECTED_LIMITS_GROUP_1,
1312
SELECTED_LIMITS_GROUP_2,
14-
} from '../../utils/field-constants';
13+
} from '../../../utils/field-constants';
1514
import { useFieldArray, useFormContext } from 'react-hook-form';
1615
import Menu from '@mui/material/Menu';
1716
import MenuItem from '@mui/material/MenuItem';
1817
import ListItemIcon from '@mui/material/ListItemIcon';
1918
import { ContentCopy, Delete, Edit } from '@mui/icons-material';
2019
import ListItemText from '@mui/material/ListItemText';
2120
import { useIntl } from 'react-intl';
22-
import { OperationalLimitsGroup } from '../../../services/network-modification-types';
21+
import { OperationalLimitsGroup } from '../../../../services/network-modification-types';
2322
import { PopoverProps } from '@mui/material/Popover';
23+
import { APPLICABILITY } from '../../../network/constants';
2424

2525
export interface LimitsGroupsContextualMenuProps {
2626
parentFormName: string;
27-
indexSelectedLimitSet1: number | null;
28-
indexSelectedLimitSet2: number | null;
29-
setIndexSelectedLimitSet1: React.Dispatch<React.SetStateAction<number | null>>;
30-
setIndexSelectedLimitSet2: React.Dispatch<React.SetStateAction<number | null>>;
27+
indexSelectedLimitSet: number | null;
28+
setIndexSelectedLimitSet: React.Dispatch<React.SetStateAction<number | null>>;
3129
menuAnchorEl: PopoverProps['anchorEl'];
3230
handleCloseMenu: () => void;
3331
activatedByMenuTabIndex: number | null;
3432
startEditingLimitsGroup: (index: number, name: string | null) => void;
3533
selectedLimitsGroups1: string;
3634
selectedLimitsGroups2: string;
37-
editedLimitGroupName: string;
3835
}
3936

4037
export function LimitsGroupsContextualMenu({
4138
parentFormName,
42-
indexSelectedLimitSet1,
43-
indexSelectedLimitSet2,
39+
indexSelectedLimitSet,
40+
setIndexSelectedLimitSet,
4441
menuAnchorEl,
4542
handleCloseMenu,
4643
activatedByMenuTabIndex,
4744
startEditingLimitsGroup,
4845
selectedLimitsGroups1,
4946
selectedLimitsGroups2,
50-
editedLimitGroupName,
51-
setIndexSelectedLimitSet1,
52-
setIndexSelectedLimitSet2,
5347
}: Readonly<LimitsGroupsContextualMenuProps>) {
5448
const intl = useIntl();
55-
const { append: appendToLimitsGroups1, remove: removeLimitsGroups1 } = useFieldArray({
56-
name: `${parentFormName}.${OPERATIONAL_LIMITS_GROUPS_1}`,
57-
});
58-
const { append: appendToLimitsGroups2, remove: removeLimitsGroups2 } = useFieldArray({
59-
name: `${parentFormName}.${OPERATIONAL_LIMITS_GROUPS_2}`,
49+
const operationalLimitsGroupsFormName: string = `${parentFormName}.${OPERATIONAL_LIMITS_GROUPS}`;
50+
const { append: appendToLimitsGroups, remove: removeLimitsGroups } = useFieldArray({
51+
name: operationalLimitsGroupsFormName,
6052
});
6153
const { getValues, setValue } = useFormContext();
6254

6355
const handleDeleteTab = () => {
64-
if (indexSelectedLimitSet1 !== null) {
56+
if (indexSelectedLimitSet !== null) {
57+
const tabName: string = getValues(operationalLimitsGroupsFormName)?.[indexSelectedLimitSet]?.name;
58+
const applicability: string = getValues(operationalLimitsGroupsFormName)?.[indexSelectedLimitSet]
59+
?.applicability;
6560
// if this operational limit was selected, deselect it
66-
if (selectedLimitsGroups1 === editedLimitGroupName) {
67-
setValue(`${parentFormName}.${SELECTED_LIMITS_GROUP_1}`, '');
61+
if (
62+
selectedLimitsGroups1 === tabName &&
63+
(applicability === APPLICABILITY.SIDE1.id || applicability === APPLICABILITY.EQUIPMENT.id)
64+
) {
65+
setValue(`${parentFormName}.${SELECTED_LIMITS_GROUP_1}`, null);
6866
}
69-
removeLimitsGroups1(indexSelectedLimitSet1);
70-
setIndexSelectedLimitSet1(null);
71-
}
72-
if (indexSelectedLimitSet2 !== null) {
73-
if (selectedLimitsGroups2 === editedLimitGroupName) {
74-
setValue(`${parentFormName}.${SELECTED_LIMITS_GROUP_2}`, '');
67+
if (
68+
selectedLimitsGroups2 === tabName &&
69+
(applicability === APPLICABILITY.SIDE2.id || applicability === APPLICABILITY.EQUIPMENT.id)
70+
) {
71+
setValue(`${parentFormName}.${SELECTED_LIMITS_GROUP_2}`, null);
7572
}
76-
removeLimitsGroups2(indexSelectedLimitSet2);
77-
setIndexSelectedLimitSet2(null);
73+
removeLimitsGroups(indexSelectedLimitSet);
74+
setIndexSelectedLimitSet(null);
7875
}
7976
handleCloseMenu();
8077
};
8178

8279
const handleDuplicateTab = () => {
8380
let newName: string = '';
84-
if (indexSelectedLimitSet1 !== null) {
81+
if (indexSelectedLimitSet !== null) {
8582
const duplicatedLimits1: OperationalLimitsGroup = getValues(
86-
`${parentFormName}.${OPERATIONAL_LIMITS_GROUPS_1}[${indexSelectedLimitSet1}]`
83+
`${operationalLimitsGroupsFormName}[${indexSelectedLimitSet}]`
8784
);
88-
newName = duplicatedLimits1.id + '_COPY';
85+
newName = duplicatedLimits1.name + '_COPY';
8986
const newLimitsGroup1: OperationalLimitsGroup = {
9087
...duplicatedLimits1,
9188
[ID]: newName,
9289
};
93-
appendToLimitsGroups1(newLimitsGroup1);
94-
}
95-
96-
if (indexSelectedLimitSet2 !== null) {
97-
const duplicatedLimits2: OperationalLimitsGroup = getValues(
98-
`${parentFormName}.${OPERATIONAL_LIMITS_GROUPS_2}[${indexSelectedLimitSet2}]`
99-
);
100-
newName = duplicatedLimits2.id + '_COPY';
101-
const newLimitsGroup2: OperationalLimitsGroup = {
102-
...duplicatedLimits2,
103-
[ID]: newName,
104-
};
105-
appendToLimitsGroups2(newLimitsGroup2);
90+
appendToLimitsGroups(newLimitsGroup1);
10691
}
107-
startEditingLimitsGroup(getValues(`${parentFormName}.${OPERATIONAL_LIMITS_GROUPS_1}`).length - 1, newName);
92+
startEditingLimitsGroup(getValues(operationalLimitsGroupsFormName).length - 1, newName);
10893
};
10994

11095
return (
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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 { Button, Grid } from '@mui/material';
9+
import {
10+
CURRENT_LIMITS,
11+
LIMITS,
12+
OPERATIONAL_LIMITS_GROUPS,
13+
SELECTED_LIMITS_GROUP_1,
14+
SELECTED_LIMITS_GROUP_2,
15+
} from 'components/utils/field-constants';
16+
import { LimitsSidePane } from '../limits-side-pane';
17+
import { SelectedOperationalLimitGroup } from '../selected-operational-limit-group.js';
18+
import { useCallback, useRef, useState } from 'react';
19+
import { useWatch } from 'react-hook-form';
20+
import { CurrentLimits, OperationalLimitsGroup } from '../../../../services/network-modification-types';
21+
import { OperationalLimitsGroupsTabs } from '../operational-limits-groups-tabs';
22+
import { tabStyles } from 'components/utils/tab-utils';
23+
import { CurrentTreeNode } from '../../../graph/tree-node.type';
24+
import GridSection from '../../commons/grid-section';
25+
import { styles } from '../../dialog-utils';
26+
import AddIcon from '@mui/icons-material/ControlPoint';
27+
import { APPLICABILITY } from '../../../network/constants';
28+
29+
export interface LimitsPaneCreationProps {
30+
id?: string;
31+
currentNode?: CurrentTreeNode;
32+
equipmentToModify?: any;
33+
clearableFields?: boolean;
34+
}
35+
36+
export function LimitsPaneCreation({
37+
id = LIMITS,
38+
currentNode,
39+
equipmentToModify,
40+
clearableFields,
41+
}: Readonly<LimitsPaneCreationProps>) {
42+
const [indexSelectedLimitSet, setIndexSelectedLimitSet] = useState<number | null>(null);
43+
44+
const myRef: any = useRef<any>(null);
45+
46+
const limitsGroups: OperationalLimitsGroup[] = useWatch({
47+
name: `${id}.${OPERATIONAL_LIMITS_GROUPS}`,
48+
});
49+
50+
const onAddClick = useCallback(() => myRef.current?.addNewLimitSet(), []);
51+
52+
const getCurrentLimits = (equipmentToModify: any): CurrentLimits | null => {
53+
if (equipmentToModify?.currentLimits1) {
54+
return equipmentToModify.currentLimits1.find(
55+
(currentLimit: CurrentLimits) => currentLimit.id === equipmentToModify.selectedOperationalLimitsGroup1
56+
);
57+
}
58+
return null;
59+
};
60+
61+
/**
62+
* returns an error message id if :
63+
* - there are more than 2 limit sets with the same name
64+
* - there are exactly 2 limit set with this name but they have the same applicability side
65+
*/
66+
const checkLimitSetUnicity = useCallback(
67+
(editedLimitGroupName: string, newSelectedApplicability: string): string => {
68+
if (indexSelectedLimitSet == null) {
69+
return '';
70+
}
71+
72+
// checks if limit sets with that name already exist
73+
const sameNameInLs: OperationalLimitsGroup[] = limitsGroups
74+
.filter((_ls, index: number) => index !== indexSelectedLimitSet)
75+
.filter(
76+
(limitsGroup: OperationalLimitsGroup) => limitsGroup.name.trim() === editedLimitGroupName.trim()
77+
);
78+
79+
// only 2 limit sets with the same name are allowed and only if there have SIDE1 and SIDE2 applicability
80+
if (sameNameInLs.length > 0) {
81+
if (sameNameInLs.length > 1) {
82+
return 'LimitSetNamingError';
83+
}
84+
85+
if (
86+
sameNameInLs[0].applicability === newSelectedApplicability ||
87+
sameNameInLs[0].applicability === APPLICABILITY.EQUIPMENT.id ||
88+
newSelectedApplicability === APPLICABILITY.EQUIPMENT.id
89+
) {
90+
// only one limit set with this name exist => their applicability has to be different
91+
return 'LimitSetApplicabilityError';
92+
}
93+
}
94+
return '';
95+
},
96+
[indexSelectedLimitSet, limitsGroups]
97+
);
98+
99+
return (
100+
<>
101+
{/* active limit sets */}
102+
<GridSection title="SelectedOperationalLimitGroups" />
103+
<Grid container item xs={8} columns={10.25} spacing={0}>
104+
<Grid item xs={3}>
105+
<SelectedOperationalLimitGroup
106+
selectedFormName={`${id}.${SELECTED_LIMITS_GROUP_1}`}
107+
optionsFormName={`${id}.${OPERATIONAL_LIMITS_GROUPS}`}
108+
label="Side1"
109+
filteredApplicability={APPLICABILITY.SIDE1.id}
110+
/>
111+
</Grid>
112+
<Grid item xs={3}>
113+
<SelectedOperationalLimitGroup
114+
selectedFormName={`${id}.${SELECTED_LIMITS_GROUP_2}`}
115+
optionsFormName={`${id}.${OPERATIONAL_LIMITS_GROUPS}`}
116+
label="Side2"
117+
filteredApplicability={APPLICABILITY.SIDE2.id}
118+
/>
119+
</Grid>
120+
</Grid>
121+
122+
{/* limits */}
123+
<Grid container item xs={4.9} display="flex" flexDirection="row">
124+
<Grid container item xs={3}>
125+
<GridSection title="LimitSets" />
126+
</Grid>
127+
<Grid container item xs={0.5}>
128+
<Button sx={styles.button} startIcon={<AddIcon onClick={onAddClick} />} />
129+
</Grid>
130+
</Grid>
131+
<Grid container item xs={12} columns={10.25}>
132+
<Grid item xs={4}>
133+
<OperationalLimitsGroupsTabs
134+
ref={myRef}
135+
parentFormName={id}
136+
limitsGroups={limitsGroups}
137+
indexSelectedLimitSet={indexSelectedLimitSet}
138+
setIndexSelectedLimitSet={setIndexSelectedLimitSet}
139+
checkLimitSetUnicity={checkLimitSetUnicity}
140+
/>
141+
</Grid>
142+
<Grid item xs={6} sx={tabStyles.parametersBox} marginLeft={2}>
143+
{indexSelectedLimitSet !== null &&
144+
limitsGroups.map(
145+
(operationalLimitsGroup: OperationalLimitsGroup, index: number) =>
146+
index === indexSelectedLimitSet && (
147+
<LimitsSidePane
148+
key={operationalLimitsGroup.id}
149+
limitsGroupFormName={`${id}.${OPERATIONAL_LIMITS_GROUPS}[${index}].${CURRENT_LIMITS}`}
150+
limitsGroupApplicabilityName={`${id}.${OPERATIONAL_LIMITS_GROUPS}[${index}]`}
151+
clearableFields={clearableFields}
152+
permanentCurrentLimitPreviousValue={
153+
getCurrentLimits(equipmentToModify)?.permanentLimit
154+
}
155+
temporaryLimitsPreviousValues={
156+
getCurrentLimits(equipmentToModify)?.temporaryLimits ?? []
157+
}
158+
currentNode={currentNode}
159+
onlySelectedLimitsGroup={false}
160+
selectedLimitSetName={operationalLimitsGroup.name}
161+
checkLimitSetUnicity={checkLimitSetUnicity}
162+
/>
163+
)
164+
)}
165+
</Grid>
166+
</Grid>
167+
</>
168+
);
169+
}

0 commit comments

Comments
 (0)