diff --git a/package-lock.json b/package-lock.json index 35a864083c..a764e3a0e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "0.102.0", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.102.0.tgz", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", @@ -3343,8 +3343,8 @@ }, "node_modules/@gridsuite/commons-ui": { "version": "0.102.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.102.0.tgz", - "integrity": "sha512-Sg2dMZ/dHQjQdBDARPI4BuUEJVN5P+nbKgT9KtQmVRMP3eepiwzWTWcCtw6h6ATLcCNk1A193fr7Te5OnsqLgw==", + "resolved": "file:../commons-ui/gridsuite-commons-ui-0.102.0.tgz", + "integrity": "sha512-MjT4hNRzkQs6T3NrmXjunVNEjZAPQntAwvWa35eGHGhsxTCj6ORVDxQviX68GelKTpP5IaZr11wbz9U6LdZU7A==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^33.1.0", @@ -3366,7 +3366,8 @@ "react-querybuilder": "^8.2.0", "reconnecting-websocket": "^4.4.0", "type-fest": "^4.34.1", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "yup-locales": "^1.2.28" }, "engines": { "node": ">=22", @@ -21047,6 +21048,12 @@ "type-fest": "^2.19.0" } }, + "node_modules/yup-locales": { + "version": "1.2.28", + "resolved": "https://registry.npmjs.org/yup-locales/-/yup-locales-1.2.28.tgz", + "integrity": "sha512-q2p5XDVThFXTLOOV1DgMbpMOfrxfZTjYSkS1WRWco6YZJOeHrbuM3ISytrLaYa8TiX/YPtE9tuQmMiUyTBZDIw==", + "license": "MIT" + }, "node_modules/yup/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", diff --git a/package.json b/package.json index c89e650d20..8e0831b36d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@gridsuite/commons-ui": "0.102.0", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.102.0.tgz", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.0.0", "@mui/icons-material": "^5.16.14", diff --git a/src/components/app.jsx b/src/components/app.jsx index 2e09c5f622..5b9b66e067 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -22,6 +22,7 @@ import { NotificationsUrlKeys, useNotificationsListener, useSnackMessage, + useYupIntl, } from '@gridsuite/commons-ui'; import PageNotFound from './page-not-found'; import { FormattedMessage } from 'react-intl'; @@ -77,6 +78,7 @@ const STUDY_VIEWS = [StudyView.MAP, StudyView.SPREADSHEET, StudyView.RESULTS, St const App = () => { const { snackError } = useSnackMessage(); + useYupIntl(); const appTabIndex = useSelector((state) => state.appTabIndex); const user = useSelector((state) => state.user); diff --git a/src/components/dialogs/active-power-control/active-power-control-utils.ts b/src/components/dialogs/active-power-control/active-power-control-utils.ts index 82372c6751..89415cfbb6 100644 --- a/src/components/dialogs/active-power-control/active-power-control-utils.ts +++ b/src/components/dialogs/active-power-control/active-power-control-utils.ts @@ -5,29 +5,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type IntlShape } from 'react-intl'; +import * as yup from 'yup'; import { DROOP, FREQUENCY_REGULATION } from '../../utils/field-constants'; -import yup from '../../utils/yup-config'; export const getActivePowerControlEmptyFormData = (isEquipmentModification = false) => ({ [FREQUENCY_REGULATION]: isEquipmentModification ? null : false, [DROOP]: null, }); -export const getActivePowerControlSchema = (isEquipmentModification = false) => ({ - [FREQUENCY_REGULATION]: yup - .bool() - .nullable() - .when([], { - is: () => !isEquipmentModification, - then: (schema) => schema.required(), - }), - [DROOP]: yup - .number() - .nullable() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') - .when([FREQUENCY_REGULATION], { - is: (frequencyRegulation: boolean) => !isEquipmentModification && frequencyRegulation, - then: (schema) => schema.required(), - }), -}); +export function getActivePowerControlSchema(intl: IntlShape, isEquipmentModification = false) { + return { + [FREQUENCY_REGULATION]: yup + .bool() + .nullable() + .when([], { + is: () => !isEquipmentModification, + then: (schema) => schema.required(), + }), + [DROOP]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .when([FREQUENCY_REGULATION], { + is: (frequencyRegulation: boolean) => !isEquipmentModification && frequencyRegulation, + then: (schema) => schema.required(), + }), + }; +} diff --git a/src/components/dialogs/connectivity/connectivity-form-utils.ts b/src/components/dialogs/connectivity/connectivity-form-utils.ts index ed0151115a..345ea5a5ac 100644 --- a/src/components/dialogs/connectivity/connectivity-form-utils.ts +++ b/src/components/dialogs/connectivity/connectivity-form-utils.ts @@ -18,7 +18,7 @@ import { NAME, VOLTAGE_LEVEL, } from 'components/utils/field-constants'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; import { VoltageLevelFormInfos } from '../network-modifications/voltage-level/voltage-level.type'; export const getConnectivityPropertiesValidationSchema = (isEquipmentModification = false) => { diff --git a/src/components/dialogs/dynamicsimulation/dynamic-simulation-parameters-selector.tsx b/src/components/dialogs/dynamicsimulation/dynamic-simulation-parameters-selector.tsx index 57b3e2eeb6..36ecc789c8 100644 --- a/src/components/dialogs/dynamicsimulation/dynamic-simulation-parameters-selector.tsx +++ b/src/components/dialogs/dynamicsimulation/dynamic-simulation-parameters-selector.tsx @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { useCallback, useEffect, useState } from 'react'; import { Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; import Typography from '@mui/material/Typography'; @@ -31,7 +32,7 @@ const formSchema = yup.object().shape({ [MAPPING]: yup.string().required(), }); -type MappingFormData = yup.InferType; +type MappingFormData = InferType; const emptyFormData: MappingFormData = { [MAPPING]: '', diff --git a/src/components/dialogs/dynamicsimulation/event/dynamic-simulation-event-dialog.tsx b/src/components/dialogs/dynamicsimulation/event/dynamic-simulation-event-dialog.tsx index 1c1eb87edc..f1190dadea 100644 --- a/src/components/dialogs/dynamicsimulation/event/dynamic-simulation-event-dialog.tsx +++ b/src/components/dialogs/dynamicsimulation/event/dynamic-simulation-event-dialog.tsx @@ -14,7 +14,7 @@ import { FORM_LOADING_DELAY } from '../../../network/constants'; import { DialogProps } from '@mui/material/Dialog/Dialog'; import { DynamicSimulationEventForm } from './dynamic-simulation-event-form'; import { Event, EventProperty, EventPropertyName, PrimitiveTypes } from './types/event.type'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { getSchema } from './util/event-yup'; import { eventDefinitions, getEventType } from './model/event.model'; import { fetchDynamicSimulationEvent, saveDynamicSimulationEvent } from '../../../../services/dynamic-simulation'; diff --git a/src/components/dialogs/dynamicsimulation/event/util/event-yup.ts b/src/components/dialogs/dynamicsimulation/event/util/event-yup.ts index 9e393f2e26..e5a7441c95 100644 --- a/src/components/dialogs/dynamicsimulation/event/util/event-yup.ts +++ b/src/components/dialogs/dynamicsimulation/event/util/event-yup.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { EventPropertyDefinition, PrimitiveTypes } from '../types/event.type'; -import { Schema } from 'yup'; -import yup from '../../../../utils/yup-config'; +import type { Schema } from 'yup'; +import * as yup from 'yup'; export const getSchema = (eventPropertyDefinition: EventPropertyDefinition) => { let schema: Schema; diff --git a/src/components/dialogs/limits/limits-pane-utils.ts b/src/components/dialogs/limits/limits-pane-utils.ts index 318136fd30..bc663e48e8 100644 --- a/src/components/dialogs/limits/limits-pane-utils.ts +++ b/src/components/dialogs/limits/limits-pane-utils.ts @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type IntlShape } from 'react-intl'; import { sanitizeString } from '../dialog-utils'; import { CURRENT_LIMITS, @@ -24,15 +25,17 @@ import { TEMPORARY_LIMITS, } from 'components/utils/field-constants'; import { areArrayElementsUnique, formatTemporaryLimits } from 'components/utils/utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { isNodeBuilt } from '../../graph/util/model-functions'; -import { OperationalLimitsGroup, TemporaryLimit } from '../../../services/network-modification-types'; -import { CurrentTreeNode } from '../../graph/tree-node.type'; +import type { OperationalLimitsGroup, TemporaryLimit } from '../../../services/network-modification-types'; +import type { CurrentTreeNode } from '../../graph/tree-node.type'; -const limitsGroupValidationSchema = (isModification: boolean) => ({ - [ID]: yup.string().nonNullable().required(), - [CURRENT_LIMITS]: yup.object().shape(currentLimitsValidationSchema(isModification)), -}); +function limitsGroupValidationSchema(intl: IntlShape, isModification: boolean) { + return { + [ID]: yup.string().nonNullable().required(), + [CURRENT_LIMITS]: yup.object().shape(currentLimitsValidationSchema(intl, isModification)), + }; +} const temporaryLimitsValidationSchema = () => { return yup.object().shape({ @@ -48,52 +51,56 @@ const temporaryLimitsValidationSchema = () => { }); }; -const currentLimitsValidationSchema = (isModification = false) => ({ - [PERMANENT_LIMIT]: yup - .number() - .nullable() - .positive('permanentCurrentLimitMustBeGreaterThanZero') - // if there are valid (named) temporary limits, permanent limit is mandatory - .when([TEMPORARY_LIMITS], { - is: (temporaryLimits: TemporaryLimit[]) => - temporaryLimits?.length > 0 && temporaryLimits.find((limit) => limit.name) && !isModification, - then: () => - yup - .number() - .required('permanentCurrentLimitMandatory') - .positive('permanentCurrentLimitMustBeGreaterThanZero'), - }), - [TEMPORARY_LIMITS]: yup - .array() - .of(temporaryLimitsValidationSchema()) - .test('distinctNames', 'TemporaryLimitNameUnicityError', (array) => { - const namesArray = !array - ? [] - : array.filter((l) => !!l[TEMPORARY_LIMIT_NAME]).map((l) => sanitizeString(l[TEMPORARY_LIMIT_NAME])); - return areArrayElementsUnique(namesArray); - }) - .test('distinctDurations', 'TemporaryLimitDurationUnicityError', (array) => { - const durationsArray = !array ? [] : array.map((l) => l[TEMPORARY_LIMIT_DURATION]).filter((d) => d); // empty lines are ignored - return areArrayElementsUnique(durationsArray); - }), -}); +function currentLimitsValidationSchema(intl: IntlShape, isModification = false) { + return { + [PERMANENT_LIMIT]: yup + .number() + .nullable() + .positive(intl.formatMessage({ id: 'permanentCurrentLimitMustBeGreaterThanZero' })) + // if there are valid (named) temporary limits, permanent limit is mandatory + .when([TEMPORARY_LIMITS], { + is: (temporaryLimits: TemporaryLimit[]) => + temporaryLimits?.length > 0 && temporaryLimits.find((limit) => limit.name) && !isModification, + then: () => + yup + .number() + .required(intl.formatMessage({ id: 'permanentCurrentLimitMandatory' })) + .positive(intl.formatMessage({ id: 'permanentCurrentLimitMustBeGreaterThanZero' })), + }), + [TEMPORARY_LIMITS]: yup + .array() + .of(temporaryLimitsValidationSchema()) + .test('distinctNames', intl.formatMessage({ id: 'TemporaryLimitNameUnicityError' }), (array) => { + const namesArray = !array + ? [] + : array + .filter((l) => !!l[TEMPORARY_LIMIT_NAME]) + .map((l) => sanitizeString(l[TEMPORARY_LIMIT_NAME])); + return areArrayElementsUnique(namesArray); + }) + .test('distinctDurations', intl.formatMessage({ id: 'TemporaryLimitDurationUnicityError' }), (array) => { + const durationsArray = !array ? [] : array.map((l) => l[TEMPORARY_LIMIT_DURATION]).filter((d) => d); // empty lines are ignored + return areArrayElementsUnique(durationsArray); + }), + }; +} -const limitsValidationSchema = (id: string, isModification: boolean = false) => { +export function getLimitsValidationSchema(intl: IntlShape, isModification: boolean = false, id: string = LIMITS) { const selectedCurrentLimitsSchema = { - [CURRENT_LIMITS_1]: yup.object().shape(currentLimitsValidationSchema(isModification)), - [CURRENT_LIMITS_2]: yup.object().shape(currentLimitsValidationSchema(isModification)), + [CURRENT_LIMITS_1]: yup.object().shape(currentLimitsValidationSchema(intl, isModification)), + [CURRENT_LIMITS_2]: yup.object().shape(currentLimitsValidationSchema(intl, isModification)), }; const completeLimitsGroupSchema = { [OPERATIONAL_LIMITS_GROUPS_1]: yup - .array(yup.object().shape(limitsGroupValidationSchema(isModification))) - .test('distinctNames', 'LimitSetCreationDuplicateError', (array) => { + .array(yup.object().shape(limitsGroupValidationSchema(intl, isModification))) + .test('distinctNames', intl.formatMessage({ id: 'LimitSetCreationDuplicateError' }), (array) => { const namesArray = !array ? [] : array.filter((o) => !!o[ID]).map((o) => sanitizeString(o[ID])); return areArrayElementsUnique(namesArray); }), [OPERATIONAL_LIMITS_GROUPS_2]: yup - .array(yup.object().shape(limitsGroupValidationSchema(isModification))) - .test('distinctNames', 'LimitSetCreationDuplicateError', (array) => { + .array(yup.object().shape(limitsGroupValidationSchema(intl, isModification))) + .test('distinctNames', intl.formatMessage({ id: 'LimitSetCreationDuplicateError' }), (array) => { const namesArray = !array ? [] : array.filter((o) => !!o[ID]).map((o) => sanitizeString(o[ID])); return areArrayElementsUnique(namesArray); }), @@ -103,13 +110,9 @@ const limitsValidationSchema = (id: string, isModification: boolean = false) => // for now modifications only use the selected limits set while the creations use complete limits sets // => this is temporary and will be removed once the modification use complete limit sets return { [id]: yup.object().shape(isModification ? selectedCurrentLimitsSchema : completeLimitsGroupSchema) }; -}; - -export const getLimitsValidationSchema = (isModification: boolean = false, id: string = LIMITS) => { - return limitsValidationSchema(id, isModification); -}; +} -const limitsEmptyFormData = (id: string, onlySelectedLimits = true) => { +export const getLimitsEmptyFormData = (onlySelectedLimits = true, id = LIMITS) => { const currentLimits = { [CURRENT_LIMITS_1]: { [PERMANENT_LIMIT]: null, @@ -130,10 +133,6 @@ const limitsEmptyFormData = (id: string, onlySelectedLimits = true) => { return { [id]: onlySelectedLimits ? currentLimits : limitsGroup }; }; -export const getLimitsEmptyFormData = (onlySelectedLimits = true, id = LIMITS) => { - return limitsEmptyFormData(id, onlySelectedLimits); -}; - /** * used when the limit set data only contain the selected limit sets */ @@ -216,9 +215,7 @@ export const updateTemporaryLimits = ( //add temporary limits from previous modifications temporaryLimitsToModify?.forEach((limit: TemporaryLimit) => { if (findTemporaryLimit(updatedTemporaryLimits, limit) === undefined) { - updatedTemporaryLimits?.push({ - ...limit, - }); + updatedTemporaryLimits?.push({ ...limit }); } }); @@ -262,35 +259,20 @@ export const addModificationTypeToTemporaryLimits = ( isNodeBuilt(currentNode)) || currentLimitWithSameName?.modificationType === TEMPORARY_LIMIT_MODIFICATION_TYPE.ADDED ) { - return { - ...limit, - modificationType: currentLimitWithSameName.modificationType, - }; + return { ...limit, modificationType: currentLimitWithSameName.modificationType }; } else { return limitWithSameName.value === limit.value - ? { - ...limit, - modificationType: null, - } - : { - ...limit, - modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.MODIFIED, - }; + ? { ...limit, modificationType: null } + : { ...limit, modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.MODIFIED }; } } else { - return { - ...limit, - modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.ADDED, - }; + return { ...limit, modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.ADDED }; } }); //add deleted limits formattedTemporaryLimitsToModify?.forEach((limit) => { if (!findTemporaryLimit(temporaryLimits, limit)) { - updatedTemporaryLimits.push({ - ...limit, - modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.DELETED, - }); + updatedTemporaryLimits.push({ ...limit, modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.DELETED }); } }); //add previously deleted limits @@ -299,10 +281,7 @@ export const addModificationTypeToTemporaryLimits = ( !findTemporaryLimit(updatedTemporaryLimits, limit) && limit.modificationType === TEMPORARY_LIMIT_MODIFICATION_TYPE.DELETED ) { - updatedTemporaryLimits.push({ - ...limit, - modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.DELETED, - }); + updatedTemporaryLimits.push({ ...limit, modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.DELETED }); } }); return updatedTemporaryLimits; diff --git a/src/components/dialogs/line-types-catalog/line-type-segment-dialog.tsx b/src/components/dialogs/line-types-catalog/line-type-segment-dialog.tsx index 9d3abe1568..87874c725a 100644 --- a/src/components/dialogs/line-types-catalog/line-type-segment-dialog.tsx +++ b/src/components/dialogs/line-types-catalog/line-type-segment-dialog.tsx @@ -5,35 +5,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback } from 'react'; +import { FunctionComponent, useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; import { SEGMENTS, TOTAL_REACTANCE, TOTAL_RESISTANCE, TOTAL_SUSCEPTANCE } from '../../utils/field-constants'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { ModificationDialog } from '../commons/modificationDialog'; import { useForm } from 'react-hook-form'; import { LineTypeSegmentForm } from './line-type-segment-form'; import { CustomFormProvider } from '@gridsuite/commons-ui'; import { ComputedLineCharacteristics } from './line-catalog.type'; -import { emptyLineSegment, SegmentSchema } from './segment-utils'; - -const LineTypeSegmentSchema = yup - .object() - .shape({ - [TOTAL_RESISTANCE]: yup.number().required(), - [TOTAL_REACTANCE]: yup.number().required(), - [TOTAL_SUSCEPTANCE]: yup.number().required(), - [SEGMENTS]: yup.array().of(SegmentSchema).required().min(1, 'AtLeastOneSegmentNeeded'), - }) - .required(); - -export type LineTypeSegmentFormData = yup.InferType; - -const emptyFormData: LineTypeSegmentFormData = { - [TOTAL_RESISTANCE]: 0.0, - [TOTAL_REACTANCE]: 0.0, - [TOTAL_SUSCEPTANCE]: 0.0, - [SEGMENTS]: [emptyLineSegment], -}; +import { emptyLineSegment, getSegmentSchema } from './segment-utils'; export interface LineTypeSegmentDialogProps { open: boolean; @@ -42,6 +25,37 @@ export interface LineTypeSegmentDialogProps { } const LineTypeSegmentDialog: FunctionComponent = ({ open, onSave, onClose }) => { + const intl = useIntl(); + + const LineTypeSegmentSchema = useMemo( + () => + yup + .object() + .shape({ + [TOTAL_RESISTANCE]: yup.number().required(), + [TOTAL_REACTANCE]: yup.number().required(), + [TOTAL_SUSCEPTANCE]: yup.number().required(), + [SEGMENTS]: yup + .array() + .of(getSegmentSchema(intl)) + .required() + .min(1, intl.formatMessage({ id: 'AtLeastOneSegmentNeeded' })), + }) + .required(), + [intl] + ); + type LineTypeSegmentFormData = InferType; + + const emptyFormData = useMemo( + () => ({ + [TOTAL_RESISTANCE]: 0.0, + [TOTAL_REACTANCE]: 0.0, + [TOTAL_SUSCEPTANCE]: 0.0, + [SEGMENTS]: [emptyLineSegment], + }), + [] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(LineTypeSegmentSchema), diff --git a/src/components/dialogs/line-types-catalog/segment-utils.ts b/src/components/dialogs/line-types-catalog/segment-utils.ts index 6154c717e6..622331d8fc 100644 --- a/src/components/dialogs/line-types-catalog/segment-utils.ts +++ b/src/components/dialogs/line-types-catalog/segment-utils.ts @@ -13,24 +13,30 @@ import { SEGMENT_TYPE_ID, SEGMENT_TYPE_VALUE, } from 'components/utils/field-constants'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import type { IntlShape } from 'react-intl'; -export const SegmentSchema = yup.object().shape({ - [SEGMENT_DISTANCE_VALUE]: yup - .number() - .required('SegmentDistanceMustBeGreaterThanZero') - .moreThan(0, 'SegmentDistanceMustBeGreaterThanZero'), - [SEGMENT_TYPE_VALUE]: yup - .string() - .required() - .test('empty-check', 'SegmentTypeMissing', (value) => (value ? value.length > 0 : false)), - [SEGMENT_TYPE_ID]: yup.string().required(), - [SEGMENT_RESISTANCE]: yup.number().required(), - [SEGMENT_REACTANCE]: yup.number().required(), - [SEGMENT_SUSCEPTANCE]: yup.number().required(), -}); +export function getSegmentSchema(intl: IntlShape) { + return yup.object().shape({ + [SEGMENT_DISTANCE_VALUE]: yup + .number() + .required(intl.formatMessage({ id: 'SegmentDistanceMustBeGreaterThanZero' })) + .moreThan(0, intl.formatMessage({ id: 'SegmentDistanceMustBeGreaterThanZero' })), + [SEGMENT_TYPE_VALUE]: yup + .string() + .required() + .test('empty-check', intl.formatMessage({ id: 'SegmentTypeMissing' }), (value) => + value ? value.length > 0 : false + ), + [SEGMENT_TYPE_ID]: yup.string().required(), + [SEGMENT_RESISTANCE]: yup.number().required(), + [SEGMENT_REACTANCE]: yup.number().required(), + [SEGMENT_SUSCEPTANCE]: yup.number().required(), + }); +} -export type SegmentFormData = yup.InferType; +export type SegmentFormData = InferType>; export const emptyLineSegment: SegmentFormData = { [SEGMENT_DISTANCE_VALUE]: 0.0, diff --git a/src/components/dialogs/network-modifications/battery/creation/battery-creation-dialog.tsx b/src/components/dialogs/network-modifications/battery/creation/battery-creation-dialog.tsx index 30f73ef147..094977a6d2 100644 --- a/src/components/dialogs/network-modifications/battery/creation/battery-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/battery/creation/battery-creation-dialog.tsx @@ -8,11 +8,11 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SET_POINT, BUS_OR_BUSBAR_SECTION, @@ -53,7 +53,7 @@ import { createBattery } from '../../../../../services/study/network-modificatio import { FetchStatus } from '../../../../../services/utils.type'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, @@ -68,6 +68,7 @@ import { BatteryCreationInfos } from '../../../../../services/network-modificati import BatteryCreationForm from './battery-creation-form'; import { getSetPointsEmptyFormData, getSetPointsSchema } from '../../../set-points/set-points-utils'; import { NetworkModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_ID]: '', @@ -81,21 +82,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [MAXIMUM_ACTIVE_POWER]: yup.number().nullable().required(), - [MINIMUM_ACTIVE_POWER]: yup.number().nullable().required(), - [CONNECTIVITY]: getConnectivityWithPositionSchema(), - [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(), - ...getSetPointsSchema(), - ...getActivePowerControlSchema(), - }) - .concat(creationPropertiesSchema) - .required(); - export type BatteryCreationDialogProps = NetworkModificationDialogProps & { editData: BatteryCreationInfos; }; @@ -111,6 +97,26 @@ export default function BatteryCreationDialog({ }: Readonly) { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [MAXIMUM_ACTIVE_POWER]: yup.number().nullable().required(), + [MINIMUM_ACTIVE_POWER]: yup.number().nullable().required(), + [CONNECTIVITY]: getConnectivityWithPositionSchema(), + [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(intl), + ...getSetPointsSchema(intl), + ...getActivePowerControlSchema(intl), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm>({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/battery/modification/battery-modification-dialog.tsx b/src/components/dialogs/network-modifications/battery/modification/battery-modification-dialog.tsx index a5d96a4f11..0d790f6ed2 100644 --- a/src/components/dialogs/network-modifications/battery/modification/battery-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/battery/modification/battery-modification-dialog.tsx @@ -6,10 +6,10 @@ */ import { useForm } from 'react-hook-form'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SET_POINT, ADDITIONAL_PROPERTIES, @@ -52,7 +52,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { @@ -74,6 +74,7 @@ import BatteryModificationForm from './battery-modification-form'; import { getSetPointsEmptyFormData, getSetPointsSchema } from '../../../set-points/set-points-utils'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { EquipmentModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -86,27 +87,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - [MAXIMUM_ACTIVE_POWER]: yup.number().nullable(), - [MINIMUM_ACTIVE_POWER]: yup - .number() - .nullable() - .when([MAXIMUM_ACTIVE_POWER], { - is: (maximumActivePower: number) => maximumActivePower != null, - then: (schema) => - schema.max(yup.ref(MAXIMUM_ACTIVE_POWER), 'MinActivePowerMustBeLessOrEqualToMaxActivePower'), - }), - [CONNECTIVITY]: getConnectivityWithPositionSchema(true), - [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(true), - ...getSetPointsSchema(true), - ...getActivePowerControlSchema(true), - }) - .concat(modificationPropertiesSchema) - .required(); - export type BatteryModificationDialogProps = EquipmentModificationDialogProps & { editData?: BatteryModificationInfos; }; @@ -123,10 +103,39 @@ export default function BatteryModificationDialog({ }: Readonly) { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); const [batteryToModify, setBatteryToModify] = useState(null); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + [MAXIMUM_ACTIVE_POWER]: yup.number().nullable(), + [MINIMUM_ACTIVE_POWER]: yup + .number() + .nullable() + .when([MAXIMUM_ACTIVE_POWER], { + is: (maximumActivePower: number) => maximumActivePower != null, + then: (schema) => + schema.max( + yup.ref(MAXIMUM_ACTIVE_POWER), + intl.formatMessage({ id: 'MinActivePowerMustBeLessOrEqualToMaxActivePower' }) + ), + }), + [CONNECTIVITY]: getConnectivityWithPositionSchema(true), + [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(intl, true), + ...getSetPointsSchema(intl, true), + ...getActivePowerControlSchema(intl, true), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), 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 index 4c8dfe48c2..5515b4ba25 100644 --- 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 @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type IntlShape } from 'react-intl'; import { EDITED_FIELD, FILTERS, @@ -13,8 +14,8 @@ import { PROPERTY_NAME_FIELD, VALUE_FIELD, } from '../../../../../utils/field-constants'; -import yup from 'components/utils/yup-config'; -import { Schema } from 'yup'; +import type { Schema } from 'yup'; +import * as yup from 'yup'; import { Assignment, DataType, FieldValue } from './assignment.type'; import { FIELD_OPTIONS } from './assignment-constants'; @@ -34,7 +35,7 @@ export const getAssignmentInitialValue = () => ({ [VALUE_FIELD]: null, }); -export function getAssignmentsSchema() { +export function getAssignmentsSchema(intl: IntlShape) { return yup .array() .of( @@ -48,21 +49,16 @@ export function getAssignmentsSchema() { }) ) .required() - .min(1, 'YupRequired'), + .min(1, intl.formatMessage({ id: '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(); - }), + [PROPERTY_NAME_FIELD]: yup + .string() + .when([EDITED_FIELD], ([editedField], schema) => + getDataType(editedField) === DataType.PROPERTY ? schema.required() : schema.nullable() + ), [VALUE_FIELD]: yup .mixed() - .when([EDITED_FIELD], ([editedField]) => { - const dataType = getDataType(editedField); - return getValueSchema(dataType); - }) + .when([EDITED_FIELD], ([editedField]) => getValueSchema(getDataType(editedField))) .required(), }) ) 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 index 86cf73d5eb..ffadd188e5 100644 --- 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 @@ -6,7 +6,7 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { convertInputValue, convertOutputValue, @@ -14,7 +14,7 @@ import { FieldType, useSnackMessage, } from '@gridsuite/commons-ui'; -import { FC, useCallback, useEffect } from 'react'; +import { FC, useCallback, useEffect, useMemo } from 'react'; import { FetchStatus } from '../../../../../services/utils'; import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; @@ -31,14 +31,7 @@ import { } 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(); +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_TYPE_FIELD]: '', @@ -55,6 +48,19 @@ const ModificationByAssignmentDialog: FC = ({ }) => { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_TYPE_FIELD]: yup.string().required(), + [ASSIGNMENTS]: getAssignmentsSchema(intl), + }) + .required(), + [intl] + ); // "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) diff --git a/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx index 4cf772fd58..fe48ac2e65 100644 --- a/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-filter-deletion/by-filter-deletion-dialog.tsx @@ -6,11 +6,12 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; 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 { type FunctionComponent, useCallback, useEffect, useMemo } from 'react'; +import { useIntl } from 'react-intl'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; @@ -24,23 +25,6 @@ import { ByFilterDeletionFormData, } from './by-filter-deletion.type'; -const formSchema = yup - .object() - .shape({ - [TYPE]: yup.mixed().required(), - [FILTERS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .min(1, 'FieldIsRequired'), - }) - .required(); - const emptyFormData = { [TYPE]: null, [FILTERS]: [], @@ -64,9 +48,30 @@ const ByFilterDeletionDialog: FunctionComponent = ( ...dialogProps }) => { const currentNodeUuid = currentNode?.id; - + const intl = useIntl(); const { snackError } = useSnackMessage(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [TYPE]: yup.mixed().required(), + [FILTERS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx index b63e648ef8..c354d82a3a 100644 --- a/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/by-formula-dialog.jsx @@ -6,7 +6,7 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { convertInputValue, convertOutputValue, @@ -14,7 +14,7 @@ import { FieldType, useSnackMessage, } from '@gridsuite/commons-ui'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { FetchStatus } from '../../../../../services/utils'; import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; @@ -34,6 +34,7 @@ import { } from '../../../../utils/field-constants'; import { modifyByFormula } from '../../../../../services/study/network-modifications'; import { getFormulaInitialValue, getFormulaSchema } from './formula/formula-utils'; +import { useIntl } from 'react-intl'; function getFieldOrConvertedUnitValue(input, fieldType, convert) { const value = input.replace(',', '.'); @@ -73,14 +74,6 @@ function shouldConvert(input1, input2, operator) { } } -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_TYPE_FIELD]: yup.string().required(), - ...getFormulaSchema(FORMULAS), - }) - .required(); - const emptyFormData = { [EQUIPMENT_TYPE_FIELD]: '', [FORMULAS]: [getFormulaInitialValue()], @@ -89,6 +82,19 @@ const emptyFormData = { const ByFormulaDialog = ({ editData, currentNode, studyUuid, isUpdate, editDataFetchStatus, ...dialogProps }) => { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_TYPE_FIELD]: yup.string().required(), + ...getFormulaSchema(intl, FORMULAS), + }) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx index 7901a83350..42c318129a 100644 --- a/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx +++ b/src/components/dialogs/network-modifications/by-filter/by-formula/formula/formula-utils.tsx @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type IntlShape } from 'react-intl'; import { EQUIPMENT_TYPES } from '../../../../../utils/equipment-types'; import { EDITED_FIELD, @@ -18,8 +19,8 @@ import { SPECIFIC_METADATA, TYPE, } from '../../../../../utils/field-constants'; -import yup from 'components/utils/yup-config'; -import { AnyObject, TestContext, TestFunction } from 'yup'; +import * as yup from 'yup'; +import type { AnyObject, TestContext, TestFunction } from 'yup'; import { KILO_AMPERE, KILO_VOLT, @@ -164,7 +165,7 @@ export const getFormulaInitialValue = () => ({ [REFERENCE_FIELD_OR_VALUE_2]: null, }); -export function getFormulaSchema(id: string) { +export function getFormulaSchema(intl: IntlShape, id: string) { return { [id]: yup.array().of( yup.object().shape({ @@ -180,32 +181,40 @@ export function getFormulaSchema(id: string) { }) ) .required() - .min(1, 'FieldIsRequired'), + .min(1, intl.formatMessage({ id: 'FieldIsRequired' })), [EDITED_FIELD]: yup.string().required(), [OPERATOR]: yup.string().required(), [REFERENCE_FIELD_OR_VALUE_1]: yup .mixed() .required() - .test('checkRefOrValue', 'WrongRefOrValueError', checkValueInEquipmentFieldsOrNumeric) + .test( + 'checkRefOrValue', + intl.formatMessage({ id: 'WrongRefOrValueError' }), + checkValueInEquipmentFieldsOrNumeric + ) .when([OPERATOR], { is: 'PERCENTAGE', then: (schema) => schema.test( 'checkValueIsReference', - 'ValueMustBeNumericWhenPercentageError', + intl.formatMessage({ id: 'ValueMustBeNumericWhenPercentageError' }), (value: any) => !isNaN(parseFloat(value)) && parseFloat(value) >= 0 ), }), [REFERENCE_FIELD_OR_VALUE_2]: yup .mixed() .required() - .test('checkRefOrValue', 'WrongRefOrValueError', checkValueInEquipmentFieldsOrNumeric) + .test( + 'checkRefOrValue', + intl.formatMessage({ id: 'WrongRefOrValueError' }), + checkValueInEquipmentFieldsOrNumeric + ) .when([OPERATOR], { is: 'PERCENTAGE', then: (schema) => schema.test( 'checkValueIsReference', - 'ValueMustBeRefWhenPercentageError', + intl.formatMessage({ id: 'ValueMustBeRefWhenPercentageError' }), checkValueInEquipmentFields ), }), diff --git a/src/components/dialogs/network-modifications/common/measurements/branch-active-reactive-power-form-utils.ts b/src/components/dialogs/network-modifications/common/measurements/branch-active-reactive-power-form-utils.ts index 101280fa72..3d213d0465 100644 --- a/src/components/dialogs/network-modifications/common/measurements/branch-active-reactive-power-form-utils.ts +++ b/src/components/dialogs/network-modifications/common/measurements/branch-active-reactive-power-form-utils.ts @@ -11,7 +11,7 @@ import { getPowerWithValidityEmptyFormData, getPowerWithValidityValidationSchema, } from './power-with-validity-utils'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; export function getBranchActiveReactivePowerEmptyFormDataProperties() { return { diff --git a/src/components/dialogs/network-modifications/common/measurements/injection-active-reactive-power-form-utils.ts b/src/components/dialogs/network-modifications/common/measurements/injection-active-reactive-power-form-utils.ts index 30cac2e551..520b851d92 100644 --- a/src/components/dialogs/network-modifications/common/measurements/injection-active-reactive-power-form-utils.ts +++ b/src/components/dialogs/network-modifications/common/measurements/injection-active-reactive-power-form-utils.ts @@ -11,7 +11,7 @@ import { getPowerWithValidityEmptyFormData, getPowerWithValidityValidationSchema, } from './power-with-validity-utils'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; export function getInjectionActiveReactivePowerEmptyFormDataProperties() { return { diff --git a/src/components/dialogs/network-modifications/common/measurements/power-with-validity-utils.ts b/src/components/dialogs/network-modifications/common/measurements/power-with-validity-utils.ts index eade9328cc..92e15d636d 100644 --- a/src/components/dialogs/network-modifications/common/measurements/power-with-validity-utils.ts +++ b/src/components/dialogs/network-modifications/common/measurements/power-with-validity-utils.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; import { VALIDITY, VALUE } from '../../../../utils/field-constants'; import { MeasurementInfo } from './measurement.type'; diff --git a/src/components/dialogs/network-modifications/common/properties/property-utils.ts b/src/components/dialogs/network-modifications/common/properties/property-utils.ts index 9c74cab697..fe21b92c93 100644 --- a/src/components/dialogs/network-modifications/common/properties/property-utils.ts +++ b/src/components/dialogs/network-modifications/common/properties/property-utils.ts @@ -4,7 +4,7 @@ * 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 yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ADDED, ADDITIONAL_PROPERTIES, @@ -15,6 +15,7 @@ import { } from 'components/utils/field-constants'; import { isBlankOrEmpty } from 'components/utils/validation-functions'; import { fetchStudyMetadata } from '@gridsuite/commons-ui'; +import type { IntlShape } from 'react-intl'; export type Property = { [NAME]: string; @@ -146,50 +147,52 @@ export const toModificationProperties = (properties: Properties) => { return filteredProperties?.length === 0 ? undefined : filteredProperties; }; -export const creationPropertiesSchema = yup.object({ - [ADDITIONAL_PROPERTIES]: yup - .array() - .of( - yup.object().shape({ - [NAME]: yup.string().required(), - [VALUE]: yup.string().required(), - [PREVIOUS_VALUE]: yup.string().nullable(), - [DELETION_MARK]: yup.boolean().required(), - [ADDED]: yup.boolean().required(), - }) - ) - .test('checkUniqueProperties', 'DuplicatedPropsError', (values) => checkUniquePropertyNames(values)), -}); - -export const modificationPropertiesSchema = yup.object({ - [ADDITIONAL_PROPERTIES]: yup - .array() - .of( - yup.object().shape({ - [NAME]: yup.string().required(), - [VALUE]: yup - .string() - .nullable() - .when([PREVIOUS_VALUE, DELETION_MARK], { - is: (previousValue: string | null, deletionMark: boolean) => - previousValue === null && !deletionMark, - then: (schema) => schema.required(), - }), - [PREVIOUS_VALUE]: yup.string().nullable(), - [DELETION_MARK]: yup.boolean().required(), - [ADDED]: yup.boolean().required(), - }) - ) - .test('checkUniqueProperties', 'DuplicatedPropsError', (values) => checkUniquePropertyNames(values)), -}); - -const checkUniquePropertyNames = ( - properties: - | { - name: string; - }[] - | undefined -) => { +export function getCreationPropertiesSchema(intl: IntlShape) { + return yup.object({ + [ADDITIONAL_PROPERTIES]: yup + .array() + .of( + yup.object().shape({ + [NAME]: yup.string().required(), + [VALUE]: yup.string().required(), + [PREVIOUS_VALUE]: yup.string().nullable(), + [DELETION_MARK]: yup.boolean().required(), + [ADDED]: yup.boolean().required(), + }) + ) + .test('checkUniqueProperties', intl.formatMessage({ id: 'DuplicatedPropsError' }), (values) => + checkUniquePropertyNames(values) + ), + }); +} + +export function getModificationPropertiesSchema(intl: IntlShape) { + return yup.object({ + [ADDITIONAL_PROPERTIES]: yup + .array() + .of( + yup.object().shape({ + [NAME]: yup.string().required(), + [VALUE]: yup + .string() + .nullable() + .when([PREVIOUS_VALUE, DELETION_MARK], { + is: (previousValue: string | null, deletionMark: boolean) => + previousValue === null && !deletionMark, + then: (schema) => schema.required(), + }), + [PREVIOUS_VALUE]: yup.string().nullable(), + [DELETION_MARK]: yup.boolean().required(), + [ADDED]: yup.boolean().required(), + }) + ) + .test('checkUniqueProperties', intl.formatMessage({ id: 'DuplicatedPropsError' }), (values) => + checkUniquePropertyNames(values) + ), + }); +} + +const checkUniquePropertyNames = (properties: { name: string }[] | undefined) => { if (properties === undefined) { return true; } diff --git a/src/components/dialogs/network-modifications/delete-attaching-line/delete-attaching-line-dialog.jsx b/src/components/dialogs/network-modifications/delete-attaching-line/delete-attaching-line-dialog.jsx index ae366d6ef5..411771e748 100644 --- a/src/components/dialogs/network-modifications/delete-attaching-line/delete-attaching-line-dialog.jsx +++ b/src/components/dialogs/network-modifications/delete-attaching-line/delete-attaching-line-dialog.jsx @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import { useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import DeleteAttachingLineForm from './delete-attaching-line-form'; import { useOpenShortWaitFetching } from '../../commons/handle-modification-form'; diff --git a/src/components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog.jsx b/src/components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog.jsx index 1736e9a453..a0132790f9 100644 --- a/src/components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog.jsx +++ b/src/components/dialogs/network-modifications/delete-voltage-level-on-line/delete-voltage-level-on-line-dialog.jsx @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import { useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import DeleteVoltageLevelOnLineForm from './delete-voltage-level-on-line-form'; import { deleteVoltageLevelOnLine } from '../../../../services/study/network-modifications'; diff --git a/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog.jsx b/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog.jsx index 53e1ca4078..2ee36206fb 100644 --- a/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog.jsx +++ b/src/components/dialogs/network-modifications/equipment-deletion/equipment-deletion-dialog.jsx @@ -6,7 +6,7 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { DELETION_SPECIFIC_DATA, EQUIPMENT_ID, TYPE } from '../../../utils/field-constants'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; diff --git a/src/components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog.jsx b/src/components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog.jsx index b0cda2fc97..bde8c1edaa 100644 --- a/src/components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog.jsx +++ b/src/components/dialogs/network-modifications/generation-dispatch/generation-dispatch-dialog.jsx @@ -24,7 +24,7 @@ import { import PropTypes from 'prop-types'; import { useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { useOpenShortWaitFetching } from '../../commons/handle-modification-form'; import { ModificationDialog } from '../../commons/modificationDialog'; import GenerationDispatchForm from './generation-dispatch-form'; diff --git a/src/components/dialogs/network-modifications/generator-scaling/generator-scaling-dialog.jsx b/src/components/dialogs/network-modifications/generator-scaling/generator-scaling-dialog.jsx index 79bfe1c904..1351427e09 100644 --- a/src/components/dialogs/network-modifications/generator-scaling/generator-scaling-dialog.jsx +++ b/src/components/dialogs/network-modifications/generator-scaling/generator-scaling-dialog.jsx @@ -7,10 +7,10 @@ import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import GeneratorScalingForm from './generator-scaling-form'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { VARIATION_TYPE, VARIATIONS } from 'components/utils/field-constants'; import { getVariationsSchema } from './variation/variation-utils'; @@ -18,20 +18,13 @@ import { FORM_LOADING_DELAY, VARIATION_TYPES } from 'components/network/constant import { useOpenShortWaitFetching } from '../../commons/handle-modification-form'; import { generatorScaling } from '../../../../services/study/network-modifications'; import { FetchStatus } from '../../../../services/utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [VARIATION_TYPE]: VARIATION_TYPES.DELTA_P.id, [VARIATIONS]: [], }; -const formSchema = yup - .object() - .shape({ - [VARIATION_TYPE]: yup.string().required(), - ...getVariationsSchema(VARIATIONS), - }) - .required(); - const GeneratorScalingDialog = ({ editData, currentNode, @@ -42,6 +35,19 @@ const GeneratorScalingDialog = ({ }) => { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [VARIATION_TYPE]: yup.string().required(), + ...getVariationsSchema(VARIATIONS), + }) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/generator-scaling/variation/variation-utils.js b/src/components/dialogs/network-modifications/generator-scaling/variation/variation-utils.js index 8fd5857e40..2ccd222c7d 100644 --- a/src/components/dialogs/network-modifications/generator-scaling/variation/variation-utils.js +++ b/src/components/dialogs/network-modifications/generator-scaling/variation/variation-utils.js @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { FILTERS, ID, @@ -18,38 +18,45 @@ import { import { VARIATION_MODES } from 'components/network/constants'; export const IDENTIFIER_LIST = 'IDENTIFIER_LIST'; -export const getVariationSchema = () => - yup - .object() - .nullable() - .shape({ - [VARIATION_MODE]: yup.string().nullable().required(), - [VARIATION_VALUE]: yup.number().nullable().required(), - [FILTERS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - [SPECIFIC_METADATA]: yup.object().shape({ - [TYPE]: yup.string(), - }), +export function getVariationsSchema(intl, id) { + return { + [id]: yup + .array() + .nullable() + .min(1, intl.formatMessage({ id: 'EmptyList.variations' })) + .of( + yup + .object() + .nullable() + .shape({ + [VARIATION_MODE]: yup.string().nullable().required(), + [VARIATION_VALUE]: yup.number().nullable().required(), + [FILTERS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + [SPECIFIC_METADATA]: yup.object().shape({ + [TYPE]: yup.string(), + }), + }) + ) + .required() + .min(1, intl.formatMessage({ id: 'FieldIsRequired' })) + .when([VARIATION_MODE], { + is: VARIATION_MODES.STACKING_UP.id || VARIATION_MODES.VENTILATION.id, + then: (schema) => + schema.test( + 'AllFiltersAreExplicitNaming', + intl.formatMessage({ id: 'AllExplicitNamingFiltersError' }), + (values) => values.every((f) => f?.specificMetadata?.type === IDENTIFIER_LIST) + ), + }), }) - ) - .required() - .min(1, 'FieldIsRequired') - .when([VARIATION_MODE], { - is: VARIATION_MODES.STACKING_UP.id || VARIATION_MODES.VENTILATION.id, - then: (schema) => - schema.test('AllFiltersAreExplicitNaming', 'AllExplicitNamingFiltersError', (values) => - values.every((f) => f?.specificMetadata?.type === IDENTIFIER_LIST) - ), - }), - }); - -export const getVariationsSchema = (id) => ({ - [id]: yup.array().nullable().min(1, 'EmptyList.variations').of(getVariationSchema()), -}); + ), + }; +} export const getVariationEmptyForm = (variationMode) => { return { diff --git a/src/components/dialogs/network-modifications/generator/creation/generator-creation-dialog.tsx b/src/components/dialogs/network-modifications/generator/creation/generator-creation-dialog.tsx index 9e0c6b18dc..b6e1c74559 100644 --- a/src/components/dialogs/network-modifications/generator/creation/generator-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/generator/creation/generator-creation-dialog.tsx @@ -8,11 +8,11 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SET_POINT, CONNECTED, @@ -66,7 +66,7 @@ import { createGenerator } from '../../../../../services/study/network-modificat import { FetchStatus } from '../../../../../services/utils.type'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, @@ -84,6 +84,7 @@ import { DeepNullable } from '../../../../utils/ts-utils'; import { GeneratorCreationDialogSchemaForm, GeneratorFormInfos } from '../generator-dialog.type'; import { getSetPointsEmptyFormData, getSetPointsSchema } from '../../../set-points/set-points-utils'; import { NetworkModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type'; +import { type IntlShape, useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_ID]: '', @@ -106,35 +107,47 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [ENERGY_SOURCE]: yup.string().nullable().required(), - [MAXIMUM_ACTIVE_POWER]: yup.number().nullable().required(), - [MINIMUM_ACTIVE_POWER]: yup.number().nullable().required(), - [RATED_NOMINAL_POWER]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [TRANSFORMER_REACTANCE]: yup.number().nullable(), - [TRANSIENT_REACTANCE]: yup - .number() - .nullable() - .when([TRANSFORMER_REACTANCE], { - is: (transformerReactance: number) => transformerReactance != null, - then: (schema) => schema.required(), - }), - [PLANNED_ACTIVE_POWER_SET_POINT]: yup.number().nullable(), - [MARGINAL_COST]: yup.number().nullable(), - [PLANNED_OUTAGE_RATE]: yup.number().nullable().min(0, 'RealPercentage').max(1, 'RealPercentage'), - [FORCED_OUTAGE_RATE]: yup.number().nullable().min(0, 'RealPercentage').max(1, 'RealPercentage'), - [CONNECTIVITY]: getConnectivityWithPositionSchema(), - ...getSetPointsSchema(), - [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(), - ...getVoltageRegulationSchema(), - ...getActivePowerControlSchema(), - }) - .concat(creationPropertiesSchema) - .required(); +const getFormSchema = (intl: IntlShape) => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [ENERGY_SOURCE]: yup.string().nullable().required(), + [MAXIMUM_ACTIVE_POWER]: yup.number().nullable().required(), + [MINIMUM_ACTIVE_POWER]: yup.number().nullable().required(), + [RATED_NOMINAL_POWER]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [TRANSFORMER_REACTANCE]: yup.number().nullable(), + [TRANSIENT_REACTANCE]: yup + .number() + .nullable() + .when([TRANSFORMER_REACTANCE], { + is: (transformerReactance: number) => transformerReactance != null, + then: (schema) => schema.required(), + }), + [PLANNED_ACTIVE_POWER_SET_POINT]: yup.number().nullable(), + [MARGINAL_COST]: yup.number().nullable(), + [PLANNED_OUTAGE_RATE]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })), + [FORCED_OUTAGE_RATE]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })), + [CONNECTIVITY]: getConnectivityWithPositionSchema(), + ...getSetPointsSchema(intl), + [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(intl), + ...getVoltageRegulationSchema(intl), + ...getActivePowerControlSchema(intl), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(); export type GeneratorCreationDialogProps = NetworkModificationDialogProps & { editData: GeneratorCreationInfos; @@ -151,7 +164,9 @@ export default function GeneratorCreationDialog({ }: Readonly) { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + const formSchema = useMemo(() => getFormSchema(intl), [intl]); const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), diff --git a/src/components/dialogs/network-modifications/generator/modification/generator-modification-dialog.tsx b/src/components/dialogs/network-modifications/generator/modification/generator-modification-dialog.tsx index 6038f44f15..3b742b5ce0 100644 --- a/src/components/dialogs/network-modifications/generator/modification/generator-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/generator/modification/generator-modification-dialog.tsx @@ -7,10 +7,10 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { CustomFormProvider, EquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SET_POINT, ADDITIONAL_PROPERTIES, @@ -59,10 +59,7 @@ import { getReactiveLimitsValidationSchema, } from '../../../reactive-limits/reactive-limits-utils'; import { getRegulatingTerminalFormData } from '../../../regulating-terminal/regulating-terminal-form-utils'; -import { - REMOVE, - setCurrentReactiveCapabilityCurveChoice, -} from '../../../reactive-limits/reactive-capability-curve/reactive-capability-utils'; +import { REMOVE } from '../../../reactive-limits/reactive-capability-curve/reactive-capability-utils'; import { useOpenShortWaitFetching } from '../../../commons/handle-modification-form'; import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types'; import { EquipmentIdSelector } from '../../../equipment-id/equipment-id-selector'; @@ -73,7 +70,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { @@ -95,6 +92,7 @@ import { DeepNullable } from '../../../../utils/ts-utils'; import { GeneratorFormInfos, GeneratorModificationDialogSchemaForm } from '../generator-dialog.type'; import { toModificationOperation } from '../../../../utils/utils'; import { EquipmentModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type'; +import { type IntlShape, useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -116,36 +114,51 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - [ENERGY_SOURCE]: yup.string().nullable(), - [MAXIMUM_ACTIVE_POWER]: yup.number().nullable(), - [MINIMUM_ACTIVE_POWER]: yup - .number() - .nullable() - .when([MAXIMUM_ACTIVE_POWER], { - is: (maximumActivePower: number) => maximumActivePower != null, - then: (schema) => - schema.max(yup.ref(MAXIMUM_ACTIVE_POWER), 'MinActivePowerMustBeLessOrEqualToMaxActivePower'), - }), - [RATED_NOMINAL_POWER]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [TRANSIENT_REACTANCE]: yup.number().nullable(), - [TRANSFORMER_REACTANCE]: yup.number().nullable(), - [PLANNED_ACTIVE_POWER_SET_POINT]: yup.number().nullable(), - [MARGINAL_COST]: yup.number().nullable(), - - [PLANNED_OUTAGE_RATE]: yup.number().nullable().min(0, 'RealPercentage').max(1, 'RealPercentage'), - [FORCED_OUTAGE_RATE]: yup.number().nullable().min(0, 'RealPercentage').max(1, 'RealPercentage'), - [CONNECTIVITY]: getConnectivityWithPositionSchema(true), - [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(true), - ...getSetPointsSchema(true), - ...getVoltageRegulationSchema(true), - ...getActivePowerControlSchema(true), - }) - .concat(modificationPropertiesSchema) - .required(); +function getFormSchema(intl: IntlShape) { + return yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + [ENERGY_SOURCE]: yup.string().nullable(), + [MAXIMUM_ACTIVE_POWER]: yup.number().nullable(), + [MINIMUM_ACTIVE_POWER]: yup + .number() + .nullable() + .when([MAXIMUM_ACTIVE_POWER], { + is: (maximumActivePower: number) => maximumActivePower != null, + then: (schema) => + schema.max( + yup.ref(MAXIMUM_ACTIVE_POWER), + intl.formatMessage({ id: 'MinActivePowerMustBeLessOrEqualToMaxActivePower' }) + ), + }), + [RATED_NOMINAL_POWER]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [TRANSIENT_REACTANCE]: yup.number().nullable(), + [TRANSFORMER_REACTANCE]: yup.number().nullable(), + [PLANNED_ACTIVE_POWER_SET_POINT]: yup.number().nullable(), + [MARGINAL_COST]: yup.number().nullable(), + [PLANNED_OUTAGE_RATE]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })), + [FORCED_OUTAGE_RATE]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })), + [CONNECTIVITY]: getConnectivityWithPositionSchema(true), + [REACTIVE_LIMITS]: getReactiveLimitsValidationSchema(intl, true), + ...getSetPointsSchema(intl, true), + ...getVoltageRegulationSchema(intl, true), + ...getActivePowerControlSchema(intl, true), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(); +} export type GeneratorModificationDialogProps = EquipmentModificationDialogProps & { editData?: GeneratorModificationInfos; @@ -163,10 +176,12 @@ export default function GeneratorModificationDialog({ }: Readonly) { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); const [generatorToModify, setGeneratorToModify] = useState(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo(() => getFormSchema(intl), [intl]); const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), @@ -283,11 +298,7 @@ export default function GeneratorModificationDialog({ previousReactiveCapabilityCurveTable ); } else { - setCurrentReactiveCapabilityCurveChoice( - previousReactiveCapabilityCurveTable, - `${REACTIVE_LIMITS}.${REACTIVE_CAPABILITY_CURVE_TABLE}`, - setValue - ); + setValue(`${REACTIVE_LIMITS}.${REACTIVE_CAPABILITY_CURVE_CHOICE}`, 'MINMAX'); } setValue(`${CONNECTIVITY}.${VOLTAGE_LEVEL}.${ID}`, value?.voltageLevelId); setValue(`${CONNECTIVITY}.${BUS_OR_BUSBAR_SECTION}.${ID}`, value?.busOrBusbarSectionId); diff --git a/src/components/dialogs/network-modifications/hvdc-line/lcc/common/lcc-utils.ts b/src/components/dialogs/network-modifications/hvdc-line/lcc/common/lcc-utils.ts index 5ddd7c2118..6d9f4336c6 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/lcc/common/lcc-utils.ts +++ b/src/components/dialogs/network-modifications/hvdc-line/lcc/common/lcc-utils.ts @@ -4,6 +4,7 @@ * 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 { type IntlShape } from 'react-intl'; import { ACTIVE_POWER_SETPOINT, BUS_OR_BUSBAR_SECTION, @@ -33,7 +34,7 @@ import { getConnectivityFormData, getConnectivityWithPositionEmptyFormData, } from '../../../../connectivity/connectivity-form-utils'; -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { LccConverterStationCreationInfos, LccConverterStationFormInfos, @@ -44,10 +45,10 @@ import { } from './lcc-type'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, } from '../../../common/properties/property-utils'; import { MODIFICATION_TYPES } from '@gridsuite/commons-ui'; import { UNDEFINED_CONNECTION_DIRECTION } from '../../../../../network/constants'; @@ -62,21 +63,21 @@ import { } from '../../../../../../services/network-modification-types'; import { toModificationOperation } from '../../../../../utils/utils'; -export const getLccConverterStationSchema = () => - yup.object().shape({ +export function getLccConverterStationSchema(intl: IntlShape) { + return yup.object().shape({ [CONVERTER_STATION_ID]: yup.string().nullable().required(), [CONVERTER_STATION_NAME]: yup.string().nullable(), [LOSS_FACTOR]: yup .number() .nullable() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) .required(), [POWER_FACTOR]: yup .number() .nullable() - .min(-1, 'powerFactorMinValueError') - .max(1, 'powerFactorMaxValueError') + .min(-1, intl.formatMessage({ id: 'powerFactorMinValueError' })) + .max(1, intl.formatMessage({ id: 'powerFactorMaxValueError' })) .required(), [FILTERS_SHUNT_COMPENSATOR_TABLE]: yup .array() @@ -87,7 +88,7 @@ export const getLccConverterStationSchema = () => [MAX_Q_AT_NOMINAL_V]: yup .number() .nullable() - .min(0, 'qMaxAtNominalVMustBeGreaterThanZero') + .min(0, intl.formatMessage({ id: 'qMaxAtNominalVMustBeGreaterThanZero' })) .required(), [SHUNT_COMPENSATOR_SELECTED]: yup.boolean().nullable(), }) @@ -95,13 +96,22 @@ export const getLccConverterStationSchema = () => .nullable(), [CONNECTIVITY]: getConnectivityWithPositionSchema(false), }); +} -export const getLccConverterStationModificationSchema = () => - yup.object().shape({ +export function getLccConverterStationModificationSchema(intl: IntlShape) { + return yup.object().shape({ [CONVERTER_STATION_ID]: yup.string().nullable(), [CONVERTER_STATION_NAME]: yup.string().nullable(), - [LOSS_FACTOR]: yup.number().nullable().min(0, 'NormalizedPercentage').max(100, 'NormalizedPercentage'), - [POWER_FACTOR]: yup.number().nullable().min(-1, 'powerFactorMinValueError').max(1, 'powerFactorMaxValueError'), + [LOSS_FACTOR]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })), + [POWER_FACTOR]: yup + .number() + .nullable() + .min(-1, intl.formatMessage({ id: 'powerFactorMinValueError' })) + .max(1, intl.formatMessage({ id: 'powerFactorMaxValueError' })), [FILTERS_SHUNT_COMPENSATOR_TABLE]: yup .array() .of( @@ -111,13 +121,14 @@ export const getLccConverterStationModificationSchema = () => [MAX_Q_AT_NOMINAL_V]: yup .number() .nullable() - .min(0, 'qMaxAtNominalVMustBeGreaterThanZero') + .min(0, intl.formatMessage({ id: 'qMaxAtNominalVMustBeGreaterThanZero' })) .required(), [SHUNT_COMPENSATOR_SELECTED]: yup.boolean().nullable(), }) ) .nullable(), }); +} export const getEmptyShuntCompensatorOnSideFormData = () => ({ [SHUNT_COMPENSATOR_ID]: null, @@ -381,38 +392,61 @@ export function getLccConverterStationModificationData( }; } -export const getLccHvdcLineSchema = () => - yup +export function getLccHvdcLineSchema(intl: IntlShape) { + return yup .object() .shape({ - [NOMINAL_V]: yup.number().nullable().min(0, 'nominalVMustBeGreaterOrEqualToZero').required(), - [R]: yup.number().nullable().min(0, 'dcResistanceMustBeGreaterOrEqualToZero').required(), - [MAX_P]: yup.number().nullable().min(0, 'maxPMustBeGreaterOrEqualToZero').required(), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'nominalVMustBeGreaterOrEqualToZero' })) + .required(), + [R]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'dcResistanceMustBeGreaterOrEqualToZero' })) + .required(), + [MAX_P]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'maxPMustBeGreaterOrEqualToZero' })) + .required(), [ACTIVE_POWER_SETPOINT]: yup .number() .nullable() - .min(0, 'activePowerSetpointMinValueError') - .max(yup.ref(MAX_P), 'activePowerSetpointMaxValueError') + .min(0, intl.formatMessage({ id: 'activePowerSetpointMinValueError' })) + .max(yup.ref(MAX_P), intl.formatMessage({ id: 'activePowerSetpointMaxValueError' })) .required(), [CONVERTERS_MODE]: yup.string().required(), }) - .concat(creationPropertiesSchema); + .concat(getCreationPropertiesSchema(intl)); +} -export const getLccHvdcLineModificationSchema = () => - yup +export function getLccHvdcLineModificationSchema(intl: IntlShape) { + return yup .object() .shape({ - [NOMINAL_V]: yup.number().nullable().min(0, 'nominalVMustBeGreaterOrEqualToZero'), - [R]: yup.number().nullable().min(0, 'dcResistanceMustBeGreaterOrEqualToZero'), - [MAX_P]: yup.number().nullable().min(0, 'maxPMustBeGreaterOrEqualToZero'), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'nominalVMustBeGreaterOrEqualToZero' })), + [R]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'dcResistanceMustBeGreaterOrEqualToZero' })), + [MAX_P]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'maxPMustBeGreaterOrEqualToZero' })), [ACTIVE_POWER_SETPOINT]: yup .number() .nullable() - .min(0, 'activePowerSetpointMinValueError') - .max(yup.ref(MAX_P), 'activePowerSetpointMaxValueError'), + .min(0, intl.formatMessage({ id: 'activePowerSetpointMinValueError' })) + .max(yup.ref(MAX_P), intl.formatMessage({ id: 'activePowerSetpointMaxValueError' })), [CONVERTERS_MODE]: yup.string().nullable(), }) - .concat(modificationPropertiesSchema); + .concat(getModificationPropertiesSchema(intl)); +} export function getLccHvdcLineEmptyFormData() { return { diff --git a/src/components/dialogs/network-modifications/hvdc-line/lcc/creation/lcc-creation-dialog.tsx b/src/components/dialogs/network-modifications/hvdc-line/lcc/creation/lcc-creation-dialog.tsx index 1f9b8f1fcd..b1cc424df9 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/lcc/creation/lcc-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/hvdc-line/lcc/creation/lcc-creation-dialog.tsx @@ -23,7 +23,7 @@ import { POWER_FACTOR, R, } from '../../../../../utils/field-constants'; -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { FetchStatus } from '../../../../../../services/utils.type'; import { useForm } from 'react-hook-form'; import { DeepNullable } from '../../../../../utils/ts-utils'; @@ -34,7 +34,7 @@ import { useFormSearchCopy } from '../../../../commons/use-form-search-copy'; import { CustomFormProvider, ExtendedEquipmentType, useSnackMessage } from '@gridsuite/commons-ui'; import { ModificationDialog } from '../../../../commons/modificationDialog'; import EquipmentSearchDialog from '../../../../equipment-search-dialog'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { FORM_LOADING_DELAY } from '../../../../../network/constants'; import { createLcc } from '../../../../../../services/study/network-modifications'; import { sanitizeString } from '../../../../dialog-utils'; @@ -56,6 +56,7 @@ import { } from '../common/lcc-utils'; import { NetworkModificationDialogProps } from '../../../../../graph/menus/network-modifications/network-modification-menu.type'; import { Connectivity } from '../../../../connectivity/connectivity.type'; +import { useIntl } from 'react-intl'; export type LccCreationSchemaForm = { [EQUIPMENT_ID]: string; @@ -94,17 +95,6 @@ const emptyFormData = { [CONVERTER_STATION_2]: getLccConverterStationEmptyFormData(), }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [HVDC_LINE_TAB]: getLccHvdcLineSchema(), - [CONVERTER_STATION_1]: getLccConverterStationSchema(), - [CONVERTER_STATION_2]: getLccConverterStationSchema(), - }) - .required(); - export type LccCreationDialogProps = NetworkModificationDialogProps & { editData: LccCreationInfos; }; @@ -120,6 +110,21 @@ export function LccCreationDialog({ }: Readonly) { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [HVDC_LINE_TAB]: getLccHvdcLineSchema(intl), + [CONVERTER_STATION_1]: getLccConverterStationSchema(intl), + [CONVERTER_STATION_2]: getLccConverterStationSchema(intl), + }) + .required(), + [intl] + ); const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), diff --git a/src/components/dialogs/network-modifications/hvdc-line/lcc/modification/lcc-modification-dialog.tsx b/src/components/dialogs/network-modifications/hvdc-line/lcc/modification/lcc-modification-dialog.tsx index d2ad4f3880..2c54423349 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/lcc/modification/lcc-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/hvdc-line/lcc/modification/lcc-modification-dialog.tsx @@ -19,12 +19,12 @@ import { NOMINAL_V, R, } from '../../../../../utils/field-constants'; -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { CustomFormProvider, ExtendedEquipmentType, MODIFICATION_TYPES, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { LccDialogTab, LccFormInfos, LccModificationSchemaForm } from '../common/lcc-type'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useOpenShortWaitFetching } from '../../../../commons/handle-modification-form'; import { FetchStatus } from 'services/utils.type'; import { @@ -51,6 +51,7 @@ import { LccModificationForm } from './lcc-modification-form'; import { toModificationOperation } from '../../../../../utils/utils'; import { LccConverterStationModificationInfos, LccModificationInfos } from 'services/network-modification-types'; import { DeepNullable } from '../../../../../utils/ts-utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_ID]: '', @@ -64,17 +65,6 @@ export type LccModificationDialogProps = EquipmentModificationDialogProps & { editData?: LccModificationInfos; }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string(), - [EQUIPMENT_NAME]: yup.string(), - [HVDC_LINE_TAB]: getLccHvdcLineModificationSchema(), - [CONVERTER_STATION_1]: getLccConverterStationModificationSchema(), - [CONVERTER_STATION_2]: getLccConverterStationModificationSchema(), - }) - .required(); - export const LccModificationDialog = ({ editData, defaultIdValue, @@ -92,6 +82,22 @@ export const LccModificationDialog = ({ const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string(), + [EQUIPMENT_NAME]: yup.string(), + [HVDC_LINE_TAB]: getLccHvdcLineModificationSchema(intl), + [CONVERTER_STATION_1]: getLccConverterStationModificationSchema(intl), + [CONVERTER_STATION_2]: getLccConverterStationModificationSchema(intl), + }) + .required(), + [intl] + ); const formMethods = useForm>({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/hvdc-line/vsc/converter-station/converter-station-utils.tsx b/src/components/dialogs/network-modifications/hvdc-line/vsc/converter-station/converter-station-utils.tsx index 0d173e203e..5f33ad8547 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/vsc/converter-station/converter-station-utils.tsx +++ b/src/components/dialogs/network-modifications/hvdc-line/vsc/converter-station/converter-station-utils.tsx @@ -6,7 +6,7 @@ */ import { MODIFICATION_TYPES } from '@gridsuite/commons-ui'; -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { BUS_OR_BUSBAR_SECTION, CONNECTED, @@ -44,6 +44,7 @@ import { toModificationOperation } from '../../../../../utils/utils'; import { ConverterStationElementInfos, ConverterStationElementModificationInfos } from './converter-station-type'; import { ReactiveCapabilityCurvePoints } from '../../../../reactive-limits/reactive-limits.type'; import { AttributeModification } from '../../../../../../services/network-modification-types'; +import type { IntlShape } from 'react-intl'; export type UpdateReactiveCapabilityCurveTable = (action: string, index: number) => void; @@ -93,7 +94,7 @@ export interface ConverterStationModificationInterfaceEditData { maxQ: AttributeModification | null; } -export function getVscConverterStationSchema(id: string) { +export function getVscConverterStationSchema(intl: IntlShape, id: string) { return { [id]: yup.object().shape({ [CONVERTER_STATION_ID]: yup.string().nullable().required(), @@ -102,8 +103,8 @@ export function getVscConverterStationSchema(id: string) { .number() .nullable() .required() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage'), + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })), [VOLTAGE_REGULATION_ON]: yup.boolean(), [REACTIVE_POWER]: yup .number() @@ -115,27 +116,34 @@ export function getVscConverterStationSchema(id: string) { [VOLTAGE]: yup .number() .nullable() - .min(0, 'mustBeGreaterOrEqualToZero') + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) .when([VOLTAGE_REGULATION_ON], { is: true, then: (schema) => schema.required(), }), ...getConnectivityWithPositionValidationSchema(), - ...getReactiveLimitsSchema(false, true), + ...getReactiveLimitsSchema(intl, false, true), }), }; } -export function getVscConverterStationModificationSchema(id: string) { +export function getVscConverterStationModificationSchema(intl: IntlShape, id: string) { return { [id]: yup.object().shape({ [CONVERTER_STATION_ID]: yup.string(), [CONVERTER_STATION_NAME]: yup.string().nullable(), - [LOSS_FACTOR]: yup.number().nullable().min(0, 'NormalizedPercentage').max(100, 'NormalizedPercentage'), + [LOSS_FACTOR]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })), [VOLTAGE_REGULATION_ON]: yup.boolean().nullable(), [REACTIVE_POWER]: yup.number().nullable(), - [VOLTAGE]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - ...getReactiveLimitsSchema(true), + [VOLTAGE]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + ...getReactiveLimitsSchema(intl, true), }), }; } diff --git a/src/components/dialogs/network-modifications/hvdc-line/vsc/creation/vsc-creation-dialog.jsx b/src/components/dialogs/network-modifications/hvdc-line/vsc/creation/vsc-creation-dialog.jsx index cbefc8f8a7..120536fdd2 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/vsc/creation/vsc-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/hvdc-line/vsc/creation/vsc-creation-dialog.jsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { CustomFormProvider, ExtendedEquipmentType, TextInput, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; @@ -29,7 +29,7 @@ import { import { Box, Grid } from '@mui/material'; import { filledTextField, sanitizeString } from '../../../../dialog-utils'; import VscTabs from '../vsc-tabs'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { FORM_LOADING_DELAY } from '../../../../../network/constants'; import { ModificationDialog } from '../../../../commons/modificationDialog'; import { useOpenShortWaitFetching } from '../../../../commons/handle-modification-form'; @@ -52,25 +52,15 @@ import { useFormSearchCopy } from '../../../../commons/use-form-search-copy'; import EquipmentSearchDialog from '../../../../equipment-search-dialog'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, } from '../../../common/properties/property-utils'; import GridItem from '../../../../commons/grid-item'; import { VSC_CREATION_TABS } from '../vsc-utils'; +import { useIntl } from 'react-intl'; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - ...getVscHvdcLinePaneSchema(HVDC_LINE_TAB), - ...getVscConverterStationSchema(CONVERTER_STATION_1), - ...getVscConverterStationSchema(CONVERTER_STATION_2), - }) - .concat(creationPropertiesSchema) - .required(); const emptyFormData = { [EQUIPMENT_ID]: '', [EQUIPMENT_NAME]: '', @@ -91,9 +81,25 @@ const VscCreationDialog = ({ }) => { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [tabIndex, setTabIndex] = useState(VSC_CREATION_TABS.HVDC_LINE_TAB); const [tabIndexesWithError, setTabIndexesWithError] = useState([]); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + ...getVscHvdcLinePaneSchema(intl, HVDC_LINE_TAB), + ...getVscConverterStationSchema(intl, CONVERTER_STATION_1), + ...getVscConverterStationSchema(intl, CONVERTER_STATION_2), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/hvdc-line/vsc/hvdc-line-pane/vsc-hvdc-line-pane-utils.tsx b/src/components/dialogs/network-modifications/hvdc-line/vsc/hvdc-line-pane/vsc-hvdc-line-pane-utils.tsx index 3d384ce6a9..eb2c38a368 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/vsc/hvdc-line-pane/vsc-hvdc-line-pane-utils.tsx +++ b/src/components/dialogs/network-modifications/hvdc-line/vsc/hvdc-line-pane/vsc-hvdc-line-pane-utils.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SETPOINT, ANGLE_DROOP_ACTIVE_POWER_CONTROL, @@ -18,13 +18,22 @@ import { OPERATOR_ACTIVE_POWER_LIMIT_SIDE2, P0, } from '../../../../../utils/field-constants'; +import type { IntlShape } from 'react-intl'; -export function getVscHvdcLinePaneSchema(id: string) { +export function getVscHvdcLinePaneSchema(intl: IntlShape, id: string) { return { [id]: yup.object().shape( { - [NOMINAL_V]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [R]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [R]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), [MAX_P]: yup.number().nullable().required(), [OPERATOR_ACTIVE_POWER_LIMIT_SIDE1]: yup.number().nullable(), [OPERATOR_ACTIVE_POWER_LIMIT_SIDE2]: yup.number().nullable(), @@ -53,12 +62,18 @@ export function getVscHvdcLinePaneSchema(id: string) { }; } -export function getVscHvdcLineModificationPaneSchema(id: string) { +export function getVscHvdcLineModificationPaneSchema(intl: IntlShape, id: string) { return { [id]: yup.object().shape( { - [NOMINAL_V]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [R]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [R]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), [MAX_P]: yup.number().nullable(), [OPERATOR_ACTIVE_POWER_LIMIT_SIDE1]: yup.number().nullable(), [OPERATOR_ACTIVE_POWER_LIMIT_SIDE2]: yup.number().nullable(), diff --git a/src/components/dialogs/network-modifications/hvdc-line/vsc/modification/vsc-modification-dialog.tsx b/src/components/dialogs/network-modifications/hvdc-line/vsc/modification/vsc-modification-dialog.tsx index 0ac80d036f..2e0c033082 100644 --- a/src/components/dialogs/network-modifications/hvdc-line/vsc/modification/vsc-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/hvdc-line/vsc/modification/vsc-modification-dialog.tsx @@ -5,14 +5,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback, useEffect, useState } from 'react'; +import { type FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../../commons/modificationDialog'; import { EquipmentIdSelector } from '../../../../equipment-id/equipment-id-selector'; import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types'; import { sanitizeString } from '../../../../dialog-utils'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ACTIVE_POWER_SETPOINT, ADDITIONAL_PROPERTIES, @@ -65,23 +65,13 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../../common/properties/property-utils'; import { isNodeBuilt } from '../../../../../graph/util/model-functions'; import { ReactiveCapabilityCurvePoints } from '../../../../reactive-limits/reactive-limits.type'; +import { useIntl } from 'react-intl'; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().nullable(), - [EQUIPMENT_NAME]: yup.string().nullable(), - ...getVscHvdcLineModificationPaneSchema(HVDC_LINE_TAB), - ...getVscConverterStationModificationSchema(CONVERTER_STATION_1), - ...getVscConverterStationModificationSchema(CONVERTER_STATION_2), - }) - .concat(modificationPropertiesSchema) - .required(); const emptyFormData = { [EQUIPMENT_ID]: '', [EQUIPMENT_NAME]: '', @@ -97,7 +87,7 @@ const VSC_MODIFICATION_TABS = { CONVERTER_STATION_2: 2, }; -const VscModificationDialog: React.FC = ({ +const VscModificationDialog: FunctionComponent = ({ editData, defaultIdValue, // Used to pre-select an equipmentId when calling this dialog from the network map or spreadsheet currentNode, @@ -107,11 +97,28 @@ const VscModificationDialog: React.FC = ({ editDataFetchStatus, ...dialogProps }) => { + const intl = useIntl(); const [tabIndex, setTabIndex] = useState(VSC_MODIFICATION_TABS.HVDC_LINE_TAB); const [equipmentId, setEquipmentId] = useState(defaultIdValue ?? null); const [vscToModify, setVscToModify] = useState(null); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().nullable(), + [EQUIPMENT_NAME]: yup.string().nullable(), + ...getVscHvdcLineModificationPaneSchema(intl, HVDC_LINE_TAB), + ...getVscConverterStationModificationSchema(intl, CONVERTER_STATION_1), + ...getVscConverterStationModificationSchema(intl, CONVERTER_STATION_2), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.jsx b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.jsx index 473f241396..cf53f08ec2 100644 --- a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.jsx +++ b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.jsx @@ -23,10 +23,10 @@ import { VOLTAGE_LEVEL, } from 'components/utils/field-constants'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import { getConnectivityData, @@ -48,6 +48,7 @@ import { FetchStatus } from '../../../../services/utils'; import { fetchVoltageLevelsListInfos } from '../../../../services/study/network'; import LineAttachToVoltageLevelIllustration from './line-attach-to-voltage-level-illustration'; import { getNewVoltageLevelOptions } from '../../../utils/utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [ATTACHMENT_LINE_ID]: '', @@ -61,21 +62,6 @@ const emptyFormData = { ...getConnectivityWithoutPositionEmptyFormData(), }; -const formSchema = yup - .object() - .shape({ - [ATTACHMENT_LINE_ID]: yup.string().required(), - [ATTACHMENT_POINT_ID]: yup.string().required(), - [ATTACHMENT_POINT_NAME]: yup.string(), - [LINE1_ID]: yup.string().required(), - [LINE1_NAME]: yup.string(), - [LINE2_ID]: yup.string().required(), - [LINE2_NAME]: yup.string(), - ...getLineToAttachOrSplitFormValidationSchema(), - ...getConnectivityWithoutPositionValidationSchema(), - }) - .required(); - /** * Dialog to attach line to voltage level in the network * @param studyUuid the study we are currently working on @@ -102,9 +88,29 @@ const LineAttachToVoltageLevelDialog = ({ const [newVoltageLevel, setNewVoltageLevel] = useState(null); const { snackError } = useSnackMessage(); + const intl = useIntl(); const [voltageLevelOptions, setVoltageLevelOptions] = useState([]); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [ATTACHMENT_LINE_ID]: yup.string().required(), + [ATTACHMENT_POINT_ID]: yup.string().required(), + [ATTACHMENT_POINT_NAME]: yup.string(), + [LINE1_ID]: yup.string().required(), + [LINE1_NAME]: yup.string(), + [LINE2_ID]: yup.string().required(), + [LINE2_NAME]: yup.string(), + ...getLineToAttachOrSplitFormValidationSchema(intl), + ...getConnectivityWithoutPositionValidationSchema(), + }) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/line-split-with-voltage-level/line-split-with-voltage-level-dialog.jsx b/src/components/dialogs/network-modifications/line-split-with-voltage-level/line-split-with-voltage-level-dialog.jsx index cbbb691b16..71a458cc31 100644 --- a/src/components/dialogs/network-modifications/line-split-with-voltage-level/line-split-with-voltage-level-dialog.jsx +++ b/src/components/dialogs/network-modifications/line-split-with-voltage-level/line-split-with-voltage-level-dialog.jsx @@ -20,10 +20,10 @@ import { VOLTAGE_LEVEL, } from 'components/utils/field-constants'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import { getConnectivityData, @@ -45,6 +45,7 @@ import { divideLine } from '../../../../services/study/network-modifications'; import { FetchStatus } from '../../../../services/utils'; import { fetchVoltageLevelsListInfos } from '../../../../services/study/network'; import { getNewVoltageLevelOptions } from '../../../utils/utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [LINE1_ID]: '', @@ -55,18 +56,6 @@ const emptyFormData = { ...getConnectivityWithoutPositionEmptyFormData(), }; -const formSchema = yup - .object() - .shape({ - [LINE1_ID]: yup.string().required(), - [LINE1_NAME]: yup.string(), - [LINE2_ID]: yup.string().required(), - [LINE2_NAME]: yup.string(), - ...getLineToAttachOrSplitFormValidationSchema(), - ...getConnectivityWithoutPositionValidationSchema(), - }) - .required(); - /** * Dialog to create line split with voltage level in the network * @param studyUuid the study we are currently working on @@ -93,6 +82,23 @@ const LineSplitWithVoltageLevelDialog = ({ const [newVoltageLevel, setNewVoltageLevel] = useState(null); const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [LINE1_ID]: yup.string().required(), + [LINE1_NAME]: yup.string(), + [LINE2_ID]: yup.string().required(), + [LINE2_NAME]: yup.string(), + ...getLineToAttachOrSplitFormValidationSchema(intl), + ...getConnectivityWithoutPositionValidationSchema(), + }) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/line-to-attach-or-split-form/line-to-attach-or-split-utils.js b/src/components/dialogs/network-modifications/line-to-attach-or-split-form/line-to-attach-or-split-utils.js index 12290fcc3f..2cfdec5e76 100644 --- a/src/components/dialogs/network-modifications/line-to-attach-or-split-form/line-to-attach-or-split-utils.js +++ b/src/components/dialogs/network-modifications/line-to-attach-or-split-form/line-to-attach-or-split-utils.js @@ -6,35 +6,30 @@ */ import { LINE_TO_ATTACH_OR_SPLIT_ID } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { getPercentageAreaData, getPercentageAreaEmptyFormData, getPercentageAreaValidationSchema, } from '../../percentage-area/percentage-area-utils'; -const lineToAttachOrSplitFormValidationSchema = () => ({ - [LINE_TO_ATTACH_OR_SPLIT_ID]: yup.string().nullable().required(), - ...getPercentageAreaValidationSchema(), -}); -export const getLineToAttachOrSplitFormValidationSchema = () => { - return lineToAttachOrSplitFormValidationSchema(); -}; - -const lineToAttachOrSplitEmptyFormData = () => ({ - [LINE_TO_ATTACH_OR_SPLIT_ID]: null, - ...getPercentageAreaEmptyFormData(), -}); +export function getLineToAttachOrSplitFormValidationSchema(intl) { + return { + [LINE_TO_ATTACH_OR_SPLIT_ID]: yup.string().nullable().required(), + ...getPercentageAreaValidationSchema(intl), + }; +} -export const getLineToAttachOrSplitEmptyFormData = () => { - return lineToAttachOrSplitEmptyFormData(); -}; +export function getLineToAttachOrSplitEmptyFormData() { + return { + [LINE_TO_ATTACH_OR_SPLIT_ID]: null, + ...getPercentageAreaEmptyFormData(), + }; +} -export const getLineToAttachOrSplitFormData = ({ lineToAttachOrSplitId, percent }) => { +export function getLineToAttachOrSplitFormData({ lineToAttachOrSplitId, percent }) { return { [LINE_TO_ATTACH_OR_SPLIT_ID]: lineToAttachOrSplitId, - ...getPercentageAreaData({ - percent: percent, - }), + ...getPercentageAreaData({ percent: percent }), }; -}; +} diff --git a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.js b/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.js index 4f14a54080..789a070c5f 100644 --- a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.js +++ b/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.js @@ -6,56 +6,65 @@ */ import { + B1, + B2, CHARACTERISTICS, CONNECTIVITY_1, CONNECTIVITY_2, - R, G1, G2, - B1, - B2, + R, X, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { getConnectivityWithPositionEmptyFormData, getConnectivityWithPositionValidationSchema, } from '../../../connectivity/connectivity-form-utils'; -const characteristicsValidationSchema = (id, displayConnectivity, modification) => ({ - [id]: yup.object().shape({ - [R]: modification - ? yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero') - : yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [X]: modification ? yup.number().nullable() : yup.number().nullable().required(), - [B1]: yup.number().nullable(), - [G1]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [B2]: yup.number().nullable(), - [G2]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - ...(displayConnectivity && getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_1)), - ...(displayConnectivity && getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_2)), - }), -}); - -export const getCharacteristicsValidationSchema = (id, displayConnectivity, modification = false) => { - return characteristicsValidationSchema(id, displayConnectivity, modification); -}; - -const characteristicsEmptyFormData = (id, displayConnectivity = true) => ({ - [id]: { - [R]: null, - [X]: null, - [B1]: null, - [G1]: null, - [B2]: null, - [G2]: null, - ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, CONNECTIVITY_1)), - ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, CONNECTIVITY_2)), - }, -}); +export function getCharacteristicsValidationSchema(intl, id, displayConnectivity, modification = false) { + return { + [id]: yup.object().shape({ + [R]: modification + ? yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + : yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [X]: modification ? yup.number().nullable() : yup.number().nullable().required(), + [B1]: yup.number().nullable(), + [G1]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [B2]: yup.number().nullable(), + [G2]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + ...(displayConnectivity && getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_1)), + ...(displayConnectivity && getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_2)), + }), + }; +} export const getCharacteristicsEmptyFormData = (id = CHARACTERISTICS, displayConnectivity = true) => { - return characteristicsEmptyFormData(id, displayConnectivity); + return { + [id]: { + [R]: null, + [X]: null, + [B1]: null, + [G1]: null, + [B2]: null, + [G2]: null, + ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, CONNECTIVITY_1)), + ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, CONNECTIVITY_2)), + }, + }; }; export const getCharacteristicsFormData = ( diff --git a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.js b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.js index 06a65ba0f9..1023938156 100644 --- a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.js +++ b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.js @@ -6,7 +6,7 @@ */ import { EQUIPMENT_ID, EQUIPMENT_NAME, TAB_HEADER } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; const headerValidationSchema = (id) => ({ [id]: yup.object().shape({ diff --git a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.jsx b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.jsx index dff7c51cf3..5c8a1a67a8 100644 --- a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.jsx @@ -45,11 +45,11 @@ import { } from 'components/utils/field-constants'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { FetchStatus } from '../../../../../services/utils'; import { FORM_LOADING_DELAY, UNDEFINED_CONNECTION_DIRECTION } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { getConnectivityFormData } from '../../../connectivity/connectivity-form-utils'; import LineCharacteristicsPane from '../characteristics-pane/line-characteristics-pane'; @@ -80,13 +80,14 @@ import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modi import { createLine } from '../../../../../services/study/network-modifications'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, } from '../../common/properties/property-utils'; import GridItem from '../../../commons/grid-item'; import { formatCompleteCurrentLimit } from '../../../../utils/utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { ...getHeaderEmptyFormData(), @@ -120,6 +121,7 @@ const LineCreationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [tabIndex, setTabIndex] = useState(LineCreationDialogTab.CHARACTERISTICS_TAB); const [tabIndexesWithError, setTabIndexesWithError] = useState([]); @@ -130,15 +132,19 @@ const LineCreationDialog = ({ setOpenLineTypesCatalogDialog(false); }; - const formSchema = yup - .object() - .shape({ - ...getHeaderValidationSchema(), - ...getCharacteristicsValidationSchema(CHARACTERISTICS, displayConnectivity), - ...getLimitsValidationSchema(false), - }) - .concat(creationPropertiesSchema) - .required(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + ...getHeaderValidationSchema(), + ...getCharacteristicsValidationSchema(intl, CHARACTERISTICS, displayConnectivity), + ...getLimitsValidationSchema(intl, false), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, 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 a527cfb40b..52defb791d 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 @@ -52,7 +52,7 @@ import { } from 'components/utils/field-constants'; import { useForm } from 'react-hook-form'; import { sanitizeString } from 'components/dialogs/dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { @@ -84,7 +84,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { @@ -100,6 +100,7 @@ import { } from '../../common/measurements/branch-active-reactive-power-form-utils.ts'; import { LineModificationDialogTab } from '../line-utils'; import { isNodeBuilt } from '../../../../graph/util/model-functions.ts'; +import { useIntl } from 'react-intl'; /** * Dialog to modify a line in the network @@ -123,6 +124,7 @@ const LineModificationDialog = ({ editDataFetchStatus, ...dialogProps }) => { + const intl = useIntl(); const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); @@ -143,17 +145,21 @@ const LineModificationDialog = ({ [displayConnectivity] ); - const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - ...getCon1andCon2WithPositionValidationSchema(true), - ...getCharacteristicsValidationSchema(CHARACTERISTICS, displayConnectivity, true), - ...getLimitsValidationSchema(true), - ...getBranchActiveReactivePowerValidationSchema(STATE_ESTIMATION), - }) - .concat(modificationPropertiesSchema) - .required(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + ...getCon1andCon2WithPositionValidationSchema(true), + ...getCharacteristicsValidationSchema(intl, CHARACTERISTICS, displayConnectivity, true), + ...getLimitsValidationSchema(intl, true), + ...getBranchActiveReactivePowerValidationSchema(STATE_ESTIMATION), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog.jsx b/src/components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog.jsx index 5ef502bd57..744bcb9ebf 100644 --- a/src/components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog.jsx +++ b/src/components/dialogs/network-modifications/lines-attach-to-split-lines/lines-attach-to-split-lines-dialog.jsx @@ -24,7 +24,7 @@ import { VOLTAGE_LEVEL, VOLTAGE_LEVEL_ID, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { useCallback, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { ModificationDialog } from 'components/dialogs/commons/modificationDialog'; diff --git a/src/components/dialogs/network-modifications/load-scaling/load-scaling-dialog.jsx b/src/components/dialogs/network-modifications/load-scaling/load-scaling-dialog.jsx index a14b6a6d0e..243ab29737 100644 --- a/src/components/dialogs/network-modifications/load-scaling/load-scaling-dialog.jsx +++ b/src/components/dialogs/network-modifications/load-scaling/load-scaling-dialog.jsx @@ -7,10 +7,10 @@ import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../commons/modificationDialog'; import LoadScalingForm from './load-scaling-form'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { VARIATION_TYPE, VARIATIONS } from 'components/utils/field-constants'; import { getVariationsSchema } from './variation/variation-utils'; @@ -18,23 +18,29 @@ import { FORM_LOADING_DELAY, VARIATION_TYPES } from 'components/network/constant import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; import { loadScaling } from '../../../../services/study/network-modifications'; import { FetchStatus } from '../../../../services/utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [VARIATION_TYPE]: VARIATION_TYPES.DELTA_P.id, [VARIATIONS]: [], }; -const formSchema = yup - .object() - .shape({ - [VARIATION_TYPE]: yup.string().required(), - ...getVariationsSchema(VARIATIONS), - }) - .required(); - const LoadScalingDialog = ({ editData, currentNode, studyUuid, isUpdate, editDataFetchStatus, ...dialogProps }) => { const currentNodeUuid = currentNode.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [VARIATION_TYPE]: yup.string().required(), + ...getVariationsSchema(intl, VARIATIONS), + }) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/load-scaling/variation/variation-utils.js b/src/components/dialogs/network-modifications/load-scaling/variation/variation-utils.js index 93c27f0ab6..47a7aee282 100644 --- a/src/components/dialogs/network-modifications/load-scaling/variation/variation-utils.js +++ b/src/components/dialogs/network-modifications/load-scaling/variation/variation-utils.js @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { FILTERS, ID, @@ -19,7 +19,7 @@ import { import { REACTIVE_VARIATION_MODES } from 'components/network/constants'; export const IDENTIFIER_LIST = 'IDENTIFIER_LIST'; -export const getVariationSchema = () => +export const getVariationSchema = (intl) => yup .object() .nullable() @@ -39,11 +39,15 @@ export const getVariationSchema = () => }) ) .required() - .min(1, 'FieldIsRequired'), + .min(1, intl.formatMessage({ id: 'FieldIsRequired' })), }); -export const getVariationsSchema = (id) => ({ - [id]: yup.array().nullable().min(1, 'EmptyList.variations').of(getVariationSchema()), +export const getVariationsSchema = (intl, id) => ({ + [id]: yup + .array() + .nullable() + .min(1, intl.formatMessage({ id: 'EmptyList.variations' })) + .of(getVariationSchema(intl)), }); export const getVariationEmptyForm = (variationMode) => { diff --git a/src/components/dialogs/network-modifications/load/creation/load-creation-dialog.tsx b/src/components/dialogs/network-modifications/load/creation/load-creation-dialog.tsx index 0460c2226a..33b8971a2a 100644 --- a/src/components/dialogs/network-modifications/load/creation/load-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/load/creation/load-creation-dialog.tsx @@ -16,13 +16,13 @@ import { LOAD_TYPE, REACTIVE_POWER_SET_POINT, } from 'components/utils/field-constants'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { FieldErrors, useForm } from 'react-hook-form'; import { sanitizeString } from '../../../dialog-utils'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { FORM_LOADING_DELAY, UNDEFINED_CONNECTION_DIRECTION, UNDEFINED_LOAD_TYPE } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { getConnectivityFormData, @@ -34,7 +34,7 @@ import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import { createLoad } from '../../../../../services/study/network-modifications'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, @@ -48,6 +48,7 @@ import { LoadDialogTab } from '../common/load-utils'; import LoadDialogTabsContent from '../common/load-dialog-tabs-content'; import { LoadFormInfos } from '../common/load.type'; import useVoltageLevelsListInfos from 'hooks/use-voltage-levels-list-infos'; +import { useIntl } from 'react-intl'; /** * Dialog to create a load in the network @@ -68,19 +69,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [LOAD_TYPE]: yup.string().nullable(), - [ACTIVE_POWER_SETPOINT]: yup.number().nullable().required(), - [REACTIVE_POWER_SET_POINT]: yup.number().nullable().required(), - [CONNECTIVITY]: getConnectivityWithPositionSchema(false), - }) - .concat(creationPropertiesSchema) - .required(); - export type LoadCreationDialogProps = NetworkModificationDialogProps & { editData: LoadCreationInfos; }; @@ -96,10 +84,28 @@ export function LoadCreationDialog({ }: Readonly) { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [tabIndexesWithError, setTabIndexesWithError] = useState([]); const [tabIndex, setTabIndex] = useState(LoadDialogTab.CONNECTIVITY_TAB); const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNode?.id, currentRootNetworkUuid); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [LOAD_TYPE]: yup.string().nullable(), + [ACTIVE_POWER_SETPOINT]: yup.number().nullable().required(), + [REACTIVE_POWER_SET_POINT]: yup.number().nullable().required(), + [CONNECTIVITY]: getConnectivityWithPositionSchema(false), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), diff --git a/src/components/dialogs/network-modifications/load/modification/load-modification-dialog.tsx b/src/components/dialogs/network-modifications/load/modification/load-modification-dialog.tsx index 9e48025af4..f957f0573a 100644 --- a/src/components/dialogs/network-modifications/load/modification/load-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/load/modification/load-modification-dialog.tsx @@ -29,10 +29,11 @@ import { VALUE, VOLTAGE_LEVEL, } from 'components/utils/field-constants'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { FieldErrors, useForm } from 'react-hook-form'; import { sanitizeString } from '../../../dialog-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; +import type { ObjectSchema } from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { EquipmentIdSelector } from '../../../equipment-id/equipment-id-selector'; import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types'; @@ -42,7 +43,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { fetchNetworkElementInfos } from '../../../../../services/study/network'; @@ -65,6 +66,7 @@ import LoadDialogTabsContent from '../common/load-dialog-tabs-content'; import { LoadFormInfos } from '../common/load.type'; import { DeepNullable } from 'components/utils/ts-utils'; import { getSetPointsEmptyFormData, getSetPointsSchema } from 'components/dialogs/set-points/set-points-utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -75,18 +77,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema: yup.ObjectSchema> = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string().nullable(), - [LOAD_TYPE]: yup.string().nullable(), - [CONNECTIVITY]: getConnectivityWithPositionSchema(true), - [STATE_ESTIMATION]: getInjectionActiveReactivePowerValidationSchemaProperties(), - ...getSetPointsSchema(true), - }) - .concat(modificationPropertiesSchema) - .required(); - export type LoadModificationDialogProps = EquipmentModificationDialogProps & { editData?: LoadModificationInfos; }; @@ -103,12 +93,28 @@ export default function LoadModificationDialog({ }: LoadModificationDialogProps) { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); const [tabIndexesWithError, setTabIndexesWithError] = useState([]); const [tabIndex, setTabIndex] = useState(LoadDialogTab.CONNECTIVITY_TAB); const [loadToModify, setLoadToModify] = useState(null); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string().nullable(), + [LOAD_TYPE]: yup.string().nullable(), + [CONNECTIVITY]: getConnectivityWithPositionSchema(true), + [STATE_ESTIMATION]: getInjectionActiveReactivePowerValidationSchemaProperties(), + ...getSetPointsSchema(intl, true), + }) + .concat(getModificationPropertiesSchema(intl)) + .required() satisfies ObjectSchema>, + [intl] + ); const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), diff --git a/src/components/dialogs/network-modifications/shunt-compensator/characteristics-pane/characteristics-form-utils.js b/src/components/dialogs/network-modifications/shunt-compensator/characteristics-pane/characteristics-form-utils.js index d132534713..97c466b25f 100644 --- a/src/components/dialogs/network-modifications/shunt-compensator/characteristics-pane/characteristics-form-utils.js +++ b/src/components/dialogs/network-modifications/shunt-compensator/characteristics-pane/characteristics-form-utils.js @@ -6,42 +6,31 @@ */ import { - SHUNT_COMPENSATOR_TYPE, CHARACTERISTICS_CHOICE, CHARACTERISTICS_CHOICES, + MAX_Q_AT_NOMINAL_V, + MAX_SUSCEPTANCE, MAXIMUM_SECTION_COUNT, SECTION_COUNT, - SWITCHED_ON_SUSCEPTANCE, + SHUNT_COMPENSATOR_TYPE, SWITCHED_ON_Q_AT_NOMINAL_V, - MAX_Q_AT_NOMINAL_V, - MAX_SUSCEPTANCE, + SWITCHED_ON_SUSCEPTANCE, } from 'components/utils/field-constants'; import { computeSwitchedOnValue } from 'components/utils/utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { SHUNT_COMPENSATOR_TYPES } from '../../../../network/constants'; -const characteristicsValidationSchema = (isModification) => ({ - [CHARACTERISTICS_CHOICE]: yup.string().required(), - [SHUNT_COMPENSATOR_TYPE]: yup.string().when([CHARACTERISTICS_CHOICE], { - is: (characteristicsChoice) => - characteristicsChoice === CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id && !isModification, - then: (schema) => - schema.oneOf([SHUNT_COMPENSATOR_TYPES.CAPACITOR.id, SHUNT_COMPENSATOR_TYPES.REACTOR.id]).required(), - otherwise: (schema) => schema.nullable(), - }), - ...(isModification - ? getCharacteristicsModificationFormValidationSchema() - : getCharacteristicsCreateFormValidationSchema()), -}); - -const getCharacteristicsCreateFormValidationSchema = () => { +const getCharacteristicsCreateFormValidationSchema = (intl) => { return { [MAX_Q_AT_NOMINAL_V]: yup .number() .nullable() .when([CHARACTERISTICS_CHOICE], { is: (characteristicsChoice) => characteristicsChoice === CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id, - then: (schema) => schema.min(0, 'ShuntCompensatorErrorQAtNominalVoltageLessThanZero').required(), + then: (schema) => + schema + .min(0, intl.formatMessage({ id: 'ShuntCompensatorErrorQAtNominalVoltageLessThanZero' })) + .required(), }), [MAX_SUSCEPTANCE]: yup .number() @@ -50,45 +39,70 @@ const getCharacteristicsCreateFormValidationSchema = () => { is: (characteristicsChoice) => characteristicsChoice === CHARACTERISTICS_CHOICES.SUSCEPTANCE.id, then: (schema) => schema.required(), }), - [MAXIMUM_SECTION_COUNT]: yup.number().required().min(1, 'MaximumSectionCountMustBeGreaterOrEqualToOne'), + [MAXIMUM_SECTION_COUNT]: yup + .number() + .required() + .min(1, intl.formatMessage({ id: 'MaximumSectionCountMustBeGreaterOrEqualToOne' })), [SECTION_COUNT]: yup .number() .required() - .min(0, 'SectionCountMustBeBetweenZeroAndMaximumSectionCount') - .max(yup.ref(MAXIMUM_SECTION_COUNT), 'SectionCountMustBeBetweenZeroAndMaximumSectionCount'), + .min(0, intl.formatMessage({ id: 'SectionCountMustBeBetweenZeroAndMaximumSectionCount' })) + .max( + yup.ref(MAXIMUM_SECTION_COUNT), + intl.formatMessage({ id: 'SectionCountMustBeBetweenZeroAndMaximumSectionCount' }) + ), [SWITCHED_ON_Q_AT_NOMINAL_V]: yup.number().notRequired(), [SWITCHED_ON_SUSCEPTANCE]: yup.number().notRequired(), }; }; -const getCharacteristicsModificationFormValidationSchema = () => { +const getCharacteristicsModificationFormValidationSchema = (intl) => { return { - [MAX_Q_AT_NOMINAL_V]: yup.number().nullable().min(0, 'ShuntCompensatorErrorQAtNominalVoltageLessThanZero'), + [MAX_Q_AT_NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'ShuntCompensatorErrorQAtNominalVoltageLessThanZero' })), [MAX_SUSCEPTANCE]: yup.number().nullable(), - [MAXIMUM_SECTION_COUNT]: yup.number().min(1, 'MaximumSectionCountMustBeGreaterOrEqualToOne').nullable(), - [SECTION_COUNT]: yup.number().nullable().min(0, 'SectionCountMustBeBetweenZeroAndMaximumSectionCount'), + [MAXIMUM_SECTION_COUNT]: yup + .number() + .min(1, intl.formatMessage({ id: 'MaximumSectionCountMustBeGreaterOrEqualToOne' })) + .nullable(), + [SECTION_COUNT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'SectionCountMustBeBetweenZeroAndMaximumSectionCount' })), [SWITCHED_ON_Q_AT_NOMINAL_V]: yup.number().nullable(), [SWITCHED_ON_SUSCEPTANCE]: yup.number().nullable(), }; }; -export const getCharacteristicsFormValidationSchema = (isModification = false) => { - return characteristicsValidationSchema(isModification); +export const getCharacteristicsFormValidationSchema = (intl, isModification = false) => { + return { + [CHARACTERISTICS_CHOICE]: yup.string().required(), + [SHUNT_COMPENSATOR_TYPE]: yup.string().when([CHARACTERISTICS_CHOICE], { + is: (characteristicsChoice) => + characteristicsChoice === CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id && !isModification, + then: (schema) => + schema.oneOf([SHUNT_COMPENSATOR_TYPES.CAPACITOR.id, SHUNT_COMPENSATOR_TYPES.REACTOR.id]).required(), + otherwise: (schema) => schema.nullable(), + }), + ...(isModification + ? getCharacteristicsModificationFormValidationSchema(intl) + : getCharacteristicsCreateFormValidationSchema(intl)), + }; }; -const characteristicsEmptyFormData = () => ({ - [MAXIMUM_SECTION_COUNT]: null, - [SECTION_COUNT]: null, - [CHARACTERISTICS_CHOICE]: CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id, - [MAX_SUSCEPTANCE]: null, - [SHUNT_COMPENSATOR_TYPE]: null, - [MAX_Q_AT_NOMINAL_V]: null, - [SWITCHED_ON_Q_AT_NOMINAL_V]: null, - [SWITCHED_ON_SUSCEPTANCE]: null, -}); - export const getCharacteristicsEmptyFormData = () => { - return characteristicsEmptyFormData(); + return { + [MAXIMUM_SECTION_COUNT]: null, + [SECTION_COUNT]: null, + [CHARACTERISTICS_CHOICE]: CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id, + [MAX_SUSCEPTANCE]: null, + [SHUNT_COMPENSATOR_TYPE]: null, + [MAX_Q_AT_NOMINAL_V]: null, + [SWITCHED_ON_Q_AT_NOMINAL_V]: null, + [SWITCHED_ON_SUSCEPTANCE]: null, + }; }; export const getCharacteristicsFormData = ({ diff --git a/src/components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog.jsx b/src/components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog.jsx index fa137f4513..5952a8a35b 100644 --- a/src/components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/shunt-compensator/creation/shunt-compensator-creation-dialog.jsx @@ -26,13 +26,13 @@ import { } from 'components/utils/field-constants'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import PropTypes from 'prop-types'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../../dialog-utils'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { FORM_LOADING_DELAY, UNDEFINED_CONNECTION_DIRECTION } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { getConnectivityFormData, @@ -50,11 +50,12 @@ import { createShuntCompensator } from '../../../../../services/study/network-mo import { FetchStatus } from '../../../../../services/utils'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, } from '../../common/properties/property-utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_ID]: '', @@ -64,17 +65,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - ...getConnectivityWithPositionValidationSchema(), - ...getCharacteristicsFormValidationSchema(), - }) - .concat(creationPropertiesSchema) - .required(); - /** * Dialog to create a shunt compensator in the network * @param voltageLevelOptionsPromise Promise handling list of voltage level options @@ -95,9 +85,24 @@ const ShuntCompensatorCreationDialog = ({ ...dialogProps }) => { const currentNodeUuid = currentNode?.id; - + const intl = useIntl(); const { snackError, snackWarning } = useSnackMessage(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + ...getConnectivityWithPositionValidationSchema(), + ...getCharacteristicsFormValidationSchema(intl), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog.jsx b/src/components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog.jsx index 1b0f67cc5f..a69990b6b2 100644 --- a/src/components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog.jsx +++ b/src/components/dialogs/network-modifications/shunt-compensator/modification/shunt-compensator-modification-dialog.jsx @@ -31,9 +31,9 @@ import { getCharacteristicsFormValidationSchema, } from '../characteristics-pane/characteristics-form-utils'; import { useForm } from 'react-hook-form'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { ModificationDialog } from '../../../commons/modificationDialog'; import ShuntCompensatorModificationForm from './shunt-compensator-modification-form'; import { useOpenShortWaitFetching } from '../../../commons/handle-modification-form'; @@ -48,7 +48,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { @@ -57,6 +57,7 @@ import { getConnectivityWithPositionValidationSchema, } from '../../../connectivity/connectivity-form-utils'; import { isNodeBuilt } from '../../../../graph/util/model-functions.ts'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -65,16 +66,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - ...getConnectivityWithPositionValidationSchema(true), - ...getCharacteristicsFormValidationSchema(true), - }) - .concat(modificationPropertiesSchema) - .required(); - const ShuntCompensatorModificationDialog = ({ editData, // contains data when we try to edit an existing hypothesis from the current node's list defaultIdValue, // Used to pre-select an equipmentId when calling this dialog from the network map @@ -86,7 +77,7 @@ const ShuntCompensatorModificationDialog = ({ ...dialogProps }) => { const currentNodeUuid = currentNode?.id; - + const intl = useIntl(); const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -95,6 +86,20 @@ const ShuntCompensatorModificationDialog = ({ const [idExists, setIdExists] = useState(null); const [loading, setLoading] = useState(false); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + ...getConnectivityWithPositionValidationSchema(true), + ...getCharacteristicsFormValidationSchema(intl, true), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/static-var-compensator/creation/set-points-limits-form-utils.ts b/src/components/dialogs/network-modifications/static-var-compensator/creation/set-points-limits-form-utils.ts index 02e609edff..d3ecacb476 100644 --- a/src/components/dialogs/network-modifications/static-var-compensator/creation/set-points-limits-form-utils.ts +++ b/src/components/dialogs/network-modifications/static-var-compensator/creation/set-points-limits-form-utils.ts @@ -23,9 +23,10 @@ import { VOLTAGE_REGULATION_TYPE, VOLTAGE_SET_POINT, } from 'components/utils/field-constants'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; import { REGULATION_TYPES } from '../../../../network/constants'; -import { Schema } from 'yup'; +import type { Schema } from 'yup'; +import type { IntlShape } from 'react-intl'; export const getReactiveFormEmptyFormData = (id = SETPOINTS_LIMITS) => ({ [id]: { @@ -65,8 +66,8 @@ const requiredWhenQatNominalVChoice = (schema: Schema) => otherwise: (schema) => schema.notRequired(), }); -export const getReactiveFormValidationSchema = () => - yup.object().shape({ +export function getReactiveFormValidationSchema(intl: IntlShape) { + return yup.object().shape({ [MAX_SUSCEPTANCE]: requiredWhenSusceptanceChoice(yup.number().nullable()), [MIN_SUSCEPTANCE]: requiredWhenSusceptanceChoice(yup.number().nullable()), [MAX_Q_AT_NOMINAL_V]: requiredWhenQatNominalVChoice(yup.number().nullable()), @@ -74,7 +75,7 @@ export const getReactiveFormValidationSchema = () => [VOLTAGE_SET_POINT]: yup .number() .nullable() - .min(0, 'mustBeGreaterOrEqualToZero') + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) .when([VOLTAGE_REGULATION_MODE], { is: (voltageRegulationMode: string) => voltageRegulationMode === VOLTAGE_REGULATION_MODES.VOLTAGE.id, then: (schema) => schema.required(), @@ -111,6 +112,7 @@ export const getReactiveFormValidationSchema = () => }) ), }); +} export const getReactiveFormData = ({ maxSusceptance, diff --git a/src/components/dialogs/network-modifications/static-var-compensator/creation/standby-automaton-form-utils.ts b/src/components/dialogs/network-modifications/static-var-compensator/creation/standby-automaton-form-utils.ts index f28b429cc0..06b874cbdd 100644 --- a/src/components/dialogs/network-modifications/static-var-compensator/creation/standby-automaton-form-utils.ts +++ b/src/components/dialogs/network-modifications/static-var-compensator/creation/standby-automaton-form-utils.ts @@ -24,7 +24,9 @@ import { VOLTAGE_REGULATION_MODE, VOLTAGE_REGULATION_MODES, } from 'components/utils/field-constants'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; +import { NumberSchema, type Schema } from 'yup'; +import type { IntlShape } from 'react-intl'; export const getStandbyAutomatonEmptyFormData = (id = AUTOMATON) => ({ [id]: { @@ -50,30 +52,38 @@ const requiredIfAddStandbyAutomaton = (yup: any) => then: (schema: any) => schema.required(), }); -const requiredWhenSusceptanceChoice = (schema: any) => - schema.when([ADD_STAND_BY_AUTOMATON, CHARACTERISTICS_CHOICE_AUTOMATON], { +function requiredWhenSusceptanceChoice(intl: IntlShape, schema: NumberSchema) { + return schema.when([ADD_STAND_BY_AUTOMATON, CHARACTERISTICS_CHOICE_AUTOMATON], { is: (addStandbyAutomaton: boolean, characteristicsChoiceAutomaton: string) => addStandbyAutomaton && characteristicsChoiceAutomaton === CHARACTERISTICS_CHOICES.SUSCEPTANCE.id, then: (schema: any) => schema - .min(yup.ref(MIN_S_AUTOMATON), 'StaticVarCompensatorErrorSFixLessThanSMin') - .max(yup.ref(MAX_S_AUTOMATON), 'StaticVarCompensatorErrorSFixGreaterThanSMax') + .min(yup.ref(MIN_S_AUTOMATON), intl.formatMessage({ id: 'StaticVarCompensatorErrorSFixLessThanSMin' })) + .max( + yup.ref(MAX_S_AUTOMATON), + intl.formatMessage({ id: 'StaticVarCompensatorErrorSFixGreaterThanSMax' }) + ) .required(), }); +} -const requiredWhenQatNominalVChoice = (schema: any) => - schema.when([ADD_STAND_BY_AUTOMATON, CHARACTERISTICS_CHOICE_AUTOMATON], { +function requiredWhenQatNominalVChoice(intl: IntlShape, schema: NumberSchema) { + return schema.when([ADD_STAND_BY_AUTOMATON, CHARACTERISTICS_CHOICE_AUTOMATON], { is: (addStandbyAutomaton: boolean, characteristicsChoiceAutomaton: string) => addStandbyAutomaton && characteristicsChoiceAutomaton === CHARACTERISTICS_CHOICES.Q_AT_NOMINAL_V.id, then: (schema: any) => schema - .min(yup.ref(MIN_Q_AUTOMATON), 'StaticVarCompensatorErrorQFixLessThanQMin') - .max(yup.ref(MAX_Q_AUTOMATON), 'StaticVarCompensatorErrorQFixGreaterThanQMax') + .min(yup.ref(MIN_Q_AUTOMATON), intl.formatMessage({ id: 'StaticVarCompensatorErrorQFixLessThanQMin' })) + .max( + yup.ref(MAX_Q_AUTOMATON), + intl.formatMessage({ id: 'StaticVarCompensatorErrorQFixGreaterThanQMax' }) + ) .required(), }); +} -export const getStandbyAutomatonFormValidationSchema = () => - yup.object().shape({ +export function getStandbyAutomatonFormValidationSchema(intl: IntlShape) { + return yup.object().shape({ [ADD_STAND_BY_AUTOMATON]: yup.boolean().nullable(), [STAND_BY_AUTOMATON]: yup .boolean() @@ -87,9 +97,10 @@ export const getStandbyAutomatonFormValidationSchema = () => [HIGH_VOLTAGE_SET_POINT]: requiredIfAddStandbyAutomaton(yup.number().min(0, 'mustBeGreaterOrEqualToZero')), [LOW_VOLTAGE_THRESHOLD]: requiredIfAddStandbyAutomaton(yup.number().min(0, 'mustBeGreaterOrEqualToZero')), [HIGH_VOLTAGE_THRESHOLD]: requiredIfAddStandbyAutomaton(yup.number().min(0, 'mustBeGreaterOrEqualToZero')), - [B0]: requiredWhenSusceptanceChoice(yup.number().nullable()), - [Q0]: requiredWhenQatNominalVChoice(yup.number().nullable()), + [B0]: requiredWhenSusceptanceChoice(intl, yup.number().nullable()), + [Q0]: requiredWhenQatNominalVChoice(intl, yup.number().nullable()), }); +} export const getStandbyAutomatonFormData = ({ addStandbyAutomaton, diff --git a/src/components/dialogs/network-modifications/static-var-compensator/creation/static-var-compensator-creation-dialog.tsx b/src/components/dialogs/network-modifications/static-var-compensator/creation/static-var-compensator-creation-dialog.tsx index 14419051dc..f1ea19af39 100644 --- a/src/components/dialogs/network-modifications/static-var-compensator/creation/static-var-compensator-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/static-var-compensator/creation/static-var-compensator-creation-dialog.tsx @@ -44,13 +44,13 @@ import { VOLTAGE_SET_POINT, } from 'components/utils/field-constants'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../../dialog-utils'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { FORM_LOADING_DELAY, REGULATION_TYPES, UNDEFINED_CONNECTION_DIRECTION } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { getConnectivityFormData, @@ -61,7 +61,7 @@ import { createStaticVarCompensator } from '../../../../../services/study/networ import { FetchStatus } from '../../../../../services/utils'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, Property, @@ -83,6 +83,7 @@ import { } from './standby-automaton-form-utils'; import { DeepNullable } from '../../../../utils/ts-utils'; import { StaticVarCompensatorCreationDialogTab } from './static-var-compensator-creation-utils'; +import { useIntl } from 'react-intl'; export type StaticVarCompensatorCreationSchemaForm = { [EQUIPMENT_ID]: string; @@ -137,18 +138,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [CONNECTIVITY]: getConnectivityWithPositionSchema(false), - [SETPOINTS_LIMITS]: getReactiveFormValidationSchema(), - [AUTOMATON]: getStandbyAutomatonFormValidationSchema(), - }) - .concat(creationPropertiesSchema) - .required(); - /** * Dialog to create a static var compensator in the network * @param studyUuid the study we are currently working on @@ -168,9 +157,25 @@ const StaticVarCompensatorCreationDialog: FC = ({ ...dialogProps }) => { const currentNodeUuid = currentNode?.id; - + const intl = useIntl(); const { snackError } = useSnackMessage(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [CONNECTIVITY]: getConnectivityWithPositionSchema(false), + [SETPOINTS_LIMITS]: getReactiveFormValidationSchema(intl), + [AUTOMATON]: getStandbyAutomatonFormValidationSchema(intl), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm>({ defaultValues: emptyFormData, resolver: yupResolver>(formSchema), diff --git a/src/components/dialogs/network-modifications/substation/creation/substation-creation-dialog.jsx b/src/components/dialogs/network-modifications/substation/creation/substation-creation-dialog.jsx index d844004d26..1ebd050304 100644 --- a/src/components/dialogs/network-modifications/substation/creation/substation-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/substation/creation/substation-creation-dialog.jsx @@ -9,10 +9,10 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; import EquipmentSearchDialog from '../../../equipment-search-dialog'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { CustomFormProvider, fetchDefaultCountry, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { useFormSearchCopy } from '../../../commons/use-form-search-copy'; import { COUNTRY, EQUIPMENT_ID, EQUIPMENT_NAME } from 'components/utils/field-constants'; import SubstationCreationForm from './substation-creation-form'; @@ -23,11 +23,12 @@ import { createSubstation } from '../../../../../services/study/network-modifica import { FetchStatus } from '../../../../../services/utils'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, } from '../../common/properties/property-utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_ID]: '', @@ -35,14 +36,6 @@ const emptyFormData = { [COUNTRY]: null, ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - [COUNTRY]: yup.string().nullable(), - }) - .concat(creationPropertiesSchema); const SubstationCreationDialog = ({ editData, @@ -55,7 +48,20 @@ const SubstationCreationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + [COUNTRY]: yup.string().nullable(), + }) + .concat(getCreationPropertiesSchema(intl)), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/substation/modification/substation-modification-dialog.jsx b/src/components/dialogs/network-modifications/substation/modification/substation-modification-dialog.jsx index f8078835f9..7dc8a2c1a3 100644 --- a/src/components/dialogs/network-modifications/substation/modification/substation-modification-dialog.jsx +++ b/src/components/dialogs/network-modifications/substation/modification/substation-modification-dialog.jsx @@ -7,10 +7,10 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ADDITIONAL_PROPERTIES, COUNTRY, EQUIPMENT_NAME } from 'components/utils/field-constants'; import SubstationModificationForm from './substation-modification-form'; import { sanitizeString } from '../../../dialog-utils'; @@ -25,10 +25,11 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { isNodeBuilt } from '../../../../graph/util/model-functions.ts'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -36,14 +37,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - [COUNTRY]: yup.string().nullable(), - }) - .concat(modificationPropertiesSchema); - /** * Dialog to modify a substation in the network * @param editData the data to edit @@ -66,10 +59,23 @@ const SubstationModificationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); const [substationToModify, setSubstationToModify] = useState(null); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + [COUNTRY]: yup.string().nullable(), + }) + .concat(getModificationPropertiesSchema(intl)), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog.jsx b/src/components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog.jsx index 326548378d..bbeca3997d 100644 --- a/src/components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/tabular-creation/tabular-creation-dialog.jsx @@ -6,10 +6,10 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; import { FORM_LOADING_DELAY } from 'components/network/constants'; @@ -27,14 +27,6 @@ import { import { useIntl } from 'react-intl'; import { formatModification } from '../tabular-modification/tabular-modification-utils'; -const formSchema = yup - .object() - .shape({ - [TYPE]: yup.string().nullable().required(), - [CREATIONS_TABLE]: yup.array().min(1, 'CreationsRequiredTabError').required(), - }) - .required(); - const emptyFormData = { [TYPE]: null, [CREATIONS_TABLE]: [], @@ -56,6 +48,21 @@ const TabularCreationDialog = ({ studyUuid, currentNode, editData, isUpdate, edi const { snackError } = useSnackMessage(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [TYPE]: yup.string().nullable().required(), + [CREATIONS_TABLE]: yup + .array() + .min(1, intl.formatMessage({ id: 'CreationsRequiredTabError' })) + .required(), + }) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog.jsx b/src/components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog.jsx index f2d1ba51d4..c12ed9c7cc 100644 --- a/src/components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog.jsx +++ b/src/components/dialogs/network-modifications/tabular-modification/tabular-modification-dialog.jsx @@ -6,10 +6,10 @@ */ import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { CustomFormProvider, useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; import { FORM_LOADING_DELAY } from 'components/network/constants'; @@ -27,14 +27,6 @@ import { } from './tabular-modification-utils'; import { useIntl } from 'react-intl'; -const formSchema = yup - .object() - .shape({ - [TYPE]: yup.string().nullable().required(), - [MODIFICATIONS_TABLE]: yup.array().min(1, 'ModificationsRequiredTabError').required(), - }) - .required(); - const emptyFormData = { [TYPE]: null, [MODIFICATIONS_TABLE]: [], @@ -63,6 +55,21 @@ const TabularModificationDialog = ({ const { snackError } = useSnackMessage(); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [TYPE]: yup.string().nullable().required(), + [MODIFICATIONS_TABLE]: yup + .array() + .min(1, intl.formatMessage({ id: 'ModificationsRequiredTabError' })) + .required(), + }) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/characteristics-pane/two-windings-transformer-characteristics-pane-utils.js b/src/components/dialogs/network-modifications/two-windings-transformer/characteristics-pane/two-windings-transformer-characteristics-pane-utils.js index 8b60a92bbe..75ea0a02e6 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/characteristics-pane/two-windings-transformer-characteristics-pane-utils.js +++ b/src/components/dialogs/network-modifications/two-windings-transformer/characteristics-pane/two-windings-transformer-characteristics-pane-utils.js @@ -5,33 +5,62 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { CHARACTERISTICS, G, B, RATED_S, RATED_U1, RATED_U2, R, X } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import { B, CHARACTERISTICS, G, R, RATED_S, RATED_U1, RATED_U2, X } from 'components/utils/field-constants'; +import * as yup from 'yup'; -const characteristicsValidationSchema = (isModification, additionalFields) => ({ - [CHARACTERISTICS]: yup.object().shape({ - [R]: isModification - ? yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero') - : yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [X]: isModification ? yup.number().nullable() : yup.number().nullable().required(), - [G]: isModification - ? yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero') - : yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [B]: isModification ? yup.number().nullable() : yup.number().nullable().required(), - [RATED_S]: yup.number().nullable().positive('RatedNominalPowerMustBeGreaterThanZero'), - [RATED_U1]: isModification - ? yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero') - : yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [RATED_U2]: isModification - ? yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero') - : yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - ...additionalFields, - }), -}); - -export const getCharacteristicsValidationSchema = (isModification = false, additionalFields = {}) => { - return characteristicsValidationSchema(isModification, additionalFields); -}; +export function getCharacteristicsValidationSchema(intl, isModification = false, additionalFields = {}) { + return { + [CHARACTERISTICS]: yup.object().shape({ + [R]: isModification + ? yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + : yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [X]: isModification ? yup.number().nullable() : yup.number().nullable().required(), + [G]: isModification + ? yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + : yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [B]: isModification ? yup.number().nullable() : yup.number().nullable().required(), + [RATED_S]: yup + .number() + .nullable() + .positive(intl.formatMessage({ id: 'RatedNominalPowerMustBeGreaterThanZero' })), + [RATED_U1]: isModification + ? yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + : yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [RATED_U2]: isModification + ? yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + : yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + ...additionalFields, + }), + }; +} const characteristicsEmptyFormData = (additionalFields) => ({ [CHARACTERISTICS]: { diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/creation/characteristics-pane/two-windings-transformer-creation-characteristics-pane-utils.js b/src/components/dialogs/network-modifications/two-windings-transformer/creation/characteristics-pane/two-windings-transformer-creation-characteristics-pane-utils.js index cc7b4f022f..9822cedaab 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/creation/characteristics-pane/two-windings-transformer-creation-characteristics-pane-utils.js +++ b/src/components/dialogs/network-modifications/two-windings-transformer/creation/characteristics-pane/two-windings-transformer-creation-characteristics-pane-utils.js @@ -16,15 +16,12 @@ import { getCharacteristicsValidationSchema, } from '../../characteristics-pane/two-windings-transformer-characteristics-pane-utils'; -const twoWindingsTransformerValidationSchema = () => - getCharacteristicsValidationSchema(false, { +export function getTwoWindingsTransformerValidationSchema(intl) { + return getCharacteristicsValidationSchema(intl, false, { ...getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_1), ...getConnectivityWithPositionValidationSchema(false, CONNECTIVITY_2), }); - -export const getTwoWindingsTransformerValidationSchema = () => { - return twoWindingsTransformerValidationSchema(); -}; +} const twoWindingsTransformerEmptyFormData = () => getCharacteristicsEmptyFormData({ diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog.jsx b/src/components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog.jsx index 0c9e8d9d3f..eb2e8aa685 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/two-windings-transformer/creation/two-windings-transformer-creation-dialog.jsx @@ -58,7 +58,7 @@ import { } from 'components/utils/field-constants'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { FetchStatus } from '../../../../../services/utils'; import { sanitizeString } from '../../../dialog-utils'; @@ -72,7 +72,7 @@ import { SIDE, UNDEFINED_CONNECTION_DIRECTION, } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import { getConnectivityFormData } from '../../../connectivity/connectivity-form-utils'; import { @@ -104,12 +104,13 @@ import { addSelectedFieldToRows, computeHighTapPosition, formatCompleteCurrentLi import { createTwoWindingsTransformer } from '../../../../../services/study/network-modifications'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, } from '../../common/properties/property-utils'; import { TwoWindingsTransformerCreationDialogTab } from '../two-windings-transformer-utils'; +import { useIntl } from 'react-intl'; /** * Dialog to create a two windings transformer in the network @@ -132,19 +133,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string(), - ...getTwoWindingsTransformerValidationSchema(), - ...getLimitsValidationSchema(false), - ...getRatioTapChangerValidationSchema(), - ...getPhaseTapChangerValidationSchema(), - }) - .concat(creationPropertiesSchema) - .required(); - export const PHASE_TAP = 'dephasing'; export const RATIO_TAP = 'ratio'; @@ -159,6 +147,24 @@ const TwoWindingsTransformerCreationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); + + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string(), + ...getTwoWindingsTransformerValidationSchema(intl), + ...getLimitsValidationSchema(intl, false), + ...getRatioTapChangerValidationSchema(intl), + ...getPhaseTapChangerValidationSchema(intl), + }) + .concat(getCreationPropertiesSchema(intl)) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/modification/2wt-to-be-estimated/to-be-estimated-form-utils.ts b/src/components/dialogs/network-modifications/two-windings-transformer/modification/2wt-to-be-estimated/to-be-estimated-form-utils.ts index 360d033cc1..5944ceb117 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/modification/2wt-to-be-estimated/to-be-estimated-form-utils.ts +++ b/src/components/dialogs/network-modifications/two-windings-transformer/modification/2wt-to-be-estimated/to-be-estimated-form-utils.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../../../utils/yup-config'; +import * as yup from 'yup'; import { RATIO_TAP_CHANGER_STATUS, PHASE_TAP_CHANGER_STATUS } from '../../../../../utils/field-constants'; import { ToBeEstimatedInfo } from './to-be-estimated.type'; diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/modification/state-estimation-form-utils.ts b/src/components/dialogs/network-modifications/two-windings-transformer/modification/state-estimation-form-utils.ts index cf08827934..d24a2dad6c 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/modification/state-estimation-form-utils.ts +++ b/src/components/dialogs/network-modifications/two-windings-transformer/modification/state-estimation-form-utils.ts @@ -6,7 +6,7 @@ */ import { TO_BE_ESTIMATED } from 'components/utils/field-constants'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; import { getBranchActiveReactivePowerEditDataProperties, getBranchActiveReactivePowerEmptyFormDataProperties, 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 f505cbec23..e0071c1c4d 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 @@ -70,7 +70,7 @@ import { X, } from 'components/utils/field-constants'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { sanitizeString } from '../../../dialog-utils'; import { @@ -79,7 +79,7 @@ import { RATIO_REGULATION_MODES, REGULATION_TYPES, } from 'components/network/constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; import TwoWindingsTransformerModificationDialogTabs from './two-windings-transformer-modification-dialog-tabs'; import TwoWindingsTransformerCharacteristicsPane from '../characteristics-pane/two-windings-transformer-characteristics-pane'; @@ -132,7 +132,7 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos'; @@ -151,6 +151,7 @@ import { getStateEstimationEmptyFormData, getStateEstimationValidationSchema, } from './state-estimation-form-utils'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -163,20 +164,6 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string(), - ...getCon1andCon2WithPositionValidationSchema(true), - ...getCharacteristicsValidationSchema(true), - ...getLimitsValidationSchema(true), - ...getStateEstimationValidationSchema(STATE_ESTIMATION), - ...getRatioTapChangerModificationValidationSchema(), - ...getPhaseTapChangerModificationValidationSchema(), - }) - .concat(modificationPropertiesSchema) - .required(); - /** * Dialog to modify a two windings transformer in the network * @param studyUuid the study we are currently working on @@ -198,6 +185,7 @@ const TwoWindingsTransformerModificationDialog = ({ editDataFetchStatus, ...dialogProps }) => { + const intl = useIntl(); const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); @@ -206,6 +194,24 @@ const TwoWindingsTransformerModificationDialog = ({ const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); const [twtToModify, setTwtToModify] = useState(null); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string(), + ...getCon1andCon2WithPositionValidationSchema(true), + ...getCharacteristicsValidationSchema(intl, true), + ...getLimitsValidationSchema(intl, true), + ...getStateEstimationValidationSchema(STATE_ESTIMATION), + ...getRatioTapChangerModificationValidationSchema(intl), + ...getPhaseTapChangerModificationValidationSchema(intl), + }) + .concat(getModificationPropertiesSchema(intl)) + .required(), + [intl] + ); + const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/create-rule/create-rule-dialog-utils.js b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/create-rule/create-rule-dialog-utils.js index c5b2a8fecc..4ddc4e0391 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/create-rule/create-rule-dialog-utils.js +++ b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/create-rule/create-rule-dialog-utils.js @@ -6,7 +6,7 @@ */ import { HIGH_TAP_POSITION, LOW_TAP_POSITION } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; const createRuleValidationSchema = () => yup.object().shape({ diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/phase-tap-changer-pane/phase-tap-changer-pane-utils.js b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/phase-tap-changer-pane/phase-tap-changer-pane-utils.js index 79143e7b95..381e222231 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/phase-tap-changer-pane/phase-tap-changer-pane-utils.js +++ b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/phase-tap-changer-pane/phase-tap-changer-pane-utils.js @@ -35,213 +35,223 @@ import { TYPE, VOLTAGE_LEVEL, } from 'components/utils/field-constants'; -import { areNumbersOrdered, areArrayElementsUnique } from 'components/utils/utils'; -import yup from 'components/utils/yup-config'; +import { areArrayElementsUnique, areNumbersOrdered } from 'components/utils/utils'; +import * as yup from 'yup'; import { getRegulatingTerminalEmptyFormData, getRegulatingTerminalFormData, } from '../../../../regulating-terminal/regulating-terminal-form-utils'; import { PHASE_REGULATION_MODES, REGULATION_TYPES, SIDE } from 'components/network/constants'; -const phaseTapChangerValidationSchema = (id) => ({ - [id]: yup.object().shape({ - [ENABLED]: yup.bool().required(), - [REGULATION_MODE]: yup - .string() - .nullable() - .when([ENABLED], { - is: true, - then: (schema) => schema.required(), - }), - [REGULATION_TYPE]: yup - .string() - .nullable() - .when([ENABLED, REGULATION_MODE], { - is: (enabled, regulationMode) => enabled && regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id, - then: (schema) => schema.required(), - }), - [REGULATION_SIDE]: yup - .string() - .nullable() - .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, regulationMode, regulationType) => - enabled && - regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && - regulationType === REGULATION_TYPES.LOCAL.id, - then: (schema) => schema.required(), - }), - [CURRENT_LIMITER_REGULATING_VALUE]: yup - .number() - .nullable() - .when([ENABLED, REGULATION_MODE], { - is: (enabled, regulationMode) => - enabled && regulationMode === PHASE_REGULATION_MODES.CURRENT_LIMITER.id, - then: (schema) => schema.positive('CurrentLimiterMustBeGreaterThanZero').required(), - }), - [FLOW_SET_POINT_REGULATING_VALUE]: yup - .number() - .nullable() - .when([ENABLED, REGULATION_MODE], { - is: (enabled, regulationMode) => - enabled && regulationMode === PHASE_REGULATION_MODES.ACTIVE_POWER_CONTROL.id, - then: (schema) => schema.required(), - }), - [TARGET_DEADBAND]: yup.number().nullable().min(0, 'TargetDeadbandMustBeGreaterOrEqualToZero'), - [LOW_TAP_POSITION]: yup - .number() - .nullable() - .when(ENABLED, { - is: true, - then: (schema) => schema.required(), - }), - [HIGH_TAP_POSITION]: yup.number().nullable(), - [TAP_POSITION]: yup - .number() - .nullable() - .when(ENABLED, { - is: true, - then: (schema) => - schema - .required() - .min(yup.ref(LOW_TAP_POSITION), 'TapPositionMustBeBetweenLowAndHighTapPositionValue') - .max(yup.ref(HIGH_TAP_POSITION), 'TapPositionMustBeBetweenLowAndHighTapPositionValue'), - }), - [STEPS]: yup - .array() - .of( - yup.object().shape({ - [STEPS_TAP]: yup.number().required(), - [STEPS_RESISTANCE]: yup.number(), - [STEPS_REACTANCE]: yup.number(), - [STEPS_CONDUCTANCE]: yup.number(), - [STEPS_SUSCEPTANCE]: yup.number(), - [STEPS_RATIO]: yup.number(), - [STEPS_ALPHA]: yup.number(), +export function getPhaseTapChangerValidationSchema(intl, id = PHASE_TAP_CHANGER) { + return { + [id]: yup.object().shape({ + [ENABLED]: yup.bool().required(), + [REGULATION_MODE]: yup + .string() + .nullable() + .when([ENABLED], { + is: true, + then: (schema) => schema.required(), + }), + [REGULATION_TYPE]: yup + .string() + .nullable() + .when([ENABLED, REGULATION_MODE], { + is: (enabled, regulationMode) => enabled && regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id, + then: (schema) => schema.required(), + }), + [REGULATION_SIDE]: yup + .string() + .nullable() + .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, regulationMode, regulationType) => + enabled && + regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && + regulationType === REGULATION_TYPES.LOCAL.id, + then: (schema) => schema.required(), + }), + [CURRENT_LIMITER_REGULATING_VALUE]: yup + .number() + .nullable() + .when([ENABLED, REGULATION_MODE], { + is: (enabled, regulationMode) => + enabled && regulationMode === PHASE_REGULATION_MODES.CURRENT_LIMITER.id, + then: (schema) => + schema.positive(intl.formatMessage({ id: 'CurrentLimiterMustBeGreaterThanZero' })).required(), + }), + [FLOW_SET_POINT_REGULATING_VALUE]: yup + .number() + .nullable() + .when([ENABLED, REGULATION_MODE], { + is: (enabled, regulationMode) => + enabled && regulationMode === PHASE_REGULATION_MODES.ACTIVE_POWER_CONTROL.id, + then: (schema) => schema.required(), + }), + [TARGET_DEADBAND]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'TargetDeadbandMustBeGreaterOrEqualToZero' })), + [LOW_TAP_POSITION]: yup + .number() + .nullable() + .when(ENABLED, { + is: true, + then: (schema) => schema.required(), + }), + [HIGH_TAP_POSITION]: yup.number().nullable(), + [TAP_POSITION]: yup + .number() + .nullable() + .when(ENABLED, { + is: true, + then: (schema) => + schema + .required() + .min( + yup.ref(LOW_TAP_POSITION), + intl.formatMessage({ id: 'TapPositionMustBeBetweenLowAndHighTapPositionValue' }) + ) + .max( + yup.ref(HIGH_TAP_POSITION), + intl.formatMessage({ id: 'TapPositionMustBeBetweenLowAndHighTapPositionValue' }) + ), + }), + [STEPS]: yup + .array() + .of( + yup.object().shape({ + [STEPS_TAP]: yup.number().required(), + [STEPS_RESISTANCE]: yup.number(), + [STEPS_REACTANCE]: yup.number(), + [STEPS_CONDUCTANCE]: yup.number(), + [STEPS_SUSCEPTANCE]: yup.number(), + [STEPS_RATIO]: yup.number(), + [STEPS_ALPHA]: yup.number(), + }) + ) + .when(ENABLED, { + is: true, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'GeneratePhaseTapRowsError' })), }) - ) - .when(ENABLED, { - is: true, - then: (schema) => schema.min(1, 'GeneratePhaseTapRowsError'), - }) - .test('distinctOrderedAlpha', 'PhaseShiftValuesError', (array) => { - const alphaArray = array.map((step) => step[STEPS_ALPHA]); - return areNumbersOrdered(alphaArray) && areArrayElementsUnique(alphaArray); - }), - //regulating terminal fields - //TODO: is it possible to move it to regulating-terminal-utils.ts properly since it depends on "ENABLED" ? - [VOLTAGE_LEVEL]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string(), - [SUBSTATION_ID]: yup.string(), - [NOMINAL_VOLTAGE]: yup.string(), - [TOPOLOGY_KIND]: yup.string().nullable(), - }) - .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, regulationMode, regulationType) => - enabled && - regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && - regulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), - [EQUIPMENT]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string().nullable(), - [TYPE]: yup.string(), - }) - .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, regulationMode, regulationType) => - enabled && - regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && - regulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), - }), -}); - -const phaseTapChangerModificationValidationSchema = (previousValues, id) => ({ - [id]: yup.object().shape({ - [ENABLED]: yup.bool().required(), - [REGULATION_MODE]: yup.string().nullable(), - [REGULATION_TYPE]: yup.string().nullable(), - [REGULATION_SIDE]: yup.string().nullable(), - [CURRENT_LIMITER_REGULATING_VALUE]: yup.number().nullable().positive('CurrentLimiterMustBeGreaterThanZero'), - [FLOW_SET_POINT_REGULATING_VALUE]: yup.number().nullable(), - [TARGET_DEADBAND]: yup.number().nullable().min(0, 'TargetDeadbandMustBeGreaterOrEqualToZero'), - [LOW_TAP_POSITION]: yup.number().nullable(), - [HIGH_TAP_POSITION]: yup.number().nullable(), - [TAP_POSITION]: yup.number().nullable(), - [STEPS]: yup - .array() - .of( - yup.object().shape({ - [STEPS_TAP]: yup.number().required(), - [STEPS_RESISTANCE]: yup.number(), - [STEPS_REACTANCE]: yup.number(), - [STEPS_CONDUCTANCE]: yup.number(), - [STEPS_SUSCEPTANCE]: yup.number(), - [STEPS_RATIO]: yup.number(), - [STEPS_ALPHA]: yup.number(), + .test('distinctOrderedAlpha', intl.formatMessage({ id: 'PhaseShiftValuesError' }), (array) => { + const alphaArray = array.map((step) => step[STEPS_ALPHA]); + return areNumbersOrdered(alphaArray) && areArrayElementsUnique(alphaArray); + }), + //regulating terminal fields + //TODO: is it possible to move it to regulating-terminal-utils.ts properly since it depends on "ENABLED" ? + [VOLTAGE_LEVEL]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string(), + [SUBSTATION_ID]: yup.string(), + [NOMINAL_VOLTAGE]: yup.string(), + [TOPOLOGY_KIND]: yup.string().nullable(), }) - ) - .test('distinctOrderedAlpha', 'PhaseShiftValuesError', (array) => { - const alphaArray = array.map((step) => step[STEPS_ALPHA]); - return areNumbersOrdered(alphaArray) && areArrayElementsUnique(alphaArray); - }), - //regulating terminal fields - [VOLTAGE_LEVEL]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string(), - [SUBSTATION_ID]: yup.string(), - [NOMINAL_VOLTAGE]: yup.string(), - [TOPOLOGY_KIND]: yup.string().nullable(), - }), - [EQUIPMENT]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string().nullable(), - [TYPE]: yup.string(), - }), - }), -}); - -export const getPhaseTapChangerValidationSchema = (id = PHASE_TAP_CHANGER) => { - return phaseTapChangerValidationSchema(id); -}; - -export const getPhaseTapChangerModificationValidationSchema = (previousValues, id = PHASE_TAP_CHANGER) => { - return phaseTapChangerModificationValidationSchema(previousValues, id); -}; + .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, regulationMode, regulationType) => + enabled && + regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && + regulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + [EQUIPMENT]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string().nullable(), + [TYPE]: yup.string(), + }) + .when([ENABLED, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, regulationMode, regulationType) => + enabled && + regulationMode !== PHASE_REGULATION_MODES.FIXED_TAP.id && + regulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + }), + }; +} -const phaseTapChangerEmptyFormData = (isModification, id) => ({ - [id]: { - [ENABLED]: false, - [REGULATION_MODE]: null, - [REGULATION_TYPE]: null, - [REGULATION_SIDE]: isModification ? null : SIDE.SIDE1.id, - [CURRENT_LIMITER_REGULATING_VALUE]: null, - [FLOW_SET_POINT_REGULATING_VALUE]: null, - [TARGET_DEADBAND]: null, - [LOW_TAP_POSITION]: null, - [HIGH_TAP_POSITION]: null, - [TAP_POSITION]: null, - [STEPS]: [], - ...getRegulatingTerminalEmptyFormData(), - }, -}); +export function getPhaseTapChangerModificationValidationSchema(intl, id = PHASE_TAP_CHANGER) { + return { + [id]: yup.object().shape({ + [ENABLED]: yup.bool().required(), + [REGULATION_MODE]: yup.string().nullable(), + [REGULATION_TYPE]: yup.string().nullable(), + [REGULATION_SIDE]: yup.string().nullable(), + [CURRENT_LIMITER_REGULATING_VALUE]: yup + .number() + .nullable() + .positive(intl.formatMessage({ id: 'CurrentLimiterMustBeGreaterThanZero' })), + [FLOW_SET_POINT_REGULATING_VALUE]: yup.number().nullable(), + [TARGET_DEADBAND]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'TargetDeadbandMustBeGreaterOrEqualToZero' })), + [LOW_TAP_POSITION]: yup.number().nullable(), + [HIGH_TAP_POSITION]: yup.number().nullable(), + [TAP_POSITION]: yup.number().nullable(), + [STEPS]: yup + .array() + .of( + yup.object().shape({ + [STEPS_TAP]: yup.number().required(), + [STEPS_RESISTANCE]: yup.number(), + [STEPS_REACTANCE]: yup.number(), + [STEPS_CONDUCTANCE]: yup.number(), + [STEPS_SUSCEPTANCE]: yup.number(), + [STEPS_RATIO]: yup.number(), + [STEPS_ALPHA]: yup.number(), + }) + ) + .test('distinctOrderedAlpha', intl.formatMessage({ id: 'PhaseShiftValuesError' }), (array) => { + const alphaArray = array.map((step) => step[STEPS_ALPHA]); + return areNumbersOrdered(alphaArray) && areArrayElementsUnique(alphaArray); + }), + //regulating terminal fields + [VOLTAGE_LEVEL]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string(), + [SUBSTATION_ID]: yup.string(), + [NOMINAL_VOLTAGE]: yup.string(), + [TOPOLOGY_KIND]: yup.string().nullable(), + }), + [EQUIPMENT]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string().nullable(), + [TYPE]: yup.string(), + }), + }), + }; +} export const getPhaseTapChangerEmptyFormData = (isModification = false, id = PHASE_TAP_CHANGER) => { - return phaseTapChangerEmptyFormData(isModification, id); + return { + [id]: { + [ENABLED]: false, + [REGULATION_MODE]: null, + [REGULATION_TYPE]: null, + [REGULATION_SIDE]: isModification ? null : SIDE.SIDE1.id, + [CURRENT_LIMITER_REGULATING_VALUE]: null, + [FLOW_SET_POINT_REGULATING_VALUE]: null, + [TARGET_DEADBAND]: null, + [LOW_TAP_POSITION]: null, + [HIGH_TAP_POSITION]: null, + [TAP_POSITION]: null, + [STEPS]: [], + ...getRegulatingTerminalEmptyFormData(), + }, + }; }; export const getPhaseTapChangerFormData = ( diff --git a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/ratio-tap-changer-pane/ratio-tap-changer-pane-utils.js b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/ratio-tap-changer-pane/ratio-tap-changer-pane-utils.js index 84fc23b88e..171e03c361 100644 --- a/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/ratio-tap-changer-pane/ratio-tap-changer-pane-utils.js +++ b/src/components/dialogs/network-modifications/two-windings-transformer/tap-changer-pane/ratio-tap-changer-pane/ratio-tap-changer-pane-utils.js @@ -34,205 +34,218 @@ import { TYPE, VOLTAGE_LEVEL, } from 'components/utils/field-constants'; -import { areNumbersOrdered, areArrayElementsUnique } from 'components/utils/utils'; -import yup from 'components/utils/yup-config'; +import { areArrayElementsUnique, areNumbersOrdered } from 'components/utils/utils'; +import * as yup from 'yup'; import { getRegulatingTerminalEmptyFormData, getRegulatingTerminalFormData, } from '../../../../regulating-terminal/regulating-terminal-form-utils'; import { RATIO_REGULATION_MODES, REGULATION_TYPES, SIDE } from 'components/network/constants'; -const ratioTapChangerValidationSchema = (id) => ({ - [id]: yup.object().shape({ - [ENABLED]: yup.bool().required(), - [LOAD_TAP_CHANGING_CAPABILITIES]: yup.bool().required(), - [REGULATION_MODE]: yup - .string() - .nullable() - .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES], { - is: (enabled, hasLoadTapChangingCapabilities) => enabled && hasLoadTapChangingCapabilities, - then: (schema) => schema.required(), - }), - [REGULATION_TYPE]: yup - .string() - .nullable() - .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE], { - is: (enabled, hasLoadTapChangingCapabilities, regulationMode) => - enabled && - hasLoadTapChangingCapabilities && - regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id, - then: (schema) => schema.required(), - }), - [REGULATION_SIDE]: yup - .string() - .nullable() - .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => - enabled && - hasLoadTapChangingCapabilities && - regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && - regulationType === REGULATION_TYPES.LOCAL.id, - then: (schema) => schema.required(), - }), - [TARGET_V]: yup - .mixed() - .when([LOAD_TAP_CHANGING_CAPABILITIES], { - is: true, - then: () => yup.number().nullable().positive('TargetVoltageMustBeGreaterThanZero'), - }) - .when([REGULATION_MODE, LOAD_TAP_CHANGING_CAPABILITIES], { - is: (regulationMode, hasLoadTapChangingCapabilities) => { - return ( +export function getRatioTapChangerValidationSchema(intl, id = RATIO_TAP_CHANGER) { + return { + [id]: yup.object().shape({ + [ENABLED]: yup.bool().required(), + [LOAD_TAP_CHANGING_CAPABILITIES]: yup.bool().required(), + [REGULATION_MODE]: yup + .string() + .nullable() + .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES], { + is: (enabled, hasLoadTapChangingCapabilities) => enabled && hasLoadTapChangingCapabilities, + then: (schema) => schema.required(), + }), + [REGULATION_TYPE]: yup + .string() + .nullable() + .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE], { + is: (enabled, hasLoadTapChangingCapabilities, regulationMode) => + enabled && + hasLoadTapChangingCapabilities && + regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id, + then: (schema) => schema.required(), + }), + [REGULATION_SIDE]: yup + .string() + .nullable() + .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => + enabled && + hasLoadTapChangingCapabilities && + regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && + regulationType === REGULATION_TYPES.LOCAL.id, + then: (schema) => schema.required(), + }), + [TARGET_V]: yup + .mixed() + .when([LOAD_TAP_CHANGING_CAPABILITIES], { + is: true, + then: () => + yup + .number() + .nullable() + .positive(intl.formatMessage({ id: 'TargetVoltageMustBeGreaterThanZero' })), + }) + .when([REGULATION_MODE, LOAD_TAP_CHANGING_CAPABILITIES], { + is: (regulationMode, hasLoadTapChangingCapabilities) => hasLoadTapChangingCapabilities === true && - regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id - ); - }, - then: (schema) => schema.required(), - otherwise: (schema) => schema.nullable(), - }), - [TARGET_DEADBAND]: yup - .mixed() - .nullable() - .when(LOAD_TAP_CHANGING_CAPABILITIES, { - is: true, - then: () => yup.number().nullable().min(0, 'TargetDeadbandMustBeGreaterOrEqualToZero'), - }), - [LOW_TAP_POSITION]: yup - .number() - .nullable() - .when(ENABLED, { - is: true, - then: (schema) => schema.required(), - }), - [HIGH_TAP_POSITION]: yup.number().nullable(), - [TAP_POSITION]: yup - .number() - .nullable() - .when(ENABLED, { - is: true, - then: (schema) => - schema - .required() - .min(yup.ref(LOW_TAP_POSITION), 'TapPositionMustBeBetweenLowAndHighTapPositionValue') - .max(yup.ref(HIGH_TAP_POSITION), 'TapPositionMustBeBetweenLowAndHighTapPositionValue'), - }), - [STEPS]: yup - .array() - .of( - yup.object().shape({ - [STEPS_TAP]: yup.number().required(), - [STEPS_RESISTANCE]: yup.number(), - [STEPS_REACTANCE]: yup.number(), - [STEPS_CONDUCTANCE]: yup.number(), - [STEPS_SUSCEPTANCE]: yup.number(), - [STEPS_RATIO]: yup.number(), + regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id, + then: (schema) => schema.required(), + otherwise: (schema) => schema.nullable(), + }), + [TARGET_DEADBAND]: yup + .mixed() + .nullable() + .when(LOAD_TAP_CHANGING_CAPABILITIES, { + is: true, + then: () => + yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'TargetDeadbandMustBeGreaterOrEqualToZero' })), + }), + [LOW_TAP_POSITION]: yup + .number() + .nullable() + .when(ENABLED, { + is: true, + then: (schema) => schema.required(), + }), + [HIGH_TAP_POSITION]: yup.number().nullable(), + [TAP_POSITION]: yup + .number() + .nullable() + .when(ENABLED, { + is: true, + then: (schema) => + schema + .required() + .min( + yup.ref(LOW_TAP_POSITION), + intl.formatMessage({ id: 'TapPositionMustBeBetweenLowAndHighTapPositionValue' }) + ) + .max( + yup.ref(HIGH_TAP_POSITION), + intl.formatMessage({ id: 'TapPositionMustBeBetweenLowAndHighTapPositionValue' }) + ), + }), + [STEPS]: yup + .array() + .of( + yup.object().shape({ + [STEPS_TAP]: yup.number().required(), + [STEPS_RESISTANCE]: yup.number(), + [STEPS_REACTANCE]: yup.number(), + [STEPS_CONDUCTANCE]: yup.number(), + [STEPS_SUSCEPTANCE]: yup.number(), + [STEPS_RATIO]: yup.number(), + }) + ) + .when(ENABLED, { + is: true, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'GenerateRatioTapRowsError' })), }) - ) - .when(ENABLED, { - is: true, - then: (schema) => schema.min(1, 'GenerateRatioTapRowsError'), - }) - .test('distinctOrderedRatio', 'RatioValuesError', (array) => { - const ratioArray = array.map((step) => step[STEPS_RATIO]); - return areNumbersOrdered(ratioArray) && areArrayElementsUnique(ratioArray); - }), - //regulating terminal fields - //TODO: is it possible to move it to regulating-terminal-utils.ts properly since it depends on "ENABLED" ? - [VOLTAGE_LEVEL]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string(), - [SUBSTATION_ID]: yup.string(), - [NOMINAL_VOLTAGE]: yup.string(), - [TOPOLOGY_KIND]: yup.string().nullable(), - }) - .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => - enabled && - hasLoadTapChangingCapabilities && - regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && - regulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), - [EQUIPMENT]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string().nullable(), - [TYPE]: yup.string(), - }) - .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { - is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => - enabled && - hasLoadTapChangingCapabilities && - regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && - regulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), - }), -}); - -const ratioTapChangerModificationValidationSchema = (previousValues, id) => ({ - [id]: yup.object().shape({ - [ENABLED]: yup.bool().required(), - [LOAD_TAP_CHANGING_CAPABILITIES]: yup.bool().nullable(), - [REGULATION_MODE]: yup.string().nullable(), - [REGULATION_TYPE]: yup.string().nullable(), - [REGULATION_SIDE]: yup.string().nullable(), - [TARGET_V]: yup.number().nullable().positive('TargetVoltageMustBeGreaterThanZero'), - [TARGET_DEADBAND]: yup.number().nullable().min(0, 'TargetDeadbandMustBeGreaterOrEqualToZero'), - [LOW_TAP_POSITION]: yup.number().nullable(), - [HIGH_TAP_POSITION]: yup.number().nullable(), - [TAP_POSITION]: yup.number().nullable(), - [STEPS]: yup - .array() - .of( - yup.object().shape({ - [STEPS_TAP]: yup.number().required(), - [STEPS_RESISTANCE]: yup.number(), - [STEPS_REACTANCE]: yup.number(), - [STEPS_CONDUCTANCE]: yup.number(), - [STEPS_SUSCEPTANCE]: yup.number(), - [STEPS_RATIO]: yup.number(), + .test('distinctOrderedRatio', intl.formatMessage({ id: 'RatioValuesError' }), (array) => { + const ratioArray = array.map((step) => step[STEPS_RATIO]); + return areNumbersOrdered(ratioArray) && areArrayElementsUnique(ratioArray); + }), + //regulating terminal fields + //TODO: is it possible to move it to regulating-terminal-utils.ts properly since it depends on "ENABLED" ? + [VOLTAGE_LEVEL]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string(), + [SUBSTATION_ID]: yup.string(), + [NOMINAL_VOLTAGE]: yup.string(), + [TOPOLOGY_KIND]: yup.string().nullable(), }) - ) - .test('distinctOrderedRatio', 'RatioValuesError', (array) => { - const ratioArray = array.map((step) => step[STEPS_RATIO]); - return areNumbersOrdered(ratioArray) && areArrayElementsUnique(ratioArray); - }), - //regulating terminal fields - [VOLTAGE_LEVEL]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string(), - [SUBSTATION_ID]: yup.string(), - [NOMINAL_VOLTAGE]: yup.string(), - [TOPOLOGY_KIND]: yup.string().nullable(), - }), - - [EQUIPMENT]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string().nullable(), - [TYPE]: yup.string(), - }), - }), -}); + .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => + enabled && + hasLoadTapChangingCapabilities && + regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && + regulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + [EQUIPMENT]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string().nullable(), + [TYPE]: yup.string(), + }) + .when([ENABLED, LOAD_TAP_CHANGING_CAPABILITIES, REGULATION_MODE, REGULATION_TYPE], { + is: (enabled, hasLoadTapChangingCapabilities, regulationMode, regulationType) => + enabled && + hasLoadTapChangingCapabilities && + regulationMode === RATIO_REGULATION_MODES.VOLTAGE_REGULATION.id && + regulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + }), + }; +} -export const getRatioTapChangerValidationSchema = (id = RATIO_TAP_CHANGER) => { - return ratioTapChangerValidationSchema(id); -}; +export function getRatioTapChangerModificationValidationSchema(intl, id = RATIO_TAP_CHANGER) { + return { + [id]: yup.object().shape({ + [ENABLED]: yup.bool().required(), + [LOAD_TAP_CHANGING_CAPABILITIES]: yup.bool().nullable(), + [REGULATION_MODE]: yup.string().nullable(), + [REGULATION_TYPE]: yup.string().nullable(), + [REGULATION_SIDE]: yup.string().nullable(), + [TARGET_V]: yup + .number() + .nullable() + .positive(intl.formatMessage({ id: 'TargetVoltageMustBeGreaterThanZero' })), + [TARGET_DEADBAND]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'TargetDeadbandMustBeGreaterOrEqualToZero' })), + [LOW_TAP_POSITION]: yup.number().nullable(), + [HIGH_TAP_POSITION]: yup.number().nullable(), + [TAP_POSITION]: yup.number().nullable(), + [STEPS]: yup + .array() + .of( + yup.object().shape({ + [STEPS_TAP]: yup.number().required(), + [STEPS_RESISTANCE]: yup.number(), + [STEPS_REACTANCE]: yup.number(), + [STEPS_CONDUCTANCE]: yup.number(), + [STEPS_SUSCEPTANCE]: yup.number(), + [STEPS_RATIO]: yup.number(), + }) + ) + .test('distinctOrderedRatio', intl.formatMessage({ id: 'RatioValuesError' }), (array) => { + const ratioArray = array.map((step) => step[STEPS_RATIO]); + return areNumbersOrdered(ratioArray) && areArrayElementsUnique(ratioArray); + }), + //regulating terminal fields + [VOLTAGE_LEVEL]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string(), + [SUBSTATION_ID]: yup.string(), + [NOMINAL_VOLTAGE]: yup.string(), + [TOPOLOGY_KIND]: yup.string().nullable(), + }), -export const getRatioTapChangerModificationValidationSchema = (previousValues, id = RATIO_TAP_CHANGER) => { - return ratioTapChangerModificationValidationSchema(previousValues, id); -}; + [EQUIPMENT]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string().nullable(), + [TYPE]: yup.string(), + }), + }), + }; +} const ratioTapChangerEmptyFormData = (isModification, id) => ({ [id]: { diff --git a/src/components/dialogs/network-modifications/voltage-level-topology-modification/voltage-level-topology-modification-dialog.tsx b/src/components/dialogs/network-modifications/voltage-level-topology-modification/voltage-level-topology-modification-dialog.tsx index bfa956fb40..4e8108b6f8 100644 --- a/src/components/dialogs/network-modifications/voltage-level-topology-modification/voltage-level-topology-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/voltage-level-topology-modification/voltage-level-topology-modification-dialog.tsx @@ -21,7 +21,8 @@ import { FORM_LOADING_DELAY } from '../../../network/constants'; import { isNodeBuilt } from '../../../graph/util/model-functions'; import { ModificationDialog } from '../../commons/modificationDialog'; import { EquipmentIdSelector } from '../../equipment-id/equipment-id-selector'; -import yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { CURRENT_CONNECTION_STATUS, PREV_CONNECTION_STATUS, @@ -62,7 +63,7 @@ const emptyFormData = { export type VoltageLevelTopologyModificationDialogProps = EquipmentModificationDialogProps & { editData: TopologyVoltageLevelModificationInfos; }; -export type VoltageLevelTopologyModificationFormSchemaType = yup.InferType; +export type VoltageLevelTopologyModificationFormSchemaType = InferType; /** * Dialog to delete a list of equipment by filter. diff --git a/src/components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog.jsx b/src/components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog.jsx index 2606d51e1b..d7a244c260 100644 --- a/src/components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog.jsx +++ b/src/components/dialogs/network-modifications/voltage-level/creation/voltage-level-creation-dialog.jsx @@ -44,12 +44,11 @@ import { SWITCHES_BETWEEN_SECTIONS, TOPOLOGY_KIND, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import PropTypes from 'prop-types'; -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { useForm } from 'react-hook-form'; import { ModificationDialog } from 'components/dialogs/commons/modificationDialog'; - import VoltageLevelCreationForm from './voltage-level-creation-form'; import { controlCouplingOmnibusBetweenSections } from '../voltage-level-creation-utils'; import { EQUIPMENT_TYPES } from 'components/utils/equipment-types'; @@ -60,7 +59,7 @@ import { createVoltageLevel } from '../../../../../services/study/network-modifi import { FetchStatus } from '../../../../../services/utils'; import { copyEquipmentPropertiesForCreation, - creationPropertiesSchema, + getCreationPropertiesSchema, emptyProperties, getPropertiesFromModification, toModificationProperties, @@ -99,66 +98,92 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string().nullable(), - [SUBSTATION_ID]: yup - .string() - .nullable() - .when([ADD_SUBSTATION_CREATION], { - is: (addSubstationCreation) => addSubstationCreation === false, - then: (schema) => schema.required(), - }), - [SUBSTATION_CREATION_ID]: yup - .string() - .nullable() - .when([ADD_SUBSTATION_CREATION], { - is: (addSubstationCreation) => addSubstationCreation === true, - then: (schema) => schema.required(), - }), - [SUBSTATION_NAME]: yup.string().nullable(), - [COUNTRY]: yup.string().nullable(), - [SUBSTATION_CREATION]: creationPropertiesSchema, - [NOMINAL_V]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero').required(), - [LOW_VOLTAGE_LIMIT]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [HIGH_VOLTAGE_LIMIT]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [LOW_SHORT_CIRCUIT_CURRENT_LIMIT]: yup - .number() - .nullable() - .min(0, 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero') - .max(yup.ref(HIGH_SHORT_CIRCUIT_CURRENT_LIMIT), 'ShortCircuitCurrentLimitMinMaxError'), - [HIGH_SHORT_CIRCUIT_CURRENT_LIMIT]: yup - .number() - .nullable() - .min(0, 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero') - .when([LOW_SHORT_CIRCUIT_CURRENT_LIMIT], { - is: (lowShortCircuitCurrentLimit) => lowShortCircuitCurrentLimit != null, - then: (schema) => schema.required(), - }), - [BUS_BAR_COUNT]: yup.number().min(1, 'BusBarCountMustBeGreaterThanOrEqualToOne').nullable().required(), - [SECTION_COUNT]: yup.number().min(1, 'SectionCountMustBeGreaterThanOrEqualToOne').nullable().required(), - [SWITCHES_BETWEEN_SECTIONS]: yup - .string() - .nullable() - .when([SECTION_COUNT], { - is: (sectionCount) => sectionCount > 1, - then: (schema) => schema.required(), - }), - [COUPLING_OMNIBUS]: yup - .array() - .of( - yup.object().shape({ - [BUS_BAR_SECTION_ID1]: yup.string().nullable().required(), - [BUS_BAR_SECTION_ID2]: yup.string().nullable().required(), - }) - ) - .test('coupling-omnibus-between-sections', (values) => - controlCouplingOmnibusBetweenSections(values, 'CouplingOmnibusBetweenSameBusbar') - ), - }) - .concat(creationPropertiesSchema); +const getFormSchema = (intl) => + yup + .object() + .shape({ + [EQUIPMENT_ID]: yup.string().required(), + [EQUIPMENT_NAME]: yup.string().nullable(), + [SUBSTATION_ID]: yup + .string() + .nullable() + .when([ADD_SUBSTATION_CREATION], { + is: (addSubstationCreation) => addSubstationCreation === false, + then: (schema) => schema.required(), + }), + [SUBSTATION_CREATION_ID]: yup + .string() + .nullable() + .when([ADD_SUBSTATION_CREATION], { + is: (addSubstationCreation) => addSubstationCreation === true, + then: (schema) => schema.required(), + }), + [SUBSTATION_NAME]: yup.string().nullable(), + [COUNTRY]: yup.string().nullable(), + [SUBSTATION_CREATION]: getCreationPropertiesSchema(intl), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .required(), + [LOW_VOLTAGE_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [HIGH_VOLTAGE_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [LOW_SHORT_CIRCUIT_CURRENT_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero' })) + .max( + yup.ref(HIGH_SHORT_CIRCUIT_CURRENT_LIMIT), + intl.formatMessage({ id: 'ShortCircuitCurrentLimitMinMaxError' }) + ), + [HIGH_SHORT_CIRCUIT_CURRENT_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero' })) + .when([LOW_SHORT_CIRCUIT_CURRENT_LIMIT], { + is: (lowShortCircuitCurrentLimit) => lowShortCircuitCurrentLimit != null, + then: (schema) => schema.required(), + }), + [BUS_BAR_COUNT]: yup + .number() + .min(1, intl.formatMessage({ id: 'BusBarCountMustBeGreaterThanOrEqualToOne' })) + .nullable() + .required(), + [SECTION_COUNT]: yup + .number() + .min(1, intl.formatMessage({ id: 'SectionCountMustBeGreaterThanOrEqualToOne' })) + .nullable() + .required(), + [SWITCHES_BETWEEN_SECTIONS]: yup + .string() + .nullable() + .when([SECTION_COUNT], { + is: (sectionCount) => sectionCount > 1, + then: (schema) => schema.required(), + }), + [COUPLING_OMNIBUS]: yup + .array() + .of( + yup.object().shape({ + [BUS_BAR_SECTION_ID1]: yup.string().nullable().required(), + [BUS_BAR_SECTION_ID2]: yup.string().nullable().required(), + }) + ) + .test('coupling-omnibus-between-sections', (values) => + controlCouplingOmnibusBetweenSections( + values, + intl.formatMessage({ id: 'CouplingOmnibusBetweenSameBusbar' }) + ) + ), + }) + .concat(getCreationPropertiesSchema(intl)); + const VoltageLevelCreationDialog = ({ editData, currentNode, @@ -171,14 +196,15 @@ const VoltageLevelCreationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError, snackWarning } = useSnackMessage(); + const intl = useIntl(); + const formSchema = useMemo(() => getFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), }); - const { reset, setValue, getValues } = formMethods; - const intl = useIntl(); + const fromExternalDataToFormValues = useCallback( (voltageLevel, fromCopy = true) => { const isSubstationCreation = !fromCopy && voltageLevel.substationCreation?.equipmentId != null; diff --git a/src/components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog.jsx b/src/components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog.jsx index 2112540847..b2ca9f6231 100644 --- a/src/components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog.jsx +++ b/src/components/dialogs/network-modifications/voltage-level/modification/voltage-level-modification-dialog.jsx @@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form'; import { ModificationDialog } from '../../../commons/modificationDialog'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import VoltageLevelModificationForm from './voltage-level-modification-form'; import { ADDITIONAL_PROPERTIES, @@ -19,7 +19,7 @@ import { NOMINAL_V, SUBSTATION_ID, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { convertInputValue, @@ -39,10 +39,11 @@ import { emptyProperties, getConcatenatedProperties, getPropertiesFromModification, - modificationPropertiesSchema, + getModificationPropertiesSchema, toModificationProperties, } from '../../common/properties/property-utils'; import { isNodeBuilt } from '../../../../graph/util/model-functions.ts'; +import { useIntl } from 'react-intl'; const emptyFormData = { [EQUIPMENT_NAME]: '', @@ -55,29 +56,42 @@ const emptyFormData = { ...emptyProperties, }; -const formSchema = yup - .object() - .shape({ - [EQUIPMENT_NAME]: yup.string().nullable(), - [SUBSTATION_ID]: yup.string().nullable(), - [NOMINAL_V]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [LOW_VOLTAGE_LIMIT]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [HIGH_VOLTAGE_LIMIT]: yup.number().nullable().min(0, 'mustBeGreaterOrEqualToZero'), - [LOW_SHORT_CIRCUIT_CURRENT_LIMIT]: yup - .number() - .nullable() - .min(0, 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero') - .when([HIGH_SHORT_CIRCUIT_CURRENT_LIMIT], { - is: (highShortCircuitCurrentLimit) => highShortCircuitCurrentLimit != null, - then: (schema) => - schema.max(yup.ref(HIGH_SHORT_CIRCUIT_CURRENT_LIMIT), 'ShortCircuitCurrentLimitMinMaxError'), - }), - [HIGH_SHORT_CIRCUIT_CURRENT_LIMIT]: yup - .number() - .nullable() - .min(0, 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero'), - }) - .concat(modificationPropertiesSchema); +const getFormSchema = (intl) => + yup + .object() + .shape({ + [EQUIPMENT_NAME]: yup.string().nullable(), + [SUBSTATION_ID]: yup.string().nullable(), + [NOMINAL_V]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [LOW_VOLTAGE_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [HIGH_VOLTAGE_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })), + [LOW_SHORT_CIRCUIT_CURRENT_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero' })) + .when([HIGH_SHORT_CIRCUIT_CURRENT_LIMIT], { + is: (highShortCircuitCurrentLimit) => highShortCircuitCurrentLimit != null, + then: (schema) => + schema.max( + yup.ref(HIGH_SHORT_CIRCUIT_CURRENT_LIMIT), + intl.formatMessage({ id: 'ShortCircuitCurrentLimitMinMaxError' }) + ), + }), + [HIGH_SHORT_CIRCUIT_CURRENT_LIMIT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'ShortCircuitCurrentLimitMustBeGreaterOrEqualToZero' })), + }) + .concat(getModificationPropertiesSchema(intl)); const VoltageLevelModificationDialog = ({ editData, // contains data when we try to edit an existing hypothesis from the current node's list defaultIdValue, // Used to pre-select an equipmentId when calling this dialog from the network map @@ -90,10 +104,12 @@ const VoltageLevelModificationDialog = ({ }) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); + const intl = useIntl(); const [selectedId, setSelectedId] = useState(defaultIdValue ?? null); const [voltageLevelInfos, setVoltageLevelInfos] = useState(null); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo(() => getFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog-utils.js b/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog-utils.js index adb992805b..98392b5dd0 100644 --- a/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog-utils.js +++ b/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog-utils.js @@ -6,7 +6,7 @@ */ import { SWITCH_KINDS, SWITCH_KIND } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; export const getCreateSwitchesValidationSchema = (id = SWITCH_KINDS) => { return { diff --git a/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog.jsx b/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog.jsx index 3c04235e6e..340a4f1b00 100644 --- a/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog.jsx +++ b/src/components/dialogs/network-modifications/voltage-level/switches-between-sections/create-switches-between-sections/create-switches-dialog.jsx @@ -12,7 +12,7 @@ import CreateSwitchesDialogSubmitButton from './create-switches-dialog-submit-bu import CreateSwitchesForm from './create-switches-form'; import { getCreateSwitchesEmptyFormData, getCreateSwitchesValidationSchema } from './create-switches-dialog-utils'; import { SWITCH_KINDS } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { useEffect } from 'react'; import { CancelButton, CustomFormProvider } from '@gridsuite/commons-ui'; diff --git a/src/components/dialogs/network-modifications/voltage-level/voltage-level-creation-utils.js b/src/components/dialogs/network-modifications/voltage-level/voltage-level-creation-utils.js index 727710e2d3..aefda437b6 100644 --- a/src/components/dialogs/network-modifications/voltage-level/voltage-level-creation-utils.js +++ b/src/components/dialogs/network-modifications/voltage-level/voltage-level-creation-utils.js @@ -6,7 +6,7 @@ */ import { COUPLING_OMNIBUS, BUS_BAR_SECTION_ID1, BUS_BAR_SECTION_ID2 } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import { ValidationError } from 'yup'; const buildValidationError = (errors, field) => { return errors.length === 0 @@ -37,8 +37,8 @@ export const controlCouplingOmnibusBetweenSections = (values, message) => { const indexes = findBusBarSectionIndexesItSelf(values); if (indexes?.length > 0) { indexes.forEach((index) => { - errors.push(new yup.ValidationError(message, null, `${COUPLING_OMNIBUS}[${index}].${BUS_BAR_SECTION_ID1}`)); - errors.push(new yup.ValidationError(message, null, `${COUPLING_OMNIBUS}[${index}].${BUS_BAR_SECTION_ID2}`)); + errors.push(new ValidationError(message, null, `${COUPLING_OMNIBUS}[${index}].${BUS_BAR_SECTION_ID1}`)); + errors.push(new ValidationError(message, null, `${COUPLING_OMNIBUS}[${index}].${BUS_BAR_SECTION_ID2}`)); }); } diff --git a/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx b/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx index f0cd89b1a8..d3a1f1c967 100644 --- a/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx +++ b/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../utils/yup-config'; +import type { InferType } from 'yup'; +import * as yup from 'yup'; import { Grid, Tab, Tabs } from '@mui/material'; import { FormattedMessage } from 'react-intl'; - import { FunctionComponent, SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react'; import ScenarioParameters, { SCENARIO_DURATION } from './scenario-parameters'; import { @@ -24,17 +24,16 @@ import { CustomFormProvider, isObjectEmpty, mergeSx, - SubmitButton, + parametersStyles, ProviderParam, + SubmitButton, useParametersBackend, - parametersStyles, } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; import { FieldErrors, useForm } from 'react-hook-form'; import { getTabStyle } from '../../../utils/tab-utils'; import ComputingType from '../../../computing-status/computing-type'; import { User } from 'oidc-client'; - import { LabelledButton, TabPanel } from '../parameters'; import ContingencyParameters, { CONTINGENCIES_LIST_INFOS, CONTINGENCIES_START_TIME } from './contingency-parameters'; import { ID, NAME, PROVIDER } from '../../../utils/field-constants'; @@ -42,35 +41,6 @@ import { useSelector } from 'react-redux'; import type { AppState } from '../../../../redux/reducer'; import { useParametersNotification } from '../use-parameters-notification'; -const scenarioFormSchema = yup - .object() - .shape({ - [SCENARIO_DURATION]: yup.number().required(), - }) - .required(); - -const scenarioEmptyFormData = { - [SCENARIO_DURATION]: 0, -}; - -const contingencyFormSchema = yup.object().shape({ - [CONTINGENCIES_START_TIME]: yup.number().required(), - [CONTINGENCIES_LIST_INFOS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required(), -}); - -const contingencyEmptyFormData = { - [CONTINGENCIES_START_TIME]: 0, - [CONTINGENCIES_LIST_INFOS]: [], -}; - enum TAB_VALUES { SCENARIO = 'scenario', CONTINGENCY = 'contingency', @@ -83,17 +53,38 @@ interface DynamicSecurityAnalysisParametersProps { const formSchema = yup.object().shape({ [PROVIDER]: yup.string().required(), - [TAB_VALUES.SCENARIO]: scenarioFormSchema, - [TAB_VALUES.CONTINGENCY]: contingencyFormSchema, + [TAB_VALUES.SCENARIO]: yup + .object() + .shape({ + [SCENARIO_DURATION]: yup.number().required(), + }) + .required(), + [TAB_VALUES.CONTINGENCY]: yup.object().shape({ + [CONTINGENCIES_START_TIME]: yup.number().required(), + [CONTINGENCIES_LIST_INFOS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required(), + }), }); const emptyFormData = { [PROVIDER]: '', - [TAB_VALUES.SCENARIO]: { ...scenarioEmptyFormData }, - [TAB_VALUES.CONTINGENCY]: { ...contingencyEmptyFormData }, + [TAB_VALUES.SCENARIO]: { + [SCENARIO_DURATION]: 0, + }, + [TAB_VALUES.CONTINGENCY]: { + [CONTINGENCIES_START_TIME]: 0, + [CONTINGENCIES_LIST_INFOS]: [], + }, }; -export type DynamicSecurityAnalysisParametersForm = yup.InferType; +export type DynamicSecurityAnalysisParametersForm = InferType; const DynamicSecurityAnalysisParameters: FunctionComponent = ({ user, diff --git a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx index ad9d9d7338..054f77ba14 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx @@ -6,7 +6,7 @@ */ import { Grid, Tab, Tabs } from '@mui/material'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; import TimeDelayParameters from './time-delay-parameters'; @@ -29,10 +29,10 @@ import { CustomFormProvider, isObjectEmpty, mergeSx, - SubmitButton, + parametersStyles, ProviderParam, + SubmitButton, useParametersBackend, - parametersStyles, } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; import { FieldErrors, useForm } from 'react-hook-form'; @@ -41,19 +41,8 @@ import ComputingType from '../../../computing-status/computing-type'; import { User } from 'oidc-client'; import { PROVIDER } from '../../../utils/field-constants'; import { SolverInfos } from 'services/study/dynamic-simulation.type'; -import { - Curve, - curveEmptyFormData, - MAPPING, - mappingEmptyFormData, - NETWORK, - networkEmptyFormData, - Solver, - solverEmptyFormData, - TimeDelay, - timeDelayEmptyFormData, -} from './dynamic-simulation-utils'; -import { DynamicSimulationForm, formSchema, TAB_VALUES } from './dynamic-simulation.type'; +import { Curve, MAPPING, NETWORK, NetworkEnum, Solver, TimeDelay } from './dynamic-simulation-utils'; +import { DynamicSimulationForm, getFormSchema, TAB_VALUES } from './dynamic-simulation.type'; import { useSelector } from 'react-redux'; import type { AppState } from '../../../../redux/reducer'; import { useParametersNotification } from '../use-parameters-notification'; @@ -65,17 +54,49 @@ interface DynamicSimulationParametersProps { const emptyFormData = { [PROVIDER]: '', - [TAB_VALUES.TIME_DELAY]: { ...timeDelayEmptyFormData }, - [TAB_VALUES.SOLVER]: { ...solverEmptyFormData }, - [TAB_VALUES.MAPPING]: { ...mappingEmptyFormData }, - [TAB_VALUES.NETWORK]: { ...networkEmptyFormData }, - [TAB_VALUES.CURVE]: { ...curveEmptyFormData }, -}; + [TAB_VALUES.TIME_DELAY]: { + [TimeDelay.START_TIME]: 0, + [TimeDelay.STOP_TIME]: 0, + }, + [TAB_VALUES.SOLVER]: { + [Solver.ID]: '', + [Solver.SOLVERS]: [], + }, + [TAB_VALUES.MAPPING]: { + [MAPPING]: '', + }, + [TAB_VALUES.NETWORK]: { + [NetworkEnum.CAPACITOR_NO_RECLOSING_DELAY]: 0, + [NetworkEnum.DANGLING_LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, + [NetworkEnum.LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, + [NetworkEnum.LOAD_TP]: 0, + [NetworkEnum.LOAD_TQ]: 0, + [NetworkEnum.LOAD_ALPHA]: 0, + [NetworkEnum.LOAD_ALPHA_LONG]: 0, + [NetworkEnum.LOAD_BETA]: 0, + [NetworkEnum.LOAD_BETA_LONG]: 0, + [NetworkEnum.LOAD_IS_CONTROLLABLE]: false, + [NetworkEnum.LOAD_IS_RESTORATIVE]: false, + [NetworkEnum.LOAD_Z_PMAX]: 0, + [NetworkEnum.LOAD_Z_QMAX]: 0, + [NetworkEnum.REACTANCE_NO_RECLOSING_DELAY]: 0, + [NetworkEnum.TRANSFORMER_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, + [NetworkEnum.TRANSFORMER_T1_ST_HT]: 0, + [NetworkEnum.TRANSFORMER_T1_ST_THT]: 0, + [NetworkEnum.TRANSFORMER_T_NEXT_HT]: 0, + [NetworkEnum.TRANSFORMER_T_NEXT_THT]: 0, + [NetworkEnum.TRANSFORMER_TO_LV]: 0, + }, + [TAB_VALUES.CURVE]: { + [Curve.CURVES]: [], + }, +} as const satisfies DynamicSimulationForm; const DynamicSimulationParameters: FunctionComponent = ({ user, setHaveDirtyFields, }) => { + const intl = useIntl(); const dynamicSimulationAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSimulation); const studyUuid = useSelector((state: AppState) => state.studyUuid); @@ -117,6 +138,7 @@ const DynamicSimulationParameters: FunctionComponent getFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-utils.ts b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-utils.ts index 79c8546344..3e0ab5c788 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-utils.ts +++ b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-utils.ts @@ -5,8 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../utils/yup-config'; -import { SolverTypeInfos } from 'services/study/dynamic-simulation.type'; +import * as yup from 'yup'; import { getFormSchema as getCommonSolverFormSchema } from './solver/common-solver-parameters'; export enum TimeDelay { @@ -14,24 +13,6 @@ export enum TimeDelay { STOP_TIME = 'stopTime', } -export const timeDelayFormSchema = yup.object().shape({ - [TimeDelay.START_TIME]: yup.number().required(), - [TimeDelay.STOP_TIME]: yup - .number() - .required() - .when([TimeDelay.START_TIME], ([startTime], schema) => { - if (startTime) { - return schema.min(startTime, 'DynamicSimulationStopTimeMustBeGreaterThanOrEqualToStartTime'); - } - return schema; - }), -}); - -export const timeDelayEmptyFormData = { - [TimeDelay.START_TIME]: 0, - [TimeDelay.STOP_TIME]: 0, -}; - export enum SimplifiedSolver { H_MIN = 'hMin', H_MAX = 'hMax', @@ -106,66 +87,14 @@ export enum Solver { SOLVERS = 'solvers', } -export const solverFormSchema = yup.object().shape({ - [Solver.ID]: yup.string().required(), - [Solver.SOLVERS]: yup.array().when([Solver.ID], ([solverId], schema) => - schema.of( - yup.lazy((item) => { - const { id, type } = item; - - // ignore validation if not current selected solver - if (solverId !== id) { - return yup.object().default(undefined); - } - - // chose the right schema for each type of solver - if (type === SolverTypeInfos.IDA) { - return getIdaFormSchema(); - } else { - return getSimplifiedFormSchema(); - } - }) - ) - ), -}); - -export const solverEmptyFormData = { - [Solver.ID]: '', - [Solver.SOLVERS]: [], -}; - export enum Curve { EQUIPMENT_ID = 'equipmentId', VARIABLE_ID = 'variableId', CURVES = 'curves', } -export const curveFormSchema = yup.object().shape({ - [Curve.CURVES]: yup - .array() - .of( - yup.object().shape({ - [Curve.EQUIPMENT_ID]: yup.string().required(), - [Curve.VARIABLE_ID]: yup.string().required(), - }) - ) - .nullable(), -}); - -export const curveEmptyFormData = { - [Curve.CURVES]: [], -}; - export const MAPPING = 'mapping'; -export const mappingFormSchema = yup.object().shape({ - [MAPPING]: yup.string().required(), -}); - -export const mappingEmptyFormData = { - [MAPPING]: '', -}; - export const NETWORK = 'network'; export enum NetworkEnum { @@ -199,49 +128,3 @@ export enum NetworkEnum { TRANSFORMER_T_NEXT_THT = 'transformerTNextTHT', TRANSFORMER_TO_LV = 'transformerTolV', } - -export const networkFormSchema = yup.object().shape({ - [NetworkEnum.CAPACITOR_NO_RECLOSING_DELAY]: yup.number().required(), - [NetworkEnum.DANGLING_LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), - [NetworkEnum.LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), - [NetworkEnum.LOAD_TP]: yup.number().required(), - [NetworkEnum.LOAD_TQ]: yup.number().required(), - [NetworkEnum.LOAD_ALPHA]: yup.number().required(), - [NetworkEnum.LOAD_ALPHA_LONG]: yup.number().required(), - [NetworkEnum.LOAD_BETA]: yup.number().required(), - [NetworkEnum.LOAD_BETA_LONG]: yup.number().required(), - [NetworkEnum.LOAD_IS_CONTROLLABLE]: yup.boolean(), - [NetworkEnum.LOAD_IS_RESTORATIVE]: yup.boolean(), - [NetworkEnum.LOAD_Z_PMAX]: yup.number().required(), - [NetworkEnum.LOAD_Z_QMAX]: yup.number().required(), - [NetworkEnum.REACTANCE_NO_RECLOSING_DELAY]: yup.number().required(), - [NetworkEnum.TRANSFORMER_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), - [NetworkEnum.TRANSFORMER_T1_ST_HT]: yup.number().required(), - [NetworkEnum.TRANSFORMER_T1_ST_THT]: yup.number().required(), - [NetworkEnum.TRANSFORMER_T_NEXT_HT]: yup.number().required(), - [NetworkEnum.TRANSFORMER_T_NEXT_THT]: yup.number().required(), - [NetworkEnum.TRANSFORMER_TO_LV]: yup.number().required(), -}); - -export const networkEmptyFormData = { - [NetworkEnum.CAPACITOR_NO_RECLOSING_DELAY]: 0, - [NetworkEnum.DANGLING_LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, - [NetworkEnum.LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, - [NetworkEnum.LOAD_TP]: 0, - [NetworkEnum.LOAD_TQ]: 0, - [NetworkEnum.LOAD_ALPHA]: 0, - [NetworkEnum.LOAD_ALPHA_LONG]: 0, - [NetworkEnum.LOAD_BETA]: 0, - [NetworkEnum.LOAD_BETA_LONG]: 0, - [NetworkEnum.LOAD_IS_CONTROLLABLE]: false, - [NetworkEnum.LOAD_IS_RESTORATIVE]: false, - [NetworkEnum.LOAD_Z_PMAX]: 0, - [NetworkEnum.LOAD_Z_QMAX]: 0, - [NetworkEnum.REACTANCE_NO_RECLOSING_DELAY]: 0, - [NetworkEnum.TRANSFORMER_CURRENT_LIMIT_MAX_TIME_OPERATION]: 0, - [NetworkEnum.TRANSFORMER_T1_ST_HT]: 0, - [NetworkEnum.TRANSFORMER_T1_ST_THT]: 0, - [NetworkEnum.TRANSFORMER_T_NEXT_HT]: 0, - [NetworkEnum.TRANSFORMER_T_NEXT_THT]: 0, - [NetworkEnum.TRANSFORMER_TO_LV]: 0, -}; diff --git a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation.type.ts b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation.type.ts index a7a473f6dc..f05a7c69cf 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation.type.ts +++ b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation.type.ts @@ -4,15 +4,20 @@ * 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 yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import { type InferType } from 'yup'; import { PROVIDER } from '../../../utils/field-constants'; import { - curveFormSchema, - mappingFormSchema, - networkFormSchema, - solverFormSchema, - timeDelayFormSchema, + Curve, + getIdaFormSchema, + getSimplifiedFormSchema, + MAPPING, + NetworkEnum, + Solver, + TimeDelay, } from './dynamic-simulation-utils'; +import { SolverTypeInfos } from '../../../../services/study/dynamic-simulation.type'; +import type { IntlShape } from 'react-intl'; export type ModelVariable = { id: string; @@ -29,13 +34,79 @@ export enum TAB_VALUES { CURVE = 'curve', } -export const formSchema = yup.object().shape({ - [PROVIDER]: yup.string().required(), - [TAB_VALUES.TIME_DELAY]: timeDelayFormSchema, - [TAB_VALUES.SOLVER]: solverFormSchema, - [TAB_VALUES.MAPPING]: mappingFormSchema, - [TAB_VALUES.NETWORK]: networkFormSchema, - [TAB_VALUES.CURVE]: curveFormSchema, -}); +export function getFormSchema(intl: IntlShape) { + return yup.object().shape({ + [PROVIDER]: yup.string().required(), + [TAB_VALUES.TIME_DELAY]: yup.object().shape({ + [TimeDelay.START_TIME]: yup.number().required(), + [TimeDelay.STOP_TIME]: yup + .number() + .required() + .when([TimeDelay.START_TIME], ([startTime], schema) => + startTime + ? schema.min( + startTime, + intl.formatMessage({ id: 'DynamicSimulationStopTimeMustBeGreaterThanOrEqualToStartTime' }) + ) + : schema + ), + }), + [TAB_VALUES.SOLVER]: yup.object().shape({ + [Solver.ID]: yup.string().required(), + [Solver.SOLVERS]: yup.array().when([Solver.ID], ([solverId], schema) => + schema.of( + yup.lazy(({ id, type }) => { + // ignore validation if not current selected solver + if (solverId !== id) { + return yup.object().default(undefined); + } + // chose the right schema for each type of solver + if (type === SolverTypeInfos.IDA) { + return getIdaFormSchema(); + } else { + return getSimplifiedFormSchema(); + } + }) + ) + ), + }), + [TAB_VALUES.MAPPING]: yup.object().shape({ + [MAPPING]: yup.string().required(), + }), + [TAB_VALUES.NETWORK]: yup.object().shape({ + [NetworkEnum.CAPACITOR_NO_RECLOSING_DELAY]: yup.number().required(), + [NetworkEnum.DANGLING_LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), + [NetworkEnum.LINE_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), + [NetworkEnum.LOAD_TP]: yup.number().required(), + [NetworkEnum.LOAD_TQ]: yup.number().required(), + [NetworkEnum.LOAD_ALPHA]: yup.number().required(), + [NetworkEnum.LOAD_ALPHA_LONG]: yup.number().required(), + [NetworkEnum.LOAD_BETA]: yup.number().required(), + [NetworkEnum.LOAD_BETA_LONG]: yup.number().required(), + [NetworkEnum.LOAD_IS_CONTROLLABLE]: yup.boolean(), + [NetworkEnum.LOAD_IS_RESTORATIVE]: yup.boolean(), + [NetworkEnum.LOAD_Z_PMAX]: yup.number().required(), + [NetworkEnum.LOAD_Z_QMAX]: yup.number().required(), + [NetworkEnum.REACTANCE_NO_RECLOSING_DELAY]: yup.number().required(), + [NetworkEnum.TRANSFORMER_CURRENT_LIMIT_MAX_TIME_OPERATION]: yup.number().required(), + [NetworkEnum.TRANSFORMER_T1_ST_HT]: yup.number().required(), + [NetworkEnum.TRANSFORMER_T1_ST_THT]: yup.number().required(), + [NetworkEnum.TRANSFORMER_T_NEXT_HT]: yup.number().required(), + [NetworkEnum.TRANSFORMER_T_NEXT_THT]: yup.number().required(), + [NetworkEnum.TRANSFORMER_TO_LV]: yup.number().required(), + }), + [TAB_VALUES.CURVE]: yup.object().shape({ + [Curve.CURVES]: yup + .array() + .of( + yup.object().shape({ + [Curve.EQUIPMENT_ID]: yup.string().required(), + [Curve.VARIABLE_ID]: yup.string().required(), + }) + ) + .nullable(), + }), + }); +} -export type DynamicSimulationForm = yup.InferType; +export type DynamicSimulationForm = InferType>; diff --git a/src/components/dialogs/parameters/dynamicsimulation/solver/common-solver-parameters.ts b/src/components/dialogs/parameters/dynamicsimulation/solver/common-solver-parameters.ts index 5789664071..5ebae8e427 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/solver/common-solver-parameters.ts +++ b/src/components/dialogs/parameters/dynamicsimulation/solver/common-solver-parameters.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { TYPES } from '../../util/make-component-utils'; -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; const COMMON_F_NORM_TOL_ALG = 'fNormTolAlg'; const COMMON_INITIAL_ADD_TOL_ALG = 'initialAddTolAlg'; diff --git a/src/components/dialogs/parameters/network-visualizations/network-visualizations-form.tsx b/src/components/dialogs/parameters/network-visualizations/network-visualizations-form.tsx index 2e9dbb4abc..3125bfb2f4 100644 --- a/src/components/dialogs/parameters/network-visualizations/network-visualizations-form.tsx +++ b/src/components/dialogs/parameters/network-visualizations/network-visualizations-form.tsx @@ -17,7 +17,8 @@ import { PARAM_MAP_MANUAL_REFRESH, PARAM_SUBSTATION_LAYOUT, } from '../../../../utils/config-params'; -import yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { TabValue } from './network-visualizations-utils'; export const initialNetworkVisualizationParametersForm: NetworkVisualizationParametersForm = { @@ -58,4 +59,4 @@ export const networkVisualizationParametersSchema = yup.object().shape({ }), }); -export type NetworkVisualizationParametersForm = yup.InferType; +export type NetworkVisualizationParametersForm = InferType; diff --git a/src/components/dialogs/parameters/non-evacuated-energy/non-evacuated-energy-parameters.tsx b/src/components/dialogs/parameters/non-evacuated-energy/non-evacuated-energy-parameters.tsx index 34b7ea3ed2..83986b02e7 100644 --- a/src/components/dialogs/parameters/non-evacuated-energy/non-evacuated-energy-parameters.tsx +++ b/src/components/dialogs/parameters/non-evacuated-energy/non-evacuated-energy-parameters.tsx @@ -16,7 +16,7 @@ import { } from '@gridsuite/commons-ui'; import { Button, DialogActions, Grid } from '@mui/material'; import { FunctionComponent, useCallback, useEffect, useMemo } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; @@ -55,7 +55,7 @@ import { import { setNonEvacuatedEnergyParameters } from '../../../../services/study/non-evacuated-energy'; import NonEvacuatedEnergyParametersSelector from './non-evacuated-energy-parameters-selector'; import { - formSchema, + getFormSchema, getContingenciesParams, getGenerationStagesDefinitionParams, getGenerationStagesSelectionParams, @@ -79,6 +79,7 @@ export const NonEvacuatedEnergyParameters: FunctionComponent { const { snackError } = useSnackMessage(); + const intl = useIntl(); const [providers, provider, , updateProvider, resetProvider] = parametersBackend; @@ -139,6 +140,7 @@ export const NonEvacuatedEnergyParameters: FunctionComponent getFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/parameters/non-evacuated-energy/utils.ts b/src/components/dialogs/parameters/non-evacuated-energy/utils.ts index 210edcd810..30a620f86a 100644 --- a/src/components/dialogs/parameters/non-evacuated-energy/utils.ts +++ b/src/components/dialogs/parameters/non-evacuated-energy/utils.ts @@ -4,6 +4,7 @@ * 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 { type Dispatch, type SetStateAction } from 'react'; import { ACTIVATED, BRANCHES, @@ -35,45 +36,49 @@ import { STAGES_DEFINITION_INDEX, STAGES_SELECTION, } from '../../../utils/field-constants'; -import yup from '../../../utils/yup-config'; -import { EnergySource, NonEvacuatedEnergyParametersInfos } from 'services/study/non-evacuated-energy.type'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import { EnergySource, type NonEvacuatedEnergyParametersInfos } from 'services/study/non-evacuated-energy.type'; +import type { IntlShape } from 'react-intl'; export type UseGetNonEvacuatedEnergyParametersReturnProps = [ NonEvacuatedEnergyParametersInfos | null, - React.Dispatch>, + Dispatch>, ]; -export const getGenerationStagesDefinitionFormSchema = () => ({ - [STAGES_DEFINITION]: yup.array().of( - yup.object().shape({ - [GENERATION_STAGES_KIND]: yup.mixed().oneOf(Object.values(EnergySource)).required(), - [STAGES_DEFINITION_GENERATORS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .min(1, 'NoGeneratorsFilterGiven'), - [GENERATION_STAGES_PERCENT_MAXP_1]: yup - .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') - .required(), - [GENERATION_STAGES_PERCENT_MAXP_2]: yup - .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') - .required(), - [GENERATION_STAGES_PERCENT_MAXP_3]: yup - .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') - .required(), - }) - ), -}); +function getGenerationStagesDefinitionFormSchema(intl: IntlShape) { + return { + [STAGES_DEFINITION]: yup.array().of( + yup.object().shape({ + [GENERATION_STAGES_KIND]: yup.mixed().oneOf(Object.values(EnergySource)).required(), + [STAGES_DEFINITION_GENERATORS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .min(1, intl.formatMessage({ id: 'NoGeneratorsFilterGiven' })), + [GENERATION_STAGES_PERCENT_MAXP_1]: yup + .number() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .required(), + [GENERATION_STAGES_PERCENT_MAXP_2]: yup + .number() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .required(), + [GENERATION_STAGES_PERCENT_MAXP_3]: yup + .number() + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .required(), + }) + ), + }; +} export const getGenerationStagesDefinitionParams = (params: NonEvacuatedEnergyParametersForm) => { return { @@ -96,44 +101,86 @@ export const getGenerationStagesDefinitionParams = (params: NonEvacuatedEnergyPa }; }; -export const getGenerationStagesSelectionFormSchema = () => ({ - [STAGES_SELECTION]: yup - .array() - .of( - yup.object().shape({ - [NAME]: yup.string().required(), - [STAGES_DEFINITION_INDEX]: yup.array().of(yup.number()), - [PMAX_PERCENTS_INDEX]: yup.array().of(yup.number()), - [ACTIVATED]: yup.boolean().required(), - }) - ) - .min(1, 'NoSimulatedStageGiven'), -}); +function getGenerationStagesSelectionFormSchema(intl: IntlShape) { + return { + [STAGES_SELECTION]: yup + .array() + .of( + yup.object().shape({ + [NAME]: yup.string().required(), + [STAGES_DEFINITION_INDEX]: yup.array().of(yup.number()), + [PMAX_PERCENTS_INDEX]: yup.array().of(yup.number()), + [ACTIVATED]: yup.boolean().required(), + }) + ) + .min(1, intl.formatMessage({ id: 'NoSimulatedStageGiven' })), + }; +} export const getGenerationStagesSelectionParams = (params: NonEvacuatedEnergyParametersForm) => { return { - [STAGES_SELECTION]: params[STAGES_SELECTION]?.map((generationStageSelection) => { - return { - [NAME]: generationStageSelection[NAME], - [STAGES_DEFINITION_INDEX]: generationStageSelection[STAGES_DEFINITION_INDEX], - [PMAX_PERCENTS_INDEX]: generationStageSelection[PMAX_PERCENTS_INDEX], - [ACTIVATED]: generationStageSelection[ACTIVATED], - }; + [STAGES_SELECTION]: params[STAGES_SELECTION]?.map((generationStageSelection) => ({ + [NAME]: generationStageSelection[NAME], + [STAGES_DEFINITION_INDEX]: generationStageSelection[STAGES_DEFINITION_INDEX], + [PMAX_PERCENTS_INDEX]: generationStageSelection[PMAX_PERCENTS_INDEX], + [ACTIVATED]: generationStageSelection[ACTIVATED], + })), + }; +}; + +function getGeneratorsCappingsFormSchema(intl: IntlShape) { + return { + [GENERATORS_CAPPINGS]: yup.object().shape({ + [SENSITIVITY_THRESHOLD]: yup + .number() + .min(0, intl.formatMessage({ id: 'CoefficientMustBeGreaterOrEqualToZero' })) + .max(1, intl.formatMessage({ id: 'CoefficientMustBeLowerOrEqualToOne' })) + .required(), + [GENERATORS_CAPPINGS]: yup.array().of( + yup.object().shape({ + [GENERATORS_CAPPINGS_KIND]: yup.mixed().oneOf(Object.values(EnergySource)).required(), + [GENERATORS_CAPPINGS_FILTER]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [ACTIVATED]: yup.boolean().required(), + }) + ), }), }; +} + +export const getGeneratorsCappingsParams = ( + sensitivityThreshold: number, + params: NonEvacuatedEnergyParametersForm['generatorsCappings'] +) => { + return { + [SENSITIVITY_THRESHOLD]: sensitivityThreshold, + [GENERATORS_CAPPINGS_FILTER]: params[GENERATORS_CAPPINGS]?.map((generatorsCapping) => ({ + [GENERATORS_CAPPINGS_KIND]: generatorsCapping[GENERATORS_CAPPINGS_KIND], + [GENERATORS_CAPPINGS_FILTER]: generatorsCapping[GENERATORS_CAPPINGS_FILTER].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: generatorsCapping[ACTIVATED], + })), + }; }; -export const getGeneratorsCappingsFormSchema = () => ({ - [GENERATORS_CAPPINGS]: yup.object().shape({ - [SENSITIVITY_THRESHOLD]: yup - .number() - .min(0, 'CoefficientMustBeGreaterOrEqualToZero') - .max(1, 'CoefficientMustBeLowerOrEqualToOne') - .required(), - [GENERATORS_CAPPINGS]: yup.array().of( +function getMonitoredBranchesFormSchema(intl: IntlShape) { + return { + [MONITORED_BRANCHES]: yup.array().of( yup.object().shape({ - [GENERATORS_CAPPINGS_KIND]: yup.mixed().oneOf(Object.values(EnergySource)).required(), - [GENERATORS_CAPPINGS_FILTER]: yup + [BRANCHES]: yup .array() .of( yup.object().shape({ @@ -144,143 +191,103 @@ export const getGeneratorsCappingsFormSchema = () => ({ .required() .when([ACTIVATED], { is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), }), + [MONITORED_BRANCHES_IST_N]: yup.boolean().required(), + [MONITORED_BRANCHES_LIMIT_NAME_N]: yup + .string() + .nullable() + .when([MONITORED_BRANCHES_IST_N, ACTIVATED], { + is: (istN: boolean, activated: boolean) => activated && !istN, + then: (schema) => schema.required(), + }), + [MONITORED_BRANCHES_COEFF_N]: yup + .number() + .min(0, intl.formatMessage({ id: 'CoefficientMustBeGreaterOrEqualToZero' })) + .required(), + [MONITORED_BRANCHES_IST_N_1]: yup.boolean().required(), + [MONITORED_BRANCHES_LIMIT_NAME_N_1]: yup + .string() + .nullable() + .when([MONITORED_BRANCHES_IST_N_1, ACTIVATED], { + is: (istN1: boolean, activated: boolean) => activated && !istN1, + then: (schema) => schema.required(), + }), + [MONITORED_BRANCHES_COEFF_N_1]: yup + .number() + .min(0, intl.formatMessage({ id: 'CoefficientMustBeGreaterOrEqualToZero' })) + .required(), [ACTIVATED]: yup.boolean().required(), }) ), - }), -}); - -export const getGeneratorsCappingsParams = ( - sensitivityThreshold: number, - params: NonEvacuatedEnergyParametersForm['generatorsCappings'] -) => { - return { - [SENSITIVITY_THRESHOLD]: sensitivityThreshold, - [GENERATORS_CAPPINGS_FILTER]: params[GENERATORS_CAPPINGS]?.map((generatorsCapping) => { - return { - [GENERATORS_CAPPINGS_KIND]: generatorsCapping[GENERATORS_CAPPINGS_KIND], - [GENERATORS_CAPPINGS_FILTER]: generatorsCapping[GENERATORS_CAPPINGS_FILTER].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: generatorsCapping[ACTIVATED], - }; - }), }; -}; - -export const getMonitoredBranchesFormSchema = () => ({ - [MONITORED_BRANCHES]: yup.array().of( - yup.object().shape({ - [BRANCHES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [MONITORED_BRANCHES_IST_N]: yup.boolean().required(), - [MONITORED_BRANCHES_LIMIT_NAME_N]: yup - .string() - .nullable() - .when([MONITORED_BRANCHES_IST_N, ACTIVATED], { - is: (istN: boolean, activated: boolean) => activated && !istN, - then: (schema) => schema.required(), - }), - [MONITORED_BRANCHES_COEFF_N]: yup.number().min(0, 'CoefficientMustBeGreaterOrEqualToZero').required(), - [MONITORED_BRANCHES_IST_N_1]: yup.boolean().required(), - [MONITORED_BRANCHES_LIMIT_NAME_N_1]: yup - .string() - .nullable() - .when([MONITORED_BRANCHES_IST_N_1, ACTIVATED], { - is: (istN1: boolean, activated: boolean) => activated && !istN1, - then: (schema) => schema.required(), - }), - [MONITORED_BRANCHES_COEFF_N_1]: yup.number().min(0, 'CoefficientMustBeGreaterOrEqualToZero').required(), - [ACTIVATED]: yup.boolean().required(), - }) - ), -}); +} export const getMonitoredBranchesParams = (params: NonEvacuatedEnergyParametersForm) => { return { - [MONITORED_BRANCHES]: params[MONITORED_BRANCHES]?.map((monitoredBranches) => { - return { - [BRANCHES]: monitoredBranches[BRANCHES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [MONITORED_BRANCHES_IST_N]: monitoredBranches[MONITORED_BRANCHES_IST_N], - [MONITORED_BRANCHES_LIMIT_NAME_N]: monitoredBranches[MONITORED_BRANCHES_LIMIT_NAME_N], - [MONITORED_BRANCHES_COEFF_N]: monitoredBranches[MONITORED_BRANCHES_COEFF_N], - [MONITORED_BRANCHES_IST_N_1]: monitoredBranches[MONITORED_BRANCHES_IST_N_1], - [MONITORED_BRANCHES_LIMIT_NAME_N_1]: monitoredBranches[MONITORED_BRANCHES_LIMIT_NAME_N_1], - [MONITORED_BRANCHES_COEFF_N_1]: monitoredBranches[MONITORED_BRANCHES_COEFF_N_1], - [ACTIVATED]: monitoredBranches[ACTIVATED], - }; - }), + [MONITORED_BRANCHES]: params[MONITORED_BRANCHES]?.map((monitoredBranches) => ({ + [BRANCHES]: monitoredBranches[BRANCHES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [MONITORED_BRANCHES_IST_N]: monitoredBranches[MONITORED_BRANCHES_IST_N], + [MONITORED_BRANCHES_LIMIT_NAME_N]: monitoredBranches[MONITORED_BRANCHES_LIMIT_NAME_N], + [MONITORED_BRANCHES_COEFF_N]: monitoredBranches[MONITORED_BRANCHES_COEFF_N], + [MONITORED_BRANCHES_IST_N_1]: monitoredBranches[MONITORED_BRANCHES_IST_N_1], + [MONITORED_BRANCHES_LIMIT_NAME_N_1]: monitoredBranches[MONITORED_BRANCHES_LIMIT_NAME_N_1], + [MONITORED_BRANCHES_COEFF_N_1]: monitoredBranches[MONITORED_BRANCHES_COEFF_N_1], + [ACTIVATED]: monitoredBranches[ACTIVATED], + })), }; }; -export const getContingenciesFormSchema = () => ({ - [CONTINGENCIES]: yup.array().of( - yup.object().shape({ - [CONTINGENCIES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [ACTIVATED]: yup.boolean().required(), - }) - ), -}); +function getContingenciesFormSchema(intl: IntlShape) { + return { + [CONTINGENCIES]: yup.array().of( + yup.object().shape({ + [CONTINGENCIES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [ACTIVATED]: yup.boolean().required(), + }) + ), + }; +} export const getContingenciesParams = (params: NonEvacuatedEnergyParametersForm) => { return { - [CONTINGENCIES]: params[CONTINGENCIES]?.map((contingencies) => { - return { - [CONTINGENCIES]: contingencies[CONTINGENCIES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: contingencies[ACTIVATED], - }; - }), + [CONTINGENCIES]: params[CONTINGENCIES]?.map((contingencies) => ({ + [CONTINGENCIES]: contingencies[CONTINGENCIES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: contingencies[ACTIVATED], + })), }; }; -export const formSchema = yup - .object() - .shape({ - [PROVIDER]: yup.string().required(), - ...getGenerationStagesDefinitionFormSchema(), - ...getGenerationStagesSelectionFormSchema(), - ...getGeneratorsCappingsFormSchema(), - ...getMonitoredBranchesFormSchema(), - ...getContingenciesFormSchema(), - }) - .required(); +export function getFormSchema(intl: IntlShape) { + return yup + .object() + .shape({ + [PROVIDER]: yup.string().required(), + ...getGenerationStagesDefinitionFormSchema(intl), + ...getGenerationStagesSelectionFormSchema(intl), + ...getGeneratorsCappingsFormSchema(intl), + ...getMonitoredBranchesFormSchema(intl), + ...getContingenciesFormSchema(intl), + }) + .required(); +} -export type NonEvacuatedEnergyParametersForm = yup.InferType; +export type NonEvacuatedEnergyParametersForm = InferType>; diff --git a/src/components/dialogs/parameters/security-analysis/security-analysis-parameters.tsx b/src/components/dialogs/parameters/security-analysis/security-analysis-parameters.tsx index 22207ca275..8e88e22458 100644 --- a/src/components/dialogs/parameters/security-analysis/security-analysis-parameters.tsx +++ b/src/components/dialogs/parameters/security-analysis/security-analysis-parameters.tsx @@ -85,9 +85,10 @@ export const SecurityAnalysisParameters: FunctionComponent<{ })); }, [providers]); - const formSchema = useMemo(() => { - return getSAParametersFromSchema(params?.limitReductions); - }, [params?.limitReductions]); + const formSchema = useMemo( + () => getSAParametersFromSchema(intl, params?.limitReductions), + [intl, params?.limitReductions] + ); const formMethods = useForm({ defaultValues: { diff --git a/src/components/dialogs/parameters/sensi/sensitivity-analysis-parameters.tsx b/src/components/dialogs/parameters/sensi/sensitivity-analysis-parameters.tsx index fae1126c6c..2ddaa056d2 100644 --- a/src/components/dialogs/parameters/sensi/sensitivity-analysis-parameters.tsx +++ b/src/components/dialogs/parameters/sensi/sensitivity-analysis-parameters.tsx @@ -59,7 +59,7 @@ import { import SensitivityAnalysisFields from './sensitivity-Flow-parameters'; import SensitivityParametersSelector from './sensitivity-parameters-selector'; import { - formSchema, + getFormSchema, getGenericRowNewParams, getSensiHvdcformatNewParams, getSensiInjectionsformatNewParams, @@ -114,6 +114,7 @@ export const SensitivityAnalysisParameters: FunctionComponent getFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), diff --git a/src/components/dialogs/parameters/sensi/utils.ts b/src/components/dialogs/parameters/sensi/utils.ts index 1cf5651eee..9ec99f21c7 100644 --- a/src/components/dialogs/parameters/sensi/utils.ts +++ b/src/components/dialogs/parameters/sensi/utils.ts @@ -32,201 +32,193 @@ import { SENSITIVITY_TYPE, SUPERVISED_VOLTAGE_LEVELS, } from '../../../utils/field-constants'; -import yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import type { IntlShape } from 'react-intl'; -export const getSensiHVDCsFormSchema = () => ({ - [PARAMETER_SENSI_HVDC]: yup.array().of( - yup.object().shape({ - [MONITORED_BRANCHES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [SENSITIVITY_TYPE]: yup - .mixed() - .oneOf(Object.values(SensitivityType)) - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.required(), - }), - [HVDC_LINES]: yup - .array() - .of( +function getSensiHVDCsFormSchema(intl: IntlShape) { + return { + [PARAMETER_SENSI_HVDC]: yup.array().of( + yup.object().shape({ + [MONITORED_BRANCHES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [SENSITIVITY_TYPE]: yup + .mixed() + .oneOf(Object.values(SensitivityType)) + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.required(), + }), + [HVDC_LINES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [CONTINGENCIES]: yup.array().of( yup.object().shape({ [ID]: yup.string().required(), [NAME]: yup.string().required(), }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [CONTINGENCIES]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [ACTIVATED]: yup.boolean().required(), - [COUNT]: yup.number().nullable(), - }) - ), -}); + ), + [ACTIVATED]: yup.boolean().required(), + [COUNT]: yup.number().nullable(), + }) + ), + }; +} export const getSensiHvdcformatNewParams = (newParams: SensitivityAnalysisParametersFormSchema) => { return { - [PARAMETER_SENSI_HVDC]: newParams.sensitivityHVDC?.map((sensitivityHVDCs) => { - return { - [MONITORED_BRANCHES]: sensitivityHVDCs[MONITORED_BRANCHES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [HVDC_LINES]: sensitivityHVDCs[HVDC_LINES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [SENSITIVITY_TYPE]: sensitivityHVDCs[SENSITIVITY_TYPE], - [CONTINGENCIES]: sensitivityHVDCs[CONTINGENCIES]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: sensitivityHVDCs[ACTIVATED], - }; - }), + [PARAMETER_SENSI_HVDC]: newParams.sensitivityHVDC?.map((sensitivityHVDCs) => ({ + [MONITORED_BRANCHES]: sensitivityHVDCs[MONITORED_BRANCHES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [HVDC_LINES]: sensitivityHVDCs[HVDC_LINES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [SENSITIVITY_TYPE]: sensitivityHVDCs[SENSITIVITY_TYPE], + [CONTINGENCIES]: sensitivityHVDCs[CONTINGENCIES]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: sensitivityHVDCs[ACTIVATED], + })), }; }; -export const getSensiInjectionsFormSchema = () => ({ - [PARAMETER_SENSI_INJECTION]: yup.array().of( - yup.object().shape({ - [MONITORED_BRANCHES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [INJECTIONS]: yup - .array() - .of( +function getSensiInjectionsFormSchema(intl: IntlShape) { + return { + [PARAMETER_SENSI_INJECTION]: yup.array().of( + yup.object().shape({ + [MONITORED_BRANCHES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [INJECTIONS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [CONTINGENCIES]: yup.array().of( yup.object().shape({ [ID]: yup.string().required(), [NAME]: yup.string().required(), }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [CONTINGENCIES]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [ACTIVATED]: yup.boolean().required(), - [COUNT]: yup.number().nullable(), - }) - ), -}); + ), + [ACTIVATED]: yup.boolean().required(), + [COUNT]: yup.number().nullable(), + }) + ), + }; +} export const getSensiInjectionsformatNewParams = (newParams: SensitivityAnalysisParametersFormSchema) => { return { - [PARAMETER_SENSI_INJECTION]: newParams.sensitivityInjection?.map((sensitivityInjections) => { - return { - [MONITORED_BRANCHES]: sensitivityInjections[MONITORED_BRANCHES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [INJECTIONS]: sensitivityInjections[INJECTIONS].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [CONTINGENCIES]: sensitivityInjections[CONTINGENCIES]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: sensitivityInjections[ACTIVATED], - }; - }), + [PARAMETER_SENSI_INJECTION]: newParams.sensitivityInjection?.map((sensitivityInjections) => ({ + [MONITORED_BRANCHES]: sensitivityInjections[MONITORED_BRANCHES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [INJECTIONS]: sensitivityInjections[INJECTIONS].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [CONTINGENCIES]: sensitivityInjections[CONTINGENCIES]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: sensitivityInjections[ACTIVATED], + })), }; }; -export const getSensiInjectionsSetFormSchema = () => ({ - [PARAMETER_SENSI_INJECTIONS_SET]: yup.array().of( - yup.object().shape({ - [MONITORED_BRANCHES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [INJECTIONS]: yup - .array() - .of( +function getSensiInjectionsSetFormSchema(intl: IntlShape) { + return { + [PARAMETER_SENSI_INJECTIONS_SET]: yup.array().of( + yup.object().shape({ + [MONITORED_BRANCHES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [INJECTIONS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [DISTRIBUTION_TYPE]: yup + .mixed() + .oneOf(Object.values(DistributionType)) + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.required(), + }), + [CONTINGENCIES]: yup.array().of( yup.object().shape({ [ID]: yup.string().required(), [NAME]: yup.string().required(), }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [DISTRIBUTION_TYPE]: yup - .mixed() - .oneOf(Object.values(DistributionType)) - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.required(), - }), - [CONTINGENCIES]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [ACTIVATED]: yup.boolean().nullable(), - [COUNT]: yup.number().nullable(), - }) - ), -}); + ), + [ACTIVATED]: yup.boolean().nullable(), + [COUNT]: yup.number().nullable(), + }) + ), + }; +} export interface IRowNewParams { [MONITORED_BRANCHES]: Array<{ @@ -263,34 +255,26 @@ export const getGenericRowNewParams = (newRowParams: IRowNewParams) => { export const getSensiInjectionsSetformatNewParams = (newParams: SensitivityAnalysisParametersFormSchema) => { return { - [PARAMETER_SENSI_INJECTIONS_SET]: newParams.sensitivityInjectionsSet?.map((sensitivityInjectionSet) => { - return { - [MONITORED_BRANCHES]: sensitivityInjectionSet[MONITORED_BRANCHES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [INJECTIONS]: sensitivityInjectionSet[INJECTIONS].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [DISTRIBUTION_TYPE]: sensitivityInjectionSet[DISTRIBUTION_TYPE], - [CONTINGENCIES]: sensitivityInjectionSet[CONTINGENCIES]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: sensitivityInjectionSet[ACTIVATED], - }; - }), + [PARAMETER_SENSI_INJECTIONS_SET]: newParams.sensitivityInjectionsSet?.map((sensitivityInjectionSet) => ({ + [MONITORED_BRANCHES]: sensitivityInjectionSet[MONITORED_BRANCHES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [INJECTIONS]: sensitivityInjectionSet[INJECTIONS].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [DISTRIBUTION_TYPE]: sensitivityInjectionSet[DISTRIBUTION_TYPE], + [CONTINGENCIES]: sensitivityInjectionSet[CONTINGENCIES]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: sensitivityInjectionSet[ACTIVATED], + })), }; }; -export const getSensiNodesFormSchema = () => ({ +const getSensiNodesFormSchema = () => ({ [PARAMETER_SENSI_NODES]: yup.array().of( yup.object().shape({ [SUPERVISED_VOLTAGE_LEVELS]: yup.array().of( @@ -319,123 +303,112 @@ export const getSensiNodesFormSchema = () => ({ export const getSensiNodesformatNewParams = (newParams: SensitivityAnalysisParametersFormSchema) => { return { - [PARAMETER_SENSI_NODES]: newParams.sensitivityNodes?.map((sensitivityNode) => { - return { - [SUPERVISED_VOLTAGE_LEVELS]: sensitivityNode[SUPERVISED_VOLTAGE_LEVELS]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [EQUIPMENTS_IN_VOLTAGE_REGULATION]: sensitivityNode[EQUIPMENTS_IN_VOLTAGE_REGULATION]?.map( - (container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - } - ), - [CONTINGENCIES]: sensitivityNode[CONTINGENCIES]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: sensitivityNode[ACTIVATED], - }; - }), + [PARAMETER_SENSI_NODES]: newParams.sensitivityNodes?.map((sensitivityNode) => ({ + [SUPERVISED_VOLTAGE_LEVELS]: sensitivityNode[SUPERVISED_VOLTAGE_LEVELS]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [EQUIPMENTS_IN_VOLTAGE_REGULATION]: sensitivityNode[EQUIPMENTS_IN_VOLTAGE_REGULATION]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [CONTINGENCIES]: sensitivityNode[CONTINGENCIES]?.map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [ACTIVATED]: sensitivityNode[ACTIVATED], + })), }; }; -export const getSensiPSTsFormSchema = () => ({ - [PARAMETER_SENSI_PST]: yup.array().of( - yup.object().shape({ - [MONITORED_BRANCHES]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [SENSITIVITY_TYPE]: yup - .mixed() - .oneOf(Object.values(SensitivityType)) - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.required(), - }), - [PSTS]: yup - .array() - .of( +function getSensiPSTsFormSchema(intl: IntlShape) { + return { + [PARAMETER_SENSI_PST]: yup.array().of( + yup.object().shape({ + [MONITORED_BRANCHES]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [SENSITIVITY_TYPE]: yup + .mixed() + .oneOf(Object.values(SensitivityType)) + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.required(), + }), + [PSTS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required() + .when([ACTIVATED], { + is: (activated: boolean) => activated, + then: (schema) => schema.min(1, intl.formatMessage({ id: 'FieldIsRequired' })), + }), + [CONTINGENCIES]: yup.array().of( yup.object().shape({ [ID]: yup.string().required(), [NAME]: yup.string().required(), }) - ) - .required() - .when([ACTIVATED], { - is: (activated: boolean) => activated, - then: (schema) => schema.min(1, 'FieldIsRequired'), - }), - [CONTINGENCIES]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [ACTIVATED]: yup.boolean().required(), - [COUNT]: yup.number().nullable(), - }) - ), -}); + ), + [ACTIVATED]: yup.boolean().required(), + [COUNT]: yup.number().nullable(), + }) + ), + }; +} export const getSensiPstformatNewParams = (newParams: SensitivityAnalysisParametersFormSchema) => { return { - [PARAMETER_SENSI_PST]: newParams.sensitivityPST?.map((sensitivityPSTs) => { - return { - [MONITORED_BRANCHES]: sensitivityPSTs[MONITORED_BRANCHES].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [PSTS]: sensitivityPSTs[PSTS].map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [SENSITIVITY_TYPE]: sensitivityPSTs[SENSITIVITY_TYPE], - [CONTINGENCIES]: sensitivityPSTs[CONTINGENCIES]?.map((container) => { - return { - [CONTAINER_ID]: container[ID], - [CONTAINER_NAME]: container[NAME], - }; - }), - [ACTIVATED]: sensitivityPSTs[ACTIVATED], - }; - }), + [PARAMETER_SENSI_PST]: newParams.sensitivityPST?.map((sensitivityPSTs) => ({ + [MONITORED_BRANCHES]: sensitivityPSTs[MONITORED_BRANCHES].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [PSTS]: sensitivityPSTs[PSTS].map((container) => ({ + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + })), + [SENSITIVITY_TYPE]: sensitivityPSTs[SENSITIVITY_TYPE], + [CONTINGENCIES]: sensitivityPSTs[CONTINGENCIES]?.map((container) => { + return { + [CONTAINER_ID]: container[ID], + [CONTAINER_NAME]: container[NAME], + }; + }), + [ACTIVATED]: sensitivityPSTs[ACTIVATED], + })), }; }; -export const formSchema = yup - .object() - .shape({ - [PROVIDER]: yup.string().required(), - [FLOW_FLOW_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), - [ANGLE_FLOW_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), - [FLOW_VOLTAGE_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), - ...getSensiInjectionsSetFormSchema(), - ...getSensiInjectionsFormSchema(), - ...getSensiHVDCsFormSchema(), - ...getSensiPSTsFormSchema(), - ...getSensiNodesFormSchema(), - }) - .required(); -export type SensitivityAnalysisParametersFormSchema = yup.InferType; +export function getFormSchema(intl: IntlShape) { + return yup + .object() + .shape({ + [PROVIDER]: yup.string().required(), + [FLOW_FLOW_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), + [ANGLE_FLOW_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), + [FLOW_VOLTAGE_SENSITIVITY_VALUE_THRESHOLD]: yup.number().required(), + ...getSensiInjectionsSetFormSchema(intl), + ...getSensiInjectionsFormSchema(intl), + ...getSensiHVDCsFormSchema(intl), + ...getSensiPSTsFormSchema(intl), + ...getSensiNodesFormSchema(), + }) + .required(); +} + +export type SensitivityAnalysisParametersFormSchema = InferType>; diff --git a/src/components/dialogs/parameters/short-circuit-parameters.tsx b/src/components/dialogs/parameters/short-circuit-parameters.tsx index 095416a27c..c8c7f0ea57 100644 --- a/src/components/dialogs/parameters/short-circuit-parameters.tsx +++ b/src/components/dialogs/parameters/short-circuit-parameters.tsx @@ -25,7 +25,8 @@ import { setShortCircuitParameters, } from '../../../services/study/short-circuit-analysis'; import { fetchShortCircuitParameters } from '../../../services/short-circuit-analysis'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { SHORT_CIRCUIT_INITIAL_VOLTAGE_PROFILE_MODE, SHORT_CIRCUIT_PREDEFINED_PARAMS, @@ -91,7 +92,7 @@ const prepareDataToSend = ( }; }; -type ShortCircuitParametersFormProps = yup.InferType; +type ShortCircuitParametersFormProps = InferType; interface ShortCircuitParametersProps { useShortCircuitParameters: UseGetShortCircuitParametersProps; diff --git a/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-utils.ts b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-utils.ts index 9b78b9fde7..8916a50d66 100644 --- a/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-utils.ts +++ b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-utils.ts @@ -4,7 +4,8 @@ * 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 yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { DEFAULT_BOUNDS, DEFAULT_FIXED_BOUNDS, @@ -386,4 +387,4 @@ export const stateEstimationParametersFormSchema = yup.object().shape({ }), }); -export type StateEstimationParametersForm = yup.InferType; +export type StateEstimationParametersForm = InferType; diff --git a/src/components/dialogs/parameters/voltageinit/voltage-init-parameters-form.tsx b/src/components/dialogs/parameters/voltageinit/voltage-init-parameters-form.tsx index fe9210a4de..82f7428aa5 100644 --- a/src/components/dialogs/parameters/voltageinit/voltage-init-parameters-form.tsx +++ b/src/components/dialogs/parameters/voltageinit/voltage-init-parameters-form.tsx @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { FILTERS, GENERATORS_SELECTION_TYPE, @@ -25,8 +26,8 @@ import { } from '../../../utils/field-constants'; import { isBlankOrEmpty } from '../../../utils/validation-functions'; import { REACTIVE_SLACKS_THRESHOLD, SHUNT_COMPENSATOR_ACTIVATION_THRESHOLD } from './voltage-init-constants'; - import { EquipmentsSelectionType } from './voltage-init.type'; +import type { IntlShape } from 'react-intl'; export const GENERAL = 'GENERAL'; export const GENERAL_APPLY_MODIFICATIONS = 'GENERAL_APPLY_MODIFICATIONS'; @@ -59,83 +60,86 @@ export const initialVoltageInitParametersForm: VoltageInitParametersForm = { [VARIABLE_SHUNT_COMPENSATORS]: [], }; -export const voltageInitParametersFormSchema = yup.object().shape({ - [TabValue.GENERAL]: yup.object().shape({ - [GENERAL_APPLY_MODIFICATIONS]: yup.boolean().required(), - [UPDATE_BUS_VOLTAGE]: yup.boolean().required(), - [REACTIVE_SLACKS_THRESHOLD]: yup - .number() - .min(0, 'ReactiveSlacksThresholdMustBeGreaterOrEqualToZero') - .required(), - [SHUNT_COMPENSATOR_ACTIVATION_THRESHOLD]: yup - .number() - .min(0, 'ShuntCompensatorActivationThresholdMustBeGreaterOrEqualToZero') - .required(), - }), - [VOLTAGE_LIMITS_MODIFICATION]: yup.array().of( - yup.object().shape({ - [FILTERS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .min(1, 'FilterInputMinError'), - [LOW_VOLTAGE_LIMIT]: yup.number().nullable(), - [HIGH_VOLTAGE_LIMIT]: yup.number().nullable(), - }) - ), - [VOLTAGE_LIMITS_DEFAULT]: yup.array().of( - yup.object().shape({ - [FILTERS]: yup - .array() - .of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .min(1, 'FilterInputMinError'), - [LOW_VOLTAGE_LIMIT]: yup +export function getVoltageInitParametersFormSchema(intl: IntlShape) { + return yup.object().shape({ + [TabValue.GENERAL]: yup.object().shape({ + [GENERAL_APPLY_MODIFICATIONS]: yup.boolean().required(), + [UPDATE_BUS_VOLTAGE]: yup.boolean().required(), + [REACTIVE_SLACKS_THRESHOLD]: yup .number() - .min(0) - .nullable() - .test((value, context) => { - return !isBlankOrEmpty(value) || !isBlankOrEmpty(context.parent[HIGH_VOLTAGE_LIMIT]); - }), - [HIGH_VOLTAGE_LIMIT]: yup + .min(0, intl.formatMessage({ id: 'ReactiveSlacksThresholdMustBeGreaterOrEqualToZero' })) + .required(), + [SHUNT_COMPENSATOR_ACTIVATION_THRESHOLD]: yup .number() - .min(0) - .nullable() - .test((value, context) => { - return !isBlankOrEmpty(value) || !isBlankOrEmpty(context.parent[LOW_VOLTAGE_LIMIT]); - }), - [SELECTED]: yup.boolean().required(), - }) - ), - [GENERATORS_SELECTION_TYPE]: yup.mixed().required(), - [VARIABLE_Q_GENERATORS]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [TRANSFORMERS_SELECTION_TYPE]: yup.mixed().required(), - [VARIABLE_TRANSFORMERS]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), - [SHUNT_COMPENSATORS_SELECTION_TYPE]: yup.mixed().required(), - [VARIABLE_SHUNT_COMPENSATORS]: yup.array().of( - yup.object().shape({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ), -}); + .min(0, intl.formatMessage({ id: 'ShuntCompensatorActivationThresholdMustBeGreaterOrEqualToZero' })) + .required(), + }), + [VOLTAGE_LIMITS_MODIFICATION]: yup.array().of( + yup.object().shape({ + [FILTERS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .min(1, intl.formatMessage({ id: 'FilterInputMinError' })), + [LOW_VOLTAGE_LIMIT]: yup.number().nullable(), + [HIGH_VOLTAGE_LIMIT]: yup.number().nullable(), + }) + ), + [VOLTAGE_LIMITS_DEFAULT]: yup.array().of( + yup.object().shape({ + [FILTERS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .min(1, intl.formatMessage({ id: 'FilterInputMinError' })), + [LOW_VOLTAGE_LIMIT]: yup + .number() + .min(0) + .nullable() + .test( + (value, context) => + !isBlankOrEmpty(value) || !isBlankOrEmpty(context.parent[HIGH_VOLTAGE_LIMIT]) + ), + [HIGH_VOLTAGE_LIMIT]: yup + .number() + .min(0) + .nullable() + .test( + (value, context) => !isBlankOrEmpty(value) || !isBlankOrEmpty(context.parent[LOW_VOLTAGE_LIMIT]) + ), + [SELECTED]: yup.boolean().required(), + }) + ), + [GENERATORS_SELECTION_TYPE]: yup.mixed().required(), + [VARIABLE_Q_GENERATORS]: yup.array().of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ), + [TRANSFORMERS_SELECTION_TYPE]: yup.mixed().required(), + [VARIABLE_TRANSFORMERS]: yup.array().of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ), + [SHUNT_COMPENSATORS_SELECTION_TYPE]: yup.mixed().required(), + [VARIABLE_SHUNT_COMPENSATORS]: yup.array().of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ), + }); +} -export type VoltageInitParametersForm = yup.InferType; +export type VoltageInitParametersForm = InferType>; diff --git a/src/components/dialogs/parameters/voltageinit/voltage-init-parameters.tsx b/src/components/dialogs/parameters/voltageinit/voltage-init-parameters.tsx index 98d5595e65..4884ebabd3 100644 --- a/src/components/dialogs/parameters/voltageinit/voltage-init-parameters.tsx +++ b/src/components/dialogs/parameters/voltageinit/voltage-init-parameters.tsx @@ -17,7 +17,7 @@ import { parametersStyles, } from '@gridsuite/commons-ui'; import { Button, DialogActions, Grid, Tab, Tabs } from '@mui/material'; -import { Dispatch, SetStateAction, SyntheticEvent, useCallback, useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { TabPanel } from '../parameters'; @@ -40,7 +40,7 @@ import { initialVoltageInitParametersForm, TabValue, VoltageInitParametersForm, - voltageInitParametersFormSchema, + getVoltageInitParametersFormSchema, } from './voltage-init-parameters-form'; import { AppState } from '../../../../redux/reducer'; import { UUID } from 'crypto'; @@ -58,6 +58,7 @@ export const VoltageInitParameters = ({ const [tabValue, setTabValue] = useState(TabValue.GENERAL); + const voltageInitParametersFormSchema = useMemo(() => getVoltageInitParametersFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: initialVoltageInitParametersForm, resolver: yupResolver(voltageInitParametersFormSchema), diff --git a/src/components/dialogs/percentage-area/percentage-area-utils.ts b/src/components/dialogs/percentage-area/percentage-area-utils.ts index d55d03ec54..646dd67055 100644 --- a/src/components/dialogs/percentage-area/percentage-area-utils.ts +++ b/src/components/dialogs/percentage-area/percentage-area-utils.ts @@ -6,26 +6,30 @@ */ import { LEFT_SIDE_PERCENTAGE, RIGHT_SIDE_PERCENTAGE, SLIDER_PERCENTAGE } from 'components/utils/field-constants'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; import { Input } from '@gridsuite/commons-ui'; +import type { IntlShape } from 'react-intl'; -const percentageAreaValidationSchema = () => ({ - [SLIDER_PERCENTAGE]: yup.number(), - [LEFT_SIDE_PERCENTAGE]: yup.number().min(0.1, 'OutOfBoundsPercentage').max(99.9, 'OutOfBoundsPercentage'), - [RIGHT_SIDE_PERCENTAGE]: yup.number().min(0.1, 'OutOfBoundsPercentage').max(99.9, 'OutOfBoundsPercentage'), -}); -export const getPercentageAreaValidationSchema = () => { - return percentageAreaValidationSchema(); -}; - -const percentageAreaEmptyFormData = () => ({ - [SLIDER_PERCENTAGE]: 50, - [LEFT_SIDE_PERCENTAGE]: 50, - [RIGHT_SIDE_PERCENTAGE]: 50, -}); +export function getPercentageAreaValidationSchema(intl: IntlShape) { + return { + [SLIDER_PERCENTAGE]: yup.number(), + [LEFT_SIDE_PERCENTAGE]: yup + .number() + .min(0.1, intl.formatMessage({ id: 'OutOfBoundsPercentage' })) + .max(99.9, intl.formatMessage({ id: 'OutOfBoundsPercentage' })), + [RIGHT_SIDE_PERCENTAGE]: yup + .number() + .min(0.1, intl.formatMessage({ id: 'OutOfBoundsPercentage' })) + .max(99.9, intl.formatMessage({ id: 'OutOfBoundsPercentage' })), + }; +} export const getPercentageAreaEmptyFormData = () => { - return percentageAreaEmptyFormData(); + return { + [SLIDER_PERCENTAGE]: 50, + [LEFT_SIDE_PERCENTAGE]: 50, + [RIGHT_SIDE_PERCENTAGE]: 50, + }; }; export const getPercentageAreaData = ({ percent }: { percent: number }) => { @@ -40,7 +44,7 @@ export const isValidPercentage = (val: string) => { return /^\d*[.,]?\d?$/.test(val); }; -//used to format substaction of two percentages (avoid having more than one decimal) +//used to format substraction of two percentages (avoid having more than one decimal) export function sanitizePercentageValue(value: number) { return Math.round(value * 10) / 10; } diff --git a/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.ts b/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.ts index d411306c1d..f72e551ea7 100644 --- a/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.ts +++ b/src/components/dialogs/reactive-limits/reactive-capability-curve/reactive-capability-utils.ts @@ -6,7 +6,7 @@ */ import { toNumber, validateValueIsANumber } from 'components/utils/validation-functions'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { MAX_Q, MIN_Q, @@ -17,18 +17,22 @@ import { } from 'components/utils/field-constants'; import { ReactiveCapabilityCurve } from '../reactive-limits.type'; import { FieldValues, UseFormSetValue } from 'react-hook-form'; +import type { IntlShape } from 'react-intl'; export const INSERT = 'INSERT'; export const REMOVE = 'REMOVE'; -const getCreationRowSchema = () => +const getCreationRowSchema = (intl: IntlShape) => yup.object().shape({ [MAX_Q]: yup.number().nullable().required(), [MIN_Q]: yup .number() .nullable() .required() - .max(yup.ref(MAX_Q), 'ReactiveCapabilityCurveCreationErrorQminPQmaxPIncoherence'), + .max( + yup.ref(MAX_Q), + intl.formatMessage({ id: 'ReactiveCapabilityCurveCreationErrorQminPQmaxPIncoherence' }) + ), [P]: yup.number().nullable().required(), }); @@ -42,8 +46,7 @@ function getNotNullPFromArray(values: ReactiveCapabilityCurve) { return values ?.map((element) => { const pValue = element[P]; - - // Note : convertion toNumber is necessary here to prevent corner cases like if + // Note : conversion toNumber is necessary here to prevent corner cases like if // two values are "-0" and "0", which would be considered different by the Set below. return validateValueIsANumber(pValue) ? toNumber(pValue) : null; }) @@ -52,8 +55,7 @@ function getNotNullPFromArray(values: ReactiveCapabilityCurve) { function checkAllPValuesAreUnique(values: ReactiveCapabilityCurve) { const validActivePowerValues = getNotNullPFromArray(values); - const setOfPs = [...new Set(validActivePowerValues)]; - return setOfPs.length === validActivePowerValues?.length; + return [...new Set(validActivePowerValues)].length === validActivePowerValues?.length; } function checkAllPValuesBetweenMinMax(values: ReactiveCapabilityCurve) { @@ -65,42 +67,53 @@ function checkAllPValuesBetweenMinMax(values: ReactiveCapabilityCurve) { } } -export const getReactiveCapabilityCurveValidationSchema = ( +export function getReactiveCapabilityCurveValidationSchema( + intl: IntlShape, id = REACTIVE_CAPABILITY_CURVE_TABLE, positiveAndNegativePExist = false -) => ({ - [id]: yup - .array() - .nullable() - .when([REACTIVE_CAPABILITY_CURVE_CHOICE], { - is: 'CURVE', - then: (schema) => - schema - .of(getCreationRowSchema()) - .when([], { - is: () => positiveAndNegativePExist, - then: (schema) => - schema - .test( - 'checkATLeastThereIsOneNegativeP', - 'ReactiveCapabilityCurveCreationErrorMissingNegativeP', - (values) => values?.some((value) => value.p < 0) - ) - .test( - 'checkATLeastThereIsOnePositiveP', - 'ReactiveCapabilityCurveCreationErrorMissingPositiveP', - (values) => values?.some((value) => value.p >= 0) - ), - }) - .min(2, 'ReactiveCapabilityCurveCreationErrorMissingPoints') - .test('checkAllValuesAreUnique', 'ReactiveCapabilityCurveCreationErrorPInvalid', (values) => - checkAllPValuesAreUnique(values) - ) - .test('checkAllValuesBetweenMinMax', 'ReactiveCapabilityCurveCreationErrorPOutOfRange', (values) => - checkAllPValuesBetweenMinMax(values) - ), - }), -}); +) { + return { + [id]: yup + .array() + .nullable() + .when([REACTIVE_CAPABILITY_CURVE_CHOICE], { + is: 'CURVE', + then: (schema) => + schema + .of(getCreationRowSchema(intl)) + .when([], { + is: () => positiveAndNegativePExist, + then: (schema) => + schema + .test( + 'checkATLeastThereIsOneNegativeP', + intl.formatMessage({ + id: 'ReactiveCapabilityCurveCreationErrorMissingNegativeP', + }), + (values) => values?.some((value) => value.p < 0) + ) + .test( + 'checkATLeastThereIsOnePositiveP', + intl.formatMessage({ + id: 'ReactiveCapabilityCurveCreationErrorMissingPositiveP', + }), + (values) => values?.some((value) => value.p >= 0) + ), + }) + .min(2, 'ReactiveCapabilityCurveCreationErrorMissingPoints') + .test( + 'checkAllValuesAreUnique', + intl.formatMessage({ id: 'ReactiveCapabilityCurveCreationErrorPInvalid' }), + (values) => checkAllPValuesAreUnique(values) + ) + .test( + 'checkAllValuesBetweenMinMax', + intl.formatMessage({ id: 'ReactiveCapabilityCurveCreationErrorPOutOfRange' }), + (values) => checkAllPValuesBetweenMinMax(values) + ), + }), + }; +} export function setSelectedReactiveLimits( id: string, diff --git a/src/components/dialogs/reactive-limits/reactive-limits-utils.ts b/src/components/dialogs/reactive-limits/reactive-limits-utils.ts index b359b911a4..eaa6e66554 100644 --- a/src/components/dialogs/reactive-limits/reactive-limits-utils.ts +++ b/src/components/dialogs/reactive-limits/reactive-limits-utils.ts @@ -16,8 +16,9 @@ import { getReactiveCapabilityCurveValidationSchema, getRowEmptyFormData, } from './reactive-capability-curve/reactive-capability-utils'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { ReactiveCapabilityCurvePoints } from './reactive-limits.type'; +import type { IntlShape } from 'react-intl'; export const getReactiveLimitsFormData = ({ id = REACTIVE_LIMITS, @@ -52,11 +53,12 @@ export const getReactiveLimitsEmptyFormData = (id = REACTIVE_LIMITS) => ({ }, }); -export const getReactiveLimitsValidationSchema = ( +export function getReactiveLimitsValidationSchema( + intl: IntlShape, isEquipmentModification = false, positiveAndNegativePExist = false // if true, we check that Reactive Capability table have at least one row with negative P and one with positive one -) => - yup.object().shape( +) { + return yup.object().shape( { [REACTIVE_CAPABILITY_CURVE_CHOICE]: yup.string().nullable().required(), [MINIMUM_REACTIVE_POWER]: yup @@ -73,15 +75,23 @@ export const getReactiveLimitsValidationSchema = ( is: (minimumReactivePower: number) => !isEquipmentModification && minimumReactivePower != null, then: (schema) => schema.required(), }), - ...getReactiveCapabilityCurveValidationSchema(REACTIVE_CAPABILITY_CURVE_TABLE, positiveAndNegativePExist), + ...getReactiveCapabilityCurveValidationSchema( + intl, + REACTIVE_CAPABILITY_CURVE_TABLE, + positiveAndNegativePExist + ), }, [MAXIMUM_REACTIVE_POWER, MINIMUM_REACTIVE_POWER] as unknown as readonly [string, string][] ); +} -export const getReactiveLimitsSchema = ( +export function getReactiveLimitsSchema( + intl: IntlShape, isEquipmentModification = false, positiveAndNegativePExist = false, id = REACTIVE_LIMITS -) => ({ - [id]: getReactiveLimitsValidationSchema(isEquipmentModification, positiveAndNegativePExist), -}); +) { + return { + [id]: getReactiveLimitsValidationSchema(intl, isEquipmentModification, positiveAndNegativePExist), + }; +} diff --git a/src/components/dialogs/root-network/root-network-dialog.tsx b/src/components/dialogs/root-network/root-network-dialog.tsx index ce91c9d728..2b9d761883 100644 --- a/src/components/dialogs/root-network/root-network-dialog.tsx +++ b/src/components/dialogs/root-network/root-network-dialog.tsx @@ -11,7 +11,7 @@ import { Grid } from '@mui/material'; import { CASE_NAME, CASE_ID, NAME, TAG } from '../../utils/field-constants'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; import { useSelector } from 'react-redux'; import { AppState } from 'redux/reducer'; import { ModificationDialog } from '../commons/modificationDialog'; diff --git a/src/components/dialogs/set-points/set-points-utils.ts b/src/components/dialogs/set-points/set-points-utils.ts index 12371f87ec..abe131a653 100644 --- a/src/components/dialogs/set-points/set-points-utils.ts +++ b/src/components/dialogs/set-points/set-points-utils.ts @@ -12,19 +12,22 @@ import { REACTIVE_POWER_SET_POINT, VOLTAGE_REGULATION, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; +import type { IntlShape } from 'react-intl'; export const getSetPointsEmptyFormData = (isEquipmentModification = false) => ({ [ACTIVE_POWER_SET_POINT]: null, [REACTIVE_POWER_SET_POINT]: null, }); -export const getSetPointsSchema = (isEquipmentModification = false) => ({ - ...getActivePowerSetPointSchema(isEquipmentModification), - ...getReactivePowerSetPointSchema(isEquipmentModification), -}); +export function getSetPointsSchema(intl: IntlShape, isEquipmentModification = false) { + return { + ...getActivePowerSetPointSchema(intl, isEquipmentModification), + ...getReactivePowerSetPointSchema(isEquipmentModification), + }; +} -export const getReactivePowerSetPointSchema = (isEquipmentModification = false) => ({ +const getReactivePowerSetPointSchema = (isEquipmentModification = false) => ({ [REACTIVE_POWER_SET_POINT]: yup .number() .nullable() @@ -34,36 +37,35 @@ export const getReactivePowerSetPointSchema = (isEquipmentModification = false) }), }); -export const getActivePowerSetPointSchema = (isEquipmentModification = false) => ({ - [ACTIVE_POWER_SET_POINT]: yup - .number() - .when([], { - is: () => isEquipmentModification, - then: (schema) => { - return schema.nullable(); - }, - }) - .when([], { - is: () => !isEquipmentModification, - then: (schema) => { - return schema - .required() - .nonNullable('FieldIsRequired') - .test( - 'activePowerSetPoint', - 'ActivePowerMustBeZeroOrBetweenMinAndMaxActivePower', - (value, context) => { - const minActivePower = context.parent[MINIMUM_ACTIVE_POWER]; - const maxActivePower = context.parent[MAXIMUM_ACTIVE_POWER]; - if (value === 0) { - return true; - } - if (minActivePower === null || maxActivePower === null) { - return false; +function getActivePowerSetPointSchema(intl: IntlShape, isEquipmentModification = false) { + return { + [ACTIVE_POWER_SET_POINT]: yup + .number() + .when([], { + is: () => isEquipmentModification, + then: (schema) => schema.nullable(), + }) + .when([], { + is: () => !isEquipmentModification, + then: (schema) => + schema + .required() + .nonNullable(intl.formatMessage({ id: 'FieldIsRequired' })) + .test( + 'activePowerSetPoint', + intl.formatMessage({ id: 'ActivePowerMustBeZeroOrBetweenMinAndMaxActivePower' }), + (value, context) => { + if (value === 0) { + return true; + } + const minActivePower = context.parent[MINIMUM_ACTIVE_POWER]; + const maxActivePower = context.parent[MAXIMUM_ACTIVE_POWER]; + if (minActivePower === null || maxActivePower === null) { + return false; + } + return value >= minActivePower && value <= maxActivePower; } - return value >= minActivePower && value <= maxActivePower; - } - ); - }, - }), -}); + ), + }), + }; +} diff --git a/src/components/dialogs/voltage-regulation/voltage-regulation-utils.ts b/src/components/dialogs/voltage-regulation/voltage-regulation-utils.ts index 3a9e8850db..10c1074b35 100644 --- a/src/components/dialogs/voltage-regulation/voltage-regulation-utils.ts +++ b/src/components/dialogs/voltage-regulation/voltage-regulation-utils.ts @@ -19,9 +19,10 @@ import { VOLTAGE_REGULATION_TYPE, VOLTAGE_SET_POINT, } from 'components/utils/field-constants'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; import { REGULATION_TYPES } from 'components/network/constants'; import { getRegulatingTerminalEmptyFormData } from '../regulating-terminal/regulating-terminal-form-utils'; +import type { IntlShape } from 'react-intl'; export const getVoltageRegulationEmptyFormData = (isEquipmentModification = false) => ({ [VOLTAGE_REGULATION]: isEquipmentModification ? null : false, @@ -31,51 +32,60 @@ export const getVoltageRegulationEmptyFormData = (isEquipmentModification = fals ...getRegulatingTerminalEmptyFormData(), }); -export const getVoltageRegulationSchema = (isEquipmentModification = false) => ({ - [VOLTAGE_REGULATION]: yup - .bool() - .nullable() - .when([], { - is: () => !isEquipmentModification, - then: (schema) => schema.required(), - }), - [VOLTAGE_REGULATION_TYPE]: yup.string().nullable(), - - [VOLTAGE_SET_POINT]: yup - .number() - .nullable() - .min(0, 'mustBeGreaterOrEqualToZero') - .when([VOLTAGE_REGULATION], { - is: (value: string) => !isEquipmentModification && value, - then: (schema) => schema.required(), - }), - [Q_PERCENT]: yup.number().nullable().max(100, 'NormalizedPercentage').min(0, 'NormalizedPercentage'), - [VOLTAGE_LEVEL]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string(), - [SUBSTATION_ID]: yup.string(), - [NOMINAL_VOLTAGE]: yup.string(), - [TOPOLOGY_KIND]: yup.string().nullable(), - }) - .when([VOLTAGE_REGULATION, VOLTAGE_REGULATION_TYPE], { - is: (voltageRegulation: number, voltageRegulationType: string) => - !isEquipmentModification && voltageRegulation && voltageRegulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), - [EQUIPMENT]: yup - .object() - .nullable() - .shape({ - [ID]: yup.string(), - [NAME]: yup.string().nullable(), - [TYPE]: yup.string(), - }) - .when([VOLTAGE_REGULATION, VOLTAGE_REGULATION_TYPE], { - is: (voltageRegulation: number, voltageRegulationType: string) => - !isEquipmentModification && voltageRegulation && voltageRegulationType === REGULATION_TYPES.DISTANT.id, - then: (schema) => schema.required(), - }), -}); +export function getVoltageRegulationSchema(intl: IntlShape, isEquipmentModification = false) { + return { + [VOLTAGE_REGULATION]: yup + .bool() + .nullable() + .when([], { + is: () => !isEquipmentModification, + then: (schema) => schema.required(), + }), + [VOLTAGE_REGULATION_TYPE]: yup.string().nullable(), + [VOLTAGE_SET_POINT]: yup + .number() + .nullable() + .min(0, intl.formatMessage({ id: 'mustBeGreaterOrEqualToZero' })) + .when([VOLTAGE_REGULATION], { + is: (value: string) => !isEquipmentModification && value, + then: (schema) => schema.required(), + }), + [Q_PERCENT]: yup + .number() + .nullable() + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })), + [VOLTAGE_LEVEL]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string(), + [SUBSTATION_ID]: yup.string(), + [NOMINAL_VOLTAGE]: yup.string(), + [TOPOLOGY_KIND]: yup.string().nullable(), + }) + .when([VOLTAGE_REGULATION, VOLTAGE_REGULATION_TYPE], { + is: (voltageRegulation: number, voltageRegulationType: string) => + !isEquipmentModification && + voltageRegulation && + voltageRegulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + [EQUIPMENT]: yup + .object() + .nullable() + .shape({ + [ID]: yup.string(), + [NAME]: yup.string().nullable(), + [TYPE]: yup.string(), + }) + .when([VOLTAGE_REGULATION, VOLTAGE_REGULATION_TYPE], { + is: (voltageRegulation: number, voltageRegulationType: string) => + !isEquipmentModification && + voltageRegulation && + voltageRegulationType === REGULATION_TYPES.DISTANT.id, + then: (schema) => schema.required(), + }), + }; +} diff --git a/src/components/graph/menus/node-name-edit-dialog.tsx b/src/components/graph/menus/node-name-edit-dialog.tsx index e8ecc50dd7..22c32bcd23 100644 --- a/src/components/graph/menus/node-name-edit-dialog.tsx +++ b/src/components/graph/menus/node-name-edit-dialog.tsx @@ -11,7 +11,7 @@ import { Grid } from '@mui/material'; import { NAME } from '../../utils/field-constants'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; import { useSelector } from 'react-redux'; import { AppState } from 'redux/reducer'; import { UniqueCheckNameInput } from 'components/graph/menus/unique-check-name-input'; diff --git a/src/components/network/selection-creation-panel/selection-creation-schema.tsx b/src/components/network/selection-creation-panel/selection-creation-schema.tsx index 4d61c56b87..79b33cf390 100644 --- a/src/components/network/selection-creation-panel/selection-creation-schema.tsx +++ b/src/components/network/selection-creation-panel/selection-creation-schema.tsx @@ -4,7 +4,11 @@ * 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 { EquipmentType, yupConfig as yup } from '@gridsuite/commons-ui'; + +import type { UUID } from 'crypto'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import { EquipmentType } from '@gridsuite/commons-ui'; import { DESTINATION_FOLDER, EQUIPMENT_TYPE_FIELD, @@ -14,7 +18,6 @@ import { SELECTION_TYPE, } from 'components/utils/field-constants'; import { SELECTION_TYPES } from './selection-types'; -import { UUID } from 'crypto'; export type DestinationFolder = { [FOLDER_ID]: UUID; @@ -53,7 +56,7 @@ const formSchema = yup.object().shape({ export const getSelectionCreationSchema = () => formSchema; // used for useForm typing -export type SelectionCreationPanelFormSchema = yup.InferType; +export type SelectionCreationPanelFormSchema = InferType; export type SelectionCreationPanelNadFields = SelectionCreationPanelFormSchema & { [SELECTION_TYPE]: SELECTION_TYPES.NAD; diff --git a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-empty-spreadsheet-dialog.tsx b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-empty-spreadsheet-dialog.tsx index 40b5f09b71..8fb99c6db7 100644 --- a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-empty-spreadsheet-dialog.tsx +++ b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-empty-spreadsheet-dialog.tsx @@ -25,6 +25,7 @@ import { dialogStyles } from '../styles/styles'; import { ModificationDialog } from 'components/dialogs/commons/modificationDialog'; import { getEmptySpreadsheetFormSchema, initialEmptySpreadsheetForm, SPREADSHEET_NAME } from './add-spreadsheet-form'; import { addNewSpreadsheet } from './add-spreadsheet-utils'; +import { useIntl } from 'react-intl'; interface AddEmptySpreadsheetDialogProps { open: UseStateBooleanReturn; @@ -56,13 +57,14 @@ const TABLES_TYPES = [ export default function AddEmptySpreadsheetDialog({ open, ...dialogProps }: Readonly) { const dispatch = useDispatch(); const { snackError } = useSnackMessage(); + const intl = useIntl(); const studyUuid = useSelector((state: AppState) => state.studyUuid); const tablesDefinitions = useSelector((state: AppState) => state.tables.definitions); const spreadsheetsCollectionUuid = useSelector((state: AppState) => state.tables.uuid); const tablesNames = useMemo(() => tablesDefinitions.map((def) => def.name), [tablesDefinitions]); - const formSchema = useMemo(() => getEmptySpreadsheetFormSchema(tablesNames), [tablesNames]); + const formSchema = useMemo(() => getEmptySpreadsheetFormSchema(intl, tablesNames), [intl, tablesNames]); const formMethods = useForm({ defaultValues: initialEmptySpreadsheetForm, diff --git a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-form.ts b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-form.ts index 1041103d2a..dfa6fef514 100644 --- a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-form.ts +++ b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-form.ts @@ -6,7 +6,9 @@ */ import { EQUIPMENT_TYPE_FIELD, ID, NAME } from 'components/utils/field-constants'; -import yup from '../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import type { IntlShape } from 'react-intl'; export const SPREADSHEET_NAME = 'spreadsheetName'; export const SPREADSHEET_MODEL = 'spreadsheetModel'; @@ -33,28 +35,32 @@ export const initialSpreadsheetCollectionForm: SpreadsheetCollectionForm = { [SPREADSHEET_COLLECTION_IMPORT_MODE]: SpreadsheetCollectionImportMode.REPLACE, }; -export const getEmptySpreadsheetFormSchema = (tablesNames: string[]) => { +export function getEmptySpreadsheetFormSchema(intl: IntlShape, tablesNames: string[]) { return yup.object().shape({ [SPREADSHEET_NAME]: yup .string() .required() - .max(60, 'spreadsheet/spreadsheet_name_le_60') - .test('unique', 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists', (value) => { - return !tablesNames.includes(value || ''); - }), + .max(60, intl.formatMessage({ id: 'spreadsheet/spreadsheet_name_le_60' })) + .test( + 'unique', + intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists' }), + (value) => !tablesNames.includes(value || '') + ), [EQUIPMENT_TYPE_FIELD]: yup.string().required(), }); -}; +} -export const getSpreadsheetFromModelFormSchema = (tablesNames: string[]) => { +export function getSpreadsheetFromModelFormSchema(intl: IntlShape, tablesNames: string[]) { return yup.object().shape({ [SPREADSHEET_NAME]: yup .string() .required() - .max(60, 'spreadsheet/spreadsheet_name_le_60') - .test('unique', 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists', (value) => { - return !tablesNames.includes(value || ''); - }), + .max(60, intl.formatMessage({ id: 'spreadsheet/spreadsheet_name_le_60' })) + .test( + 'unique', + intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists' }), + (value) => !tablesNames.includes(value || '') + ), [SPREADSHEET_MODEL]: yup .array() .of( @@ -63,13 +69,16 @@ export const getSpreadsheetFromModelFormSchema = (tablesNames: string[]) => { [NAME]: yup.string().required(), }) ) - .required('spreadsheet/create_new_spreadsheet/must_select_spreadsheet_model') - .min(1, 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_model') - .max(1, 'spreadsheet/create_new_spreadsheet/must_select_only_one_spreadsheet_model'), + .required(intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_model' })) + .min(1, intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_model' })) + .max( + 1, + intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/must_select_only_one_spreadsheet_model' }) + ), }); -}; +} -export const getSpreadsheetCollectionFormSchema = () => { +export function getSpreadsheetCollectionFormSchema(intl: IntlShape) { return yup.object().shape({ [SPREADSHEET_COLLECTION_IMPORT_MODE]: yup.mixed().required(), [SPREADSHEET_COLLECTION]: yup @@ -80,12 +89,19 @@ export const getSpreadsheetCollectionFormSchema = () => { [NAME]: yup.string().required(), }) ) - .required('spreadsheet/create_new_spreadsheet/must_select_spreadsheet_collection') - .min(1, 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_collection') - .max(1, 'spreadsheet/create_new_spreadsheet/must_select_only_one_spreadsheet_collection'), + .required( + intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_collection' }) + ) + .min(1, intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/must_select_spreadsheet_collection' })) + .max( + 1, + intl.formatMessage({ + id: 'spreadsheet/create_new_spreadsheet/must_select_only_one_spreadsheet_collection', + }) + ), }); -}; +} -export type SpreadsheetFromModelForm = yup.InferType>; -export type EmptySpreadsheetForm = yup.InferType>; -export type SpreadsheetCollectionForm = yup.InferType>; +export type SpreadsheetFromModelForm = InferType>; +export type EmptySpreadsheetForm = InferType>; +export type SpreadsheetCollectionForm = InferType>; diff --git a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-from-model-dialog.tsx b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-from-model-dialog.tsx index 0bdc9094ee..422d7c5a52 100644 --- a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-from-model-dialog.tsx +++ b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheet-from-model-dialog.tsx @@ -30,6 +30,7 @@ import { getSpreadsheetModel } from 'services/study-config'; import { UUID } from 'crypto'; import { ModificationDialog } from 'components/dialogs/commons/modificationDialog'; import { dialogStyles } from '../styles/styles'; +import { useIntl } from 'react-intl'; interface AddSpreadsheetFromModelDialogProps { open: UseStateBooleanReturn; @@ -44,13 +45,14 @@ export default function AddSpreadsheetFromModelDialog({ }: Readonly) { const dispatch = useDispatch(); const { snackError } = useSnackMessage(); + const intl = useIntl(); const studyUuid = useSelector((state: AppState) => state.studyUuid); const tablesDefinitions = useSelector((state: AppState) => state.tables.definitions); const spreadsheetsCollectionUuid = useSelector((state: AppState) => state.tables.uuid); const tablesNames = useMemo(() => tablesDefinitions.map((def) => def.name), [tablesDefinitions]); - const formSchema = useMemo(() => getSpreadsheetFromModelFormSchema(tablesNames), [tablesNames]); + const formSchema = useMemo(() => getSpreadsheetFromModelFormSchema(intl, tablesNames), [intl, tablesNames]); const formMethods = useForm({ defaultValues: initialSpreadsheetFromModelForm, diff --git a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheets-from-collection-dialog.tsx b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheets-from-collection-dialog.tsx index 0d4a4496c0..0a8290c276 100644 --- a/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheets-from-collection-dialog.tsx +++ b/src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheets-from-collection-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Grid } from '@mui/material'; import { CustomFormProvider, @@ -63,7 +63,7 @@ export default function AddSpreadsheetsFromCollectionDialog({ const tablesDefinitions = useSelector((state: AppState) => state.tables.definitions); const studyUuid = useSelector((state: AppState) => state.studyUuid); - const formSchema = getSpreadsheetCollectionFormSchema(); + const formSchema = useMemo(() => getSpreadsheetCollectionFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: initialSpreadsheetCollectionForm, diff --git a/src/components/spreadsheet-view/columns/column-creation-dialog.tsx b/src/components/spreadsheet-view/columns/column-creation-dialog.tsx index e2dd59d119..ab912dc3a3 100644 --- a/src/components/spreadsheet-view/columns/column-creation-dialog.tsx +++ b/src/components/spreadsheet-view/columns/column-creation-dialog.tsx @@ -49,7 +49,7 @@ import { COLUMN_NAME, COLUMN_TYPE, ColumnCreationForm, - columnCreationFormSchema, + getColumnCreationFormSchema, FORMULA, initialColumnCreationForm, PRECISION, @@ -86,6 +86,8 @@ export default function ColumnCreationDialog({ tableDefinition, isCreate = true, }: Readonly) { + const intl = useIntl(); + const columnCreationFormSchema = useMemo(() => getColumnCreationFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: initialColumnCreationForm, resolver: yupResolver(columnCreationFormSchema), @@ -107,8 +109,6 @@ export default function ColumnCreationDialog({ const { handleSubmit, reset } = formMethods; const dispatch = useDispatch(); - const intl = useIntl(); - const generateColumnId = useCallback(() => { if (columnId === '') { setValue(COLUMN_ID, watchColumnName.replace(COLUMN_NAME_REGEX, '')); diff --git a/src/components/spreadsheet-view/columns/column-creation-form.ts b/src/components/spreadsheet-view/columns/column-creation-form.ts index d628480607..cd70173555 100644 --- a/src/components/spreadsheet-view/columns/column-creation-form.ts +++ b/src/components/spreadsheet-view/columns/column-creation-form.ts @@ -6,7 +6,9 @@ */ import { COLUMN_TYPES } from 'components/custom-aggrid/custom-aggrid-header.type'; -import yup from '../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; +import type { IntlShape } from 'react-intl'; export const COLUMN_ID = 'id'; export const COLUMN_NAME = 'name'; @@ -24,22 +26,29 @@ export const initialColumnCreationForm: ColumnCreationForm = { [COLUMN_DEPENDENCIES]: [], }; -export const columnCreationFormSchema = yup.object().shape({ - [COLUMN_ID]: yup - .string() - .required() - .max(60, 'spreadsheet/custom_column/error/id_le_60') - .matches(/^[a-zA-Z_]\w*$/, 'spreadsheet/custom_column/error/id_not_conform'), - [COLUMN_NAME]: yup.string().required().max(60, 'spreadsheet/custom_column/error/name_le_60'), - [COLUMN_TYPE]: yup.mixed().oneOf(Object.values(COLUMN_TYPES)).required(), - [PRECISION]: yup - .number() - .integer() - .when(COLUMN_TYPE, ([type]) => { - return type === COLUMN_TYPES.NUMBER ? yup.number().integer().required() : yup.number().nullable().integer(); - }), - [FORMULA]: yup.string().required(), - [COLUMN_DEPENDENCIES]: yup.array().of(yup.string().required()).required(), -}); +export function getColumnCreationFormSchema(intl: IntlShape) { + return yup.object().shape({ + [COLUMN_ID]: yup + .string() + .required() + .max(60, intl.formatMessage({ id: 'spreadsheet/custom_column/error/id_le_60' })) + .matches(/^[a-zA-Z_]\w*$/, intl.formatMessage({ id: 'spreadsheet/custom_column/error/id_not_conform' })), + [COLUMN_NAME]: yup + .string() + .required() + .max(60, intl.formatMessage({ id: 'spreadsheet/custom_column/error/name_le_60' })), + [COLUMN_TYPE]: yup.mixed().oneOf(Object.values(COLUMN_TYPES)).required(), + [PRECISION]: yup + .number() + .integer() + .when(COLUMN_TYPE, ([type]) => { + return type === COLUMN_TYPES.NUMBER + ? yup.number().integer().required() + : yup.number().nullable().integer(); + }), + [FORMULA]: yup.string().required(), + [COLUMN_DEPENDENCIES]: yup.array().of(yup.string().required()).required(), + }); +} -export type ColumnCreationForm = yup.InferType; +export type ColumnCreationForm = InferType>; diff --git a/src/components/spreadsheet-view/spreadsheet-tabs/rename-tab-dialog.tsx b/src/components/spreadsheet-view/spreadsheet-tabs/rename-tab-dialog.tsx index edd1da389f..7a13bd8c0c 100644 --- a/src/components/spreadsheet-view/spreadsheet-tabs/rename-tab-dialog.tsx +++ b/src/components/spreadsheet-view/spreadsheet-tabs/rename-tab-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { useIntl } from 'react-intl'; import { Grid } from '@mui/material'; import { CustomFormProvider, TextInput } from '@gridsuite/commons-ui'; @@ -40,23 +40,27 @@ export default function RenameTabDialog({ }: Readonly) { const intl = useIntl(); - const schema = yup.object().shape({ - name: yup - .string() - .required() - .max(60, 'spreadsheet/spreadsheet_name_le_60') - .test( - 'unique-name', - intl.formatMessage({ id: 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists' }), - (value) => { - if (!value) { - return true; - } - // Check if name is already in use by another tab (not the one being renamed) - return !tablesDefinitions.some((tab) => tab.name === value && tab.uuid !== tabUuid); - } - ), - }); + const schema = useMemo( + () => + yup.object().shape({ + name: yup + .string() + .required() + .max(60, intl.formatMessage({ id: 'spreadsheet/spreadsheet_name_le_60' })) + .test( + 'unique-name', + intl.formatMessage({ + id: 'spreadsheet/create_new_spreadsheet/spreadsheet_name_already_exists', + }), + (value) => + !value + ? true + : // Check if name is already in use by another tab (not the one being renamed) + !tablesDefinitions.some((tab) => tab.name === value && tab.uuid !== tabUuid) + ), + }), + [intl, tabUuid, tablesDefinitions] + ); const formMethods = useForm({ defaultValues: { name: currentName }, diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.tsx index 1ccbfe4797..385a9a043e 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.tsx @@ -14,16 +14,14 @@ import { useDispatch, useSelector } from 'react-redux'; import { CustomFormProvider, DirectoryItemsInput, ElementType } from '@gridsuite/commons-ui'; import { saveSpreadsheetGsFilters } from '../../../../../redux/actions'; import { SpreadsheetEquipmentType } from '../../../types/spreadsheet.type'; -import { - toFormFormat, - initialSpreadsheetGsFilterForm, - SpreadsheetGsFilterForm, - spreadsheetGsFilterFormSchema, -} from './spreadsheet-gs-filter.utils'; -import { SPREADSHEET_GS_FILTER } from '../../../../utils/field-constants'; +import { initialSpreadsheetGsFilterForm, toFormFormat } from './spreadsheet-gs-filter.utils'; +import { FILTERS, ID, NAME, SPREADSHEET_GS_FILTER } from '../../../../utils/field-constants'; import { AppState } from '../../../../../redux/reducer'; import { ExpertFilter, SpreadsheetGlobalFilter } from '../../../../../services/study/filter'; import { setGlobalFiltersToSpreadsheetConfig } from 'services/study/study-config'; +import type { InferType } from 'yup'; +import * as yup from 'yup'; +import { useIntl } from 'react-intl'; export type SpreadsheetGsFilterProps = { equipmentType: SpreadsheetEquipmentType; @@ -31,10 +29,35 @@ export type SpreadsheetGsFilterProps = { }; export default function SpreadsheetGsFilter({ equipmentType, uuid }: Readonly) { + const intl = useIntl(); const dispatch = useDispatch(); const gsFilterSpreadsheetState = useSelector((state: AppState) => state.gsFilterSpreadsheetState[uuid]); const studyUuid = useSelector((state: AppState) => state.studyUuid); + const spreadsheetGsFilterFormSchema = useMemo( + () => + yup.object({ + [SPREADSHEET_GS_FILTER]: yup + .array() + .of( + yup.object({ + [FILTERS]: yup + .array() + .of( + yup.object({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .min(1, intl.formatMessage({ id: 'FilterInputMinError' })), + }) + ) + .required(), + }), + [intl] + ); + type SpreadsheetGsFilterForm = InferType; + const formMethods = useForm({ defaultValues: initialSpreadsheetGsFilterForm, resolver: yupResolver(spreadsheetGsFilterFormSchema), diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.utils.ts b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.utils.ts index a3cd7aaae8..a6e1a389df 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.utils.ts +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/gs-filter/spreadsheet-gs-filter.utils.ts @@ -6,32 +6,10 @@ */ import { ExpertFilter, SpreadsheetGlobalFilter } from '../../../../../services/study/filter'; -import yup from '../../../../utils/yup-config'; -import { FILTERS, ID, NAME, SPREADSHEET_GS_FILTER } from '../../../../utils/field-constants'; +import { SPREADSHEET_GS_FILTER } from '../../../../utils/field-constants'; export type ExpertFilterForm = Omit; -export const spreadsheetGsFilterFormSchema = yup.object({ - [SPREADSHEET_GS_FILTER]: yup - .array() - .of( - yup.object({ - [FILTERS]: yup - .array() - .of( - yup.object({ - [ID]: yup.string().required(), - [NAME]: yup.string().required(), - }) - ) - .min(1, 'FilterInputMinError'), - }) - ) - .required(), -}); - -export type SpreadsheetGsFilterForm = yup.InferType; - export const initialSpreadsheetGsFilterForm: Record = { [SPREADSHEET_GS_FILTER]: [], }; diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.tsx b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.tsx index da0ecd4dbc..504383b23a 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.tsx +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.tsx @@ -18,7 +18,8 @@ import { UUID } from 'crypto'; import { ModificationDialog } from 'components/dialogs/commons/modificationDialog'; import { NodeAlias } from '../../../types/node-alias.type'; import { CurrentTreeNode } from '../../../../graph/tree-node.type'; -import { initialNodesForm, NODES_ALIASES, NodesForm, nodesFormSchema } from './nodes-config-dialog.utils'; +import { initialNodesForm, NODES_ALIASES, NodesForm, getNodesFormSchema } from './nodes-config-dialog.utils'; +import { useIntl } from 'react-intl'; export type NodesConfigDialogProps = { open: UseStateBooleanReturn; @@ -45,6 +46,8 @@ export default function NodesConfigDialog({ updateNodeAliases, ...dialogProps }: Readonly) { + const intl = useIntl(); + const nodesFormSchema = useMemo(() => getNodesFormSchema(intl), [intl]); const formMethods = useForm({ defaultValues: initialNodesForm, resolver: yupResolver(nodesFormSchema), diff --git a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.utils.ts b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.utils.ts index d03ad31506..b61306b0f4 100644 --- a/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.utils.ts +++ b/src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/nodes-config/nodes-config-dialog.utils.ts @@ -5,8 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup from '../../../../utils/yup-config'; +import * as yup from 'yup'; +import type { InferType } from 'yup'; import { areArrayElementsUnique } from '../../../../utils/utils'; +import type { IntlShape } from 'react-intl'; export const NODE_ALIAS = 'alias'; export const NODE_NAME = 'name'; @@ -16,36 +18,48 @@ export const initialNodesForm: NodesForm = { [NODES_ALIASES]: [], }; -export const nodesFormSchema = yup.object().shape({ - [NODES_ALIASES]: yup - .array() - .of( - yup.object().shape({ - [NODE_ALIAS]: yup - .string() - .default('') - .required() - .test( - 'maxSize', - 'spreadsheet/parameter_aliases/max_characters_reached', - (value) => value.length < 11 - ) - .test('noSpecialCharacters', 'spreadsheet/parameter_aliases/no_special_characters', (value) => - /^([a-zA-Z0-9])+$/.test(value) - ), - [NODE_NAME]: yup.string().default('').required(), - }) - ) - .required() - .test('distinctAliases', 'spreadsheet/parameter_aliases/unique_aliases', (array) => { - //filter to remove empty values, so we don't get this error instead of required when 2 fields are empty - const aliasesArray = array.map((l) => l[NODE_ALIAS]).filter((value) => value); - return areArrayElementsUnique(aliasesArray); - }) - .test('uniqueNodeNames', 'spreadsheet/parameter_aliases/unique_node_names', (array) => { - const nodeNamesArray = array.map((l) => l[NODE_NAME]).filter((value) => value); - return areArrayElementsUnique(nodeNamesArray); - }), -}); +export function getNodesFormSchema(intl: IntlShape) { + return yup.object().shape({ + [NODES_ALIASES]: yup + .array() + .of( + yup.object().shape({ + [NODE_ALIAS]: yup + .string() + .default('') + .required() + .test( + 'maxSize', + intl.formatMessage({ id: 'spreadsheet/parameter_aliases/max_characters_reached' }), + (value) => value.length < 11 + ) + .test( + 'noSpecialCharacters', + intl.formatMessage({ id: 'spreadsheet/parameter_aliases/no_special_characters' }), + (value) => /^([a-zA-Z0-9])+$/.test(value) + ), + [NODE_NAME]: yup.string().default('').required(), + }) + ) + .required() + .test( + 'distinctAliases', + intl.formatMessage({ id: 'spreadsheet/parameter_aliases/unique_aliases' }), + (array) => { + //filter to remove empty values, so we don't get this error instead of required when 2 fields are empty + const aliasesArray = array.map((l) => l[NODE_ALIAS]).filter((value) => value); + return areArrayElementsUnique(aliasesArray); + } + ) + .test( + 'uniqueNodeNames', + intl.formatMessage({ id: 'spreadsheet/parameter_aliases/unique_node_names' }), + (array) => { + const nodeNamesArray = array.map((l) => l[NODE_NAME]).filter((value) => value); + return areArrayElementsUnique(nodeNamesArray); + } + ), + }); +} -export type NodesForm = yup.InferType; +export type NodesForm = InferType>; diff --git a/src/components/utils/rhf-inputs/read-only/button-read-only-input.tsx b/src/components/utils/rhf-inputs/read-only/button-read-only-input.tsx index 0aeec9e17a..d2fda15075 100644 --- a/src/components/utils/rhf-inputs/read-only/button-read-only-input.tsx +++ b/src/components/utils/rhf-inputs/read-only/button-read-only-input.tsx @@ -7,8 +7,7 @@ import { useController } from 'react-hook-form'; import { InputAdornment, TextField, useTheme } from '@mui/material'; -import { genHelperError } from '@gridsuite/commons-ui'; -import { PropsWithChildren } from 'react'; +import { type PropsWithChildren } from 'react'; interface ButtonReadOnlyInputProps extends PropsWithChildren { name: string; @@ -54,7 +53,8 @@ export function ButtonReadOnlyInput({ name, isNumerical = false, children }: But size="small" fullWidth value={value} - {...genHelperError(error?.message)} + error={error !== undefined} + helperText={error?.message} /> ); } diff --git a/src/components/utils/rhf-inputs/read-only/read-only-input.tsx b/src/components/utils/rhf-inputs/read-only/read-only-input.tsx index 11d3ae41c5..c33d6d300f 100644 --- a/src/components/utils/rhf-inputs/read-only/read-only-input.tsx +++ b/src/components/utils/rhf-inputs/read-only/read-only-input.tsx @@ -7,7 +7,6 @@ import { useController } from 'react-hook-form'; import { TextField } from '@mui/material'; -import { genHelperError } from '@gridsuite/commons-ui'; interface ReadOnlyInputProps { name: string; @@ -36,7 +35,8 @@ export function ReadOnlyInput({ name, isNumerical = false }: ReadOnlyInputProps) fullWidth value={value} variant="standard" - {...genHelperError(error?.message)} + error={error !== undefined} + helperText={error?.message} /> ); } diff --git a/src/components/utils/utils.ts b/src/components/utils/utils.ts index 12fd674319..ac3ca267f4 100644 --- a/src/components/utils/utils.ts +++ b/src/components/utils/utils.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { getIn, SchemaDescription } from 'yup'; +import { getIn, type SchemaDescription } from 'yup'; import { isNotBlankOrEmpty, toNumber } from './validation-functions'; import { AttributeModification, diff --git a/src/components/utils/yup-config.ts b/src/components/utils/yup-config.ts deleted file mode 100644 index 8ad2569609..0000000000 --- a/src/components/utils/yup-config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2022, 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 * as yup from 'yup'; - -yup.setLocale({ - mixed: { - required: 'YupRequired', - notType: ({ type }) => { - if (type === 'number') { - return 'YupNotTypeNumber'; - } else { - return 'YupNotTypeDefault'; - } - }, - }, -}); - -export default yup; diff --git a/src/redux/session-storage/search-equipment-history.ts b/src/redux/session-storage/search-equipment-history.ts index 0bec4e4e95..462a41ca91 100644 --- a/src/redux/session-storage/search-equipment-history.ts +++ b/src/redux/session-storage/search-equipment-history.ts @@ -8,7 +8,7 @@ import { UUID } from 'crypto'; import { mixed, string } from 'yup'; import { EquipmentInfos, EquipmentType, ExtendedEquipmentType } from '@gridsuite/commons-ui'; import { APP_NAME } from 'utils/config-params'; -import yup from 'components/utils/yup-config'; +import * as yup from 'yup'; const MAX_SEARCH_EQUIPMENT_HISTORY_SIZE = 5; diff --git a/src/translations/en.json b/src/translations/en.json index 4829aafac9..e769730209 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1168,10 +1168,6 @@ "NodeNotBuildPositionMessage": "Build the node to display taken positions", "NoVoltageLevelPositionMessage": "Select a voltage level to display taken positions", - "YupRequired": "This field is required", - "YupNotTypeNumber": "This field only accepts numeric values", - "YupNotTypeDefault": "Field value format is incorrect", - "SubstationNotFound": "This substation does not exist in this network", "VoltageLevelNotFound": "This voltage level does not exist in this network", "NetworkEquipmentNotFound": "The equipment \"{equipmentId}\" does not exist in this network", diff --git a/src/translations/fr.json b/src/translations/fr.json index ba01637ef3..79dd25c6db 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -1163,9 +1163,6 @@ "DisplayTakenPositions": "Afficher les positions prises", "NodeNotBuildPositionMessage": "Veuillez réaliser le nœud pour afficher les positions prises", "NoVoltageLevelPositionMessage": "Veuillez choisir un poste pour afficher les positions prises", - "YupRequired": "Ce champ doit être renseigné", - "YupNotTypeNumber": "Ce champ n'accepte que des valeurs numériques", - "YupNotTypeDefault": "La valeur du champ n'est pas au bon format", "SubstationNotFound": "Ce site n'existe pas dans ce réseau", "VoltageLevelNotFound": "Ce poste n'existe pas dans ce réseau",