diff --git a/.eslintrc.json b/.eslintrc.json
index 5a4c5a4c72..ea1edb9167 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,7 +3,8 @@
"extends": ["react-app", "plugin:prettier/recommended"],
"ignorePatterns": [
// node_modules is implicitly always ignored
- "build"
+ "build",
+ "coverage"
],
"rules": {
"prettier/prettier": "warn",
diff --git a/.github/config/.licenserc.yaml b/.github/config/.licenserc.yaml
index 17acef9400..eba10c7b0f 100644
--- a/.github/config/.licenserc.yaml
+++ b/.github/config/.licenserc.yaml
@@ -22,5 +22,6 @@ header:
- '**/*.svg'
- '**/*.css'
- '**/*.conf'
+ - '**/*.properties'
comment: on-failure
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 018e4675a4..99f3637ebf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,12 @@
name: CI
-on: [push]
+on:
+ push:
+ branches:
+ - 'main'
+ tags:
+ - 'v[0-9]*'
+ pull_request:
jobs:
license-headers:
@@ -26,6 +32,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
with:
+ fetch-depth: 0
persist-credentials: false
- name: Parse tag
@@ -39,9 +46,15 @@ jobs:
npm install
npm run licenses-check
npm run lint
- npm run test
+ npm run test:coverage
npm run build
+ - name: SonarCloud Scan
+ uses: SonarSource/sonarcloud-github-action@v3.0.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
- name: Build and publish Docker image - Main
if: github.ref == 'refs/heads/main'
uses: elgohr/Publish-Docker-Github-Action@33a481be3e179353cb7793a92b57cf9a6c985860 # v4
diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json
new file mode 100644
index 0000000000..80871e3dd1
--- /dev/null
+++ b/.sonarlint/connectedMode.json
@@ -0,0 +1,4 @@
+{
+ "sonarCloudOrganization": "gridsuite",
+ "projectKey": "gridsuite_gridstudy-app"
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d2af34c4b5..292e7a3721 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
- "@gridsuite/commons-ui": "0.65.1",
+ "@gridsuite/commons-ui": "0.65.2",
"@hookform/resolvers": "^3.3.4",
"@mui/icons-material": "^5.15.14",
"@mui/lab": "5.0.0-alpha.169",
@@ -3203,9 +3203,9 @@
}
},
"node_modules/@gridsuite/commons-ui": {
- "version": "0.65.1",
- "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.65.1.tgz",
- "integrity": "sha512-sm4GEUwITSJbi7BxDhc68CuSuUPovNRosa1TRiK0OSZQMI6hevntl/H26KlMtqCVBI1/Fde9vN8rToEOWtfC9Q==",
+ "version": "0.65.2",
+ "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.65.2.tgz",
+ "integrity": "sha512-DC1RNjQSceo46ld96xCM26z99T/DuR+0YHDgzEC2Sx0wEuEeN+siBtqME0CCbBJWtZMqZMjx5s4MwUNVYrFp1g==",
"dependencies": {
"@react-querybuilder/dnd": "^7.2.0",
"@react-querybuilder/material": "^7.2.0",
diff --git a/package.json b/package.json
index 07a22a4d7b..dd5ea9e086 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
- "@gridsuite/commons-ui": "0.65.1",
+ "@gridsuite/commons-ui": "0.65.2",
"@hookform/resolvers": "^3.3.4",
"@mui/icons-material": "^5.15.14",
"@mui/lab": "5.0.0-alpha.169",
@@ -59,6 +59,7 @@
"build": "tsc && vite build",
"serve": "vite preview",
"test": "jest",
+ "test:coverage": "jest --coverage",
"lint": "eslint . --ext js,mjs,jsx,ts,mts,tsx --max-warnings 0",
"licenses-check": "license-checker --summary --excludePrivatePackages --production --onlyAllow \"$( jq -r .onlyAllow[] license-checker-config.json | tr '\n' ';')\" --excludePackages \"$( jq -r .excludePackages[] license-checker-config.json | tr '\n' ';')\""
},
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000000..8be02fde3a
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,4 @@
+sonar.projectKey=gridsuite_gridstudy-app
+sonar.organization=gridsuite
+sonar.projectBaseDir=src
+sonar.javascript.lcov.reportPaths=./coverage/lcov.info
diff --git a/src/components/diagrams/diagram-pane.jsx b/src/components/diagrams/diagram-pane.jsx
index 74b6687ad1..cb669d4b05 100644
--- a/src/components/diagrams/diagram-pane.jsx
+++ b/src/components/diagrams/diagram-pane.jsx
@@ -44,7 +44,7 @@ import { useNameOrId } from '../utils/equipmentInfosHandler';
import { syncDiagramStateWithSessionStorage } from '../../redux/session-storage/diagram-state';
import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
-import { useDebounce, useSnackMessage } from '@gridsuite/commons-ui';
+import { OverflowableText, useDebounce, useSnackMessage } from '@gridsuite/commons-ui';
import { setNetworkAreaDiagramNbVoltageLevels } from '../../redux/actions';
import { useIntl } from 'react-intl';
import { getSubstationSingleLineDiagram, getVoltageLevelSingleLineDiagram } from '../../services/study/network';
@@ -263,6 +263,10 @@ const styles = {
position: 'absolute',
marginLeft: '3em',
},
+ minimizedDiagramTitle: {
+ maxWidth: '17em',
+ paddingTop: '4px',
+ },
separator: {
flexGrow: 1,
display: 'flex',
@@ -1010,7 +1014,12 @@ export function DiagramPane({ studyUuid, showInSpreadsheet, currentNode, visible
)
}
- label={getDiagramTitle(diagramView)}
+ label={
+
+ }
onClick={() => handleOpenDiagramView(diagramView.id, diagramView.svgType)}
onDelete={() => handleCloseDiagramView(diagramView.id, diagramView.svgType)}
/>
diff --git a/src/components/diagrams/diagram.jsx b/src/components/diagrams/diagram.jsx
index bf574117e7..dcbe5737f6 100644
--- a/src/components/diagrams/diagram.jsx
+++ b/src/components/diagrams/diagram.jsx
@@ -130,31 +130,28 @@ const Diagram = (props) => {
showCloseControl
onClose={onCloseHandler}
/>
-
- {props.warningToDisplay ? (
-
+
+ {props.warningToDisplay ? (
-
- ) : (
-
- {props.children}
-
-
- )}
+ ) : (
+ <>{props.children}>
+ )}
+
+
);
diff --git a/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx b/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx
index 19a9fbe41a..44dbd8a299 100644
--- a/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx
+++ b/src/components/diagrams/networkAreaDiagram/network-area-diagram-content.jsx
@@ -27,65 +27,93 @@ const dynamicCssRules = [
cssSelector: '.nad-edge-infos', // data on edges (arrows and values)
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 2200,
+ threshold: 2500,
thresholdStatus: THRESHOLD_STATUS.ABOVE,
},
{
cssSelector: '.nad-label-box', // tooltips linked to nodes
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 3000,
+ threshold: 3500,
thresholdStatus: THRESHOLD_STATUS.ABOVE,
},
{
cssSelector: '.nad-text-edges', // visual link between nodes and their tooltip
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 3000,
+ threshold: 3500,
thresholdStatus: THRESHOLD_STATUS.ABOVE,
},
{
cssSelector: '[class^="nad-vl0to30"], [class*=" nad-vl0to30"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 4000,
+ threshold: 12000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
{
cssSelector: '[class^="nad-vl30to50"], [class*=" nad-vl30to50"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 4000,
+ threshold: 12000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
{
cssSelector: '[class^="nad-vl50to70"], [class*=" nad-vl50to70"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 9000,
+ threshold: 27000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
{
cssSelector: '[class^="nad-vl70to120"], [class*=" nad-vl70to120"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 9000,
+ threshold: 27000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
{
cssSelector: '[class^="nad-vl120to180"], [class*=" nad-vl120to180"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 12000,
+ threshold: 36000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
{
cssSelector: '[class^="nad-vl180to300"], [class*=" nad-vl180to300"]',
belowThresholdCssDeclaration: { display: 'block' },
aboveThresholdCssDeclaration: { display: 'none' },
- threshold: 20000,
+ threshold: 80000,
thresholdStatus: THRESHOLD_STATUS.BELOW,
},
+ {
+ cssSelector: '.nad-disconnected .nad-edge-path',
+ belowThresholdCssDeclaration: { 'stroke-dasharray': '10, 10' },
+ aboveThresholdCssDeclaration: { 'stroke-dasharray': '0.5%, 0.5%' },
+ threshold: 2500,
+ thresholdStatus: THRESHOLD_STATUS.ABOVE,
+ },
+ {
+ cssSelector: '.nad-branch-edges .nad-edge-path, .nad-3wt-edges .nad-edge-path',
+ belowThresholdCssDeclaration: { 'stroke-width': '3' },
+ aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' },
+ threshold: 1000,
+ thresholdStatus: THRESHOLD_STATUS.ABOVE,
+ },
+ {
+ cssSelector: '.nad-branch-edges .nad-winding, .nad-3wt-nodes .nad-winding',
+ belowThresholdCssDeclaration: { 'stroke-width': '3' },
+ aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' },
+ threshold: 1000,
+ thresholdStatus: THRESHOLD_STATUS.ABOVE,
+ },
+ {
+ cssSelector: '.nad-vl-nodes circle.nad-unknown-busnode',
+ belowThresholdCssDeclaration: { 'stroke-width': '3' },
+ aboveThresholdCssDeclaration: { 'stroke-width': '0.25%' },
+ threshold: 1000,
+ thresholdStatus: THRESHOLD_STATUS.ABOVE,
+ },
];
function NetworkAreaDiagramContent(props) {
diff --git a/src/components/dialogs/connectivity/branch-connectivity-form.tsx b/src/components/dialogs/connectivity/branch-connectivity-form.tsx
new file mode 100644
index 0000000000..3ea08dd98a
--- /dev/null
+++ b/src/components/dialogs/connectivity/branch-connectivity-form.tsx
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2024, 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 { Grid } from '@mui/material';
+import { gridItem, GridSection } from '../dialogUtils';
+import { ConnectivityForm } from './connectivity-form';
+import { CONNECTIVITY, CONNECTIVITY_1, CONNECTIVITY_2 } from 'components/utils/field-constants';
+import React, { FunctionComponent } from 'react';
+import useVoltageLevelsListInfos from '../../../hooks/use-voltage-levels-list-infos';
+import { CurrentTreeNode } from '../../../redux/reducer';
+import { UUID } from 'crypto';
+
+interface BranchConnectivityFormProps {
+ currentNode: CurrentTreeNode;
+ studyUuid: UUID;
+ isModification?: boolean;
+ previousValues?: any;
+}
+
+const BranchConnectivityForm: FunctionComponent = ({
+ currentNode,
+ studyUuid,
+ isModification = false,
+ previousValues,
+}) => {
+ const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNode.id);
+ const id1 = `${CONNECTIVITY}.${CONNECTIVITY_1}`;
+ const id2 = `${CONNECTIVITY}.${CONNECTIVITY_2}`;
+
+ const connectivity1Field = (
+
+ );
+
+ const connectivity2Field = (
+
+ );
+
+ return (
+ <>
+
+
+ {gridItem(connectivity1Field, 12)}
+
+
+
+ {gridItem(connectivity2Field, 12)}
+
+ >
+ );
+};
+
+export default BranchConnectivityForm;
diff --git a/src/components/dialogs/connectivity/connectivity-form-utils.js b/src/components/dialogs/connectivity/connectivity-form-utils.js
index 87731cf4b1..a88e98832a 100644
--- a/src/components/dialogs/connectivity/connectivity-form-utils.js
+++ b/src/components/dialogs/connectivity/connectivity-form-utils.js
@@ -12,6 +12,8 @@ import {
CONNECTION_NAME,
CONNECTION_POSITION,
CONNECTIVITY,
+ CONNECTIVITY_1,
+ CONNECTIVITY_2,
ID,
NAME,
VOLTAGE_LEVEL,
@@ -44,6 +46,13 @@ export const getConnectivityPropertiesValidationSchema = (isEquipmentModificatio
};
};
+export const getCon1andCon2WithPositionValidationSchema = (isEquipmentModification = false, id = CONNECTIVITY) => ({
+ [id]: yup.object().shape({
+ ...getConnectivityWithPositionValidationSchema(isEquipmentModification, CONNECTIVITY_1),
+ ...getConnectivityWithPositionValidationSchema(isEquipmentModification, CONNECTIVITY_2),
+ }),
+});
+
export const getConnectivityWithPositionValidationSchema = (isEquipmentModification = false, id = CONNECTIVITY) => ({
[id]: yup.object().shape({
[CONNECTION_DIRECTION]: yup.string().nullable(),
@@ -74,6 +83,13 @@ export const getConnectivityPropertiesEmptyFormData = (isEquipmentModification =
};
};
+export const getCont1Cont2WithPositionEmptyFormData = (isEquipmentModification = false, id = CONNECTIVITY) => ({
+ [id]: {
+ ...getConnectivityWithPositionEmptyFormData(isEquipmentModification, CONNECTIVITY_1),
+ ...getConnectivityWithPositionEmptyFormData(isEquipmentModification, CONNECTIVITY_2),
+ },
+});
+
export const getConnectivityWithPositionEmptyFormData = (isEquipmentModification = false, id = CONNECTIVITY) => ({
[id]: {
...getConnectivityPropertiesEmptyFormData(isEquipmentModification),
@@ -164,3 +180,13 @@ export const getConnectivityFormData = (
},
};
};
+
+export const createConnectivityData = (equipmentToModify, index) => ({
+ busbarSectionId: equipmentToModify?.[`busOrBusbarSectionId${index}`]?.value ?? null,
+ connectionDirection: equipmentToModify?.[`connectionDirection${index}`]?.value ?? null,
+ connectionName: equipmentToModify?.[`connectionName${index}`]?.value ?? '',
+ connectionPosition: equipmentToModify?.[`connectionPosition${index}`]?.value ?? null,
+ voltageLevelId: equipmentToModify?.[`voltageLevelId${index}`]?.value ?? null,
+ terminalConnected: equipmentToModify?.[`terminal${index}Connected`]?.value ?? null,
+ isEquipmentModification: true,
+});
diff --git a/src/components/dialogs/contingency-list-selector.jsx b/src/components/dialogs/contingency-list-selector.jsx
index 95782c8070..df3095f139 100644
--- a/src/components/dialogs/contingency-list-selector.jsx
+++ b/src/components/dialogs/contingency-list-selector.jsx
@@ -27,6 +27,7 @@ import { DirectoryItemSelector } from '@gridsuite/commons-ui';
import { isNodeBuilt } from 'components/graph/util/model-functions';
import DeleteIcon from '@mui/icons-material/Delete.js';
import IconButton from '@mui/material/IconButton';
+import { toggleElementFromList } from 'components/utils/utils';
import { DialogActions } from '@mui/material';
function makeButton(onClick, message, disabled) {
@@ -155,21 +156,22 @@ const ContingencyListSelector = (props) => {
};
const handleSecondaryAction = useCallback(
- (item) => (
- {
- e.stopPropagation();
- removeFromFavorites([item]);
- }}
- size={'small'}
- >
-
-
- ),
+ (item, isItemHovered) =>
+ isItemHovered && (
+ {
+ e.stopPropagation();
+ removeFromFavorites([item]);
+ }}
+ size={'small'}
+ >
+
+
+ ),
[removeFromFavorites]
);
@@ -189,7 +191,11 @@ const ContingencyListSelector = (props) => {
selectedItems={checkedContingencyList}
onSelectionChange={setCheckedContingencyList}
secondaryAction={handleSecondaryAction}
- enableSecondaryActionOnHover
+ onItemClick={(contingencyList) =>
+ setCheckedContingencyList((oldCheckedElements) => [
+ ...toggleElementFromList(contingencyList, oldCheckedElements, (element) => element.id),
+ ])
+ }
/>
void;
- currentNode: { id: string };
- studyUuid: UUID;
}
-const ImportModificationDialog: FunctionComponent = ({
- open,
- onClose,
- currentNode,
- studyUuid,
-}) => {
+const ImportModificationDialog: FunctionComponent = ({ open, onClose }) => {
const intl = useIntl();
const { snackError } = useSnackMessage();
+ const studyUuid = useSelector((state: AppState) => state.studyUuid);
+ const currentNode = useSelector((state: AppState) => state.currentTreeNode);
const processSelectedElements = (selectedElements: TreeViewFinderNodeProps[]) => {
const copyInfos = {
- copyType: CopyType.INSERT,
+ copyType: NetworkModificationCopyType.INSERT,
};
const modificationUuidList = selectedElements.map((e) => e.id);
// import selected modifications
if (modificationUuidList.length > 0) {
- copyOrMoveModifications(studyUuid, currentNode.id, modificationUuidList, copyInfos).catch((errmsg) => {
+ copyOrMoveModifications(studyUuid, currentNode?.id, modificationUuidList, copyInfos).catch((errmsg) => {
snackError({
messageTxt: errmsg,
headerId: 'errDuplicateModificationMsg',
diff --git a/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx b/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx
index e06e95344f..d45d3b0a86 100644
--- a/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx
+++ b/src/components/dialogs/network-modifications/line/line-dialog-tabs.jsx
@@ -8,10 +8,12 @@
import { Grid, Tab, Tabs } from '@mui/material';
import React from 'react';
import { FormattedMessage } from 'react-intl';
-import { LineCreationDialogTab } from './creation/line-creation-dialog';
import { getTabIndicatorStyle, getTabStyle } from '../../../utils/tab-utils';
+import { LineCreationDialogTab } from './creation/line-creation-dialog';
+import { LineModificationDialogTab } from './modification/line-modification-dialog';
-const LineDialogTabs = ({ tabIndex, tabIndexesWithError, setTabIndex }) => {
+const LineDialogTabs = ({ tabIndex, tabIndexesWithError, setTabIndex, isModification = false }) => {
+ const LineDialogTab = isModification ? LineModificationDialogTab : LineCreationDialogTab;
return (
{
sx: getTabIndicatorStyle(tabIndexesWithError, tabIndex),
}}
>
+ {isModification && (
+ }
+ sx={getTabStyle(tabIndexesWithError, LineDialogTab.CONNECTIVITY_TAB)}
+ />
+ )}
}
- sx={getTabStyle(tabIndexesWithError, LineCreationDialogTab.CHARACTERISTICS_TAB)}
+ sx={getTabStyle(tabIndexesWithError, LineDialogTab.CHARACTERISTICS_TAB)}
/>
}
- sx={getTabStyle(tabIndexesWithError, LineCreationDialogTab.LIMITS_TAB)}
+ sx={getTabStyle(tabIndexesWithError, LineDialogTab.LIMITS_TAB)}
/>
diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx
index 000e8496d0..5fbe37a0f8 100644
--- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx
+++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.jsx
@@ -7,11 +7,10 @@
import React from 'react';
import { EQUIPMENT_NAME } from 'components/utils/field-constants';
-import { Box, Grid } from '@mui/material';
+import { Box, Grid, TextField } from '@mui/material';
import { filledTextField, gridItem } from 'components/dialogs/dialogUtils';
import LineDialogTabs from '../line-dialog-tabs';
import { TextInput } from '@gridsuite/commons-ui';
-import { TextField } from '@mui/material';
const LineModificationDialogHeader = ({ lineToModify, tabIndexesWithError, tabIndex, setTabIndex, equipmentId }) => {
const lineIdField = (
@@ -55,6 +54,7 @@ const LineModificationDialogHeader = ({ lineToModify, tabIndexesWithError, tabIn
tabIndex={tabIndex}
tabIndexesWithError={tabIndexesWithError}
setTabIndex={setTabIndex}
+ isModification={true}
/>
>
diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx
index 7e16e34e15..4d3bc43853 100644
--- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx
+++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.jsx
@@ -5,15 +5,26 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { LineCreationDialogTab } from './line-modification-dialog';
+import { LineModificationDialogTab } from './line-modification-dialog';
import { Box } from '@mui/material';
import LimitsPane from '../../../limits/limits-pane';
import LineCharacteristicsPane from '../characteristics-pane/line-characteristics-pane';
+import React from 'react';
+import BranchConnectivityForm from '../../../connectivity/branch-connectivity-form.tsx';
const LineModificationDialogTabs = ({ studyUuid, currentNode, lineToModify, tabIndex }) => {
return (
<>
-
+
+
+
+
-
+
>
diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog.jsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog.jsx
index b5dd83432c..e6780b071c 100644
--- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog.jsx
+++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog.jsx
@@ -12,12 +12,21 @@ import {
ADDITIONAL_PROPERTIES,
B1,
B2,
+ BUS_OR_BUSBAR_SECTION,
CHARACTERISTICS,
+ CONNECTED,
+ CONNECTION_DIRECTION,
+ CONNECTION_NAME,
+ CONNECTION_POSITION,
+ CONNECTIVITY,
+ CONNECTIVITY_1,
+ CONNECTIVITY_2,
CURRENT_LIMITS_1,
CURRENT_LIMITS_2,
EQUIPMENT_NAME,
G1,
G2,
+ ID,
LIMITS,
PERMANENT_LIMIT,
R,
@@ -25,6 +34,7 @@ import {
TOTAL_REACTANCE,
TOTAL_RESISTANCE,
TOTAL_SUSCEPTANCE,
+ VOLTAGE_LEVEL,
X,
} from 'components/utils/field-constants';
import { useForm } from 'react-hook-form';
@@ -65,10 +75,17 @@ import {
modificationPropertiesSchema,
toModificationProperties,
} from '../../common/properties/property-utils';
+import {
+ createConnectivityData,
+ getCon1andCon2WithPositionValidationSchema,
+ getConnectivityFormData,
+ getCont1Cont2WithPositionEmptyFormData,
+} from '../../../connectivity/connectivity-form-utils.js';
-export const LineCreationDialogTab = {
- CHARACTERISTICS_TAB: 0,
- LIMITS_TAB: 1,
+export const LineModificationDialogTab = {
+ CONNECTIVITY_TAB: 0,
+ CHARACTERISTICS_TAB: 1,
+ LIMITS_TAB: 2,
};
/**
@@ -98,12 +115,13 @@ const LineModificationDialog = ({
const [tabIndexesWithError, setTabIndexesWithError] = useState([]);
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
const [lineToModify, setLineToModify] = useState(null);
- const [tabIndex, setTabIndex] = useState(LineCreationDialogTab.CHARACTERISTICS_TAB);
+ const [tabIndex, setTabIndex] = useState(LineModificationDialogTab.CONNECTIVITY_TAB);
const [isOpenLineTypesCatalogDialog, setOpenLineTypesCatalogDialog] = useState(false);
const emptyFormData = useMemo(
() => ({
[EQUIPMENT_NAME]: '',
+ ...getCont1Cont2WithPositionEmptyFormData(true),
...getCharacteristicsEmptyFormData(CHARACTERISTICS, displayConnectivity),
...getLimitsEmptyFormData(),
...emptyProperties,
@@ -115,6 +133,7 @@ const LineModificationDialog = ({
.object()
.shape({
[EQUIPMENT_NAME]: yup.string(),
+ ...getCon1andCon2WithPositionValidationSchema(true),
...getCharacteristicsValidationSchema(CHARACTERISTICS, displayConnectivity, true),
...getLimitsValidationSchema(),
})
@@ -135,6 +154,10 @@ const LineModificationDialog = ({
}
reset({
[EQUIPMENT_NAME]: line.equipmentName?.value ?? '',
+ [CONNECTIVITY]: {
+ ...getConnectivityFormData(createConnectivityData(line, 1), CONNECTIVITY_1),
+ ...getConnectivityFormData(createConnectivityData(line, 2), CONNECTIVITY_2),
+ },
...getCharacteristicsWithOutConnectivityFormData({
r: line.r?.value ?? null,
x: line.x?.value ?? null,
@@ -181,6 +204,8 @@ const LineModificationDialog = ({
const onSubmit = useCallback(
(line) => {
+ const connectivity1 = line[CONNECTIVITY]?.[CONNECTIVITY_1];
+ const connectivity2 = line[CONNECTIVITY]?.[CONNECTIVITY_2];
const characteristics = line[CHARACTERISTICS];
const limits = line[LIMITS];
const temporaryLimits1 = addModificationTypeToTemporaryLimits(
@@ -223,6 +248,18 @@ const LineModificationDialog = ({
microUnitToUnit(characteristics[B2]),
currentLimits1,
currentLimits2,
+ connectivity1[VOLTAGE_LEVEL]?.id,
+ connectivity1[BUS_OR_BUSBAR_SECTION]?.id,
+ connectivity2[VOLTAGE_LEVEL]?.id,
+ connectivity2[BUS_OR_BUSBAR_SECTION]?.id,
+ sanitizeString(connectivity1[CONNECTION_NAME]),
+ sanitizeString(connectivity2[CONNECTION_NAME]),
+ connectivity1[CONNECTION_DIRECTION],
+ connectivity2[CONNECTION_DIRECTION],
+ connectivity1[CONNECTION_POSITION],
+ connectivity2[CONNECTION_POSITION],
+ connectivity1[CONNECTED],
+ connectivity2[CONNECTED],
!!editData,
editData?.uuid,
toModificationProperties(line)
@@ -240,6 +277,13 @@ const LineModificationDialog = ({
reset(emptyFormData);
}, [emptyFormData, reset]);
+ const setConnectivityValue = useCallback(
+ (index, field, value) => {
+ setValue(`${CONNECTIVITY}.${index}.${field}.${ID}`, value);
+ },
+ [setValue]
+ );
+
const onEquipmentIdChange = useCallback(
(equipmentId) => {
if (equipmentId) {
@@ -255,6 +299,10 @@ const LineModificationDialog = ({
.then((line) => {
if (line) {
setLineToModify(line);
+ setConnectivityValue(CONNECTIVITY_1, VOLTAGE_LEVEL, line?.voltageLevelId1);
+ setConnectivityValue(CONNECTIVITY_2, VOLTAGE_LEVEL, line?.voltageLevelId2);
+ setConnectivityValue(CONNECTIVITY_1, BUS_OR_BUSBAR_SECTION, line?.busOrBusbarSectionId1);
+ setConnectivityValue(CONNECTIVITY_2, BUS_OR_BUSBAR_SECTION, line?.busOrBusbarSectionId2);
if (editData?.equipmentId !== selectedId) {
reset((formValues) => ({
...formValues,
@@ -284,7 +332,7 @@ const LineModificationDialog = ({
reset(emptyFormData, { keepDefaultValues: true });
}
},
- [studyUuid, currentNodeUuid, selectedId, editData, reset, emptyFormData, getValues]
+ [studyUuid, currentNodeUuid, selectedId, editData, reset, emptyFormData, getValues, setConnectivityValue]
);
useEffect(() => {
@@ -296,10 +344,13 @@ const LineModificationDialog = ({
const onValidationError = (errors) => {
let tabsInError = [];
if (errors?.[LIMITS] !== undefined) {
- tabsInError.push(LineCreationDialogTab.LIMITS_TAB);
+ tabsInError.push(LineModificationDialogTab.LIMITS_TAB);
}
if (errors?.[CHARACTERISTICS] !== undefined) {
- tabsInError.push(LineCreationDialogTab.CHARACTERISTICS_TAB);
+ tabsInError.push(LineModificationDialogTab.CHARACTERISTICS_TAB);
+ }
+ if (errors?.[CONNECTIVITY] !== undefined) {
+ tabsInError.push(LineModificationDialogTab.CONNECTIVITY_TAB);
}
if (tabsInError.length > 0) {
diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog-tabs.jsx b/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog-tabs.jsx
index 69ea671a9e..9c6ad295af 100644
--- a/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog-tabs.jsx
+++ b/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog-tabs.jsx
@@ -31,6 +31,10 @@ const TwoWindingsTransformerModificationDialogTabs = ({ tabIndex, tabIndexesWith
sx: getTabIndicatorStyle(tabIndexesWithError, tabIndex),
}}
>
+ }
+ sx={getTabStyle(tabIndexesWithError, TwoWindingsTransformerModificationDialogTab.CONNECTIVITY_TAB)}
+ />
}
sx={getTabStyle(
diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog.jsx b/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog.jsx
index f4b2887f7d..f94f016be8 100644
--- a/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog.jsx
+++ b/src/components/dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog.jsx
@@ -11,7 +11,15 @@ import { Box, Grid } from '@mui/material';
import {
ADDITIONAL_PROPERTIES,
B,
+ BUS_OR_BUSBAR_SECTION,
CHARACTERISTICS,
+ CONNECTED,
+ CONNECTION_DIRECTION,
+ CONNECTION_NAME,
+ CONNECTION_POSITION,
+ CONNECTIVITY,
+ CONNECTIVITY_1,
+ CONNECTIVITY_2,
CURRENT_LIMITER_REGULATING_VALUE,
CURRENT_LIMITS_1,
CURRENT_LIMITS_2,
@@ -110,9 +118,17 @@ import {
toModificationProperties,
} from '../../common/properties/property-utils';
import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos';
+import BranchConnectivityForm from '../../../connectivity/branch-connectivity-form.tsx';
+import {
+ createConnectivityData,
+ getCon1andCon2WithPositionValidationSchema,
+ getConnectivityFormData,
+ getCont1Cont2WithPositionEmptyFormData,
+} from '../../../connectivity/connectivity-form-utils';
const emptyFormData = {
[EQUIPMENT_NAME]: '',
+ ...getCont1Cont2WithPositionEmptyFormData(true),
...getCharacteristicsEmptyFormData(),
...getLimitsEmptyFormData(),
...getRatioTapChangerEmptyFormData(),
@@ -124,6 +140,7 @@ const formSchema = yup
.object()
.shape({
[EQUIPMENT_NAME]: yup.string(),
+ ...getCon1andCon2WithPositionValidationSchema(true),
...getCharacteristicsValidationSchema(true),
...getLimitsValidationSchema(),
...getRatioTapChangerModificationValidationSchema(),
@@ -133,10 +150,11 @@ const formSchema = yup
.required();
export const TwoWindingsTransformerModificationDialogTab = {
- CHARACTERISTICS_TAB: 0,
- LIMITS_TAB: 1,
- RATIO_TAP_TAB: 2,
- PHASE_TAP_TAB: 3,
+ CONNECTIVITY_TAB: 0,
+ CHARACTERISTICS_TAB: 1,
+ LIMITS_TAB: 2,
+ RATIO_TAP_TAB: 3,
+ PHASE_TAP_TAB: 4,
};
/**
@@ -161,7 +179,7 @@ const TwoWindingsTransformerModificationDialog = ({
const currentNodeUuid = currentNode?.id;
const { snackError } = useSnackMessage();
const [selectedId, setSelectedId] = useState(defaultIdValue ?? null);
- const [tabIndex, setTabIndex] = useState(TwoWindingsTransformerModificationDialogTab.CHARACTERISTICS_TAB);
+ const [tabIndex, setTabIndex] = useState(TwoWindingsTransformerModificationDialogTab.CONNECTIVITY_TAB);
const [tabIndexesWithError, setTabIndexesWithError] = useState([]);
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);
const [twtToModify, setTwtToModify] = useState(null);
@@ -170,7 +188,7 @@ const TwoWindingsTransformerModificationDialog = ({
defaultValues: emptyFormData,
resolver: yupResolver(formSchema),
});
- const { reset, getValues } = formMethods;
+ const { reset, getValues, setValue } = formMethods;
const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNodeUuid);
const computeRatioTapChangerRegulationMode = (ratioTapChangerFormValues) => {
@@ -211,6 +229,10 @@ const TwoWindingsTransformerModificationDialog = ({
}
reset({
[EQUIPMENT_NAME]: twt.equipmentName?.value,
+ [CONNECTIVITY]: {
+ ...getConnectivityFormData(createConnectivityData(twt, 1), CONNECTIVITY_1),
+ ...getConnectivityFormData(createConnectivityData(twt, 2), CONNECTIVITY_2),
+ },
...getCharacteristicsFormData({
r: twt.r?.value,
x: twt.x?.value,
@@ -369,6 +391,8 @@ const TwoWindingsTransformerModificationDialog = ({
const onSubmit = useCallback(
(twt) => {
+ const connectivity1 = twt[CONNECTIVITY]?.[CONNECTIVITY_1];
+ const connectivity2 = twt[CONNECTIVITY]?.[CONNECTIVITY_2];
const characteristics = twt[CHARACTERISTICS];
const limits = twt[LIMITS];
const temporaryLimits1 = addModificationTypeToTemporaryLimits(
@@ -474,6 +498,18 @@ const TwoWindingsTransformerModificationDialog = ({
currentLimits2,
ratioTap,
phaseTap,
+ connectivity1[VOLTAGE_LEVEL]?.id,
+ connectivity1[BUS_OR_BUSBAR_SECTION]?.id,
+ connectivity2[VOLTAGE_LEVEL]?.id,
+ connectivity2[BUS_OR_BUSBAR_SECTION]?.id,
+ sanitizeString(connectivity1[CONNECTION_NAME]),
+ sanitizeString(connectivity2[CONNECTION_NAME]),
+ connectivity1[CONNECTION_DIRECTION],
+ connectivity2[CONNECTION_DIRECTION],
+ connectivity1[CONNECTION_POSITION],
+ connectivity2[CONNECTION_POSITION],
+ connectivity1[CONNECTED],
+ connectivity2[CONNECTED],
!!editData,
editData?.uuid,
toModificationProperties(twt)
@@ -499,6 +535,9 @@ const TwoWindingsTransformerModificationDialog = ({
const onValidationError = (errors) => {
let tabsInError = [];
+ if (errors?.[CONNECTIVITY] !== undefined) {
+ tabsInError.push(TwoWindingsTransformerModificationDialogTab.CONNECTIVITY_TAB);
+ }
if (errors?.[CHARACTERISTICS] !== undefined) {
tabsInError.push(TwoWindingsTransformerModificationDialogTab.CHARACTERISTICS_TAB);
}
@@ -529,6 +568,13 @@ const TwoWindingsTransformerModificationDialog = ({
delay: FORM_LOADING_DELAY,
});
+ const setConnectivityValue = useCallback(
+ (index, field, value) => {
+ setValue(`${CONNECTIVITY}.${index}.${field}.${ID}`, value);
+ },
+ [setValue]
+ );
+
const onEquipmentIdChange = useCallback(
(equipmentId) => {
if (equipmentId) {
@@ -544,6 +590,10 @@ const TwoWindingsTransformerModificationDialog = ({
.then((twt) => {
if (twt) {
setTwtToModify(twt);
+ setConnectivityValue(CONNECTIVITY_1, VOLTAGE_LEVEL, twt?.voltageLevelId1);
+ setConnectivityValue(CONNECTIVITY_2, VOLTAGE_LEVEL, twt?.voltageLevelId2);
+ setConnectivityValue(CONNECTIVITY_1, BUS_OR_BUSBAR_SECTION, twt?.busOrBusbarSectionId1);
+ setConnectivityValue(CONNECTIVITY_2, BUS_OR_BUSBAR_SECTION, twt?.busOrBusbarSectionId2);
if (editData?.equipmentId !== selectedId) {
reset((formValues) => ({
...formValues,
@@ -584,7 +634,7 @@ const TwoWindingsTransformerModificationDialog = ({
reset(emptyFormData, { keepDefaultValues: true });
}
},
- [studyUuid, currentNodeUuid, selectedId, editData, reset, getValues]
+ [studyUuid, currentNodeUuid, selectedId, editData, reset, getValues, setConnectivityValue]
);
useEffect(() => {
@@ -638,6 +688,15 @@ const TwoWindingsTransformerModificationDialog = ({
)}
{selectedId != null && (
<>
+
+
+
{
@@ -56,6 +56,9 @@ export const GeneralParameters = () => {
label={'VoltageInitParametersGeneralApplyModificationsLabel'}
onChange={setApplyModificationsValue}
/>
+
+
+
{
const { fields: rows, insert, remove } = useFieldArray({ name: `${id}` });
+ const insertRow = useCallback(
+ (index) => {
+ if (previousValues && updatePreviousReactiveCapabilityCurveTable) {
+ updatePreviousReactiveCapabilityCurveTable(INSERT, index);
+ }
+ insert(index, {
+ [P]: null,
+ [MIN_Q]: null,
+ [MAX_Q]: null,
+ });
+ },
+ [insert, updatePreviousReactiveCapabilityCurveTable, previousValues]
+ );
+
const handleInsertRow = () => {
- if (previousValues && updatePreviousReactiveCapabilityCurveTable) {
- updatePreviousReactiveCapabilityCurveTable(INSERT, rows.length - 1);
- }
- insert(rows.length - 1, {
- [P]: null,
- [MIN_Q]: null,
- [MAX_Q]: null,
- });
+ insertRow(rows.length - 1);
};
const handleRemoveRow = (index) => {
@@ -45,6 +53,14 @@ export const ReactiveCapabilityCurveTable = ({
remove(index);
};
+ useEffect(() => {
+ if (rows?.length < MIN_LENGTH) {
+ for (let i = 0; i < MIN_LENGTH - rows.length; i++) {
+ insertRow(rows.length);
+ }
+ }
+ }, [insertRow, rows]);
+
return (
diff --git a/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.js b/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.js
index 8e474d1cfc..fe0ffeb438 100644
--- a/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.js
+++ b/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.js
@@ -154,13 +154,14 @@ export const calculateCurvePointsToStore = (reactiveCapabilityCurve, equipmentTo
const pointsToStore = [];
reactiveCapabilityCurve.forEach((point, index) => {
if (point) {
+ const previousPoint = equipmentToModify?.reactiveCapabilityCurveTable?.[index];
let pointToStore = {
p: point?.[P],
- oldP: equipmentToModify.reactiveCapabilityCurveTable?.[index]?.p ?? null,
+ oldP: previousPoint?.p ?? null,
minQ: point?.minQ,
- oldMinQ: equipmentToModify.reactiveCapabilityCurveTable?.[index]?.minQ ?? null,
+ oldMinQ: previousPoint?.minQ ?? null,
maxQ: point?.maxQ,
- oldMaxQ: equipmentToModify.reactiveCapabilityCurveTable?.[index]?.maxQ ?? null,
+ oldMaxQ: previousPoint?.maxQ ?? null,
};
pointsToStore.push(pointToStore);
}
diff --git a/src/components/dialogs/restore-modification-dialog.jsx b/src/components/dialogs/restore-modification-dialog.tsx
similarity index 75%
rename from src/components/dialogs/restore-modification-dialog.jsx
rename to src/components/dialogs/restore-modification-dialog.tsx
index 1ab53a5e46..006a15dba2 100644
--- a/src/components/dialogs/restore-modification-dialog.jsx
+++ b/src/components/dialogs/restore-modification-dialog.tsx
@@ -4,43 +4,52 @@
* 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 React, { useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
+import { useEffect, useState } from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import { FormattedMessage, useIntl } from 'react-intl';
-import { Box, DialogContentText } from '@mui/material';
+import { Box, DialogContentText, Theme } from '@mui/material';
import { CancelButton, CheckboxList } from '@gridsuite/commons-ui';
import { deleteModifications, restoreModifications } from 'services/study/network-modifications';
import { CustomDialog } from 'components/utils/custom-dialog';
import { useModificationLabelComputer } from '../graph/util/use-modification-label-computer.jsx';
+import { useSelector } from 'react-redux';
+import { AppState } from 'redux/reducer.js';
+import { NetworkModificationMetadata } from 'components/graph/menus/network-modification-menu.type.js';
+import { toggleElementFromList } from 'components/utils/utils.js';
const styles = {
- text: (theme) => ({
+ text: (theme: Theme) => ({
padding: theme.spacing(1),
}),
- listContainer: (theme) => ({
+ listContainer: (theme: Theme) => ({
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
paddingBottom: theme.spacing(8),
}),
- selectAll: (theme) => ({
+ selectAll: (theme: Theme) => ({
display: 'flex',
alignItems: 'center',
paddingLeft: theme.spacing(3),
paddingBottom: theme.spacing(1),
}),
- list: (theme) => ({
+ list: (theme: Theme) => ({
paddingTop: theme.spacing(0),
flexGrow: 1,
}),
};
+interface RestoreModificationDialogProps {
+ open: boolean;
+ onClose: () => void;
+ modifToRestore: NetworkModificationMetadata[];
+}
+
/**
* Dialog to select network modification to restore
* @param {Boolean} open Is the dialog open ?
@@ -50,11 +59,13 @@ const styles = {
* @param studyUuid Id of the current study
*/
-const RestoreModificationDialog = ({ open, onClose, modifToRestore, currentNode, studyUuid }) => {
+const RestoreModificationDialog = ({ open, onClose, modifToRestore }: RestoreModificationDialogProps) => {
const intl = useIntl();
+ const currentNode = useSelector((state: AppState) => state.currentTreeNode);
+ const studyUuid = useSelector((state: AppState) => state.studyUuid);
- const [stashedModifications, setStashedModifications] = useState([]);
- const [selectedItems, setSelectedItems] = useState([]);
+ const [stashedModifications, setStashedModifications] = useState([]);
+ const [selectedItems, setSelectedItems] = useState([]);
const [openDeleteConfirmationPopup, setOpenDeleteConfirmationPopup] = useState(false);
const { computeLabel } = useModificationLabelComputer();
@@ -67,14 +78,14 @@ const RestoreModificationDialog = ({ open, onClose, modifToRestore, currentNode,
const handleDelete = () => {
const selectedModificationsUuidsToDelete = selectedItems.map((item) => item.uuid);
setOpenDeleteConfirmationPopup(false);
- deleteModifications(studyUuid, currentNode.id, selectedModificationsUuidsToDelete);
+ deleteModifications(studyUuid, currentNode?.id, selectedModificationsUuidsToDelete);
handleClose();
};
const handleRestore = () => {
const selectedModificationsUuidToRestore = selectedItems.map((item) => item.uuid);
- restoreModifications(studyUuid, currentNode.id, selectedModificationsUuidToRestore);
+ restoreModifications(studyUuid, currentNode?.id, selectedModificationsUuidToRestore);
handleClose();
};
@@ -82,7 +93,7 @@ const RestoreModificationDialog = ({ open, onClose, modifToRestore, currentNode,
setStashedModifications(modifToRestore);
}, [modifToRestore]);
- const getLabel = (modif) => {
+ const getLabel = (modif: NetworkModificationMetadata) => {
if (!modif) {
return null;
}
@@ -117,6 +128,15 @@ const RestoreModificationDialog = ({ open, onClose, modifToRestore, currentNode,
onSelectionChange={setSelectedItems}
getItemId={(v) => v.uuid}
getItemLabel={getLabel}
+ onItemClick={(stashedModification) =>
+ setSelectedItems((oldCheckedElements) =>
+ toggleElementFromList(
+ stashedModification,
+ oldCheckedElements,
+ (v: NetworkModificationMetadata) => v.uuid
+ )
+ )
+ }
addSelectAllCheckbox
selectAllCheckBoxLabel={'SelectAll'}
divider
@@ -150,12 +170,4 @@ const RestoreModificationDialog = ({ open, onClose, modifToRestore, currentNode,
);
};
-RestoreModificationDialog.propTypes = {
- open: PropTypes.bool.isRequired,
- onClose: PropTypes.func.isRequired,
- modifications: PropTypes.array,
- currentNode: PropTypes.any,
- studyUuid: PropTypes.any,
-};
-
export default RestoreModificationDialog;
diff --git a/src/components/dialogs/restore-node-dialog.jsx b/src/components/dialogs/restore-node-dialog.jsx
index adb5245f74..dd9eca3fbf 100644
--- a/src/components/dialogs/restore-node-dialog.jsx
+++ b/src/components/dialogs/restore-node-dialog.jsx
@@ -17,6 +17,7 @@ import { deleteStashedNodes, fetchStashedNodes, restoreStashedNodes } from '../.
import LoaderWithOverlay from '../utils/loader-with-overlay';
import { CancelButton, CheckboxList } from '@gridsuite/commons-ui';
import { CustomDialog } from 'components/utils/custom-dialog';
+import { toggleElementFromList } from 'components/utils/utils';
/**
* Dialog to select network modification to create
@@ -97,6 +98,11 @@ const RestoreNodesDialog = ({ open, onClose, anchorNodeId, studyUuid }) => {
onSelectionChange={setSelectedNodes}
getItemId={(v) => v.first.id}
getItemLabel={(v) => v.first.name + (v.second !== 0 ? ' ( + ' + v.second + ' )' : '')}
+ onItemClick={(node) =>
+ setSelectedNodes((oldCheckedElements) =>
+ toggleElementFromList(node, oldCheckedElements, (v) => v.first.id)
+ )
+ }
divider
/>
)}
diff --git a/src/components/dialogs/set-points/set-points-utils.js b/src/components/dialogs/set-points/set-points-utils.js
index a311d6ad96..5c3d3005eb 100644
--- a/src/components/dialogs/set-points/set-points-utils.js
+++ b/src/components/dialogs/set-points/set-points-utils.js
@@ -147,7 +147,7 @@ export const getActivePowerSetPointSchema = (isEquipmentModification) => ({
then: (schema) => {
return schema
.required()
- .nonNullable()
+ .nonNullable('FieldIsRequired')
.test(
'activePowerSetPoint',
'ActivePowerMustBeZeroOrBetweenMinAndMaxActivePower',
diff --git a/src/components/graph/menus/dynamic-simulation/event-modification-scenario-editor.tsx b/src/components/graph/menus/dynamic-simulation/event-modification-scenario-editor.tsx
index 052a8e1860..d25ad299a2 100644
--- a/src/components/graph/menus/dynamic-simulation/event-modification-scenario-editor.tsx
+++ b/src/components/graph/menus/dynamic-simulation/event-modification-scenario-editor.tsx
@@ -8,7 +8,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { CheckboxList, useSnackMessage } from '@gridsuite/commons-ui';
import { useDispatch, useSelector } from 'react-redux';
-import { Box, Checkbox, CircularProgress, SxProps, Theme, Toolbar, Typography } from '@mui/material';
+import { Box, Checkbox, CircularProgress, Toolbar, Typography } from '@mui/material';
import { FormattedMessage, useIntl } from 'react-intl';
import { useParams } from 'react-router-dom';
import DeleteIcon from '@mui/icons-material/Delete';
@@ -224,8 +224,8 @@ const EventModificationScenarioEditor = () => {
};
const handleSecondaryAction = useCallback(
- (item: Event) =>
- !isAnyNodeBuilding ? (
+ (item: Event, isItemHovered?: boolean) =>
+ isItemHovered && !isAnyNodeBuilding ? (
doEditEvent(item)}
size={'small'}
@@ -242,11 +242,13 @@ const EventModificationScenarioEditor = () => {
return (
theme.spacing(2),
- paddingBottom: 'unset',
- paddingTop: 'unset',
- } as SxProps,
+ items: {
+ checkboxListItem: {
+ paddingLeft: (theme) => theme.spacing(2),
+ paddingBottom: 'unset',
+ paddingTop: 'unset',
+ },
+ },
}}
items={events}
selectedItems={selectedItems}
@@ -254,9 +256,7 @@ const EventModificationScenarioEditor = () => {
getItemId={(v: Event) => v.equipmentId}
getItemLabel={getItemLabel}
secondaryAction={handleSecondaryAction}
- enableSecondaryActionOnHover
isDisabled={() => isLoading()}
- isCheckboxClickableOnly
divider
/>
);
diff --git a/src/components/graph/menus/network-modification-menu.type.ts b/src/components/graph/menus/network-modification-menu.type.ts
new file mode 100644
index 0000000000..48ca673560
--- /dev/null
+++ b/src/components/graph/menus/network-modification-menu.type.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2024, 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 { UUID } from 'crypto';
+
+export interface NetworkModificationMetadata {
+ uuid: UUID;
+ type: string;
+ date: Date;
+ stashed: boolean;
+ activated: boolean;
+ messageType: string;
+ messageValues: string;
+}
+
+export enum NetworkModificationCopyType {
+ COPY = 'COPY',
+ MOVE = 'MOVE',
+ INSERT = 'INSERT',
+}
+
+export interface NetworkModificationCopyInfo {
+ copyType: NetworkModificationCopyType;
+ originNodeUuid?: UUID;
+}
+
+export interface MenuDefinitionSubItem {
+ id: string;
+ label: string;
+ action: () => JSX.Element;
+}
+
+interface MenuDefinitionBase {
+ id: string;
+ label: string;
+ hide?: boolean;
+}
+
+export interface MenuDefinitionWithSubItem extends MenuDefinitionBase {
+ subItems: MenuDefinitionSubItem[];
+}
+
+export interface MenuDefinitionWithoutSubItem extends MenuDefinitionBase {
+ action?: () => JSX.Element;
+}
+
+export type MenuDefinition = MenuDefinitionWithSubItem | MenuDefinitionWithoutSubItem;
+
+export interface NetworkModificationData {
+ uuid: UUID;
+ type: string;
+ [key: string]: any;
+}
diff --git a/src/components/graph/menus/network-modification-node-editor.jsx b/src/components/graph/menus/network-modification-node-editor.tsx
similarity index 81%
rename from src/components/graph/menus/network-modification-node-editor.jsx
rename to src/components/graph/menus/network-modification-node-editor.tsx
index a68c6c036a..e88da58332 100644
--- a/src/components/graph/menus/network-modification-node-editor.jsx
+++ b/src/components/graph/menus/network-modification-node-editor.tsx
@@ -5,77 +5,89 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { useSnackMessage, CheckboxList } from '@gridsuite/commons-ui';
-import { useDispatch, useSelector } from 'react-redux';
-import LineAttachToVoltageLevelDialog from 'components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog';
-import NetworkModificationsMenu from 'components/graph/menus/network-modifications-menu';
-import { Checkbox, CircularProgress, Toolbar, Tooltip, Typography } from '@mui/material';
-import { FormattedMessage, useIntl } from 'react-intl';
-import { useParams } from 'react-router-dom';
-import LoadCreationDialog from 'components/dialogs/network-modifications/load/creation/load-creation-dialog';
-import LoadModificationDialog from 'components/dialogs/network-modifications/load/modification/load-modification-dialog';
-import LineCreationDialog from 'components/dialogs/network-modifications/line/creation/line-creation-dialog';
-import TwoWindingsTransformerCreationDialog from 'components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog';
-import ShuntCompensatorCreationDialog from 'components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog';
-import EquipmentDeletionDialog from 'components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog';
+import { CheckboxList, useSnackMessage } from '@gridsuite/commons-ui';
import AddIcon from '@mui/icons-material/Add';
-import DeleteIcon from '@mui/icons-material/Delete';
-import SaveIcon from '@mui/icons-material/Save';
-import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
-import ContentCutIcon from '@mui/icons-material/ContentCut';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import ContentCutIcon from '@mui/icons-material/ContentCut';
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
+import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
+import DeleteIcon from '@mui/icons-material/Delete';
+import SaveIcon from '@mui/icons-material/Save';
+import { Box, Checkbox, CircularProgress, Theme, Toolbar, Tooltip, Typography } from '@mui/material';
import IconButton from '@mui/material/IconButton';
-import { useIsAnyNodeBuilding } from '../../utils/is-any-node-building-hook';
-import { addNotification, removeNotificationByNode, setModificationsInProgress } from '../../../redux/actions';
-import LoadScalingDialog from 'components/dialogs/network-modifications/load-scaling/load-scaling-dialog';
-import VoltageLevelCreationDialog from 'components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog';
-import GeneratorCreationDialog from 'components/dialogs/network-modifications/generator/creation/generator-creation-dialog';
-import DeleteVoltageLevelOnLineDialog from 'components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog';
+import ByFormulaDialog from 'components//dialogs/network-modifications/by-formula/by-formula-dialog';
+import BatteryCreationDialog from 'components/dialogs/network-modifications/battery/creation/battery-creation-dialog';
+import BatteryModificationDialog from 'components/dialogs/network-modifications/battery/modification/battery-modification-dialog';
import DeleteAttachingLineDialog from 'components/dialogs/network-modifications/delete-attaching-line/delete-attaching-line-dialog';
-import LinesAttachToSplitLinesDialog from 'components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog';
+import DeleteVoltageLevelOnLineDialog from 'components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog';
+import EquipmentDeletionDialog from 'components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog';
+import GenerationDispatchDialog from 'components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog';
import GeneratorScalingDialog from 'components/dialogs/network-modifications/generator-scaling/generator-scaling-dialog';
+import GeneratorCreationDialog from 'components/dialogs/network-modifications/generator/creation/generator-creation-dialog';
import GeneratorModificationDialog from 'components/dialogs/network-modifications/generator/modification/generator-modification-dialog';
-import SubstationCreationDialog from 'components/dialogs/network-modifications/substation/creation/substation-creation-dialog';
-import SubstationModificationDialog from 'components/dialogs/network-modifications/substation/modification/substation-modification-dialog';
-import GenerationDispatchDialog from 'components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog';
-import LineModificationDialog from 'components/dialogs/network-modifications/line/modification/line-modification-dialog';
-import VoltageLevelModificationDialog from 'components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog';
-import { UPDATE_TYPE } from 'components/network/constants';
+import LineAttachToVoltageLevelDialog from 'components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog';
import LineSplitWithVoltageLevelDialog from 'components/dialogs/network-modifications/line-split-with-voltage-level/line-split-with-voltage-level-dialog';
-import TwoWindingsTransformerModificationDialog from '../../dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog';
-import BatteryCreationDialog from 'components/dialogs/network-modifications/battery/creation/battery-creation-dialog';
-import BatteryModificationDialog from 'components/dialogs/network-modifications/battery/modification/battery-modification-dialog';
+import LineCreationDialog from 'components/dialogs/network-modifications/line/creation/line-creation-dialog';
+import LineModificationDialog from 'components/dialogs/network-modifications/line/modification/line-modification-dialog';
+import LinesAttachToSplitLinesDialog from 'components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog';
+import LoadScalingDialog from 'components/dialogs/network-modifications/load-scaling/load-scaling-dialog';
+import LoadCreationDialog from 'components/dialogs/network-modifications/load/creation/load-creation-dialog';
+import LoadModificationDialog from 'components/dialogs/network-modifications/load/modification/load-modification-dialog';
+import ShuntCompensatorCreationDialog from 'components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog';
import ShuntCompensatorModificationDialog from 'components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog';
+import SubstationCreationDialog from 'components/dialogs/network-modifications/substation/creation/substation-creation-dialog';
+import SubstationModificationDialog from 'components/dialogs/network-modifications/substation/modification/substation-modification-dialog';
+import TabularCreationDialog from 'components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog';
+import TabularModificationDialog from 'components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog';
+import TwoWindingsTransformerCreationDialog from 'components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog';
import VoltageInitModificationDialog from 'components/dialogs/network-modifications/voltage-init-modification/voltage-init-modification-dialog';
+import VoltageLevelCreationDialog from 'components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog';
+import VoltageLevelModificationDialog from 'components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog';
import VscCreationDialog from 'components/dialogs/network-modifications/vsc/creation/vsc-creation-dialog';
-import ByFormulaDialog from 'components//dialogs/network-modifications/by-formula/by-formula-dialog';
-import TabularModificationDialog from 'components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog';
import VscModificationDialog from 'components/dialogs/network-modifications/vsc/modification/vsc-modification-dialog';
-import TabularCreationDialog from 'components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog';
+import NetworkModificationsMenu from 'components/graph/menus/network-modifications-menu';
+import { UPDATE_TYPE } from 'components/network/constants';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { useDispatch, useSelector } from 'react-redux';
+import { addNotification, removeNotificationByNode, setModificationsInProgress } from '../../../redux/actions';
+import TwoWindingsTransformerModificationDialog from '../../dialogs/network-modifications/two-windings-transformer/modification/two-windings-transformer-modification-dialog';
+import { useIsAnyNodeBuilding } from '../../utils/is-any-node-building-hook';
+import { RestoreFromTrash } from '@mui/icons-material';
+import ImportModificationDialog from 'components/dialogs/import-modification-dialog';
+import RestoreModificationDialog from 'components/dialogs/restore-modification-dialog';
+import { MODIFICATION_TYPES } from 'components/utils/modification-type';
+import { UUID } from 'crypto';
+import { DropResult } from 'react-beautiful-dnd';
+import { AppState, StudyUpdated } from 'redux/reducer';
+import { createCompositeModifications } from '../../../services/explore';
import { fetchNetworkModification } from '../../../services/network-modification';
+import { copyOrMoveModifications } from '../../../services/study';
import {
changeNetworkModificationOrder,
fetchNetworkModifications,
stashModifications,
} from '../../../services/study/network-modifications';
import { FetchStatus } from '../../../services/utils';
-import { copyOrMoveModifications } from '../../../services/study';
-import { MODIFICATION_TYPES } from 'components/utils/modification-type';
-import RestoreModificationDialog from 'components/dialogs/restore-modification-dialog';
-import ImportModificationDialog from 'components/dialogs/import-modification-dialog';
-import { Box } from '@mui/system';
-import { RestoreFromTrash } from '@mui/icons-material';
+import CreateCompositeModificationDialog, {
+ ICompositeCreateModificationDialog,
+} from '../../dialogs/create-composite-modification-dialog';
import ByFilterDeletionDialog from '../../dialogs/network-modifications/by-filter-deletion/by-filter-deletion-dialog';
-import { createCompositeModifications } from '../../../services/explore';
-import EditIcon from '@mui/icons-material/Edit.js';
import { useModificationLabelComputer } from '../util/use-modification-label-computer.jsx';
-import CreateCompositeModificationDialog from '../../dialogs/create-composite-modification-dialog';
+import {
+ MenuDefinition,
+ MenuDefinitionSubItem,
+ MenuDefinitionWithoutSubItem,
+ NetworkModificationCopyInfo,
+ NetworkModificationCopyType,
+ NetworkModificationData,
+ NetworkModificationMetadata,
+} from './network-modification-menu.type';
+import { SwitchNetworkModificationActive } from './switch-network-modification-active';
export const styles = {
- listContainer: (theme) => ({
+ listContainer: (theme: Theme) => ({
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
@@ -84,6 +96,7 @@ export const styles = {
}),
listItem: { paddingLeft: 0, paddingTop: 0, paddingBottom: 0 },
checkBoxLabel: { flexGrow: '1' },
+ disabledModification: { opacity: 0.4 },
checkBoxIcon: { minWidth: 0, padding: 0 },
checkboxButton: {
padding: 0,
@@ -91,8 +104,7 @@ export const styles = {
display: 'flex',
alignItems: 'center',
},
- checkbox: { minWidth: 0, padding: 0 },
- modificationsTitle: (theme) => ({
+ modificationsTitle: (theme: Theme) => ({
display: 'flex',
alignItems: 'center',
margin: theme.spacing(0),
@@ -101,7 +113,7 @@ export const styles = {
color: theme.palette.primary.contrastText,
overflow: 'hidden',
}),
- toolbar: (theme) => ({
+ toolbar: (theme: Theme) => ({
'&': {
// Necessary to overrides some @media specific styles that are defined elsewhere
padding: 0,
@@ -111,28 +123,28 @@ export const styles = {
margin: 0,
flexShrink: 0,
}),
- toolbarIcon: (theme) => ({
+ toolbarIcon: (theme: Theme) => ({
marginRight: theme.spacing(1),
}),
- toolbarCheckbox: (theme) => ({
+ toolbarCheckbox: (theme: Theme) => ({
marginLeft: theme.spacing(1.5),
}),
filler: {
flexGrow: 1,
},
- circularProgress: (theme) => ({
+ circularProgress: (theme: Theme) => ({
marginRight: theme.spacing(2),
color: theme.palette.primary.contrastText,
}),
- toolbarCircularProgress: (theme) => ({
+ toolbarCircularProgress: (theme: Theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: theme.spacing(1.25),
marginRight: theme.spacing(2),
- color: theme.palette.secondary,
+ color: theme.palette.secondary.main,
}),
- notification: (theme) => ({
+ notification: (theme: Theme) => ({
flex: 1,
alignContent: 'center',
justifyContent: 'center',
@@ -140,10 +152,10 @@ export const styles = {
textAlign: 'center',
color: theme.palette.primary.main,
}),
- icon: (theme) => ({
+ icon: (theme: Theme) => ({
width: theme.spacing(3),
}),
- iconEdit: (theme) => ({
+ iconEdit: (theme: Theme) => ({
marginRight: theme.spacing(1),
}),
};
@@ -154,63 +166,57 @@ const nonEditableModificationTypes = new Set([
'OPERATING_STATUS_MODIFICATION',
]);
-const isEditableModification = (modif) => {
+const isEditableModification = (modif: NetworkModificationMetadata) => {
if (!modif) {
return false;
}
return !nonEditableModificationTypes.has(modif.type);
};
-export function isChecked(s1) {
+export function isChecked(s1: number) {
return s1 !== 0;
}
-export function isPartial(s1, s2) {
+export function isPartial(s1: number, s2: number) {
if (s1 === 0) {
return false;
}
return s1 !== s2;
}
-export const CopyType = {
- COPY: 'COPY',
- MOVE: 'MOVE',
- INSERT: 'INSERT',
-};
-
const NetworkModificationNodeEditor = () => {
- const notificationIdList = useSelector((state) => state.notificationIdList);
- const studyUuid = decodeURIComponent(useParams().studyUuid);
+ const notificationIdList = useSelector((state: AppState) => state.notificationIdList);
+ const studyUuid = useSelector((state: AppState) => state.studyUuid);
const { snackInfo, snackError } = useSnackMessage();
- const [modifications, setModifications] = useState(undefined);
+ const [modifications, setModifications] = useState([]);
const [saveInProgress, setSaveInProgress] = useState(false);
const [deleteInProgress, setDeleteInProgress] = useState(false);
- const [modificationsToRestore, setModificationsToRestore] = useState([]);
- const currentNode = useSelector((state) => state.currentTreeNode);
+ const [modificationsToRestore, setModificationsToRestore] = useState([]);
+ const currentNode = useSelector((state: AppState) => state.currentTreeNode);
- const currentNodeIdRef = useRef(); // initial empty to get first update
+ const currentNodeIdRef = useRef(); // initial empty to get first update
const [pendingState, setPendingState] = useState(false);
- const [selectedItems, setSelectedItems] = useState([]);
- const [copiedModifications, setCopiedModifications] = useState([]);
- const [copyInfos, setCopyInfos] = useState(null);
- const copyInfosRef = useRef();
+ const [selectedItems, setSelectedItems] = useState([]);
+ const [copiedModifications, setCopiedModifications] = useState([]);
+ const [copyInfos, setCopyInfos] = useState(null);
+ const copyInfosRef = useRef();
copyInfosRef.current = copyInfos;
const [isDragging, setIsDragging] = useState(false);
- const [editDialogOpen, setEditDialogOpen] = useState(undefined);
- const [editData, setEditData] = useState(undefined);
+ const [editDialogOpen, setEditDialogOpen] = useState(undefined);
+ const [editData, setEditData] = useState(undefined);
const [editDataFetchStatus, setEditDataFetchStatus] = useState(FetchStatus.IDLE);
const [restoreDialogOpen, setRestoreDialogOpen] = useState(false);
const [importDialogOpen, setImportDialogOpen] = useState(false);
const [createCompositeModificationDialogOpen, setCreateCompositeModificationDialogOpen] = useState(false);
const dispatch = useDispatch();
- const studyUpdatedForce = useSelector((state) => state.studyUpdated);
+ const studyUpdatedForce = useSelector((state: AppState) => state.studyUpdated);
const [messageId, setMessageId] = useState('');
const [launchLoader, setLaunchLoader] = useState(false);
const [isUpdate, setIsUpdate] = useState(false);
- const buttonAddRef = useRef();
+ const buttonAddRef = useRef(null);
const cleanClipboard = useCallback(() => {
setCopyInfos(null);
@@ -239,7 +245,7 @@ const NetworkModificationNodeEditor = () => {
setEditData(undefined);
};
- function withDefaultParams(Dialog, props) {
+ function withDefaultParams(Dialog: React.FC) {
return (