From 3ea6d0195cbbbaeab1c730a10cbbf96a31806c1d Mon Sep 17 00:00:00 2001 From: Thang PHAM <117309322+thangqp@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:20:42 +0200 Subject: [PATCH] Modification by filter (#2186) Signed-off-by: Thang PHAM --- .../select-with-confirmation-input.tsx | 17 +- .../assignment/assignment-constants.ts | 296 ++++++++++++++++++ .../assignment/assignment-form.tsx | 157 ++++++++++ .../assignment/assignment-utils.ts | 97 ++++++ .../assignment/assignment.type.ts | 94 ++++++ .../modification-by-assignment-dialog.tsx | 127 ++++++++ .../modification-by-assignment-form.tsx | 83 +++++ .../by-filter-deletion-dialog.tsx | 11 +- .../by-filter-deletion-form.tsx | 6 +- .../by-filter-deletion.type.ts | 11 +- .../by-formula/by-formula-dialog.jsx | 14 +- .../by-formula/by-formula-form.tsx | 8 +- .../by-formula/formula/formula-form.tsx | 9 +- .../by-formula/formula/formula-utils.tsx | 4 +- .../formula/reference-autocomplete-input.tsx | 2 +- .../by-filter/commons/by-filter.type.ts | 13 + .../network-modification-node-editor.tsx | 11 +- src/components/utils/field-constants.js | 5 + src/components/utils/modification-type.ts | 4 + src/components/utils/ts-utils.ts | 3 + src/components/utils/utils.js | 4 + src/services/study/network-modifications.js | 250 +++++++-------- .../network-modifications-locale-en.ts | 3 +- .../network-modifications-locale-fr.ts | 1 + src/translations/en.json | 6 +- src/translations/fr.json | 6 +- 26 files changed, 1057 insertions(+), 185 deletions(-) rename src/components/dialogs/{network-modifications/by-formula => commons}/select-with-confirmation-input.tsx (81%) create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-constants.ts create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-form.tsx create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-utils.ts create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment.type.ts create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog.tsx create mode 100644 src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-form.tsx rename src/components/dialogs/network-modifications/{ => by-filter}/by-filter-deletion/by-filter-deletion-dialog.tsx (91%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-filter-deletion/by-filter-deletion-form.tsx (92%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-filter-deletion/by-filter-deletion.type.ts (80%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-formula/by-formula-dialog.jsx (90%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-formula/by-formula-form.tsx (86%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-formula/formula/formula-form.tsx (92%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-formula/formula/formula-utils.tsx (98%) rename src/components/dialogs/network-modifications/{ => by-filter}/by-formula/formula/reference-autocomplete-input.tsx (98%) create mode 100644 src/components/dialogs/network-modifications/by-filter/commons/by-filter.type.ts diff --git a/src/components/dialogs/network-modifications/by-formula/select-with-confirmation-input.tsx b/src/components/dialogs/commons/select-with-confirmation-input.tsx similarity index 81% rename from src/components/dialogs/network-modifications/by-formula/select-with-confirmation-input.tsx rename to src/components/dialogs/commons/select-with-confirmation-input.tsx index 5057ed223c..68e39db27b 100644 --- a/src/components/dialogs/network-modifications/by-formula/select-with-confirmation-input.tsx +++ b/src/components/dialogs/commons/select-with-confirmation-input.tsx @@ -8,8 +8,8 @@ import React, { FunctionComponent, useState } from 'react'; import { useController } from 'react-hook-form'; import { Select, SelectChangeEvent } from '@mui/material'; import MenuItem from '@mui/material/MenuItem'; -import { FormattedMessage } from 'react-intl'; -import { CustomDialog } from '../../../utils/custom-dialog'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { CustomDialog } from '../../utils/custom-dialog'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; @@ -17,6 +17,7 @@ interface SelectWithConfirmationInputProps { name: string; options: string[]; onValidate: () => void; + getOptionLabel?: (option: string) => string; label: string; } @@ -24,8 +25,10 @@ const SelectWithConfirmationInput: FunctionComponent { + const intl = useIntl(); const [openConfirmationDialog, setOpenConfirmationDialog] = useState(false); const [newValue, setNewValue] = useState(''); const { @@ -44,7 +47,7 @@ const SelectWithConfirmationInput: FunctionComponent { - onValidate && onValidate(); + onValidate?.(); onChange(newValue); }; @@ -61,16 +64,16 @@ const SelectWithConfirmationInput: FunctionComponent} > - {options.map((option, index) => ( - - + {options.map((option) => ( + + {getOptionLabel ? getOptionLabel(option) : intl.formatMessage({ id: option })} ))} {openConfirmationDialog && ( } + content={} onValidate={handleValidate} validateButtonLabel="button.changeType" onClose={() => setOpenConfirmationDialog(false)} diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-constants.ts b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-constants.ts new file mode 100644 index 0000000000..dfa53f6e8f --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-constants.ts @@ -0,0 +1,296 @@ +/** + * 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 { DataType, FieldOptionType, FieldType } from './assignment.type'; +import { LOAD_TYPES } from '../../../../../network/constants'; +import { EquipmentType } from '@gridsuite/commons-ui'; + +export const FIELD_OPTIONS: { + [key: string]: FieldOptionType; +} = { + PROPERTY: { + id: FieldType.PROPERTY, + label: 'Property', + dataType: DataType.PROPERTY, + }, + RATED_NOMINAL_POWER: { + id: FieldType.RATED_NOMINAL_POWER, + label: 'RatedNominalPowerText', + dataType: DataType.DOUBLE, + }, + MINIMUM_ACTIVE_POWER: { + id: FieldType.MINIMUM_ACTIVE_POWER, + label: 'MinimumActivePowerText', + dataType: DataType.DOUBLE, + }, + MAXIMUM_ACTIVE_POWER: { + id: FieldType.MAXIMUM_ACTIVE_POWER, + label: 'MaximumActivePowerText', + dataType: DataType.DOUBLE, + }, + ACTIVE_POWER_SET_POINT: { + id: FieldType.ACTIVE_POWER_SET_POINT, + label: 'ActivePowerText', + dataType: DataType.DOUBLE, + }, + REACTIVE_POWER_SET_POINT: { + id: FieldType.REACTIVE_POWER_SET_POINT, + label: 'ReactivePowerText', + dataType: DataType.DOUBLE, + }, + VOLTAGE_SET_POINT: { + id: FieldType.VOLTAGE_SET_POINT, + label: 'GeneratorTargetV', + dataType: DataType.DOUBLE, + }, + PLANNED_ACTIVE_POWER_SET_POINT: { + id: FieldType.PLANNED_ACTIVE_POWER_SET_POINT, + label: 'PlannedActivePowerSetPointForm', + dataType: DataType.DOUBLE, + }, + MARGINAL_COST: { + id: FieldType.MARGINAL_COST, + label: 'marginalCost', + dataType: DataType.DOUBLE, + }, + PLANNED_OUTAGE_RATE: { + id: FieldType.PLANNED_OUTAGE_RATE, + label: 'plannedOutageRate', + dataType: DataType.DOUBLE, + }, + FORCED_OUTAGE_RATE: { + id: FieldType.FORCED_OUTAGE_RATE, + label: 'forcedOutageRate', + dataType: DataType.DOUBLE, + }, + DROOP: { + id: FieldType.DROOP, + label: 'ActivePowerRegulationDroop', + dataType: DataType.DOUBLE, + }, + TRANSIENT_REACTANCE: { + id: FieldType.TRANSIENT_REACTANCE, + label: 'TransientReactanceForm', + dataType: DataType.DOUBLE, + }, + STEP_UP_TRANSFORMER_REACTANCE: { + id: FieldType.STEP_UP_TRANSFORMER_REACTANCE, + label: 'TransformerReactanceForm', + dataType: DataType.DOUBLE, + }, + Q_PERCENT: { + id: FieldType.Q_PERCENT, + label: 'ReactivePercentageVoltageRegulation', + dataType: DataType.DOUBLE, + }, + VOLTAGE_REGULATOR_ON: { + id: FieldType.VOLTAGE_REGULATOR_ON, + label: 'voltageRegulationOn', + dataType: DataType.BOOLEAN, + }, + MAXIMUM_SECTION_COUNT: { + id: FieldType.MAXIMUM_SECTION_COUNT, + label: 'maximumSectionCount', + dataType: DataType.INTEGER, + }, + SECTION_COUNT: { + id: FieldType.SECTION_COUNT, + label: 'sectionCount', + dataType: DataType.INTEGER, + }, + MAXIMUM_SUSCEPTANCE: { + id: FieldType.MAXIMUM_SUSCEPTANCE, + label: 'maxSusceptance', + dataType: DataType.DOUBLE, + }, + MAXIMUM_Q_AT_NOMINAL_VOLTAGE: { + id: FieldType.MAXIMUM_Q_AT_NOMINAL_VOLTAGE, + label: 'maxQAtNominalV', + dataType: DataType.DOUBLE, + }, + NOMINAL_VOLTAGE: { + id: FieldType.NOMINAL_VOLTAGE, + label: 'NominalVoltage', + dataType: DataType.DOUBLE, + }, + LOW_VOLTAGE_LIMIT: { + id: FieldType.LOW_VOLTAGE_LIMIT, + label: 'LowVoltageLimit', + dataType: DataType.DOUBLE, + }, + HIGH_VOLTAGE_LIMIT: { + id: FieldType.HIGH_VOLTAGE_LIMIT, + label: 'HighVoltageLimit', + dataType: DataType.DOUBLE, + }, + LOW_SHORT_CIRCUIT_CURRENT_LIMIT: { + id: FieldType.LOW_SHORT_CIRCUIT_CURRENT_LIMIT, + label: 'LowShortCircuitCurrentLimit', + dataType: DataType.DOUBLE, + }, + HIGH_SHORT_CIRCUIT_CURRENT_LIMIT: { + id: FieldType.HIGH_SHORT_CIRCUIT_CURRENT_LIMIT, + label: 'HighShortCircuitCurrentLimit', + dataType: DataType.DOUBLE, + }, + ACTIVE_POWER: { + id: FieldType.ACTIVE_POWER, + label: 'ActivePowerText', + dataType: DataType.DOUBLE, + }, + REACTIVE_POWER: { + id: FieldType.REACTIVE_POWER, + label: 'ReactivePowerText', + dataType: DataType.DOUBLE, + }, + R: { + id: FieldType.R, + label: 'SeriesResistanceText', + dataType: DataType.DOUBLE, + }, + X: { + id: FieldType.X, + label: 'SeriesReactanceText', + dataType: DataType.DOUBLE, + }, + G: { id: FieldType.G, label: 'G', dataType: DataType.DOUBLE }, + B: { id: FieldType.B, label: 'B', dataType: DataType.DOUBLE }, + RATED_U1: { + id: FieldType.RATED_U1, + label: 'RatedU1', + dataType: DataType.DOUBLE, + }, + RATED_U2: { + id: FieldType.RATED_U2, + label: 'RatedU2', + dataType: DataType.DOUBLE, + }, + RATED_S: { + id: FieldType.RATED_S, + label: 'RatedNominalPowerText', + dataType: DataType.DOUBLE, + }, + TARGET_V: { + id: FieldType.TARGET_V, + label: 'RatioTargetV', + dataType: DataType.DOUBLE, + }, + RATIO_LOW_TAP_POSITION: { + id: FieldType.RATIO_LOW_TAP_POSITION, + label: 'RatioLowTapPosition', + dataType: DataType.INTEGER, + }, + RATIO_TAP_POSITION: { + id: FieldType.RATIO_TAP_POSITION, + label: 'RatioTapPosition', + dataType: DataType.INTEGER, + }, + RATIO_TARGET_DEADBAND: { + id: FieldType.RATIO_TARGET_DEADBAND, + label: 'RatioDeadBand', + dataType: DataType.DOUBLE, + }, + REGULATION_VALUE: { + id: FieldType.REGULATION_VALUE, + label: 'PhaseRegulatingValue', + dataType: DataType.DOUBLE, + }, + PHASE_LOW_TAP_POSITION: { + id: FieldType.PHASE_LOW_TAP_POSITION, + label: 'PhaseLowTapPosition', + dataType: DataType.INTEGER, + }, + PHASE_TAP_POSITION: { + id: FieldType.PHASE_TAP_POSITION, + label: 'PhaseTapPosition', + dataType: DataType.INTEGER, + }, + PHASE_TARGET_DEADBAND: { + id: FieldType.PHASE_TARGET_DEADBAND, + label: 'PhaseDeadBand', + dataType: DataType.DOUBLE, + }, + LOAD_TYPE: { + id: FieldType.LOAD_TYPE, + label: 'loadType', + dataType: DataType.ENUM, + values: LOAD_TYPES, + }, +}; + +export const EQUIPMENTS_FIELDS = { + [EquipmentType.SUBSTATION]: [FIELD_OPTIONS.PROPERTY], + [EquipmentType.VOLTAGE_LEVEL]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.NOMINAL_VOLTAGE, + FIELD_OPTIONS.LOW_VOLTAGE_LIMIT, + FIELD_OPTIONS.HIGH_VOLTAGE_LIMIT, + FIELD_OPTIONS.LOW_SHORT_CIRCUIT_CURRENT_LIMIT, + FIELD_OPTIONS.HIGH_SHORT_CIRCUIT_CURRENT_LIMIT, + ], + [EquipmentType.LINE]: [FIELD_OPTIONS.PROPERTY], + [EquipmentType.TWO_WINDINGS_TRANSFORMER]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.R, + FIELD_OPTIONS.X, + FIELD_OPTIONS.G, + FIELD_OPTIONS.B, + FIELD_OPTIONS.RATED_U1, + FIELD_OPTIONS.RATED_U2, + FIELD_OPTIONS.RATED_S, + FIELD_OPTIONS.TARGET_V, + FIELD_OPTIONS.RATIO_LOW_TAP_POSITION, + FIELD_OPTIONS.RATIO_TAP_POSITION, + FIELD_OPTIONS.RATIO_TARGET_DEADBAND, + FIELD_OPTIONS.REGULATION_VALUE, + FIELD_OPTIONS.PHASE_LOW_TAP_POSITION, + FIELD_OPTIONS.PHASE_TAP_POSITION, + FIELD_OPTIONS.PHASE_TARGET_DEADBAND, + ], + [EquipmentType.THREE_WINDINGS_TRANSFORMER]: [FIELD_OPTIONS.PROPERTY], + [EquipmentType.GENERATOR]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.VOLTAGE_REGULATOR_ON, + FIELD_OPTIONS.RATED_NOMINAL_POWER, + FIELD_OPTIONS.MINIMUM_ACTIVE_POWER, + FIELD_OPTIONS.MAXIMUM_ACTIVE_POWER, + FIELD_OPTIONS.ACTIVE_POWER_SET_POINT, + FIELD_OPTIONS.REACTIVE_POWER_SET_POINT, + FIELD_OPTIONS.VOLTAGE_SET_POINT, + FIELD_OPTIONS.PLANNED_ACTIVE_POWER_SET_POINT, + FIELD_OPTIONS.MARGINAL_COST, + FIELD_OPTIONS.PLANNED_OUTAGE_RATE, + FIELD_OPTIONS.FORCED_OUTAGE_RATE, + FIELD_OPTIONS.DROOP, + FIELD_OPTIONS.TRANSIENT_REACTANCE, + FIELD_OPTIONS.STEP_UP_TRANSFORMER_REACTANCE, + FIELD_OPTIONS.Q_PERCENT, + ], + [EquipmentType.BATTERY]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.MINIMUM_ACTIVE_POWER, + FIELD_OPTIONS.MAXIMUM_ACTIVE_POWER, + FIELD_OPTIONS.ACTIVE_POWER_SET_POINT, + FIELD_OPTIONS.REACTIVE_POWER_SET_POINT, + FIELD_OPTIONS.DROOP, + ], + [EquipmentType.LOAD]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.LOAD_TYPE, + FIELD_OPTIONS.ACTIVE_POWER, + FIELD_OPTIONS.REACTIVE_POWER, + ], + [EquipmentType.SHUNT_COMPENSATOR]: [ + FIELD_OPTIONS.PROPERTY, + FIELD_OPTIONS.MAXIMUM_SECTION_COUNT, + FIELD_OPTIONS.SECTION_COUNT, + FIELD_OPTIONS.MAXIMUM_SUSCEPTANCE, + FIELD_OPTIONS.MAXIMUM_Q_AT_NOMINAL_VOLTAGE, + ], + [EquipmentType.STATIC_VAR_COMPENSATOR]: [FIELD_OPTIONS.PROPERTY], + [EquipmentType.HVDC_LINE]: [FIELD_OPTIONS.PROPERTY], +}; diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-form.tsx b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-form.tsx new file mode 100644 index 0000000000..3c85caeb6e --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-form.tsx @@ -0,0 +1,157 @@ +/** + * 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 React, { FC, useMemo } from 'react'; +import { + AutocompleteInput, + DirectoryItemsInput, + ElementType, + FloatInput, + IntegerInput, + SelectInput, + SwitchInput, + usePrevious, +} from '@gridsuite/commons-ui'; +import DensityLargeIcon from '@mui/icons-material/DensityLarge'; +import { EDITED_FIELD, FILTERS, PROPERTY_NAME_FIELD, VALUE_FIELD } from '../../../../../utils/field-constants'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { gridItem } from '../../../../dialogUtils'; +import { getIdOrValue, getLabelOrValue } from '../../../../commons/utils'; +import { useIntl } from 'react-intl'; +import { DataType, FieldOptionType } from './assignment.type'; +import { areIdsEqual, comparatorStrIgnoreCase } from '../../../../../utils/utils'; +import { PredefinedProperties } from '../../../common/properties/property-utils'; + +interface AssignmentFormProps { + name: string; + index: number; + predefinedProperties: PredefinedProperties; + equipmentFields: FieldOptionType[]; + equipmentType: string; +} + +const AssignmentForm: FC = ({ + name, + index, + predefinedProperties, + equipmentFields, + equipmentType, +}) => { + const intl = useIntl(); + + const { setValue } = useFormContext(); + + const watchEditedField = useWatch({ + name: `${name}.${index}.${EDITED_FIELD}`, + }); + + const dataType = useMemo(() => { + return equipmentFields?.find((fieldOption) => fieldOption?.id === watchEditedField)?.dataType; + }, [watchEditedField, equipmentFields]); + + const watchPropertyName = useWatch({ + name: `${name}.${index}.${PROPERTY_NAME_FIELD}`, + }); + + const predefinedPropertiesNames = useMemo(() => { + return Object.keys(predefinedProperties ?? {}).sort(comparatorStrIgnoreCase); + }, [predefinedProperties]); + + const predefinedPropertiesValues = useMemo(() => { + return [...(predefinedProperties?.[watchPropertyName] ?? [])].sort(comparatorStrIgnoreCase); + }, [watchPropertyName, predefinedProperties]); + + const options = useMemo(() => { + return equipmentFields?.find((fieldOption) => fieldOption?.id === watchEditedField)?.values ?? []; + }, [watchEditedField, equipmentFields]); + + // reset value field only when data type is changed + const prevDataType = usePrevious(dataType); + // important, check prevDataType should not be undefined to ensure that setValue is not called in initialization + if (prevDataType && prevDataType !== dataType) { + setValue(`${name}.${index}.${VALUE_FIELD}`, dataType === DataType.BOOLEAN ? false : null); + } + + const filtersField = ( + + ); + + const editedField = ( + equipmentFields.find((option) => option?.id === value) || value} + outputTransform={(option: any) => getIdOrValue(option) ?? null} + getOptionLabel={(option: any) => (option ? intl.formatMessage({ id: getLabelOrValue(option) }) : option)} + isOptionEqualToValue={areIdsEqual} + /> + ); + + const propertyNameField = ( + + ); + + const valueField = useMemo(() => { + if (dataType === DataType.PROPERTY) { + return ( + + ); + } + + if (dataType === DataType.INTEGER) { + return ; + } + + if (dataType === DataType.BOOLEAN) { + return ; + } + + if (dataType === DataType.ENUM) { + return ( + + ); + } + + // by default is a numeric type + return ; + }, [dataType, name, index, predefinedPropertiesValues, options]); + + return ( + <> + {gridItem(filtersField, 3.25)} + {gridItem(editedField, 3)} + <> + {dataType === DataType.PROPERTY && gridItem(propertyNameField, 2.0)} + {gridItem(, 0.25)} + + {gridItem(valueField, dataType === DataType.PROPERTY ? 2.25 : 4.25)} + + ); +}; + +export default AssignmentForm; diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-utils.ts b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-utils.ts new file mode 100644 index 0000000000..188b95f1e6 --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-utils.ts @@ -0,0 +1,97 @@ +/** + * 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 { + EDITED_FIELD, + FILTERS, + ID, + NAME, + PROPERTY_NAME_FIELD, + VALUE_FIELD, +} from '../../../../../utils/field-constants'; +import yup from 'components/utils/yup-config'; +import { Schema } from 'yup'; +import { Assignment, DataType } from './assignment.type'; +import { FIELD_OPTIONS } from './assignment-constants'; + +export const getDataType = (fieldName?: string | null) => { + return Object.values(FIELD_OPTIONS).find((fieldOption) => fieldOption.id === fieldName)?.dataType; +}; + +// ("undefined" is accepted here in RHF, but it conflicts with MUI behaviour which does not like undefined values) +export const getAssignmentInitialValue = () => ({ + [FILTERS]: [], + [EDITED_FIELD]: null, + [PROPERTY_NAME_FIELD]: null, + [VALUE_FIELD]: null, +}); + +export function getAssignmentsSchema() { + return yup + .array() + .of( + yup.object().shape({ + [FILTERS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .min(1, 'YupRequired'), + [EDITED_FIELD]: yup.string().required(), + [PROPERTY_NAME_FIELD]: yup.string().when([EDITED_FIELD], ([editedField], schema) => { + const dataType = getDataType(editedField); + if (dataType === DataType.PROPERTY) { + return schema.required(); + } + return schema.nullable(); + }), + [VALUE_FIELD]: yup + .mixed() + .when([EDITED_FIELD], ([editedField], schema) => { + const dataType = getDataType(editedField); + return getValueSchema(dataType); + }) + .required(), + }) + ) + .required(); +} + +function getValueSchema(dataType?: DataType) { + let schema: Schema; + // set type + switch (dataType) { + case DataType.DOUBLE: + schema = yup.number(); + break; + case DataType.INTEGER: + schema = yup.number().integer(); + break; + case DataType.ENUM: + case DataType.PROPERTY: + schema = yup.string(); + break; + case DataType.BOOLEAN: + schema = yup.boolean(); + break; + default: + schema = yup.number(); + } + + return schema.required(); +} + +export function getAssignmentFromEditData(assignment: Assignment): Assignment { + return { + ...assignment, + [FILTERS]: assignment.filters.map((filter) => ({ ...filter })), + }; +} diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment.type.ts b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment.type.ts new file mode 100644 index 0000000000..f58deb0d47 --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment.type.ts @@ -0,0 +1,94 @@ +/** + * 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 { Option } from '@gridsuite/commons-ui'; +import { + ASSIGNMENTS, + EDITED_FIELD, + EQUIPMENT_TYPE_FIELD, + FILTERS, + PROPERTY_NAME_FIELD, + VALUE_FIELD, +} from '../../../../../utils/field-constants'; +import { Filter } from '../../commons/by-filter.type'; + +// --- types for the configuration, see the constants file --- // + +export enum DataType { + ENUM = 'ENUM', + BOOLEAN = 'BOOLEAN', + INTEGER = 'INTEGER', + DOUBLE = 'DOUBLE', + PROPERTY = 'PROPERTY', +} + +export type FieldOptionType = { + id: string; + label: string; + dataType: DataType; + values?: Option[]; +}; + +export enum FieldType { + PROPERTY = 'PROPERTY', + RATED_NOMINAL_POWER = 'RATED_NOMINAL_POWER', + MINIMUM_ACTIVE_POWER = 'MINIMUM_ACTIVE_POWER', + MAXIMUM_ACTIVE_POWER = 'MAXIMUM_ACTIVE_POWER', + ACTIVE_POWER_SET_POINT = 'ACTIVE_POWER_SET_POINT', + REACTIVE_POWER_SET_POINT = 'REACTIVE_POWER_SET_POINT', + VOLTAGE_SET_POINT = 'VOLTAGE_SET_POINT', + PLANNED_ACTIVE_POWER_SET_POINT = 'PLANNED_ACTIVE_POWER_SET_POINT', + MARGINAL_COST = 'MARGINAL_COST', + PLANNED_OUTAGE_RATE = 'PLANNED_OUTAGE_RATE', + FORCED_OUTAGE_RATE = 'FORCED_OUTAGE_RATE', + DROOP = 'DROOP', + TRANSIENT_REACTANCE = 'TRANSIENT_REACTANCE', + STEP_UP_TRANSFORMER_REACTANCE = 'STEP_UP_TRANSFORMER_REACTANCE', + Q_PERCENT = 'Q_PERCENT', + VOLTAGE_REGULATOR_ON = 'VOLTAGE_REGULATOR_ON', + MAXIMUM_SECTION_COUNT = 'MAXIMUM_SECTION_COUNT', + SECTION_COUNT = 'SECTION_COUNT', + MAXIMUM_SUSCEPTANCE = 'MAXIMUM_SUSCEPTANCE', + MAXIMUM_Q_AT_NOMINAL_VOLTAGE = 'MAXIMUM_Q_AT_NOMINAL_VOLTAGE', + NOMINAL_VOLTAGE = 'NOMINAL_VOLTAGE', + LOW_VOLTAGE_LIMIT = 'LOW_VOLTAGE_LIMIT', + HIGH_VOLTAGE_LIMIT = 'HIGH_VOLTAGE_LIMIT', + LOW_SHORT_CIRCUIT_CURRENT_LIMIT = 'LOW_SHORT_CIRCUIT_CURRENT_LIMIT', + HIGH_SHORT_CIRCUIT_CURRENT_LIMIT = 'HIGH_SHORT_CIRCUIT_CURRENT_LIMIT', + ACTIVE_POWER = 'ACTIVE_POWER', + REACTIVE_POWER = 'REACTIVE_POWER', + R = 'R', + X = 'X', + G = 'G', + B = 'B', + RATED_U1 = 'RATED_U1', + RATED_U2 = 'RATED_U2', + RATED_S = 'RATED_S', + TARGET_V = 'TARGET_V', + RATIO_LOW_TAP_POSITION = 'RATIO_LOW_TAP_POSITION', + RATIO_TAP_POSITION = 'RATIO_TAP_POSITION', + RATIO_TARGET_DEADBAND = 'RATIO_TARGET_DEADBAND', + REGULATION_VALUE = 'REGULATION_VALUE', + PHASE_LOW_TAP_POSITION = 'PHASE_LOW_TAP_POSITION', + PHASE_TAP_POSITION = 'PHASE_TAP_POSITION', + PHASE_TARGET_DEADBAND = 'PHASE_TARGET_DEADBAND', + LOAD_TYPE = 'LOAD_TYPE', +} + +// --- types for the form model --- // + +export type Assignment = { + [FILTERS]: Filter[]; + [EDITED_FIELD]: string; + [VALUE_FIELD]: string | number | boolean; + [PROPERTY_NAME_FIELD]?: string; +}; + +export type ModificationByAssignment = { + [EQUIPMENT_TYPE_FIELD]: string; + [ASSIGNMENTS]: Assignment[]; +}; diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog.tsx b/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog.tsx new file mode 100644 index 0000000000..09577fd9af --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog.tsx @@ -0,0 +1,127 @@ +/** + * 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 { yupResolver } from '@hookform/resolvers/yup'; +import yup from 'components/utils/yup-config'; +import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; +import { FC, useCallback, useEffect } from 'react'; +import { FetchStatus } from '../../../../../services/utils'; +import { useForm } from 'react-hook-form'; +import ModificationDialog from '../../../commons/modificationDialog'; +import { useOpenShortWaitFetching } from '../../../commons/handle-modification-form'; +import { FORM_LOADING_DELAY } from '../../../../network/constants'; +import ModificationByAssignmentForm from './modification-by-assignment-form'; +import { ASSIGNMENTS, EDITED_FIELD, EQUIPMENT_TYPE_FIELD } from '../../../../utils/field-constants'; +import { modifyByAssignment } from '../../../../../services/study/network-modifications'; +import { + getAssignmentFromEditData, + getAssignmentInitialValue, + getAssignmentsSchema, + getDataType, +} from './assignment/assignment-utils'; +import { Assignment, ModificationByAssignment } from './assignment/assignment.type'; +import { DeepNullable } from '../../../../utils/ts-utils'; + +const formSchema = yup + .object() + .shape({ + [EQUIPMENT_TYPE_FIELD]: yup.string().required(), + [ASSIGNMENTS]: getAssignmentsSchema(), + }) + .required(); + +const emptyFormData = { + [EQUIPMENT_TYPE_FIELD]: '', + [ASSIGNMENTS]: [getAssignmentInitialValue()], +}; + +const ModificationByAssignmentDialog: FC = ({ + editData, + currentNode, + studyUuid, + isUpdate, + editDataFetchStatus, + ...dialogProps +}) => { + const currentNodeUuid = currentNode.id; + const { snackError } = useSnackMessage(); + + // "DeepNullable" to allow deeply null values as default values for required values + // ("undefined" is accepted here in RHF, but it conflicts with MUI behaviour which does not like undefined values) + const formMethods = useForm>({ + defaultValues: emptyFormData, + resolver: yupResolver>(formSchema), + }); + + const open = useOpenShortWaitFetching({ + isDataFetched: + !isUpdate || editDataFetchStatus === FetchStatus.SUCCEED || editDataFetchStatus === FetchStatus.FAILED, + delay: FORM_LOADING_DELAY, + }); + + const { reset } = formMethods; + + useEffect(() => { + if (editData) { + const assignments: Assignment[] = editData.assignmentInfosList?.map(getAssignmentFromEditData); + reset({ + [EQUIPMENT_TYPE_FIELD]: editData.equipmentType, + [ASSIGNMENTS]: assignments, + }); + } + }, [editData, reset]); + + const clear = useCallback(() => { + reset(emptyFormData); + }, [reset]); + + const onSubmit = useCallback( + (formData: ModificationByAssignment) => { + const assignmentsList = formData[ASSIGNMENTS].map((assignment) => { + const dataType = getDataType(assignment[EDITED_FIELD]); + return { + ...assignment, + dataType, + }; + }); + modifyByAssignment( + studyUuid, + currentNodeUuid, + formData[EQUIPMENT_TYPE_FIELD], + assignmentsList, + !!editData, + editData?.uuid ?? null + ).catch((error) => { + snackError({ + messageTxt: error.message, + headerId: 'ModifyByAssignment', + }); + }); + }, + [currentNodeUuid, editData, snackError, studyUuid] + ); + + return ( + + + + + + ); +}; + +export default ModificationByAssignmentDialog; diff --git a/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-form.tsx b/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-form.tsx new file mode 100644 index 0000000000..b137b27e7d --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-form.tsx @@ -0,0 +1,83 @@ +/** + * 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 React, { FC, useEffect } from 'react'; +import { ASSIGNMENTS, EQUIPMENT_TYPE_FIELD } from '../../../../utils/field-constants'; +import ExpandableInput from '../../../../utils/rhf-inputs/expandable-input'; +import AssignmentForm from './assignment/assignment-form'; +import Grid from '@mui/material/Grid'; +import { gridItem } from '../../../dialogUtils'; +import { getAssignmentInitialValue } from './assignment/assignment-utils'; +import { useFormContext, useWatch } from 'react-hook-form'; +import SelectWithConfirmationInput from '../../../commons/select-with-confirmation-input'; +import { usePredefinedProperties } from '@gridsuite/commons-ui'; +import { EQUIPMENTS_FIELDS } from './assignment/assignment-constants'; +import useGetLabelEquipmentTypes from '../../../../../hooks/use-get-label-equipment-types'; + +interface ModificationByAssignmentFormProps {} + +type EquipmentTypeOptionType = keyof typeof EQUIPMENTS_FIELDS; + +const EQUIPMENT_TYPE_OPTIONS: EquipmentTypeOptionType[] = Object.keys(EQUIPMENTS_FIELDS) as EquipmentTypeOptionType[]; + +const ModificationByAssignmentForm: FC = () => { + const { setValue, getValues } = useFormContext(); + const equipmentType: EquipmentTypeOptionType = useWatch({ + name: EQUIPMENT_TYPE_FIELD, + }); + const equipmentFields = EQUIPMENTS_FIELDS[equipmentType] ?? []; + + // get predefined properties + const [predefinedProperties, setEquipmentType] = usePredefinedProperties(equipmentType); + useEffect(() => { + setEquipmentType(equipmentType); + }, [equipmentType, setEquipmentType]); + + const getOptionLabel = useGetLabelEquipmentTypes(); + + const equipmentTypeField = ( + { + setValue( + ASSIGNMENTS, + getValues(ASSIGNMENTS).map(() => ({ + ...getAssignmentInitialValue(), + })) + ); + }} + getOptionLabel={getOptionLabel} + /> + ); + + const assignmentsField = ( + + ); + + return ( + <> + + {gridItem(equipmentTypeField, 2.15)} + + {gridItem(assignmentsField, 12)} + + ); +}; + +export default ModificationByAssignmentForm; diff --git a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-dialog.tsx b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx similarity index 91% rename from src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-dialog.tsx rename to src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx index 98f21b166e..bdb0c3cce7 100644 --- a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-dialog.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx @@ -7,16 +7,16 @@ import { yupResolver } from '@hookform/resolvers/yup'; import yup from 'components/utils/yup-config'; -import { FILTERS, ID, NAME, SPECIFIC_METADATA, TYPE } from '../../../utils/field-constants'; +import { FILTERS, ID, NAME, TYPE } from '../../../../utils/field-constants'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; import { FunctionComponent, useCallback, useEffect } from 'react'; -import ModificationDialog from '../../commons/modificationDialog'; +import ModificationDialog from '../../../commons/modificationDialog'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; import { FORM_LOADING_DELAY } from 'components/network/constants'; -import { deleteEquipmentByFilter } from '../../../../services/study/network-modifications'; -import { FetchStatus } from '../../../../services/utils'; +import { deleteEquipmentByFilter } from '../../../../../services/study/network-modifications'; +import { FetchStatus } from '../../../../../services/utils'; import ByFilterDeletionForm from './by-filter-deletion-form'; import { ByFilterDeletionDialogProps, @@ -34,9 +34,6 @@ const formSchema = yup yup.object().shape({ [ID]: yup.string().required(), [NAME]: yup.string().required(), - [SPECIFIC_METADATA]: yup.object().shape({ - [TYPE]: yup.string(), - }), }) ) .required() diff --git a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-form.tsx b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-form.tsx similarity index 92% rename from src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-form.tsx rename to src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-form.tsx index 8bb4b22ee6..42efd828d7 100644 --- a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion-form.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-form.tsx @@ -8,14 +8,12 @@ import Grid from '@mui/material/Grid'; import React, { useCallback, useMemo } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; -import { AutocompleteInput, ElementType } from '@gridsuite/commons-ui'; +import { AutocompleteInput, DirectoryItemsInput, ElementType } from '@gridsuite/commons-ui'; import { gridItem } from 'components/dialogs/dialogUtils'; import { FILTERS, TYPE } from 'components/utils/field-constants'; import { richTypeEquals } from 'components/utils/utils'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; - -import { DirectoryItemsInput } from '@gridsuite/commons-ui'; -import useGetLabelEquipmentTypes from '../../../../hooks/use-get-label-equipment-types'; +import useGetLabelEquipmentTypes from '../../../../../hooks/use-get-label-equipment-types'; const ByFilterDeletionForm = () => { const equipmentTypeWatch = useWatch({ diff --git a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion.type.ts b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion.type.ts similarity index 80% rename from src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion.type.ts rename to src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion.type.ts index 361a078371..e3baf51fb5 100644 --- a/src/components/dialogs/network-modifications/by-filter-deletion/by-filter-deletion.type.ts +++ b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion.type.ts @@ -5,15 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { EQUIPMENT_TYPES } from '../../../utils/equipment-types'; - -interface Filter { - id: string; - name: string; - specificMetadata: { - type?: string; - }; -} +import { EQUIPMENT_TYPES } from '../../../../utils/equipment-types'; +import { Filter } from '../commons/by-filter.type'; export interface ByFilterDeletionEditData { uuid: string; diff --git a/src/components/dialogs/network-modifications/by-formula/by-formula-dialog.jsx b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx similarity index 90% rename from src/components/dialogs/network-modifications/by-formula/by-formula-dialog.jsx rename to src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx index 3f2ec4d591..43d5b38aa0 100644 --- a/src/components/dialogs/network-modifications/by-formula/by-formula-dialog.jsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx @@ -9,11 +9,11 @@ import { yupResolver } from '@hookform/resolvers/yup'; import yup from 'components/utils/yup-config'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { useCallback, useEffect } from 'react'; -import { FetchStatus } from '../../../../services/utils'; +import { FetchStatus } from '../../../../../services/utils'; import { useForm } from 'react-hook-form'; -import ModificationDialog from '../../commons/modificationDialog'; -import { useOpenShortWaitFetching } from '../../commons/handle-modification-form'; -import { FORM_LOADING_DELAY } from '../../../network/constants'; +import ModificationDialog from '../../../commons/modificationDialog'; +import { useOpenShortWaitFetching } from '../../../commons/handle-modification-form'; +import { FORM_LOADING_DELAY } from '../../../../network/constants'; import ByFormulaForm from './by-formula-form'; import { EDITED_FIELD, @@ -25,8 +25,8 @@ import { REFERENCE_FIELD_OR_VALUE_1, REFERENCE_FIELD_OR_VALUE_2, VALUE, -} from '../../../utils/field-constants'; -import { modifyByFormula } from '../../../../services/study/network-modifications'; +} from '../../../../utils/field-constants'; +import { modifyByFormula } from '../../../../../services/study/network-modifications'; import { getFormulaInitialValue, getFormulaSchema } from './formula/formula-utils'; function getFieldOrValue(input) { @@ -113,7 +113,7 @@ const ByFormulaDialog = ({ editData, currentNode, studyUuid, isUpdate, editDataF ).catch((error) => { snackError({ messageTxt: error.message, - headerId: 'ByFormulaModification', + headerId: 'ModifyByFormula', }); }); }, diff --git a/src/components/dialogs/network-modifications/by-formula/by-formula-form.tsx b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-form.tsx similarity index 86% rename from src/components/dialogs/network-modifications/by-formula/by-formula-form.tsx rename to src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-form.tsx index 47b3b96081..dd8564dd34 100644 --- a/src/components/dialogs/network-modifications/by-formula/by-formula-form.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-form.tsx @@ -7,14 +7,14 @@ import React, { FunctionComponent } from 'react'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; -import { EQUIPMENT_TYPE_FIELD, FORMULAS } from '../../../utils/field-constants'; -import ExpandableInput from '../../../utils/rhf-inputs/expandable-input'; +import { EQUIPMENT_TYPE_FIELD, FORMULAS } from '../../../../utils/field-constants'; +import ExpandableInput from '../../../../utils/rhf-inputs/expandable-input'; import FormulaForm from './formula/formula-form'; import Grid from '@mui/material/Grid'; -import { gridItem } from '../../dialogUtils'; +import { gridItem } from '../../../dialogUtils'; import { getFormulaInitialValue } from './formula/formula-utils'; import { useFormContext } from 'react-hook-form'; -import SelectWithConfirmationInput from './select-with-confirmation-input'; +import SelectWithConfirmationInput from '../../../commons/select-with-confirmation-input'; interface ByFormulaFormProps {} diff --git a/src/components/dialogs/network-modifications/by-formula/formula/formula-form.tsx b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-form.tsx similarity index 92% rename from src/components/dialogs/network-modifications/by-formula/formula/formula-form.tsx rename to src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-form.tsx index bc9be73b50..cce0741578 100644 --- a/src/components/dialogs/network-modifications/by-formula/formula/formula-form.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-form.tsx @@ -6,7 +6,7 @@ */ import React, { FunctionComponent } from 'react'; -import { AutocompleteInput, ElementType } from '@gridsuite/commons-ui'; +import { AutocompleteInput, DirectoryItemsInput, ElementType } from '@gridsuite/commons-ui'; import { EDITED_FIELD, EQUIPMENT_TYPE_FIELD, @@ -14,14 +14,13 @@ import { OPERATOR, REFERENCE_FIELD_OR_VALUE_1, REFERENCE_FIELD_OR_VALUE_2, -} from '../../../../utils/field-constants'; +} from '../../../../../utils/field-constants'; import { useWatch } from 'react-hook-form'; -import { DirectoryItemsInput } from '@gridsuite/commons-ui'; -import { gridItem } from '../../../dialogUtils'; +import { gridItem } from '../../../../dialogUtils'; import { EQUIPMENTS_FIELDS } from './formula-utils'; import ReferenceAutocompleteInput from './reference-autocomplete-input'; import DragHandleIcon from '@mui/icons-material/DragHandle'; -import { getIdOrValue, getLabelOrValue } from '../../../commons/utils'; +import { getIdOrValue, getLabelOrValue } from '../../../../commons/utils'; import { useIntl } from 'react-intl'; import Grid from '@mui/material/Grid'; diff --git a/src/components/dialogs/network-modifications/by-formula/formula/formula-utils.tsx b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx similarity index 98% rename from src/components/dialogs/network-modifications/by-formula/formula/formula-utils.tsx rename to src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx index db7a27ad79..f7175f3c28 100644 --- a/src/components/dialogs/network-modifications/by-formula/formula/formula-utils.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { EQUIPMENT_TYPES } from '../../../../utils/equipment-types'; +import { EQUIPMENT_TYPES } from '../../../../../utils/equipment-types'; import { EDITED_FIELD, EQUIPMENT_TYPE_FIELD, @@ -17,7 +17,7 @@ import { REFERENCE_FIELD_OR_VALUE_2, SPECIFIC_METADATA, TYPE, -} from '../../../../utils/field-constants'; +} from '../../../../../utils/field-constants'; import yup from 'components/utils/yup-config'; import { AnyObject, TestContext, TestFunction } from 'yup'; diff --git a/src/components/dialogs/network-modifications/by-formula/formula/reference-autocomplete-input.tsx b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/reference-autocomplete-input.tsx similarity index 98% rename from src/components/dialogs/network-modifications/by-formula/formula/reference-autocomplete-input.tsx rename to src/components/dialogs/network-modifications/by-filter/by-formula/formula/reference-autocomplete-input.tsx index 6d5191af82..61a51d0894 100644 --- a/src/components/dialogs/network-modifications/by-formula/formula/reference-autocomplete-input.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/reference-autocomplete-input.tsx @@ -10,7 +10,7 @@ import { useController } from 'react-hook-form'; import { FilterOptionsState } from '@mui/material'; import { useIntl } from 'react-intl'; import { AutocompleteInput } from '@gridsuite/commons-ui'; -import { areIdsEqual } from '../../../../utils/utils'; +import { areIdsEqual } from '../../../../../utils/utils'; const ReferenceAutocompleteInput: FunctionComponent<{ name: string; diff --git a/src/components/dialogs/network-modifications/by-filter/commons/by-filter.type.ts b/src/components/dialogs/network-modifications/by-filter/commons/by-filter.type.ts new file mode 100644 index 0000000000..8c61dac54f --- /dev/null +++ b/src/components/dialogs/network-modifications/by-filter/commons/by-filter.type.ts @@ -0,0 +1,13 @@ +/** + * 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 { ID, NAME } from '../../../../utils/field-constants'; + +export type Filter = { + [ID]: string; + [NAME]: string; +}; diff --git a/src/components/graph/menus/network-modification-node-editor.tsx b/src/components/graph/menus/network-modification-node-editor.tsx index e88da58332..6b164a8a3a 100644 --- a/src/components/graph/menus/network-modification-node-editor.tsx +++ b/src/components/graph/menus/network-modification-node-editor.tsx @@ -15,7 +15,7 @@ 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 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'; @@ -73,7 +73,6 @@ import { FetchStatus } from '../../../services/utils'; import CreateCompositeModificationDialog, { ICompositeCreateModificationDialog, } from '../../dialogs/create-composite-modification-dialog'; -import ByFilterDeletionDialog from '../../dialogs/network-modifications/by-filter-deletion/by-filter-deletion-dialog'; import { useModificationLabelComputer } from '../util/use-modification-label-computer.jsx'; import { MenuDefinition, @@ -85,6 +84,9 @@ import { NetworkModificationMetadata, } from './network-modification-menu.type'; import { SwitchNetworkModificationActive } from './switch-network-modification-active'; +import ModificationByAssignmentDialog from '../../dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog'; +import ByFormulaDialog from '../../dialogs/network-modifications/by-filter/by-formula/by-formula-dialog'; +import ByFilterDeletionDialog from '../../dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog'; export const styles = { listContainer: (theme: Theme) => ({ @@ -381,6 +383,11 @@ const NetworkModificationNodeEditor = () => { label: 'BY_FORMULA', action: () => withDefaultParams(ByFormulaDialog), }, + { + id: MODIFICATION_TYPES.MODIFICATION_BY_ASSIGNMENT.type, + label: 'BY_FILTER', + action: () => withDefaultParams(ModificationByAssignmentDialog), + }, ], }, { diff --git a/src/components/utils/field-constants.js b/src/components/utils/field-constants.js index c8e2c14e5b..7af5225661 100644 --- a/src/components/utils/field-constants.js +++ b/src/components/utils/field-constants.js @@ -328,6 +328,11 @@ export const REFERENCE_FIELD_OR_VALUE_1 = 'referenceFieldOrValue1'; export const REFERENCE_FIELD_OR_VALUE_2 = 'referenceFieldOrValue2'; export const EQUIPMENT_FIELD = 'equipmentField'; +// By filter +export const ASSIGNMENTS = 'assignments'; +export const PROPERTY_NAME_FIELD = 'propertyName'; +export const VALUE_FIELD = 'value'; + // non evacuated energy export const GENERATION_STAGES_KIND = 'energySource'; export const GENERATION_STAGES_PERCENT_MAXP_1 = 'percentMaxP1'; diff --git a/src/components/utils/modification-type.ts b/src/components/utils/modification-type.ts index 8bac7eaabe..98afb67322 100644 --- a/src/components/utils/modification-type.ts +++ b/src/components/utils/modification-type.ts @@ -40,6 +40,7 @@ export enum MODIFICATION_TYPE { CONVERTER_STATION_CREATION = 'CONVERTER_STATION_CREATION', TABULAR_MODIFICATION = 'TABULAR_MODIFICATION', BY_FORMULA_MODIFICATION = 'BY_FORMULA_MODIFICATION', + MODIFICATION_BY_ASSIGNMENT = 'MODIFICATION_BY_ASSIGNMENT', TABULAR_CREATION = 'TABULAR_CREATION', VSC_MODIFICATION = 'VSC_MODIFICATION', CONVERTER_STATION_MODIFICATION = 'CONVERTER_STATION_MODIFICATION', @@ -149,6 +150,9 @@ export const MODIFICATION_TYPES = { BY_FORMULA_MODIFICATION: { type: MODIFICATION_TYPE.BY_FORMULA_MODIFICATION, }, + MODIFICATION_BY_ASSIGNMENT: { + type: 'MODIFICATION_BY_ASSIGNMENT', + }, TABULAR_CREATION: { type: MODIFICATION_TYPE.TABULAR_CREATION, }, diff --git a/src/components/utils/ts-utils.ts b/src/components/utils/ts-utils.ts index 04e2ce07fd..32724b9b66 100644 --- a/src/components/utils/ts-utils.ts +++ b/src/components/utils/ts-utils.ts @@ -5,3 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export type Nullable = { [K in keyof T]: T[K] | null }; +export type DeepNullable = { + [K in keyof T]: DeepNullable | null; +}; diff --git a/src/components/utils/utils.js b/src/components/utils/utils.js index 713a2c084f..3cadb3fe6e 100644 --- a/src/components/utils/utils.js +++ b/src/components/utils/utils.js @@ -250,3 +250,7 @@ export function toggleElementFromList(elementToToggle, list, getFieldId) { } return resultList; } + +export const comparatorStrIgnoreCase = (str1, str2) => { + return str1.toLowerCase().localeCompare(str2.toLowerCase()); +}; diff --git a/src/services/study/network-modifications.js b/src/services/study/network-modifications.js index 6fa5e29c62..0cff5ba0fb 100644 --- a/src/services/study/network-modifications.js +++ b/src/services/study/network-modifications.js @@ -8,17 +8,19 @@ import { MODIFICATION_TYPES } from '../../components/utils/modification-type'; import { toModificationOperation, toModificationUnsetOperation } from '../../components/utils/utils'; import { backendFetch, backendFetchJson, backendFetchText } from '../utils'; -import { getStudyUrlWithNodeUuid, PREFIX_STUDY_QUERIES } from './index'; +import { getStudyUrlWithNodeUuid } from './index'; import { EQUIPMENT_TYPES } from '../../components/utils/equipment-types'; import { BRANCH_SIDE, OPERATING_STATUS_ACTION } from '../../components/network/constants'; -export function changeNetworkModificationOrder(studyUuid, currentNodeUuid, itemUuid, beforeUuid) { - console.info('reorder node ' + currentNodeUuid + ' of study ' + studyUuid + ' ...'); +function getNetworkModificationUrl(studyUuid, nodeUuid) { + return getStudyUrlWithNodeUuid(studyUuid, nodeUuid) + '/network-modifications'; +} + +export function changeNetworkModificationOrder(studyUuid, nodeUuid, itemUuid, beforeUuid) { + console.info('reorder node ' + nodeUuid + ' of study ' + studyUuid + ' ...'); const url = - getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + - '/network-modification/' + - itemUuid + - '?' + + getNetworkModificationUrl(studyUuid, nodeUuid) + + `/${itemUuid}?` + new URLSearchParams({ beforeUuid: beforeUuid || '' }).toString(); console.debug(url); return backendFetch(url, { method: 'put' }); @@ -28,15 +30,7 @@ export function stashModifications(studyUuid, nodeUuid, modificationUuids) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('stashed', true); urlSearchParams.append('uuids', modificationUuids); - const modificationDeleteUrl = - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - encodeURIComponent(studyUuid) + - '/nodes/' + - encodeURIComponent(nodeUuid) + - '/network-modifications' + - '?' + - urlSearchParams.toString(); + const modificationDeleteUrl = getNetworkModificationUrl(studyUuid, nodeUuid) + '?' + urlSearchParams.toString(); console.debug(modificationDeleteUrl); return backendFetch(modificationDeleteUrl, { method: 'PUT', @@ -48,14 +42,7 @@ export function setModificationActivated(studyUuid, nodeUuid, modificationUuid, urlSearchParams.append('activated', activated); urlSearchParams.append('uuids', [modificationUuid]); const modificationUpdateActiveUrl = - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - encodeURIComponent(studyUuid) + - '/nodes/' + - encodeURIComponent(nodeUuid) + - '/network-modifications' + - '?' + - urlSearchParams.toString(); + getNetworkModificationUrl(studyUuid, nodeUuid) + '?' + urlSearchParams.toString(); console.debug(modificationUpdateActiveUrl); return backendFetch(modificationUpdateActiveUrl, { method: 'PUT', @@ -66,15 +53,7 @@ export function restoreModifications(studyUuid, nodeUuid, modificationUuids) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('stashed', false); urlSearchParams.append('uuids', modificationUuids); - const RestoreModificationsUrl = - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - encodeURIComponent(studyUuid) + - '/nodes/' + - encodeURIComponent(nodeUuid) + - '/network-modifications' + - '?' + - urlSearchParams.toString(); + const RestoreModificationsUrl = getNetworkModificationUrl(studyUuid, nodeUuid) + '?' + urlSearchParams.toString(); console.debug(RestoreModificationsUrl); return backendFetch(RestoreModificationsUrl, { @@ -86,14 +65,7 @@ export function deleteModifications(studyUuid, nodeUuid, modificationUuids) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('uuids', modificationUuids); - const modificationDeleteUrl = - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - encodeURIComponent(studyUuid) + - '/nodes/' + - encodeURIComponent(nodeUuid) + - '/network-modifications?' + - urlSearchParams.toString(); + const modificationDeleteUrl = getNetworkModificationUrl(studyUuid, nodeUuid) + '?' + urlSearchParams.toString(); console.debug(modificationDeleteUrl); return backendFetch(modificationDeleteUrl, { @@ -101,9 +73,9 @@ export function deleteModifications(studyUuid, nodeUuid, modificationUuids) { }); } -export function requestNetworkChange(studyUuid, currentNodeUuid, groovyScript) { +export function requestNetworkChange(studyUuid, nodeUuid, groovyScript) { console.info('Creating groovy script (request network change)'); - const changeUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + const changeUrl = getNetworkModificationUrl(studyUuid, nodeUuid); console.debug(changeUrl); return backendFetchText(changeUrl, { method: 'POST', @@ -118,8 +90,8 @@ export function requestNetworkChange(studyUuid, currentNodeUuid, groovyScript) { }); } -function changeOperatingStatus(studyUuid, currentNodeUuid, equipment, action) { - const changeOperatingStatusUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; +function changeOperatingStatus(studyUuid, nodeUuid, equipment, action) { + const changeOperatingStatusUrl = getNetworkModificationUrl(studyUuid, nodeUuid); console.debug('%s with action: %s', changeOperatingStatusUrl, action); let energizedVoltageLevelId; @@ -149,21 +121,21 @@ function changeOperatingStatus(studyUuid, currentNodeUuid, equipment, action) { }); } -export function lockoutEquipment(studyUuid, currentNodeUuid, equipment) { +export function lockoutEquipment(studyUuid, nodeUuid, equipment) { console.info('locking out equipment ' + equipment.id + ' ...'); - return changeOperatingStatus(studyUuid, currentNodeUuid, equipment, OPERATING_STATUS_ACTION.LOCKOUT); + return changeOperatingStatus(studyUuid, nodeUuid, equipment, OPERATING_STATUS_ACTION.LOCKOUT); } -export function tripEquipment(studyUuid, currentNodeUuid, equipment) { +export function tripEquipment(studyUuid, nodeUuid, equipment) { console.info('tripping equipment ' + equipment.id + ' ...'); - return changeOperatingStatus(studyUuid, currentNodeUuid, equipment, OPERATING_STATUS_ACTION.TRIP); + return changeOperatingStatus(studyUuid, nodeUuid, equipment, OPERATING_STATUS_ACTION.TRIP); } -export function energiseEquipmentEnd(studyUuid, currentNodeUuid, branch, branchSide) { +export function energiseEquipmentEnd(studyUuid, nodeUuid, branch, branchSide) { console.info('energise branch ' + branch.id + ' on side ' + branchSide + ' ...'); return changeOperatingStatus( studyUuid, - currentNodeUuid, + nodeUuid, branch, branchSide === BRANCH_SIDE.ONE ? OPERATING_STATUS_ACTION.ENERGISE_END_ONE @@ -171,14 +143,14 @@ export function energiseEquipmentEnd(studyUuid, currentNodeUuid, branch, branchS ); } -export function switchOnEquipment(studyUuid, currentNodeUuid, branch) { +export function switchOnEquipment(studyUuid, nodeUuid, branch) { console.info('switching on branch ' + branch.id + ' ...'); - return changeOperatingStatus(studyUuid, currentNodeUuid, branch, OPERATING_STATUS_ACTION.SWITCH_ON); + return changeOperatingStatus(studyUuid, nodeUuid, branch, OPERATING_STATUS_ACTION.SWITCH_ON); } export function generationDispatch( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lossCoefficient, defaultOutageRate, @@ -197,7 +169,7 @@ export function generationDispatch( substationsGeneratorsOrdering: substationsGeneratorsOrdering, }); - let generationDispatchUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let generationDispatchUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { console.info('Updating generation dispatch ', body); generationDispatchUrl = generationDispatchUrl + '/' + encodeURIComponent(modificationUuid); @@ -215,14 +187,14 @@ export function generationDispatch( }); } -export function generatorScaling(studyUuid, currentNodeUuid, modificationUuid, variationType, variations) { +export function generatorScaling(studyUuid, nodeUuid, modificationUuid, variationType, variations) { const body = JSON.stringify({ type: MODIFICATION_TYPES.GENERATOR_SCALING.type, variationType, variations, }); - let generatorScalingUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let generatorScalingUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { console.info('generator scaling update', body); generatorScalingUrl = generatorScalingUrl + '/' + encodeURIComponent(modificationUuid); @@ -240,7 +212,7 @@ export function generatorScaling(studyUuid, currentNodeUuid, modificationUuid, v export function createBattery( studyUuid, - currentNodeUuid, + nodeUuid, id, name, voltageLevelId, @@ -263,7 +235,7 @@ export function createBattery( modificationUuid, properties ) { - let createBatteryUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createBatteryUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createBatteryUrl += '/' + encodeURIComponent(modificationUuid); @@ -305,7 +277,7 @@ export function createBattery( export function modifyBattery( studyUuid, - currentNodeUuid, + nodeUuid, batteryId, name, minP, @@ -327,7 +299,7 @@ export function modifyBattery( reactiveCapabilityCurve, properties ) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationId) { modificationUrl += '/' + encodeURIComponent(modificationId); @@ -370,7 +342,7 @@ export function modifyBattery( export function createLoad( studyUuid, - currentNodeUuid, + nodeUuid, id, name, loadType, @@ -386,7 +358,7 @@ export function createLoad( terminalConnected, properties ) { - let createLoadUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createLoadUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createLoadUrl += '/' + encodeURIComponent(modificationUuid); @@ -421,7 +393,7 @@ export function createLoad( export function modifyLoad( studyUuid, - currentNodeUuid, + nodeUuid, id, name, loadType, @@ -437,7 +409,7 @@ export function modifyLoad( modificationUuid, properties ) { - let modifyLoadUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modifyLoadUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modifyLoadUrl += '/' + encodeURIComponent(modificationUuid); @@ -472,7 +444,7 @@ export function modifyLoad( export function modifyGenerator( studyUuid, - currentNodeUuid, + nodeUuid, generatorId, name, energySource, @@ -509,7 +481,7 @@ export function modifyGenerator( reactiveCapabilityCurve, properties ) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationId) { modificationUrl += '/' + encodeURIComponent(modificationId); @@ -567,7 +539,7 @@ export function modifyGenerator( export function createGenerator( studyUuid, - currentNodeUuid, + nodeUuid, id, name, energySource, @@ -604,7 +576,7 @@ export function createGenerator( terminalConnected, properties ) { - let createGeneratorUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createGeneratorUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createGeneratorUrl += '/' + encodeURIComponent(modificationUuid); @@ -660,7 +632,7 @@ export function createGenerator( export function createShuntCompensator( studyUuid, - currentNodeUuid, + nodeUuid, shuntCompensatorId, shuntCompensatorName, maxSusceptance, @@ -677,7 +649,7 @@ export function createShuntCompensator( terminalConnected, properties ) { - let createShuntUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createShuntUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createShuntUrl += '/' + encodeURIComponent(modificationUuid); @@ -714,7 +686,7 @@ export function createShuntCompensator( export function modifyShuntCompensator( studyUuid, - currentNodeUuid, + nodeUuid, shuntCompensatorId, shuntCompensatorName, maximumSectionCount, @@ -732,7 +704,7 @@ export function modifyShuntCompensator( modificationUuid, properties ) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modificationUrl += '/' + encodeURIComponent(modificationUuid); @@ -769,7 +741,7 @@ export function modifyShuntCompensator( export function createLine( studyUuid, - currentNodeUuid, + nodeUuid, lineId, lineName, r, @@ -798,7 +770,7 @@ export function createLine( connected2, properties ) { - let createLineUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createLineUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createLineUrl += '/' + encodeURIComponent(modificationUuid); @@ -850,7 +822,7 @@ export function createLine( export function modifyLine( studyUuid, - currentNodeUuid, + nodeUuid, lineId, lineName, r, @@ -877,7 +849,7 @@ export function modifyLine( modificationUuid, properties ) { - let modifyLineUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modifyLineUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modifyLineUrl += '/' + encodeURIComponent(modificationUuid); @@ -923,7 +895,7 @@ export function modifyLine( export function createTwoWindingsTransformer( studyUuid, - currentNodeUuid, + nodeUuid, twoWindingsTransformerId, twoWindingsTransformerName, r, @@ -953,8 +925,7 @@ export function createTwoWindingsTransformer( connected2, properties ) { - let createTwoWindingsTransformerUrl = - getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createTwoWindingsTransformerUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createTwoWindingsTransformerUrl += '/' + encodeURIComponent(modificationUuid); @@ -1003,7 +974,7 @@ export function createTwoWindingsTransformer( export function modifyTwoWindingsTransformer( studyUuid, - currentNodeUuid, + nodeUuid, twoWindingsTransformerId, twoWindingsTransformerName, r, @@ -1033,8 +1004,7 @@ export function modifyTwoWindingsTransformer( modificationUuid, properties ) { - let modifyTwoWindingsTransformerUrl = - getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modifyTwoWindingsTransformerUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modifyTwoWindingsTransformerUrl += '/' + encodeURIComponent(modificationUuid); @@ -1083,13 +1053,13 @@ export function modifyTwoWindingsTransformer( export function createTabulareModification( studyUuid, - currentNodeUuid, + nodeUuid, modificationType, modifications, isUpdate, modificationUuid ) { - let createTabulareModificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createTabulareModificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createTabulareModificationUrl += '/' + encodeURIComponent(modificationUuid); @@ -1114,7 +1084,7 @@ export function createTabulareModification( export function createSubstation( studyUuid, - currentNodeUuid, + nodeUuid, substationId, substationName, country, @@ -1122,7 +1092,7 @@ export function createSubstation( modificationUuid, properties ) { - let url = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let url = getNetworkModificationUrl(studyUuid, nodeUuid); const body = JSON.stringify({ type: MODIFICATION_TYPES.SUBSTATION_CREATION.type, @@ -1203,7 +1173,7 @@ export function formatPropertiesForBackend(previousProperties, newProperties) { export function modifySubstation( studyUuid, - currentNodeUuid, + nodeUuid, id, name, country, @@ -1211,7 +1181,7 @@ export function modifySubstation( modificationUuid, properties ) { - let modifyUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modifyUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modifyUrl += '/' + encodeURIComponent(modificationUuid); @@ -1238,7 +1208,7 @@ export function modifySubstation( export function createVoltageLevel({ studyUuid, - currentNodeUuid, + nodeUuid, voltageLevelId, voltageLevelName, substationId, @@ -1255,7 +1225,7 @@ export function createVoltageLevel({ modificationUuid, properties, }) { - let createVoltageLevelUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createVoltageLevelUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createVoltageLevelUrl += '/' + encodeURIComponent(modificationUuid); @@ -1293,7 +1263,7 @@ export function createVoltageLevel({ export function modifyVoltageLevel( studyUuid, - currentNodeUuid, + nodeUuid, voltageLevelId, voltageLevelName, nominalV, @@ -1305,7 +1275,7 @@ export function modifyVoltageLevel( modificationUuid, properties ) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modificationUrl += '/' + encodeURIComponent(modificationUuid); @@ -1336,7 +1306,7 @@ export function modifyVoltageLevel( export function divideLine( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lineToSplitId, percent, @@ -1361,7 +1331,7 @@ export function divideLine( newLine2Name, }); - let lineSplitUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let lineSplitUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { lineSplitUrl += '/' + encodeURIComponent(modificationUuid); @@ -1382,7 +1352,7 @@ export function divideLine( export function attachLine( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lineToAttachToId, percent, @@ -1413,7 +1383,7 @@ export function attachLine( newLine2Name, }); - let lineAttachUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let lineAttachUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { lineAttachUrl += '/' + encodeURIComponent(modificationUuid); @@ -1432,14 +1402,14 @@ export function attachLine( }); } -export function loadScaling(studyUuid, currentNodeUuid, modificationUuid, variationType, variations) { +export function loadScaling(studyUuid, nodeUuid, modificationUuid, variationType, variations) { const body = JSON.stringify({ type: MODIFICATION_TYPES.LOAD_SCALING.type, variationType, variations, }); - let loadScalingUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let loadScalingUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { console.info('load scaling update', body); loadScalingUrl = loadScalingUrl + '/' + encodeURIComponent(modificationUuid); @@ -1457,7 +1427,7 @@ export function loadScaling(studyUuid, currentNodeUuid, modificationUuid, variat export function linesAttachToSplitLines( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lineToAttachTo1Id, lineToAttachTo2Id, @@ -1482,7 +1452,7 @@ export function linesAttachToSplitLines( replacingLine2Name, }); - let lineAttachUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let lineAttachUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { lineAttachUrl += '/' + encodeURIComponent(modificationUuid); @@ -1503,7 +1473,7 @@ export function linesAttachToSplitLines( export function deleteVoltageLevelOnLine( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lineToAttachTo1Id, lineToAttachTo2Id, @@ -1518,7 +1488,7 @@ export function deleteVoltageLevelOnLine( replacingLine1Name, }); - let deleteVoltageLevelOnLineUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let deleteVoltageLevelOnLineUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { console.info('Updating delete voltage level on line', body); deleteVoltageLevelOnLineUrl += '/' + encodeURIComponent(modificationUuid); @@ -1538,7 +1508,7 @@ export function deleteVoltageLevelOnLine( export function deleteAttachingLine( studyUuid, - currentNodeUuid, + nodeUuid, modificationUuid, lineToAttachTo1Id, lineToAttachTo2Id, @@ -1555,7 +1525,7 @@ export function deleteAttachingLine( replacingLine1Name, }); - let deleteVoltageLevelOnLineUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let deleteVoltageLevelOnLineUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { console.info('Updating delete attaching line', body); deleteVoltageLevelOnLineUrl += '/' + encodeURIComponent(modificationUuid); @@ -1573,15 +1543,8 @@ export function deleteAttachingLine( }); } -export function deleteEquipment( - studyUuid, - currentNodeUuid, - equipmentType, - equipmentId, - modificationUuid, - equipmentInfos -) { - let deleteEquipmentUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; +export function deleteEquipment(studyUuid, nodeUuid, equipmentType, equipmentId, modificationUuid, equipmentInfos) { + let deleteEquipmentUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { deleteEquipmentUrl += '/' + encodeURIComponent(modificationUuid); @@ -1605,8 +1568,8 @@ export function deleteEquipment( }); } -export function deleteEquipmentByFilter(studyUuid, currentNodeUuid, equipmentType, filters, modificationUuid) { - let deleteEquipmentUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; +export function deleteEquipmentByFilter(studyUuid, nodeUuid, equipmentType, filters, modificationUuid) { + let deleteEquipmentUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { deleteEquipmentUrl += '/' + encodeURIComponent(modificationUuid); @@ -1634,21 +1597,14 @@ export function fetchNetworkModifications(studyUuid, nodeUuid, onlyStashed) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('onlyStashed', onlyStashed); urlSearchParams.append('onlyMetadata', true); - const modificationsGetUrl = - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - encodeURIComponent(studyUuid) + - '/nodes/' + - encodeURIComponent(nodeUuid) + - '/network-modifications?' + - urlSearchParams.toString(); + const modificationsGetUrl = getNetworkModificationUrl(studyUuid, nodeUuid) + '?' + urlSearchParams.toString(); console.debug(modificationsGetUrl); return backendFetchJson(modificationsGetUrl); } -export function updateSwitchState(studyUuid, currentNodeUuid, switchId, open) { +export function updateSwitchState(studyUuid, nodeUuid, switchId, open) { console.info('updating switch ' + switchId + ' ...'); - const updateSwitchUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + const updateSwitchUrl = getNetworkModificationUrl(studyUuid, nodeUuid); console.debug(updateSwitchUrl); return backendFetch(updateSwitchUrl, { method: 'POST', @@ -1668,7 +1624,7 @@ export function updateSwitchState(studyUuid, currentNodeUuid, switchId, open) { export function createVsc( studyUuid, - currentNodeUuid, + nodeUuid, id, name, nominalV, @@ -1687,7 +1643,7 @@ export function createVsc( isUpdate, modificationUuid ) { - let createVscUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let createVscUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createVscUrl += '/' + encodeURIComponent(modificationUuid); @@ -1727,7 +1683,7 @@ export function createVsc( export function modifyVsc( studyUuid, - currentNodeUuid, + nodeUuid, id, name, nominalV, @@ -1746,7 +1702,7 @@ export function modifyVsc( isUpdate, modificationUuid ) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (modificationUuid) { modificationUrl += '/' + encodeURIComponent(modificationUuid); @@ -1783,8 +1739,8 @@ export function modifyVsc( body: JSON.stringify(vscModification), }); } -export function modifyByFormula(studyUuid, currentNodeUuid, equipmentType, formulas, isUpdate, modificationUuid) { - let modificationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; +export function modifyByFormula(studyUuid, nodeUuid, equipmentType, formulas, isUpdate, modificationUuid) { + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { modificationUrl += '/' + encodeURIComponent(modificationUuid); @@ -1809,8 +1765,34 @@ export function modifyByFormula(studyUuid, currentNodeUuid, equipmentType, formu }); } -export function createTabularCreation(studyUuid, currentNodeUuid, creationType, creations, isUpdate, modificationUuid) { - let createTabularCreationUrl = getStudyUrlWithNodeUuid(studyUuid, currentNodeUuid) + '/network-modifications'; +export function modifyByAssignment(studyUuid, nodeUuid, equipmentType, assignmentsList, isUpdate, modificationUuid) { + let modificationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); + + if (isUpdate) { + modificationUrl += '/' + encodeURIComponent(modificationUuid); + console.info('Updating modification by assignment'); + } else { + console.info('Creating modification by assignment'); + } + + const body = JSON.stringify({ + type: MODIFICATION_TYPES.MODIFICATION_BY_ASSIGNMENT.type, + equipmentType: equipmentType, + assignmentInfosList: assignmentsList, + }); + + return backendFetchText(modificationUrl, { + method: isUpdate ? 'PUT' : 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: body, + }); +} + +export function createTabularCreation(studyUuid, nodeUuid, creationType, creations, isUpdate, modificationUuid) { + let createTabularCreationUrl = getNetworkModificationUrl(studyUuid, nodeUuid); if (isUpdate) { createTabularCreationUrl += '/' + encodeURIComponent(modificationUuid); diff --git a/src/translations/dynamic/network-modifications-locale-en.ts b/src/translations/dynamic/network-modifications-locale-en.ts index 766b5cd850..2e50f45776 100644 --- a/src/translations/dynamic/network-modifications-locale-en.ts +++ b/src/translations/dynamic/network-modifications-locale-en.ts @@ -51,7 +51,8 @@ const network_modifications_locale_en = { 'network_modifications.TABULAR_MODIFICATION': 'Tabular modification - {computedLabel}', 'network_modifications.tabular.GENERATOR_MODIFICATION': 'generator modifications', 'network_modifications.tabular.LOAD_MODIFICATION': 'load modifications', - 'network_modifications.BY_FORMULA_MODIFICATION': 'By formula modification {computedLabel}', + 'network_modifications.BY_FORMULA_MODIFICATION': 'Modification by formula {computedLabel}', + 'network_modifications.MODIFICATION_BY_ASSIGNMENT': 'Modification by filter {computedLabel}', 'network_modifications.tabular.LINE_MODIFICATION': 'line modifications', 'network_modifications.tabular.BATTERY_MODIFICATION': 'battery modifications', 'network_modifications.tabular.VOLTAGE_LEVEL_MODIFICATION': 'voltage level modifications', diff --git a/src/translations/dynamic/network-modifications-locale-fr.ts b/src/translations/dynamic/network-modifications-locale-fr.ts index 18a761f15e..1771f17553 100644 --- a/src/translations/dynamic/network-modifications-locale-fr.ts +++ b/src/translations/dynamic/network-modifications-locale-fr.ts @@ -54,6 +54,7 @@ const network_modifications_locale_fr = { 'network_modifications.tabular.GENERATOR_MODIFICATION': 'modifications de générateurs', 'network_modifications.tabular.LOAD_MODIFICATION': 'modifications de consommations', 'network_modifications.BY_FORMULA_MODIFICATION': 'Modification par formule {computedLabel}', + 'network_modifications.MODIFICATION_BY_ASSIGNMENT': 'Modification par filtre {computedLabel}', 'network_modifications.tabular.LINE_MODIFICATION': 'modifications de lignes', 'network_modifications.tabular.BATTERY_MODIFICATION': 'modifications de batteries', 'network_modifications.tabular.VOLTAGE_LEVEL_MODIFICATION': 'modifications de postes', diff --git a/src/translations/en.json b/src/translations/en.json index f2c542adc3..168a392ecf 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -646,6 +646,7 @@ "BUS": "Bus", "BUSBAR_SECTION": "Bus bar section", "BY_FORMULA": "By formula", + "BY_FILTER": "By filter", "SHUNT_COMPENSATOR": "Shunt Compensator", "STATIC_VAR_COMPENSATOR": "Static Var Compensator", "BY_TABLE": "By table", @@ -797,6 +798,7 @@ "SubstationCreationError": "Error while creating substation", "AdditionalInformation": "Additional information", "Information": "Information", + "Property": "Property", "AddProperty": "Add property", "PropertyName": "Property name", "PropertyValue": "Property value", @@ -1257,12 +1259,14 @@ "Operator": "Operator", "ModifyByFormula": "Modify by formula", + "ModifyByAssignment": "Modify by filter", "UseDashToSelectField": "Use # to select a field", "WrongRefOrValueError": "Please enter a valid numeric value or a valid field reference. Use # to select a field", "ValueMustBeNumericWhenPercentageError": "When using %, this field must be a valid positive numeric value", "ValueMustBeRefWhenPercentageError": "When using %, this field must be a valid field reference", "addNewFormula": "Add new formula", - "byFormulaChangeTypeConfirmation": "The type will be changed. All fields values will be erased.", + "addNewAssignment": "Modify another field", + "changeTypeConfirmation": "The type will be changed. All fields values will be erased.", "RatioLowTapPosition": "Ratio low tap position", "RatioTapPosition": "Ratio tap position", "RatioDeadBand": "Ratio deadband", diff --git a/src/translations/fr.json b/src/translations/fr.json index 87c41d6002..e2491b7015 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -647,6 +647,7 @@ "BUS": "Bus", "BUSBAR_SECTION": "Section de jeux de barres", "BY_FORMULA": "Par formule", + "BY_FILTER": "Par filtre", "SHUNT_COMPENSATOR": "Moyen de compensation", "STATIC_VAR_COMPENSATOR": "CSPR", "BY_TABLE": "Par tableau", @@ -797,6 +798,7 @@ "SubstationCreationError": "Erreur lors de la création d'un site", "AdditionalInformation": "Informations complémentaires", "Information": "Information", + "Property": "Propriété", "AddProperty": "Ajouter une propriété", "PropertyName": "Nom de la propriété", "PropertyValue": "Valeur de la propriété", @@ -1256,12 +1258,14 @@ "ReferenceFieldOrValue": "Champ de référence ou valeur", "Operator": "Opérateur", "ModifyByFormula": "Modification par formule", + "ModifyByAssignment": "Modification par filtre", "UseDashToSelectField": "Utiliser # pour sélectionner un champ", "WrongRefOrValueError": "Veuillez saisir une valeur numérique valide ou une référence de champ valide. Utiliser # pour sélectionner un champ", "ValueMustBeNumericWhenPercentageError": "Lors de l'utilisation de %, ce champ doit être une valeur numérique positive valide", "ValueMustBeRefWhenPercentageError": "Lors de l'utilisation de %, ce champ doit être une référence de champ valide", "addNewFormula": "Ajouter une nouvelle formule", - "byFormulaChangeTypeConfirmation": "Le type va être modifié. Toutes les valeurs des champs seront effacées.", + "addNewAssignment": "Modifier un autre champ", + "changeTypeConfirmation": "Le type va être modifié. Toutes les valeurs des champs seront effacées.", "RatioLowTapPosition": "Prise min régleur", "RatioTapPosition": "Prise courante du régleur", "RatioDeadBand": "Bande morte du régleur",