diff --git a/package-lock.json b/package-lock.json index 772a07a430..af4931192b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3358,6 +3358,7 @@ "@mui/icons-material": "^6.5.0", "@mui/lab": "6.0.1-beta.36", "@mui/material": "^6.5.0", + "@mui/x-charts": "^8.21.0", "@mui/x-tree-view": "^8.21.0", "ag-grid-community": "^35.3.1", "ag-grid-react": "^35.3.1", diff --git a/src/components/dialogs/limits/limits-constants.ts b/src/components/dialogs/limits/limits-constants.ts deleted file mode 100644 index 16dd921df9..0000000000 --- a/src/components/dialogs/limits/limits-constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025, 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/. - */ - -export enum LimitsPropertyName { - LIMITS_TYPE = 'Limit type', -} - -export function getPropertyAvatar(type: string): string { - const transformedType: LimitsPropertyName = type as LimitsPropertyName; - - const descriptions: Record = { - [LimitsPropertyName.LIMITS_TYPE]: 'Ty', - }; - - return descriptions[transformedType] ?? transformedType.substring(0, 2); -} diff --git a/src/components/dialogs/limits/limits-groups-contextual-menu.tsx b/src/components/dialogs/limits/limits-groups-contextual-menu.tsx deleted file mode 100644 index 683914713d..0000000000 --- a/src/components/dialogs/limits/limits-groups-contextual-menu.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright (c) 2025, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { - ID, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, -} from '../../utils/field-constants'; -import { FieldValues, UseFieldArrayAppend, UseFieldArrayRemove, useFormContext } from 'react-hook-form'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import { ContentCopy, Delete } from '@mui/icons-material'; -import ListItemText from '@mui/material/ListItemText'; -import { useIntl } from 'react-intl'; -import { APPLICABILITY } from '../../network/constants'; -import { useCallback } from 'react'; -import { CurrentLimitsData } from '../../../services/study/network-map.type'; -import { OperationalLimitsGroupFormSchema } from './operational-limits-groups-types'; - -export interface ContextMenuCoordinates { - x: null | number; - y: null | number; - tabIndex: null | number; -} - -export interface LimitsGroupsContextualMenuProps { - parentFormName: string; - indexSelectedLimitSet: number | null; - setIndexSelectedLimitSet: React.Dispatch>; - handleCloseMenu: () => void; - contextMenuCoordinates: ContextMenuCoordinates; - selectedLimitsGroups1: string; - selectedLimitsGroups2: string; - currentLimitsToModify: CurrentLimitsData[]; - operationalLimitsGroups: FieldValues; - appendToLimitsGroups: UseFieldArrayAppend< - { - [p: string]: OperationalLimitsGroupFormSchema[]; - }, - string - >; - removeLimitsGroups: UseFieldArrayRemove; -} - -export function LimitsGroupsContextualMenu({ - parentFormName, - indexSelectedLimitSet, - setIndexSelectedLimitSet, - handleCloseMenu, - contextMenuCoordinates, - selectedLimitsGroups1, - selectedLimitsGroups2, - operationalLimitsGroups, - appendToLimitsGroups, - removeLimitsGroups, -}: Readonly) { - const intl = useIntl(); - const { setValue } = useFormContext(); - - const handleDeleteTab = useCallback(() => { - if (indexSelectedLimitSet !== null) { - const tabName: string = operationalLimitsGroups[indexSelectedLimitSet]?.name; - const applicability: string = operationalLimitsGroups[indexSelectedLimitSet]?.applicability ?? ''; - // if this operational limit was selected, deselect it - if ( - selectedLimitsGroups1 === tabName && - (applicability === APPLICABILITY.SIDE1.id || applicability === APPLICABILITY.EQUIPMENT.id) - ) { - setValue(`${parentFormName}.${SELECTED_OPERATIONAL_LIMITS_GROUP_ID1}`, null); - } - if ( - selectedLimitsGroups2 === tabName && - (applicability === APPLICABILITY.SIDE2.id || applicability === APPLICABILITY.EQUIPMENT.id) - ) { - setValue(`${parentFormName}.${SELECTED_OPERATIONAL_LIMITS_GROUP_ID2}`, null); - } - removeLimitsGroups(indexSelectedLimitSet); - setIndexSelectedLimitSet(null); - } - handleCloseMenu(); - }, [ - handleCloseMenu, - indexSelectedLimitSet, - operationalLimitsGroups, - parentFormName, - removeLimitsGroups, - selectedLimitsGroups1, - selectedLimitsGroups2, - setIndexSelectedLimitSet, - setValue, - ]); - - const handleDuplicateTab = useCallback(() => { - let newName: string = ''; - if (indexSelectedLimitSet !== null) { - const duplicatedLimits1: OperationalLimitsGroupFormSchema = operationalLimitsGroups[indexSelectedLimitSet]; - newName = duplicatedLimits1.name + '_COPY'; - const newLimitsGroup1: OperationalLimitsGroupFormSchema = { - ...duplicatedLimits1, - [ID]: newName, - }; - appendToLimitsGroups(newLimitsGroup1); - handleCloseMenu(); - setIndexSelectedLimitSet(operationalLimitsGroups.length); - } - }, [ - indexSelectedLimitSet, - operationalLimitsGroups, - appendToLimitsGroups, - setIndexSelectedLimitSet, - handleCloseMenu, - ]); - - return ( - - - - - - {intl.formatMessage({ id: 'DeleteFromMenu' })} - - - - - - {intl.formatMessage({ id: 'Duplicate' })} - - - ); -} diff --git a/src/components/dialogs/limits/limits-pane-utils.ts b/src/components/dialogs/limits/limits-pane-utils.ts deleted file mode 100644 index 6bb6f83ce9..0000000000 --- a/src/components/dialogs/limits/limits-pane-utils.ts +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Copyright (c) 2023, 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 { - APPLICABILITY_FIELD, - CURRENT_LIMITS, - ENABLE_OLG_MODIFICATION, - ID, - LIMITS, - LIMITS_PROPERTIES, - NAME, - OLG_IS_DUPLICATE, - OPERATIONAL_LIMITS_GROUPS, - OPERATIONAL_LIMITS_GROUPS_MODIFICATION_TYPE, - PERMANENT_LIMIT, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, - TEMPORARY_LIMIT_DURATION, - TEMPORARY_LIMIT_MODIFICATION_TYPE, - TEMPORARY_LIMIT_NAME, - TEMPORARY_LIMIT_VALUE, - TEMPORARY_LIMITS, -} from 'components/utils/field-constants'; -import { - areArrayElementsUnique, - formatMapInfosToTemporaryLimitsFormSchema, - formatTemporaryLimitsModificationToFormSchema, -} from 'components/utils/utils'; -import * as yup from 'yup'; -import { - CurrentLimits, - OperationalLimitsGroup, - OperationalLimitsGroupModificationInfos, - TemporaryLimit, -} from '../../../services/network-modification-types'; -import { CurrentLimitsData } from '../../../services/study/network-map.type'; -import { LineModificationFormSchema } from '../network-modifications/line/modification/line-modification-type'; -import { OperationalLimitsGroupFormSchema, TemporaryLimitFormSchema } from './operational-limits-groups-types'; -import { TestContext } from 'yup'; -import { APPLICABILITY } from 'components/network/constants'; -import { - AttributeModification, - FieldConstants, - OperationType, - sanitizeString, - toModificationOperation, -} from '@gridsuite/commons-ui'; - -const limitsGroupValidationSchema = () => ({ - [ID]: yup.string().nonNullable().required(), - [NAME]: yup.string().nonNullable().required(), - [APPLICABILITY_FIELD]: yup.string().nonNullable().required(), - [OLG_IS_DUPLICATE]: yup.boolean().nullable().test('testDistincts', 'LimitSetApplicabilityError', hasDuplicate), - [CURRENT_LIMITS]: yup.object().shape(currentLimitsValidationSchema()), - [LIMITS_PROPERTIES]: yup.array().of(limitsPropertyValidationSchema()), -}); - -const temporaryLimitsValidationSchema = () => { - return yup.object().shape( - { - [TEMPORARY_LIMIT_DURATION]: yup - .number() - .nullable() - .min(0, 'mustBeGreaterOrEqualToZero') - .when([TEMPORARY_LIMIT_VALUE, TEMPORARY_LIMIT_NAME], { - is: (value: number | null, name: string | null) => value != null || !!name, - then: (schema) => schema.required(), - }), - [TEMPORARY_LIMIT_VALUE]: yup.number().nullable().positive(), - [TEMPORARY_LIMIT_NAME]: yup - .string() - .nullable() - .trim() - .when([TEMPORARY_LIMIT_VALUE, TEMPORARY_LIMIT_DURATION], { - is: (value: number | null, duration: number | null) => value != null || duration != null, - then: (schema) => schema.required(), - }), - }, - [[TEMPORARY_LIMIT_DURATION, TEMPORARY_LIMIT_NAME]] - ); -}; -const limitsPropertyValidationSchema = () => { - return yup.object().shape({ - [NAME]: yup.string().required(), - [FieldConstants.VALUE]: yup.string().required(), - }); -}; - -const currentLimitsValidationSchema = () => ({ - [PERMANENT_LIMIT]: yup.number().positive('permanentCurrentLimitMustBeGreaterThanZero').required(), - [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); - }), -}); - -interface OperationalLimitsGroupFormSchemaWithPath extends OperationalLimitsGroupFormSchema { - rhfPath: string; -} - -function hasDuplicate(field: boolean | null | undefined, context: TestContext) { - return hasDuplicateOperationalLimitsGroups(context); -} - -function hasDuplicateOperationalLimitsGroups(context: TestContext) { - const limitsGroup: OperationalLimitsGroupFormSchema = context.parent; - const operationalLimitsGroups: OperationalLimitsGroupFormSchema[] = - context.from?.[1]?.value?.[OPERATIONAL_LIMITS_GROUPS]; - const operationalLimitsGroupsWithPath: OperationalLimitsGroupFormSchemaWithPath[] = operationalLimitsGroups.map( - (item, index) => { - return { ...item, rhfPath: `${LIMITS}.${OPERATIONAL_LIMITS_GROUPS}[${index}]` }; - } - ); - - const limitsGroupName = sanitizeString(limitsGroup[NAME]); - const filtered = operationalLimitsGroupsWithPath.filter( - (item: OperationalLimitsGroupFormSchemaWithPath) => sanitizeString(item[NAME]) === limitsGroupName - ); - - if (filtered.length <= 1) { - return true; - } - - const applicabilityEquipment: number = filtered.filter( - (item) => item[APPLICABILITY_FIELD] === APPLICABILITY.EQUIPMENT.id - ).length; - const applicabilitySide1: number = filtered.filter( - (item) => item[APPLICABILITY_FIELD] === APPLICABILITY.SIDE1.id - ).length; - - const isDuplicate = - filtered.length > 2 || applicabilityEquipment > 0 || applicabilitySide1 === 0 || applicabilitySide1 > 1; - - return !isDuplicate; -} - -const limitsValidationSchemaCreation = (id: string, isModification: boolean) => { - const completeLimitsGroupSchema = { - [OPERATIONAL_LIMITS_GROUPS]: isModification - ? yup.array(yup.object().shape(limitsGroupValidationSchema())).when([ENABLE_OLG_MODIFICATION], { - is: true, - then: (schema) => schema.required(), - otherwise: (schema) => schema.strip(), - }) - : yup.array(yup.object().shape(limitsGroupValidationSchema())).required(), - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID1]: yup.string().nullable(), - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID2]: yup.string().nullable(), - [ENABLE_OLG_MODIFICATION]: yup.boolean(), - }; - return { [id]: yup.object().shape(completeLimitsGroupSchema) }; -}; - -export type LimitsFormSchema = yup.InferType[typeof LIMITS]>; - -export const getLimitsValidationSchema = (id: string = LIMITS, isModification: boolean = false) => { - return limitsValidationSchemaCreation(id, isModification); -}; - -const limitsEmptyFormData = (isModification: boolean, id: string) => { - const limitsGroup = { - [OPERATIONAL_LIMITS_GROUPS]: [], - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID1]: null, - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID2]: null, - [ENABLE_OLG_MODIFICATION]: !isModification, - }; - - return { [id]: limitsGroup }; -}; - -export const getLimitsEmptyFormData = (isModification = true, id = LIMITS) => { - return limitsEmptyFormData(isModification, id); -}; - -export const formatOpLimitGroupsToFormInfos = ( - limitGroups?: OperationalLimitsGroup[] | OperationalLimitsGroupModificationInfos[] | null -): OperationalLimitsGroupFormSchema[] => { - if (!limitGroups) { - return []; - } - - return limitGroups - .filter( - (opLimitGroup: OperationalLimitsGroup | OperationalLimitsGroupModificationInfos) => - opLimitGroup.modificationType !== OPERATIONAL_LIMITS_GROUPS_MODIFICATION_TYPE.DELETE - ) - .map((opLimitGroup: OperationalLimitsGroup | OperationalLimitsGroupModificationInfos) => { - return { - id: opLimitGroup.id + opLimitGroup.applicability, - name: opLimitGroup.id, - applicability: opLimitGroup.applicability, - limitsProperties: opLimitGroup.limitsProperties, - currentLimits: { - permanentLimit: opLimitGroup?.currentLimits?.permanentLimit, - temporaryLimits: formatTemporaryLimitsModificationToFormSchema( - opLimitGroup?.currentLimits?.temporaryLimits as TemporaryLimit[] - ), - }, - }; - }) as OperationalLimitsGroupFormSchema[]; -}; - -export const getAllLimitsFormData = ( - operationalLimitsGroups: OperationalLimitsGroupFormSchema[] = [], - selectedOperationalLimitsGroupId1: string | null = null, - selectedOperationalLimitsGroupId2: string | null = null, - enableOLGModification: boolean | null = true, - id = LIMITS -) => { - return { - [id]: { - [OPERATIONAL_LIMITS_GROUPS]: operationalLimitsGroups, - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID1]: selectedOperationalLimitsGroupId1, - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID2]: selectedOperationalLimitsGroupId2, - [ENABLE_OLG_MODIFICATION]: !!enableOLGModification, - }, - }; -}; - -/** - * sanitizes limit names and filters out the empty temporary limits lines - */ -export const sanitizeLimitsGroups = ( - limitsGroups: OperationalLimitsGroupFormSchema[] -): OperationalLimitsGroupFormSchema[] => - limitsGroups.map(({ currentLimits, ...baseData }) => ({ - ...baseData, - id: baseData.name, - currentLimits: !currentLimits - ? { - id: '', - permanentLimit: null, - temporaryLimits: [], - } - : { - permanentLimit: currentLimits.permanentLimit, - temporaryLimits: !currentLimits.temporaryLimits - ? [] - : currentLimits.temporaryLimits - // completely empty lines should be filtered out (the interface always displays some lines even if empty) - .filter(({ name }) => name?.trim()) - .map(({ name, ...temporaryLimit }) => ({ - ...temporaryLimit, - name: sanitizeString(name) ?? '', - })), - }, - })); - -export const sanitizeLimitNames = (temporaryLimitList: TemporaryLimitFormSchema[]): TemporaryLimitFormSchema[] => - temporaryLimitList - ?.filter((limit: TemporaryLimitFormSchema) => limit?.name?.trim()) - .map(({ name, ...temporaryLimit }) => ({ - ...temporaryLimit, - name: sanitizeString(name) ?? '', - })) || []; - -export const mapServerLimitsGroupsToFormInfos = (currentLimits: CurrentLimitsData[]) => { - return currentLimits?.map((currentLimit: CurrentLimitsData) => { - return { - id: currentLimit.id + currentLimit.applicability, - name: currentLimit.id, - applicability: currentLimit.applicability, - limitsProperties: currentLimit.limitsProperties, - currentLimits: { - id: currentLimit.id, - permanentLimit: currentLimit.permanentLimit, - temporaryLimits: formatMapInfosToTemporaryLimitsFormSchema(currentLimit.temporaryLimits), - }, - }; - }); -}; - -export const convertToOperationalLimitsGroupFormSchema = ( - currentLimits: CurrentLimitsData[] -): OperationalLimitsGroupFormSchema[] => { - let updatedOpLG: OperationalLimitsGroupFormSchema[] = []; - - for (const currentLimit of currentLimits) { - const equivalentFromNetMod = updatedOpLG.find( - (opLG: OperationalLimitsGroupFormSchema) => - currentLimit.id === opLG.name && currentLimit.applicability === opLG[APPLICABILITY_FIELD] - ); - if (equivalentFromNetMod === undefined) { - updatedOpLG.push({ - id: currentLimit.id + currentLimit.applicability, - name: currentLimit.id, - applicability: currentLimit.applicability, - limitsProperties: currentLimit.limitsProperties, - currentLimits: { - permanentLimit: currentLimit.permanentLimit, - temporaryLimits: formatMapInfosToTemporaryLimitsFormSchema(currentLimit.temporaryLimits), - }, - }); - } - } - - return updatedOpLG; -}; - -export const getOpLimitsGroupInfosFromBranchModification = ( - formBranchModification: LineModificationFormSchema -): OperationalLimitsGroupFormSchema[] => { - return formBranchModification?.limits?.operationalLimitsGroups ?? []; -}; -export const addModificationTypeToTemporaryLimits = ( - formTemporaryLimits: TemporaryLimitFormSchema[] -): TemporaryLimit[] => { - return formTemporaryLimits.map((limit: TemporaryLimitFormSchema) => { - return { - name: toModificationOperation(limit?.name), - acceptableDuration: toModificationOperation(limit?.acceptableDuration), - value: toModificationOperation(limit?.value), - modificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.MODIFY_OR_ADD, - }; - }); -}; - -export function addOperationTypeToSelectedOpLG( - selectedOpLG: string | null | undefined, - noSelectionString: string -): AttributeModification | null { - return selectedOpLG === noSelectionString - ? { - value: selectedOpLG, - op: OperationType.UNSET, - } - : toModificationOperation(selectedOpLG); -} - -/** - * converts the limits groups into a modification limits group - * ie mostly add the ADD, MODIFY, MODIFY_OR_ADD, DELETE and REPLACE tags to the data using a delta between the form and the network values - * note : for now only MODIFY_OR_ADD is handled, the others have been disabled for various reasons - * - * @param limitsGroupsForm current data from the form - */ -export const addModificationTypeToOpLimitsGroups = ( - limitsGroupsForm: OperationalLimitsGroupFormSchema[] -): OperationalLimitsGroup[] => { - let modificationLimitsGroupsForm: OperationalLimitsGroupFormSchema[] = sanitizeLimitsGroups(limitsGroupsForm); - - return modificationLimitsGroupsForm.map((limitsGroupForm: OperationalLimitsGroupFormSchema) => { - const temporaryLimits: TemporaryLimit[] = addModificationTypeToTemporaryLimits( - sanitizeLimitNames(limitsGroupForm[CURRENT_LIMITS]?.[TEMPORARY_LIMITS]) - ); - const currentLimits: CurrentLimits = { - permanentLimit: limitsGroupForm[CURRENT_LIMITS]?.[PERMANENT_LIMIT] ?? null, - temporaryLimits: temporaryLimits ?? [], - }; - - return { - id: limitsGroupForm.id, - name: limitsGroupForm.name, - applicability: limitsGroupForm.applicability, - limitsProperties: limitsGroupForm.limitsProperties, - currentLimits: currentLimits, - modificationType: OPERATIONAL_LIMITS_GROUPS_MODIFICATION_TYPE.MODIFY_OR_ADD, - temporaryLimitsModificationType: TEMPORARY_LIMIT_MODIFICATION_TYPE.REPLACE, - }; - }); -}; diff --git a/src/components/dialogs/limits/limits-pane.tsx b/src/components/dialogs/limits/limits-pane.tsx deleted file mode 100644 index 67a352296b..0000000000 --- a/src/components/dialogs/limits/limits-pane.tsx +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Box, Grid2 as Grid } from '@mui/material'; -import { - ENABLE_OLG_MODIFICATION, - LIMITS, - OPERATIONAL_LIMITS_GROUPS, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, -} from 'components/utils/field-constants'; -import { LimitsSidePane } from './limits-side-pane'; -import { SelectedOperationalLimitGroup } from './selected-operational-limit-group.js'; -import { useCallback, useMemo, useState } from 'react'; -import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; -import { CurrentLimits } from '../../../services/network-modification-types'; -import { OperationalLimitsGroupsTabs } from './operational-limits-groups-tabs'; -import { tabStyles } from 'components/utils/tab-utils'; -import IconButton from '@mui/material/IconButton'; -import { CurrentTreeNode } from '../../graph/tree-node.type'; -import { GridSection } from '../commons/grid-section'; -import AddIcon from '@mui/icons-material/ControlPoint'; -import { APPLICABILITY } from '../../network/constants'; -import { InputWithPopupConfirmation, SwitchInput } from '@gridsuite/commons-ui'; -import { mapServerLimitsGroupsToFormInfos } from './limits-pane-utils'; -import { BranchInfos, CurrentLimitsData } from '../../../services/study/network-map.type'; -import { OperationalLimitsGroupFormSchema } from './operational-limits-groups-types'; -import { generateEmptyOperationalLimitsGroup, generateUniqueId } from './operational-limits-groups-utils'; - -export interface LimitsPaneProps { - id?: string; - currentNode?: CurrentTreeNode; - equipmentToModify?: BranchInfos | null; - clearableFields?: boolean; -} - -export function LimitsPane({ - id = LIMITS, - currentNode, - equipmentToModify, - clearableFields, -}: Readonly) { - const [indexSelectedLimitSet, setIndexSelectedLimitSet] = useState(null); - const { getValues, reset } = useFormContext(); - - const olgEditable: boolean = useWatch({ - name: `${id}.${ENABLE_OLG_MODIFICATION}`, - }); - - const operationalLimitsGroupsFormName: string = `${id}.${OPERATIONAL_LIMITS_GROUPS}`; - const { - fields: operationalLimitsGroups, - append: appendToLimitsGroups, - prepend: prependToLimitsGroups, - remove: removeLimitsGroups, - } = useFieldArray<{ - [key: string]: OperationalLimitsGroupFormSchema[]; - }>({ - name: operationalLimitsGroupsFormName, - }); - - const watchedOperationalLimitsGroups: OperationalLimitsGroupFormSchema[] = useWatch({ - name: operationalLimitsGroupsFormName, - }); - - const isAModification: boolean = useMemo(() => !!equipmentToModify, [equipmentToModify]); - - const getCurrentLimits = (equipmentToModify: any, operationalLimitsGroupId: string): CurrentLimitsData | null => { - if (equipmentToModify?.currentLimits) { - return equipmentToModify.currentLimits.find( - (currentLimit: CurrentLimitsData) => - currentLimit.id + currentLimit.applicability === operationalLimitsGroupId - ); - } - return null; - }; - - const getCurrentLimitsIgnoreApplicability = ( - equipmentToModify: any, - operationalLimitsGroupName: string - ): CurrentLimits | null => { - if (equipmentToModify?.currentLimits) { - return equipmentToModify.currentLimits.find( - (currentLimit: CurrentLimitsData) => currentLimit.id === operationalLimitsGroupName - ); - } - return null; - }; - - const handlePopupConfirmation = () => { - const resetOLGs: OperationalLimitsGroupFormSchema[] = mapServerLimitsGroupsToFormInfos( - equipmentToModify?.currentLimits ?? [] - ); - const currentValues = getValues(); - reset( - { - ...currentValues, - [LIMITS]: { - [OPERATIONAL_LIMITS_GROUPS]: resetOLGs, - [ENABLE_OLG_MODIFICATION]: false, - }, - }, - { keepDefaultValues: true } - ); - }; - - const prependEmptyOperationalLimitsGroup = useCallback( - (name: string) => { - prependToLimitsGroups(generateEmptyOperationalLimitsGroup(name)); - }, - [prependToLimitsGroups] - ); - - const addNewLimitSet = useCallback(() => { - let name = 'DEFAULT'; - - // Try to generate unique name (we relie on watched table because name can be changed without using useFieldArray functions) - if (watchedOperationalLimitsGroups?.length > 0) { - const ids: string[] = watchedOperationalLimitsGroups.map((l) => l.name); - name = generateUniqueId('DEFAULT', ids); - } - prependEmptyOperationalLimitsGroup(name); - setIndexSelectedLimitSet(0); - }, [watchedOperationalLimitsGroups, prependEmptyOperationalLimitsGroup]); - - return ( - <> - {/* active limit sets */} - - - - - - {/* if the user wants to switch off the modification a modal asks him to confirm */} - {isAModification && ( - olgEditable} - resetOnConfirmation={handlePopupConfirmation} - message="disableOLGedition" - validateButtonLabel="validate" - /> - )} - - - - - - - - - - {/* limits */} - - - - - - - - - - - - {indexSelectedLimitSet !== null && - operationalLimitsGroups.map( - (operationalLimitsGroup: OperationalLimitsGroupFormSchema, index: number) => - index === indexSelectedLimitSet && ( - - ) - )} - - - - ); -} diff --git a/src/components/dialogs/limits/limits-properties-side-stack.tsx b/src/components/dialogs/limits/limits-properties-side-stack.tsx deleted file mode 100644 index 3737bf64cf..0000000000 --- a/src/components/dialogs/limits/limits-properties-side-stack.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Copyright (c) 2025, 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 { LimitsTagChip } from './limits-tag-chip'; -import { Autocomplete, AutocompleteRenderInputParams, Box, Stack, TextField, IconButton } from '@mui/material'; -import { useCallback, useMemo, useState } from 'react'; -import { Delete } from '@mui/icons-material'; -import { useIntl } from 'react-intl'; -import { LimitsProperty } from '../../../services/network-modification-types'; -import { useFieldArray } from 'react-hook-form'; -import { AddButton, usePredefinedProperties } from '@gridsuite/commons-ui'; - -export interface LimitsPropertiesSideStackProps { - name: string; - disabled?: boolean; -} -export function LimitsPropertiesSideStack({ name, disabled }: Readonly) { - const { - fields: limitsProperties, - append, - remove, - } = useFieldArray<{ [key: string]: LimitsProperty[] }>({ name: name }); - - const [isEditing, setIsEditing] = useState(false); - const [hovered, setHovered] = useState(false); - const [propertyName, setPropertyName] = useState(''); - const [propertyValue, setPropertyValue] = useState(''); - const [nameEditorError, setNameEditorError] = useState(''); - const [valueEditorError, setValueEditorError] = useState(''); - const intl = useIntl(); - - const [predefinedProperties] = usePredefinedProperties('limitsGroup'); - const predefinedPropertiesNames = useMemo(() => { - return Object.keys(predefinedProperties ?? {}).sort((a, b) => a.localeCompare(b)); - }, [predefinedProperties]); - - const handleKeyPress = useCallback( - (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - setNameEditorError(''); - setValueEditorError(''); - - let error = false; - if (propertyName.trim() === '') { - setNameEditorError(intl.formatMessage({ id: 'FieldNotEmpty' })); - error = true; - } - if (propertyValue.trim() === '') { - setValueEditorError(intl.formatMessage({ id: 'FieldNotEmpty' })); - error = true; - } - - if (error) { - return; - } - - if (limitsProperties.some((l) => l.name === propertyName)) { - setNameEditorError(intl.formatMessage({ id: 'UniqueName' })); - return; - } else { - append({ name: propertyName, value: propertyValue }); - setPropertyName(''); - setPropertyValue(''); - } - setIsEditing(false); - } - }, - [append, intl, limitsProperties, propertyName, propertyValue] - ); - - const handleOnChange = useCallback((value: string) => { - setPropertyName(value); - setNameEditorError(''); - }, []); - - return ( - - - {limitsProperties?.map((property: LimitsProperty, index: number) => ( - remove(index)} - disabled={disabled} - showTooltip - /> - ))} - {!isEditing && setIsEditing(true)} />} - - {isEditing && !disabled ? ( - setHovered(true)} - onMouseLeave={() => setHovered(false)} - > - handleOnChange(value ?? '')} - renderInput={(params: AutocompleteRenderInputParams) => ( - handleOnChange(event.target.value)} - fullWidth - error={nameEditorError !== ''} - helperText={nameEditorError} - onKeyDown={handleKeyPress} - /> - )} - sx={{ flex: 1 }} - freeSolo={true} - /> - setPropertyValue(event.target.value)} - error={valueEditorError !== ''} - helperText={valueEditorError} - /> - { - setIsEditing(false); - setNameEditorError(''); - }} - > - - - - ) : ( - '' - )} - - ); -} diff --git a/src/components/dialogs/limits/limits-properties-stack.tsx b/src/components/dialogs/limits/limits-properties-stack.tsx deleted file mode 100644 index 3806301d9d..0000000000 --- a/src/components/dialogs/limits/limits-properties-stack.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Avatar, Stack } from '@mui/material'; -import { LimitsProperty } from '../../../services/network-modification-types'; -import { LimitsTagChip } from './limits-tag-chip'; -import { useWatch } from 'react-hook-form'; - -const MAX_PROPERTIES_TO_RENDER: number = 2; - -export interface LimitsPropertiesStackProps { - name: string; -} - -function getLimitsPropertiesToRender(limitsProperties: LimitsProperty[]) { - return limitsProperties.length < MAX_PROPERTIES_TO_RENDER ? limitsProperties : limitsProperties?.slice(0, 2); -} - -export function LimitsPropertiesStack({ name }: Readonly) { - const limitsProperties: LimitsProperty[] | undefined = useWatch({ name: name }); - const propertiesToRender: LimitsProperty[] = getLimitsPropertiesToRender(limitsProperties ?? []); - - return ( - - {propertiesToRender.map((property: LimitsProperty) => ( - - ))} - {limitsProperties && propertiesToRender.length !== limitsProperties.length ? ( - {`+${limitsProperties.length - propertiesToRender.length}`} - ) : ( - '' - )} - - ); -} diff --git a/src/components/dialogs/limits/limits-side-pane.tsx b/src/components/dialogs/limits/limits-side-pane.tsx deleted file mode 100644 index 35522235e2..0000000000 --- a/src/components/dialogs/limits/limits-side-pane.tsx +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Box, Grid2 as Grid } from '@mui/material'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { - AmpereAdornment, - ColumnNumeric, - ColumnText, - DndColumn, - DndColumnType, - ErrorInput, - FieldErrorAlert, - FloatInput, - SelectInput, - TextInput, -} from '@gridsuite/commons-ui'; -import { - APPLICABILITY_FIELD, - CURRENT_LIMITS, - LIMITS, - LIMITS_PROPERTIES, - NAME, - OLG_IS_DUPLICATE, - OPERATIONAL_LIMITS_GROUPS, - PERMANENT_LIMIT, - TEMPORARY_LIMIT_DURATION, - TEMPORARY_LIMIT_MODIFICATION_TYPE, - TEMPORARY_LIMIT_NAME, - TEMPORARY_LIMIT_VALUE, - TEMPORARY_LIMITS, -} from 'components/utils/field-constants'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useController, useFormContext } from 'react-hook-form'; -import { isNodeBuilt } from '../../graph/util/model-functions'; -import TemporaryLimitsTable from './temporary-limits-table'; -import LimitsChart from './limitsChart'; -import { CurrentTreeNode } from '../../graph/tree-node.type'; -import { APPLICABILITY } from '../../network/constants'; -import { LimitsPropertiesSideStack } from './limits-properties-side-stack'; -import { TemporaryLimitsData } from '../../../services/study/network-map.type'; - -export interface LimitsSidePaneProps { - name?: string; - permanentCurrentLimitPreviousValue: number | null | undefined; - temporaryLimitsPreviousValues: TemporaryLimitsData[]; - applicabilityPreviousValue?: string; - clearableFields: boolean | undefined; - currentNode?: CurrentTreeNode; - disabled: boolean; -} - -export function LimitsSidePane({ - name, - permanentCurrentLimitPreviousValue, - temporaryLimitsPreviousValues, - applicabilityPreviousValue, - clearableFields, - currentNode, - disabled, -}: Readonly) { - const intl = useIntl(); - const { getValues, subscribe, trigger } = useFormContext(); - const limitsGroupFormName = `${name}.${CURRENT_LIMITS}`; - const columnsDefinition: ((ColumnText | ColumnNumeric) & { initialValue: string | null })[] = useMemo(() => { - return [ - { - label: 'TemporaryLimitName', - dataKey: TEMPORARY_LIMIT_NAME, - initialValue: '', - editable: true, - type: DndColumnType.TEXT as const, - maxWidth: 200, - }, - { - label: 'TemporaryLimitDuration', - dataKey: TEMPORARY_LIMIT_DURATION, - initialValue: null, - editable: true, - type: DndColumnType.NUMERIC as const, - maxWidth: 100, - }, - { - label: 'TemporaryLimitValue', - dataKey: TEMPORARY_LIMIT_VALUE, - initialValue: null, - editable: true, - type: DndColumnType.NUMERIC as const, - maxWidth: 100, - }, - ].map((column) => ({ - ...column, - label: intl.formatMessage({ id: column.label }), - })); - }, [intl]); - - const newRowData = useMemo(() => { - let newRowData: any = {}; - columnsDefinition.forEach((column) => (newRowData[column.dataKey] = column.initialValue)); - return newRowData; - }, [columnsDefinition]); - const createRows = () => [newRowData]; - - const temporaryLimitHasPreviousValue = useCallback( - (rowIndex: number, arrayFormName: string, temporaryLimits?: TemporaryLimitsData[]) => { - if (!temporaryLimits) { - return false; - } - return ( - temporaryLimits?.filter( - (l: TemporaryLimitsData) => - l.name === getValues(arrayFormName)[rowIndex]?.name && - l.acceptableDuration === getValues(arrayFormName)[rowIndex]?.acceptableDuration - )?.length > 0 - ); - }, - [getValues] - ); - - const shouldReturnPreviousValue = useCallback( - (rowIndex: number, column: DndColumn, arrayFormName: string, temporaryLimits: TemporaryLimitsData[]) => { - return ( - (temporaryLimitHasPreviousValue(rowIndex, arrayFormName, temporaryLimits) && - column.dataKey === TEMPORARY_LIMIT_VALUE) || - getValues(arrayFormName)[rowIndex]?.modificationType === TEMPORARY_LIMIT_MODIFICATION_TYPE.ADD - ); - }, - [getValues, temporaryLimitHasPreviousValue] - ); - - const findTemporaryLimit = useCallback( - (rowIndex: number, arrayFormName: string, temporaryLimits: TemporaryLimitsData[]) => { - return temporaryLimits?.find( - (e: TemporaryLimitsData) => - e.name === getValues(arrayFormName)[rowIndex]?.name && - e.acceptableDuration === getValues(arrayFormName)[rowIndex]?.acceptableDuration - ); - }, - [getValues] - ); - - const getPreviousValue = useCallback( - (rowIndex: number, column: DndColumn, arrayFormName: string, temporaryLimits?: TemporaryLimitsData[]) => { - if (!temporaryLimits) { - return undefined; - } - if (!temporaryLimits?.length) { - return undefined; - } - if (!shouldReturnPreviousValue(rowIndex, column, arrayFormName, temporaryLimits)) { - return undefined; - } - const temporaryLimit = findTemporaryLimit(rowIndex, arrayFormName, temporaryLimits); - if (temporaryLimit === undefined) { - return undefined; - } - if (column.dataKey === TEMPORARY_LIMIT_VALUE) { - return temporaryLimit?.value ?? Number.MAX_VALUE; - } else if (column.dataKey === TEMPORARY_LIMIT_DURATION) { - return temporaryLimit?.acceptableDuration ?? Number.MAX_VALUE; - } - }, - [findTemporaryLimit, shouldReturnPreviousValue] - ); - - const isValueModified = useCallback( - (rowIndex: number, arrayFormName: string) => { - const temporaryLimits = getValues(arrayFormName); - const temporaryLimit = temporaryLimits ? temporaryLimits[rowIndex] : null; - if ( - temporaryLimit?.modificationType === TEMPORARY_LIMIT_MODIFICATION_TYPE.MODIFY && - !isNodeBuilt(currentNode ?? null) - ) { - return false; - } else { - return temporaryLimit?.modificationType !== null; - } - }, - [currentNode, getValues] - ); - - // Trigger all OLG_IS_DUPLICATE fields when change on applicability or name field - useEffect(() => { - const unsubscribeCallBack = subscribe({ - name: [`${name}.${APPLICABILITY_FIELD}`, `${name}.${NAME}`], - formState: { - values: true, - }, - callback: ({ isSubmitted }) => { - if (isSubmitted) { - const operationalLimitsGroups = getValues(`${LIMITS}.${OPERATIONAL_LIMITS_GROUPS}`); - for (let index = 0; index < operationalLimitsGroups?.length; index++) { - trigger(`${LIMITS}.${OPERATIONAL_LIMITS_GROUPS}[${index}].${OLG_IS_DUPLICATE}`).then(); - } - } - }, - }); - return () => unsubscribeCallBack(); - }, [trigger, subscribe, name, getValues]); - - const { - fieldState: { error }, - } = useController({ - name: `${name}.${OLG_IS_DUPLICATE}`, - }); - - return ( - - - {name && ( - - - - - - - - - - - - - - - )} - - - - - - - - ); -} diff --git a/src/components/dialogs/limits/limits-tag-chip.tsx b/src/components/dialogs/limits/limits-tag-chip.tsx deleted file mode 100644 index ee2a92bab8..0000000000 --- a/src/components/dialogs/limits/limits-tag-chip.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Avatar, Chip, ChipProps, Tooltip } from '@mui/material'; -import { getPropertyAvatar } from './limits-constants'; -import * as React from 'react'; -import { LimitsProperty } from '../../../services/network-modification-types'; - -export interface LimitTagChipProps extends Omit { - limitsProperty: LimitsProperty; - showTooltip?: boolean; -} - -export function LimitsTagChip({ limitsProperty, showTooltip, onDelete, ...props }: Readonly) { - const chipContent = ( - {getPropertyAvatar(limitsProperty.name)}} - label={limitsProperty.value} - sx={{ maxWidth: onDelete ? '200px' : '180px', margin: 0.5, borderRadius: '4px' }} - onDelete={onDelete} - {...props} - /> - ); - - return showTooltip ? ( - {chipContent} - ) : ( - chipContent - ); -} diff --git a/src/components/dialogs/limits/limitsChart.tsx b/src/components/dialogs/limits/limitsChart.tsx deleted file mode 100644 index 32dccf9995..0000000000 --- a/src/components/dialogs/limits/limitsChart.tsx +++ /dev/null @@ -1,262 +0,0 @@ -/** - * Copyright (c) 2025, 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 { BarChart } from '@mui/x-charts/BarChart'; -import { useCallback, useMemo } from 'react'; -import { useWatch } from 'react-hook-form'; -import { useIntl } from 'react-intl'; -import { TemporaryLimitsData } from '../../../services/study/network-map.type'; -import { AxisValueFormatterContext, BarSeriesType } from '@mui/x-charts/models'; - -export interface LimitsGraphProps { - limitsGroupFormName: string; - previousPermanentLimit?: number | null; // back value when there are no permanent value in the current form -} -const colorPermanentLimit = '#58d058'; -const colors: string[] = ['#ffc019', '#e47400', '#cc5500', '#ff5757', '#ff0000']; -const colorForbidden: string = '#b10303'; - -interface Ticks { - label: string; - position: number | null; - incoherent: boolean; -} - -const formatTempo = (tempo: number | null) => { - if (!tempo) { - return ''; - } - const min = Math.floor(tempo / 60); - const sec = tempo % 60; - if (min > 0 && sec === 0) { - return `${min}min`; - } - if (min === 0 && sec > 0) { - return `${sec}s`; - } - return `${min}min${sec}s`; -}; - -export default function LimitsChart({ limitsGroupFormName, previousPermanentLimit }: Readonly) { - const currentLimits = useWatch({ name: `${limitsGroupFormName}` }); - const intl = useIntl(); - const permanentLimit: number | null = currentLimits.permanentLimit ?? previousPermanentLimit ?? null; - - const isIncoherent = useCallback( - (maxValuePermanentLimit: number, item: TemporaryLimitsData, previousItem?: TemporaryLimitsData) => { - // Incoherent cases : - // threshold without tempo that is not permanent limit when permanent limit exists - // threshold with biggest tempo and biggest value than the previous threshold - // more than one threshold without value - const isPermanentLimit = - (item.name === intl.formatMessage({ id: 'permanentLimit' }) && permanentLimit) || - (!permanentLimit && !item.acceptableDuration); - - const permanentLimitValue = permanentLimit || maxValuePermanentLimit; - - const itemTempoGreaterThanPrevious: boolean = - (item?.acceptableDuration && - previousItem?.acceptableDuration && - item.acceptableDuration > previousItem.acceptableDuration) || - false; - - const atLeastTwoItemsWithNotempo: boolean = - (previousItem?.acceptableDuration && !item.acceptableDuration) || false; - - return ( - (!item.acceptableDuration && permanentLimit && !isPermanentLimit) || - (!isPermanentLimit && item.value && item.value < permanentLimitValue) || - itemTempoGreaterThanPrevious || - atLeastTwoItemsWithNotempo || - false - ); - }, - [intl, permanentLimit] - ); - - const { series, ticks, max } = useMemo(() => { - const thresholds: TemporaryLimitsData[] = []; - let noValueThresholdFound = false; - let maxValuePermanentLimit: number = 0; - - if (permanentLimit) { - thresholds.push({ - name: intl.formatMessage({ id: 'permanentLimit' }), - value: permanentLimit ? +permanentLimit : permanentLimit, - acceptableDuration: null, - }); - maxValuePermanentLimit = permanentLimit ?? 0; - } - - if (currentLimits?.temporaryLimits) { - currentLimits.temporaryLimits - .filter((field: TemporaryLimitsData) => field.name && (field.acceptableDuration || field.value)) - .forEach((field: TemporaryLimitsData) => { - if (!field.value) { - noValueThresholdFound = true; - } - if (!field.acceptableDuration && field.value) { - maxValuePermanentLimit = Math.max(maxValuePermanentLimit, field.value); - } - thresholds.push({ - name: field.name, - value: field.value ? +field.value : field.value, - acceptableDuration: field.acceptableDuration, - }); - }); - } - - // Sort by value, if no value put at the end - thresholds.sort((a, b) => { - if (a.value && !b.value) { - return -1; - } - if (!a.value && !b.value && a.acceptableDuration && b.acceptableDuration) { - return a.acceptableDuration - b.acceptableDuration; - } - if (!a.value && b.value) { - return 1; - } - if (a.value && b.value) { - return a.value - b.value; - } - return 0; - }); - - const maxValue = Math.max(...thresholds.map((item) => item.value ?? 0)); - let colorIndex = 0; - let previousSum = 0; - - return thresholds.reduce<{ series: BarSeriesType[]; ticks: Ticks[]; max: number }>( - (acc, item, index) => { - const isPermanentLimit = - (item.name === intl.formatMessage({ id: 'permanentLimit' }) && permanentLimit) || - (!permanentLimit && !item.acceptableDuration && item.value === maxValuePermanentLimit); - const difference = item.value ? item.value - previousSum : undefined; - - const color = - isPermanentLimit || - !item.acceptableDuration || - (item.acceptableDuration && item.value && maxValuePermanentLimit >= item.value) - ? colorPermanentLimit - : (colors?.[colorIndex] ?? colors[colors.length - 1]); - - const incoherent = isIncoherent( - maxValuePermanentLimit, - item, - index > 0 ? thresholds[index - 1] : undefined - ); - - if (item.value && item.value >= maxValuePermanentLimit) { - previousSum = item.value; - } - if (item.value && item.value > maxValuePermanentLimit) { - colorIndex++; - } - - const updatedSeries: BarSeriesType[] = - (item.acceptableDuration && !item.value) || (item.value && item.value >= maxValuePermanentLimit) - ? [ - ...acc.series, - { - type: 'bar', - label: - isPermanentLimit || item.value === maxValuePermanentLimit - ? intl.formatMessage({ id: 'unlimited' }) - : formatTempo(item.acceptableDuration), - data: [difference ?? maxValue * 0.15], - color: item.value ? color : colorForbidden, - stack: 'total', - }, - ] - : [...acc.series]; - const updatedTicks: Ticks[] = [ - ...acc.ticks, - { position: item.value, label: item.name, incoherent: incoherent }, - ]; - - if ( - index === thresholds.length - 1 && - updatedTicks[updatedTicks.length - 1].position && - !noValueThresholdFound - ) { - updatedSeries.push({ - type: 'bar', - label: intl.formatMessage({ id: 'forbidden' }), - data: [(updatedTicks?.[updatedTicks?.length - 1]?.position ?? 0.0) * 0.15], - color: colorForbidden, - stack: 'total', - }); - } - return { - series: updatedSeries, - ticks: updatedTicks, - max: - updatedSeries - .flatMap((serie) => serie.data) // Only one number by array. - .filter(Number.isFinite) - // @ts-ignore : We filtered values above. - .reduce((a, b) => a + b, 0) ?? 100, - }; - }, - { series: [], ticks: [], max: 0 } - ); - }, [currentLimits.temporaryLimits, intl, isIncoherent, permanentLimit]); - - const tickPositions = ticks.map((t) => t.position); - - return ( - 0 - ? series - : [{ label: intl.formatMessage({ id: 'unlimited' }), data: [100], color: colorPermanentLimit }] - } - layout="horizontal" - yAxis={[ - // We can't totally disable the yAxis, so we have to give empty data - { - id: 'leftAxis', - position: 'none', - data: [''], - }, - ]} - xAxis={[ - { - id: 'bottomAxis', - position: 'bottom', - tickInterval: tickPositions, - tickLabelStyle: { fontSize: 10 }, - disableLine: true, - min: 0, - max: max, - }, - { - id: 'topAxis', - position: 'top', - tickInterval: tickPositions, - tickLabelStyle: { fontSize: 10 }, - valueFormatter: (value: number, _context: AxisValueFormatterContext) => - ticks.find((item: Ticks) => item.position === value)?.label ?? '', - disableLine: true, - min: 0, - max: max, - }, - ]} - sx={{ pointerEvents: 'none' }} - /> - ); -} diff --git a/src/components/dialogs/limits/operational-limits-group-tab-label.tsx b/src/components/dialogs/limits/operational-limits-group-tab-label.tsx deleted file mode 100644 index 26becb5496..0000000000 --- a/src/components/dialogs/limits/operational-limits-group-tab-label.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Box, FormHelperText, Stack, Typography, Tooltip, IconButton } from '@mui/material'; -import { FormattedMessage } from 'react-intl'; -import { APPLICABILITY } from '../../network/constants'; -import { LimitsPropertiesStack } from './limits-properties-stack'; -import { grey, red } from '@mui/material/colors'; -import { useFormState } from 'react-hook-form'; -import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; -import { LIMITS, OLG_IS_DUPLICATE, OPERATIONAL_LIMITS_GROUPS } from '../../utils/field-constants'; -import { LimitsFormSchema, OperationalLimitsGroupFormSchema } from './operational-limits-groups-types'; -import MoreVertIcon from '@mui/icons-material/MoreVert'; - -interface OperationalLimitsGroupTabLabelProps { - operationalLimitsGroup: OperationalLimitsGroupFormSchema; - showIconButton: boolean; - limitsPropertiesName: string; - handleOpenMenu: (event: React.MouseEvent, index: number) => void; - index: number; -} - -export function OperationalLimitsGroupTabLabel({ - operationalLimitsGroup, - showIconButton, - limitsPropertiesName, - handleOpenMenu, - index, -}: Readonly) { - const { errors } = useFormState({ name: `${LIMITS}.${OPERATIONAL_LIMITS_GROUPS}` }); - - const temporaryLimitsErrors = errors?.limits?.operationalLimitsGroups?.[index]?.currentLimits?.temporaryLimits; - const hasTemporaryLimitError = - Array.isArray(temporaryLimitsErrors) && - temporaryLimitsErrors.some((tl) => tl?.name?.message || tl?.acceptableDuration?.message); - - const hasError = - errors?.limits?.operationalLimitsGroups?.[index]?.name?.message || - errors?.limits?.operationalLimitsGroups?.[index]?.currentLimits?.permanentLimit?.message || - errors?.limits?.operationalLimitsGroups?.[index]?.[OLG_IS_DUPLICATE]?.message || - hasTemporaryLimitError; - - return ( - - - - - - - {operationalLimitsGroup.name} - - {hasError && ( - - - - )} - - - {operationalLimitsGroup?.applicability ? ( - - item.id === operationalLimitsGroup.applicability - )?.label - } - /> - - ) : ( - '' - )} - - - - {showIconButton && ( - handleOpenMenu(e, index)}> - - - )} - - ); -} diff --git a/src/components/dialogs/limits/operational-limits-groups-styles.ts b/src/components/dialogs/limits/operational-limits-groups-styles.ts deleted file mode 100644 index 39ab02ef4b..0000000000 --- a/src/components/dialogs/limits/operational-limits-groups-styles.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2025, 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 type { MuiStyles } from '@gridsuite/commons-ui'; -import { stylesLayout } from '../../utils/tab-utils'; -import { blue } from '@mui/material/colors'; - -export const limitsStyles = { - tabs: () => ({ - ...stylesLayout.listDisplay, - maxHeight: '45vh', - height: '100%', - borderRight: 1, - borderColor: 'divider', - transition: 'transform 0.3s ease-in-out', - '& .MuiTabs-indicator': { - borderRight: `3px solid ${blue[700]}`, - }, - '.MuiTab-root.MuiButtonBase-root': { - textTransform: 'none', - textAlign: 'left', - alignItems: 'stretch', - p: 0, - }, - }), - tabBackground: { - '&:hover': { - backgroundColor: 'rgba(25, 118, 210, 0.05)', // blue[700] - }, - maxWidth: 600, - width: '100%', - p: 1, - minHeight: 60, - }, - copyLimitsToRightBackground: { - height: 200, - display: 'flex', - }, - copyLimitsToLeftBackground: { - height: '50%', - }, - copyLimitsButtons: { - alignSelf: 'flex-end', - minWidth: '0px', - height: 'auto', - padding: '1', - }, -} as const satisfies MuiStyles; diff --git a/src/components/dialogs/limits/operational-limits-groups-tabs.tsx b/src/components/dialogs/limits/operational-limits-groups-tabs.tsx deleted file mode 100644 index 7ea76c712d..0000000000 --- a/src/components/dialogs/limits/operational-limits-groups-tabs.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright (c) 2025, 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 { Grid2 as Grid, Tab, Tabs } from '@mui/material'; -import React, { useCallback, useEffect, useState } from 'react'; -import { - LIMITS_PROPERTIES, - OPERATIONAL_LIMITS_GROUPS, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, -} from '../../utils/field-constants'; -import { UseFieldArrayAppend, useWatch } from 'react-hook-form'; -import { ContextMenuCoordinates, LimitsGroupsContextualMenu } from './limits-groups-contextual-menu'; -import { OperationalLimitsGroupTabLabel } from './operational-limits-group-tab-label'; -import { OperationalLimitsGroupFormSchema } from './operational-limits-groups-types'; -import { CurrentLimitsData } from 'services/study/network-map.type'; -import { limitsStyles } from './operational-limits-groups-styles'; - -export interface OperationalLimitsGroupsTabsProps { - parentFormName: string; - indexSelectedLimitSet: number | null; - setIndexSelectedLimitSet: React.Dispatch>; - currentLimitsToModify: CurrentLimitsData[]; - editable: boolean; - appendToLimitsGroups: UseFieldArrayAppend< - { - [p: string]: OperationalLimitsGroupFormSchema[]; - }, - string - >; - removeLimitsGroups: () => void; -} - -export const OperationalLimitsGroupsTabs = ({ - parentFormName, - setIndexSelectedLimitSet, - indexSelectedLimitSet, - editable, - appendToLimitsGroups, - removeLimitsGroups, - currentLimitsToModify, -}: Readonly) => { - const [hoveredRowIndex, setHoveredRowIndex] = useState(-1); - const [contextMenuCoordinates, setContextMenuCoordinates] = useState({ - x: null, - y: null, - tabIndex: null, - }); - const selectedLimitsGroups1: string = useWatch({ - name: `${parentFormName}.${SELECTED_OPERATIONAL_LIMITS_GROUP_ID1}`, - }); - const selectedLimitsGroups2: string = useWatch({ - name: `${parentFormName}.${SELECTED_OPERATIONAL_LIMITS_GROUP_ID2}`, - }); - - const operationalLimitsGroups: OperationalLimitsGroupFormSchema[] = useWatch({ - name: `${parentFormName}.${OPERATIONAL_LIMITS_GROUPS}`, - }); - - const handleOpenMenu = useCallback( - (event: React.MouseEvent, index: number): void => { - if (!editable) { - return; - } - event.preventDefault(); - event.stopPropagation(); - setIndexSelectedLimitSet(index); - setContextMenuCoordinates({ - x: event.clientX, - y: event.clientY, - tabIndex: index, - }); - }, - [editable, setIndexSelectedLimitSet] - ); - - const handleCloseMenu = useCallback(() => { - setContextMenuCoordinates({ - x: null, - y: null, - tabIndex: null, - }); - }, [setContextMenuCoordinates]); - - useEffect(() => { - // as long as there are limit sets, one should be selected - if (indexSelectedLimitSet === null && operationalLimitsGroups.length > 0) { - setIndexSelectedLimitSet(0); - } - }, [indexSelectedLimitSet, setIndexSelectedLimitSet, operationalLimitsGroups]); - - const handleTabChange = useCallback( - (_event: React.SyntheticEvent, newValue: number): void => { - setIndexSelectedLimitSet(newValue); - }, - [setIndexSelectedLimitSet] - ); - - return ( - - - {operationalLimitsGroups.map((opLg: OperationalLimitsGroupFormSchema, index: number) => ( - setHoveredRowIndex(index)} - onContextMenu={(e) => handleOpenMenu(e, index)} - key={opLg.id + index} - disableRipple - sx={limitsStyles.tabBackground} - label={ - - } - /> - ))} - - - - ); -}; diff --git a/src/components/dialogs/limits/operational-limits-groups-types.ts b/src/components/dialogs/limits/operational-limits-groups-types.ts deleted file mode 100644 index d929a8d61d..0000000000 --- a/src/components/dialogs/limits/operational-limits-groups-types.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2025, 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 { - APPLICABILITY_FIELD, - CURRENT_LIMITS, - ENABLE_OLG_MODIFICATION, - ID, - LIMITS, - LIMITS_PROPERTIES, - NAME, - OLG_IS_DUPLICATE, - OPERATIONAL_LIMITS_GROUPS, - PERMANENT_LIMIT, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, - TEMPORARY_LIMIT_DURATION, - TEMPORARY_LIMIT_NAME, - TEMPORARY_LIMIT_VALUE, - TEMPORARY_LIMITS, -} from '../../utils/field-constants'; -import { FieldConstants } from '@gridsuite/commons-ui'; - -export interface LimitsFormSchema { - [LIMITS]: OperationalLimitsGroupsFormSchema; -} - -export interface OperationalLimitsGroupsFormSchema { - [OPERATIONAL_LIMITS_GROUPS]: OperationalLimitsGroupFormSchema[]; - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID1]?: string; - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID2]?: string; - [ENABLE_OLG_MODIFICATION]: boolean; -} - -export interface OperationalLimitsGroupFormSchema { - [ID]: string; - [NAME]: string; - [APPLICABILITY_FIELD]?: string; - [OLG_IS_DUPLICATE]?: boolean; - [CURRENT_LIMITS]: CurrentLimitsFormSchema; - [LIMITS_PROPERTIES]?: LimitsPropertyFormSchema[]; -} - -export interface CurrentLimitsFormSchema { - [ID]?: string; - [PERMANENT_LIMIT]: number | null; - [TEMPORARY_LIMITS]: TemporaryLimitFormSchema[]; -} - -interface LimitsPropertyFormSchema { - [NAME]: string; - [FieldConstants.VALUE]: string; -} - -export interface TemporaryLimitFormSchema { - [TEMPORARY_LIMIT_DURATION]?: number | null; - [TEMPORARY_LIMIT_VALUE]?: number | null; - [TEMPORARY_LIMIT_NAME]?: string | null; -} diff --git a/src/components/dialogs/limits/operational-limits-groups-utils.ts b/src/components/dialogs/limits/operational-limits-groups-utils.ts deleted file mode 100644 index 925915c03c..0000000000 --- a/src/components/dialogs/limits/operational-limits-groups-utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2025, 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 { - APPLICABILITY_FIELD, - CURRENT_LIMITS, - ID, - LIMITS_PROPERTIES, - NAME, - PERMANENT_LIMIT, - TEMPORARY_LIMIT_DURATION, - TEMPORARY_LIMIT_NAME, - TEMPORARY_LIMIT_VALUE, - TEMPORARY_LIMITS, -} from 'components/utils/field-constants'; -import { APPLICABILITY } from 'components/network/constants'; -import { OperationalLimitsGroupFormSchema, TemporaryLimitFormSchema } from './operational-limits-groups-types'; - -function generateEmptyTemporaryLimitArray(): TemporaryLimitFormSchema[] { - return [ - { - [TEMPORARY_LIMIT_NAME]: '', - [TEMPORARY_LIMIT_DURATION]: null, - [TEMPORARY_LIMIT_VALUE]: null, - }, - ]; -} - -export function generateEmptyOperationalLimitsGroup(name: string): OperationalLimitsGroupFormSchema { - return { - [ID]: crypto.randomUUID(), - [NAME]: name, - [APPLICABILITY_FIELD]: APPLICABILITY.EQUIPMENT.id, - [LIMITS_PROPERTIES]: [], - [CURRENT_LIMITS]: { - [TEMPORARY_LIMITS]: generateEmptyTemporaryLimitArray(), - [PERMANENT_LIMIT]: null, - }, - }; -} - -export function generateUniqueId(baseName: string, names: string[]): string { - let finalId = baseName; - let found = false; - let increment = 1; - let suffix = ''; - do { - found = names.includes(baseName + suffix, 0); - if (found) { - increment++; - suffix = '(' + increment + ')'; - finalId = baseName + suffix; - } - } while (found); - - return finalId; -} diff --git a/src/components/dialogs/limits/selected-operational-limit-group.tsx b/src/components/dialogs/limits/selected-operational-limit-group.tsx deleted file mode 100644 index 8c255204f2..0000000000 --- a/src/components/dialogs/limits/selected-operational-limit-group.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2025, 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 { useMemo } from 'react'; -import { useWatch } from 'react-hook-form'; -import { Box } from '@mui/material'; -import { AutocompleteInput } from '@gridsuite/commons-ui'; -import { APPLICABILITY } from '../../network/constants'; -import { useIntl } from 'react-intl'; -import { OperationalLimitsGroupFormSchema } from './operational-limits-groups-types'; - -export interface SelectedOperationalLimitGroupProps { - selectedFormName: string; - optionsFormName: string; - label?: string; - filteredApplicability?: string; - previousValue?: string; - isABranchModif: boolean; // if false, this is a branch creation -} - -export const SelectedOperationalLimitGroup = ({ - selectedFormName, - optionsFormName, - label, - filteredApplicability, - previousValue, - isABranchModif, -}: Readonly) => { - const optionsValues = useWatch({ - name: optionsFormName, - }); - const intl = useIntl(); - - const opLimitsGroupsNames: string[] = useMemo((): string[] => { - const finalOptions: string[] = optionsValues - ? optionsValues - .filter( - (optionObj: OperationalLimitsGroupFormSchema) => - optionObj.applicability && - (optionObj.applicability === filteredApplicability || - optionObj.applicability === APPLICABILITY.EQUIPMENT.id) - ) - .map((filteredoptionObj: OperationalLimitsGroupFormSchema) => filteredoptionObj.name) - .filter((id: string) => id != null) - : []; - if (isABranchModif) { - finalOptions.push( - intl.formatMessage({ - id: 'None', - }) - ); - } - return finalOptions; - }, [filteredApplicability, intl, isABranchModif, optionsValues]); - - return ( - - - - ); -}; diff --git a/src/components/dialogs/limits/temporary-limits-table.tsx b/src/components/dialogs/limits/temporary-limits-table.tsx deleted file mode 100644 index 160e4e8590..0000000000 --- a/src/components/dialogs/limits/temporary-limits-table.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (c) 2023, 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 { useState } from 'react'; -import { useFieldArray } from 'react-hook-form'; -import { Box, Grid2 as Grid, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; -import { - type ColumnNumeric, - type ColumnText, - DndColumnType, - ErrorInput, - FieldErrorAlert, - type MuiStyles, - TableNumericalInput, - TableTextInput, -} from '@gridsuite/commons-ui'; -import IconButton from '@mui/material/IconButton'; -import AddCircleIcon from '@mui/icons-material/AddCircle'; -import DeleteIcon from '@mui/icons-material/Delete'; -import { SELECTED } from '../../utils/field-constants'; -import { TemporaryLimitsData } from '../../../services/study/network-map.type'; - -const styles = { - columnsStyle: { - display: 'inline-flex', - justifyContent: 'space-between', - alignItems: 'center', - margin: 1, - textTransform: 'none', - }, -} as const satisfies MuiStyles; - -interface CustomTableCellProps { - name: string; - column: ColumnText | ColumnNumeric; - disabled: boolean; - previousValue: number | undefined; - valueModified: boolean; -} - -function EditableTableCell({ name, column, previousValue, valueModified, ...props }: Readonly) { - return ( - - {column.type === DndColumnType.NUMERIC ? ( - - ) : ( - - )} - - ); -} - -interface TemporaryLimitsTableProps { - arrayFormName: string; - columnsDefinition: (ColumnText | ColumnNumeric)[]; - createRow: () => any[]; - disabled?: boolean; - previousValues: TemporaryLimitsData[]; - getPreviousValue: ( - rowIndex: number, - column: ColumnText | ColumnNumeric, - arrayFormName: string, - temporaryLimits: TemporaryLimitsData[] - ) => number | undefined; - isValueModified: (rowIndex: number, arrayFormName: string) => boolean; - disableAddingRows?: boolean; -} - -function TemporaryLimitsTable({ - arrayFormName, - columnsDefinition, - createRow, - disabled = false, - previousValues, - getPreviousValue, - isValueModified, - disableAddingRows = false, -}: Readonly) { - const { fields, append, remove } = useFieldArray({ name: arrayFormName }); - const [hoveredRowIndex, setHoveredRowIndex] = useState(-1); - - function renderTableCell(rowId: string, rowIndex: number, column: ColumnText | ColumnNumeric) { - const name = `${arrayFormName}[${rowIndex}].${column.dataKey}`; - return ( - - ); - } - - function handleAddRowButton() { - const rowsToAdd = createRow().map((row) => { - return { ...row, [SELECTED]: false }; - }); - - append(rowsToAdd); - } - - function renderTableHead() { - return ( - - - {columnsDefinition.map((column) => ( - - {column.label} - - ))} - - - - - - - - ); - } - - const renderTableRow = (rowId: string, index: number) => ( - setHoveredRowIndex(index)} onMouseLeave={() => setHoveredRowIndex(-1)}> - {columnsDefinition.map((column) => renderTableCell(rowId, index, column))} - - remove(index)}> - - - - - ); - - function renderTableBody() { - return ( - - {fields.map((value: Record<'id', string>, index: number) => renderTableRow(value.id, index))} - - ); - } - - return ( - - - - - {renderTableHead()} - {renderTableBody()} -
-
- -
-
- ); -} - -export default TemporaryLimitsTable; diff --git a/src/components/dialogs/line-types-catalog/limit-custom-aggrid.tsx b/src/components/dialogs/line-types-catalog/limit-custom-aggrid.tsx index db85a2f704..f60ce0b34e 100644 --- a/src/components/dialogs/line-types-catalog/limit-custom-aggrid.tsx +++ b/src/components/dialogs/line-types-catalog/limit-custom-aggrid.tsx @@ -5,10 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { AgGridReact } from 'ag-grid-react'; -import { CATEGORIES_TABS, LineTypeInfo } from './line-catalog.type'; import { ColDef } from 'ag-grid-community'; import { RefObject, useMemo } from 'react'; -import { CustomAGGrid } from '@gridsuite/commons-ui'; +import { CATEGORIES_TABS, CustomAGGrid, LineTypeInfo } from '@gridsuite/commons-ui'; import { AGGRID_LOCALES } from '../../../translations/not-intl/aggrid-locales'; import { suppressEventsToPreventEditMode } from '../commons/utils'; diff --git a/src/components/dialogs/line-types-catalog/line-catalog.type.ts b/src/components/dialogs/line-types-catalog/line-catalog.type.ts deleted file mode 100644 index 632e083539..0000000000 --- a/src/components/dialogs/line-types-catalog/line-catalog.type.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2025, 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/. - */ - -type AerialLineTypeInfo = { - conductorsNumber: number; - circuitsNumber: number; - groundWiresNumber: number; -}; -type UndergroundLineTypeInfo = { - insulator: string; - screen: string; -}; -// DTO received from back-end -export type LineTypeInfo = { - id: string; - type: string; - category: 'AERIAL' | 'UNDERGROUND'; - voltage: number; - conductorType: string; - section: number; - linearResistance: number; - linearReactance: number; - linearCapacity: number; - limitsForLineType: CurrentLimitsInfo[]; - shapeFactors: number[]; -} & (AerialLineTypeInfo | UndergroundLineTypeInfo); - -// Interface with Line creation/modification -export type ComputedLineCharacteristics = { - totalResistance: number; - totalReactance: number; - totalSusceptance: number; - finalCurrentLimits: CurrentLimitsInfo[]; -}; - -export type CurrentLimitHeader = { - limitSetName: string; - permanentLimit: number; -}; - -export type CurrentLimitsInfo = CurrentLimitHeader & { - temporaryLimits: TemporaryLimitsInfo[]; - area: string; - temperature: string; -}; - -export type LimitSelectedRowData = CurrentLimitHeader & TemporaryLimitSelectedRowData; - -export type TemporaryLimitSelectedRowData = Record; - -export type AreaTemperatureShapeFactorInfo = { - area: string | null; - temperature: string | null; - shapeFactor: number | null; -}; - -export type TemporaryLimitsInfo = { - limitValue: number; - acceptableDuration: number; - name: string; -}; - -export const CATEGORIES_TABS = { - AERIAL: { id: 0, name: 'AERIAL' }, - UNDERGROUND: { id: 1, name: 'UNDERGROUND' }, -} as const; 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 7887cc3679..9a99529300 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 @@ -11,9 +11,15 @@ import * as yup from 'yup'; import { ModificationDialog } from '../commons/modificationDialog'; import { useForm } from 'react-hook-form'; import { LineTypeSegmentForm } from './line-type-segment-form'; -import { CustomFormProvider, DeepNullable } from '@gridsuite/commons-ui'; -import { ComputedLineCharacteristics } from './line-catalog.type'; -import { SegmentFormData, SegmentSchema, SegmentsFormData } from './segment-utils'; +import { + ComputedLineCharacteristics, + CustomFormProvider, + DeepNullable, + SegmentSchema, + LineSegmentsFormData, + SegmentFormData, + SegmentsFormData, +} from '@gridsuite/commons-ui'; import { APPLY_SEGMENTS_LIMITS, FINAL_CURRENT_LIMITS, @@ -37,9 +43,9 @@ const LineTypeSegmentSchema = yup .required(); const emptyFormData = { - [TOTAL_RESISTANCE]: 0.0, - [TOTAL_REACTANCE]: 0.0, - [TOTAL_SUSCEPTANCE]: 0.0, + [TOTAL_RESISTANCE]: 0, + [TOTAL_REACTANCE]: 0, + [TOTAL_SUSCEPTANCE]: 0, [APPLY_SEGMENTS_LIMITS]: true, [FINAL_CURRENT_LIMITS]: [], [SEGMENTS]: [], @@ -48,23 +54,25 @@ const emptyFormData = { export interface LineTypeSegmentDialogProps { open: boolean; onClose: () => void; - onSave: ( + onSaveCreationCase?: (data: ComputedLineCharacteristics, lineSegments: LineSegmentsFormData) => void; + editDataCreationCase?: LineSegmentsFormData | null; + onSaveModificationCase?: ( data: ComputedLineCharacteristics, lineSegments: DeepNullable[], applyLimits: boolean | null ) => void; - editData?: SegmentsFormData; - isModification?: boolean; + editDataModificationCase?: SegmentsFormData; } export type LineTypeSegmentDialogSchemaForm = InferType; export default function LineTypeSegmentDialog({ open, - onSave, + onSaveCreationCase, + onSaveModificationCase, onClose, - editData, - isModification = false, + editDataCreationCase, + editDataModificationCase, }: Readonly) { const formMethods = useForm>({ defaultValues: emptyFormData, @@ -79,9 +87,14 @@ export default function LineTypeSegmentDialog({ const onSubmit = useCallback( (data: ComputedLineCharacteristics) => { - onSave(data, getValues(`${SEGMENTS}`) ?? [], getValues(APPLY_SEGMENTS_LIMITS)); + if (onSaveModificationCase) { + onSaveModificationCase(data, getValues(`${SEGMENTS}`) ?? [], getValues(APPLY_SEGMENTS_LIMITS)); + } + if (onSaveCreationCase) { + onSaveCreationCase(data, (getValues(`${SEGMENTS}`) as LineSegmentsFormData) ?? []); + } }, - [getValues, onSave] + [getValues, onSaveCreationCase, onSaveModificationCase] ); return ( @@ -95,7 +108,11 @@ export default function LineTypeSegmentDialog({ onClose={onClose} onSave={onSubmit} > - + ); diff --git a/src/components/dialogs/line-types-catalog/line-type-segment-form.tsx b/src/components/dialogs/line-types-catalog/line-type-segment-form.tsx index f6757c26c2..ae86af7450 100644 --- a/src/components/dialogs/line-types-catalog/line-type-segment-form.tsx +++ b/src/components/dialogs/line-types-catalog/line-type-segment-form.tsx @@ -13,7 +13,6 @@ import { APPLY_SEGMENTS_LIMITS, AREA, FINAL_CURRENT_LIMITS, - LINE_SEGMENTS, SEGMENT_CURRENT_LIMITS, SEGMENT_DISTANCE_VALUE, SEGMENT_REACTANCE, @@ -33,26 +32,29 @@ import { roundToDefaultPrecision } from '../../../utils/rounding'; import LineTypeSegmentCreation from './line-type-segment-creation'; import { calculateReactance, calculateResistance, calculateSusceptance } from '../../utils/utils'; import { + AreaTemperatureShapeFactorInfo, + CurrentLimitsInfo, CustomAGGrid, DefaultCellRenderer, + emptyLineSegment, ExpandableInput, ExpandableInputHandle, fetchStudyMetadata, + LimitSelectedRowData, + LineTypeInfo, type MuiStyles, ReadOnlyInput, + SegmentFormData, + LineSegmentsFormData, snackWithFallback, SwitchInput, useSnackMessage, + SegmentInfoFormData, + SegmentsFormData, + FieldConstants, } from '@gridsuite/commons-ui'; import { getLineTypesCatalog, getLineTypeWithLimits } from '../../../services/network-modification'; import { GridItem } from '../commons/grid-item'; -import { - AreaTemperatureShapeFactorInfo, - CurrentLimitsInfo, - LimitSelectedRowData, - LineTypeInfo, -} from './line-catalog.type'; -import { emptyLineSegment, SegmentFormData, SegmentsFormData } from './segment-utils'; import { ColDef } from 'ag-grid-community'; import { GridSection } from '../commons/grid-section'; @@ -72,11 +74,16 @@ const styles = { } as const satisfies MuiStyles; export interface LineTypeSegmentFormProps { - editData?: SegmentsFormData; + editDataCreationCase?: LineSegmentsFormData | null; + editDataModificationCase?: SegmentsFormData; isModification: boolean; } -export const LineTypeSegmentForm: FunctionComponent = ({ editData, isModification }) => { +export const LineTypeSegmentForm: FunctionComponent = ({ + editDataCreationCase, + editDataModificationCase, + isModification, +}) => { const { setValue, getValues, clearErrors, watch } = useFormContext(); const [lineTypesCatalog, setLineTypesCatalog] = useState([]); const [openCatalogDialogIndex, setOpenCatalogDialogIndex] = useState(null); @@ -198,7 +205,7 @@ export const LineTypeSegmentForm: FunctionComponent = setValue(FINAL_CURRENT_LIMITS, Array.from(mostContrainingLimits.values())); }, [getValues, setValue, setCurrentLimitResult]); - const getSegmentLimits = useCallback((segment: SegmentFormData) => { + const getSegmentLimits = useCallback((segment: SegmentInfoFormData) => { return getLineTypeWithLimits( segment[SEGMENT_TYPE_ID], segment[AREA], @@ -217,11 +224,11 @@ export const LineTypeSegmentForm: FunctionComponent = return { ...emptyLineSegment, [SEGMENT_TYPE_ID]: segment[SEGMENT_TYPE_ID], - [SEGMENT_TYPE_VALUE]: lineTypeWithLimits?.type ?? '', + [SEGMENT_DISTANCE_VALUE]: segment[SEGMENT_DISTANCE_VALUE], [AREA]: segment[AREA], [TEMPERATURE]: segment[TEMPERATURE], [SHAPE_FACTOR]: segment[SHAPE_FACTOR], - [SEGMENT_DISTANCE_VALUE]: segment[SEGMENT_DISTANCE_VALUE], + [SEGMENT_TYPE_VALUE]: lineTypeWithLimits?.type ?? '', [SEGMENT_RESISTANCE]: newResistance, [SEGMENT_REACTANCE]: newReactance, [SEGMENT_SUSCEPTANCE]: newSusceptance, @@ -231,12 +238,23 @@ export const LineTypeSegmentForm: FunctionComponent = }, []); useEffect(() => { - if (!editData?.[LINE_SEGMENTS]?.length) { + if (isModification && !editDataModificationCase?.[FieldConstants.LINE_SEGMENTS]?.length) { + return; + } + if (!isModification && !editDataCreationCase?.length) { return; } arrayRef.current?.replaceItems([]); const updateSegmentsLimits = async () => { - const promises = editData[LINE_SEGMENTS]?.map((segment) => getSegmentLimits(segment)); + let promises; + if (!isModification && editDataCreationCase) { + promises = editDataCreationCase.map((segment) => getSegmentLimits(segment)); + } + if (isModification && editDataModificationCase) { + promises = editDataModificationCase[FieldConstants.LINE_SEGMENTS]?.map((segment) => + getSegmentLimits(segment) + ); + } try { if (promises) { @@ -254,9 +272,19 @@ export const LineTypeSegmentForm: FunctionComponent = updateSegmentsLimits().then(() => { updateTotals(); keepMostConstrainingLimits(); - setValue(APPLY_SEGMENTS_LIMITS, editData?.applySegmentsLimits ?? true); + const applyLimits = isModification ? (editDataModificationCase?.applySegmentsLimits ?? true) : true; + setValue(APPLY_SEGMENTS_LIMITS, applyLimits); }); - }, [editData, getSegmentLimits, snackError, updateTotals, keepMostConstrainingLimits, setValue]); + }, [ + editDataCreationCase, + editDataModificationCase, + isModification, + getSegmentLimits, + snackError, + updateTotals, + keepMostConstrainingLimits, + setValue, + ]); const onSelectCatalogLine = useCallback( (selectedLine: LineTypeInfo, selectedAreaAndTemperature2LineTypeData: AreaTemperatureShapeFactorInfo) => { diff --git a/src/components/dialogs/line-types-catalog/line-types-catalog-selector-dialog.tsx b/src/components/dialogs/line-types-catalog/line-types-catalog-selector-dialog.tsx index 779a083744..7c74c1acd0 100644 --- a/src/components/dialogs/line-types-catalog/line-types-catalog-selector-dialog.tsx +++ b/src/components/dialogs/line-types-catalog/line-types-catalog-selector-dialog.tsx @@ -6,9 +6,18 @@ */ import { useCallback, useRef, useState } from 'react'; -import { CustomFormProvider, DeepNullable, Option, snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui'; +import { + AreaTemperatureShapeFactorInfo, + CATEGORIES_TABS, + CurrentLimitsInfo, + CustomFormProvider, + DeepNullable, + LineTypeInfo, + Option, + snackWithFallback, + useSnackMessage, +} from '@gridsuite/commons-ui'; import { AgGridReact } from 'ag-grid-react'; -import { AreaTemperatureShapeFactorInfo, CATEGORIES_TABS, CurrentLimitsInfo, LineTypeInfo } from './line-catalog.type'; import { AERIAL_AREAS, AERIAL_TEMPERATURES, @@ -214,8 +223,8 @@ export default function LineTypesCatalogSelectorDialog({ onClose={onClose} onSave={onSubmit} open={true} - PaperProps={{ - sx: { height: '95vh' }, + slotProps={{ + paper: { sx: { height: '95vh' } }, }} titleId="SelectType" {...dialogProps} diff --git a/src/components/dialogs/line-types-catalog/line-types-catalog-selector-form.tsx b/src/components/dialogs/line-types-catalog/line-types-catalog-selector-form.tsx index b4dac37da0..263550122c 100644 --- a/src/components/dialogs/line-types-catalog/line-types-catalog-selector-form.tsx +++ b/src/components/dialogs/line-types-catalog/line-types-catalog-selector-form.tsx @@ -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 { areIdsEqual, AutocompleteInput, Option } from '@gridsuite/commons-ui'; +import { areIdsEqual, AutocompleteInput, CATEGORIES_TABS, LineTypeInfo, Option } from '@gridsuite/commons-ui'; import { GridItem } from '../commons/grid-item'; import { GridSection } from '../commons/grid-section'; import { Grid2 as Grid, Tab, Tabs } from '@mui/material'; @@ -15,7 +15,6 @@ import { UNDERGROUND_AREAS, UNDERGROUND_SHAPE_FACTORS, } from '../../utils/field-constants'; -import { CATEGORIES_TABS, LineTypeInfo } from './line-catalog.type'; import { useFormContext } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import LimitCustomAgGrid from './limit-custom-aggrid'; diff --git a/src/components/dialogs/line-types-catalog/segment-utils.ts b/src/components/dialogs/line-types-catalog/segment-utils.ts deleted file mode 100644 index dcde9f1c4b..0000000000 --- a/src/components/dialogs/line-types-catalog/segment-utils.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2025, 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 { - APPLY_SEGMENTS_LIMITS, - AREA, - LIMIT_SET_NAME, - LIMIT_VALUE, - LINE_SEGMENTS, - PERMANENT_LIMIT, - SEGMENT_CURRENT_LIMITS, - SEGMENT_DISTANCE_VALUE, - SEGMENT_REACTANCE, - SEGMENT_RESISTANCE, - SEGMENT_SUSCEPTANCE, - SEGMENT_TYPE_ID, - SEGMENT_TYPE_VALUE, - SHAPE_FACTOR, - TEMPERATURE, - TEMPORARY_LIMIT_DURATION, - TEMPORARY_LIMIT_NAME, - TEMPORARY_LIMITS, -} from 'components/utils/field-constants'; -import * as yup from 'yup'; -import { InferType } from 'yup'; -import { LineSegmentInfos } from '../../../services/network-modification-types'; -import { DeepNullable } from '@gridsuite/commons-ui'; -import { CurrentLimitsInfo } from './line-catalog.type'; -import { OperationalLimitsGroupFormSchema, TemporaryLimitFormSchema } from '../limits/operational-limits-groups-types'; -import { APPLICABILITY } from '../../network/constants'; - -export const SegmentTemporaryLimitSchema = yup.object().shape({ - [LIMIT_VALUE]: yup.number().required(), - [TEMPORARY_LIMIT_DURATION]: yup.number().required(), - [TEMPORARY_LIMIT_NAME]: yup.string().required(), -}); - -export const SegmentCurrentLimitsSchema = yup.object().shape({ - [LIMIT_SET_NAME]: yup.string().required(), - [PERMANENT_LIMIT]: yup.number().required(), - [TEMPORARY_LIMITS]: yup.array().of(SegmentTemporaryLimitSchema).nullable(), -}); - -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(), - [AREA]: yup.string().nullable().default(null), - [TEMPERATURE]: yup.string().nullable().default(null), - [SHAPE_FACTOR]: yup.number().nullable().default(null), - [SEGMENT_RESISTANCE]: yup.number().required(), - [SEGMENT_REACTANCE]: yup.number().required(), - [SEGMENT_SUSCEPTANCE]: yup.number().required(), - [SEGMENT_CURRENT_LIMITS]: yup.array().of(SegmentCurrentLimitsSchema), -}); - -export type SegmentFormData = InferType; -export type SegmentTemporaryLimitFormData = InferType; -export type SegmentCurrentLimitsFormData = InferType; - -export interface SegmentsFormData { - [LINE_SEGMENTS]: SegmentFormData[]; - [APPLY_SEGMENTS_LIMITS]?: boolean; -} - -export const emptyLineSegment: SegmentFormData = { - [SEGMENT_DISTANCE_VALUE]: 0.0, - [SEGMENT_TYPE_VALUE]: '', - [SEGMENT_TYPE_ID]: '', - [SEGMENT_RESISTANCE]: 0.0, - [SEGMENT_REACTANCE]: 0.0, - [SEGMENT_SUSCEPTANCE]: 0.0, - [SEGMENT_CURRENT_LIMITS]: [], - [AREA]: null, - [TEMPERATURE]: null, - [SHAPE_FACTOR]: null, -}; - -export function convertToLineSegmentInfos(lineSegments: DeepNullable[]): LineSegmentInfos[] { - return ( - lineSegments - ?.filter((segment): segment is SegmentFormData => segment != null && segment[SEGMENT_TYPE_ID] !== null) - .map((segment) => ({ - [SEGMENT_TYPE_ID]: segment[SEGMENT_TYPE_ID], - [SEGMENT_DISTANCE_VALUE]: segment[SEGMENT_DISTANCE_VALUE], - [AREA]: segment[AREA], - [TEMPERATURE]: segment[TEMPERATURE] ?? '', - [SHAPE_FACTOR]: segment[SHAPE_FACTOR], - })) ?? [] - ); -} - -export function convertLimitsToOperationalLimitsGroupFormSchema(limitSets: CurrentLimitsInfo[]) { - const finalLimitSets: OperationalLimitsGroupFormSchema[] = []; - limitSets.forEach((limitSet: CurrentLimitsInfo) => { - const temporaryLimitsList: TemporaryLimitFormSchema[] = []; - limitSet.temporaryLimits?.forEach((temporaryLimit) => { - temporaryLimitsList.push({ - name: temporaryLimit.name, - acceptableDuration: temporaryLimit.acceptableDuration, - value: temporaryLimit.limitValue, - }); - }); - finalLimitSets.push({ - id: limitSet.limitSetName + APPLICABILITY.EQUIPMENT.id, - name: limitSet.limitSetName, - applicability: APPLICABILITY.EQUIPMENT.id, - currentLimits: { - id: limitSet.limitSetName, - permanentLimit: limitSet.permanentLimit, - temporaryLimits: temporaryLimitsList, - }, - }); - }); - return finalLimitSets; -} diff --git a/src/components/dialogs/line-types-catalog/use-row-data.ts b/src/components/dialogs/line-types-catalog/use-row-data.ts index 8fb7f116fc..768defe7d0 100644 --- a/src/components/dialogs/line-types-catalog/use-row-data.ts +++ b/src/components/dialogs/line-types-catalog/use-row-data.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { useMemo } from 'react'; -import { CATEGORIES_TABS, LineTypeInfo } from './line-catalog.type'; +import { CATEGORIES_TABS, LineTypeInfo } from '@gridsuite/commons-ui'; export const useRowData = (rowData: LineTypeInfo[]) => { const aerialRowData = useMemo( diff --git a/src/components/dialogs/network-modifications/limit-sets/limit-sets-modification-dialog.tsx b/src/components/dialogs/network-modifications/limit-sets/limit-sets-modification-dialog.tsx index b2d19ba4ac..ea4731eeed 100644 --- a/src/components/dialogs/network-modifications/limit-sets/limit-sets-modification-dialog.tsx +++ b/src/components/dialogs/network-modifications/limit-sets/limit-sets-modification-dialog.tsx @@ -9,11 +9,16 @@ import { CSV_FILENAME, MODIFICATIONS_TABLE, OLGS_MODIFICATION_TYPE, - OPERATIONAL_LIMITS_GROUPS_MODIFICATION_TYPE, TYPE, } from '../../../utils/field-constants'; import { useIntl } from 'react-intl'; -import { CustomFormProvider, ModificationType, snackWithFallback, useSnackMessage } from '@gridsuite/commons-ui'; +import { + CustomFormProvider, + ModificationType, + OPERATIONAL_LIMITS_GROUPS_MODIFICATION_TYPE, + snackWithFallback, + useSnackMessage, +} from '@gridsuite/commons-ui'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { useCallback, useEffect, useMemo } from 'react'; diff --git a/src/components/dialogs/network-modifications/limit-sets/limit-sets-tabular-modification-utils.ts b/src/components/dialogs/network-modifications/limit-sets/limit-sets-tabular-modification-utils.ts index 851333a732..8860827f1b 100644 --- a/src/components/dialogs/network-modifications/limit-sets/limit-sets-tabular-modification-utils.ts +++ b/src/components/dialogs/network-modifications/limit-sets/limit-sets-tabular-modification-utils.ts @@ -26,8 +26,7 @@ import { import * as yup from 'yup'; import type { UUID } from 'node:crypto'; import { LIMIT_SETS_TABULAR_MODIFICATION_EQUIPMENTS } from '../tabular/tabular-modification-utils'; -import { APPLICABILITY } from '../../../network/constants'; -import { AttributeModification, EquipmentType, toModificationOperation } from '@gridsuite/commons-ui'; +import { APPLICABILITY, AttributeModification, EquipmentType, toModificationOperation } from '@gridsuite/commons-ui'; type TemporaryLimit = { name: AttributeModification; diff --git a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.tsx b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.tsx index 6bbec38029..6534ff2a42 100644 --- a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.tsx +++ b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-dialog.tsx @@ -12,6 +12,7 @@ import { getConnectivityPropertiesValidationSchema, getConnectivityWithoutPositionEmptyFormData, getNewVoltageLevelData, + LineCreationDto, ModificationType, sanitizeString, snackWithFallback, @@ -56,7 +57,6 @@ import { FetchStatus } from '../../../../services/utils.type'; import { AttachLineInfo, ExtendedVoltageLevelCreationInfo, - LineCreationInfos, VoltageLevelCreationInfo, } from '../../../../services/network-modification-types'; @@ -124,7 +124,7 @@ const LineAttachToVoltageLevelDialog = ({ }: LineAttachToVoltageLevelDialogProps) => { const currentNodeUuid = currentNode?.id; - const [attachmentLine, setAttachmentLine] = useState(); + const [attachmentLine, setAttachmentLine] = useState(); const [newVoltageLevel, setNewVoltageLevel] = useState(); const [attachmentPoint, setAttachmentPoint] = useState({ type: ModificationType.VOLTAGE_LEVEL_CREATION, @@ -254,7 +254,7 @@ const LineAttachToVoltageLevelDialog = ({ }, [reset]); const onLineCreationDo = useCallback( - ({ lineCreationInfos }: { lineCreationInfos: LineCreationInfos }) => { + ({ lineCreationInfos }: { lineCreationInfos: LineCreationDto }) => { return new Promise(() => { // clean unused (required) fields by a simple copy with casting const { @@ -273,7 +273,7 @@ const LineAttachToVoltageLevelDialog = ({ properties, } = lineCreationInfos; - const preparedLine: LineCreationInfos = { + const preparedLine: LineCreationDto = { type, equipmentId, equipmentName, @@ -287,7 +287,7 @@ const LineAttachToVoltageLevelDialog = ({ selectedOperationalLimitsGroupId1, selectedOperationalLimitsGroupId2, properties, - } as LineCreationInfos; + } as LineCreationDto; setAttachmentLine(preparedLine); setValue(`${ATTACHMENT_LINE_ID}`, preparedLine.equipmentId, { @@ -368,7 +368,7 @@ const LineAttachToVoltageLevelDialog = ({ setNewVoltageLevel(preparedVoltageLevel); setValue( - `${CONNECTIVITY}.${VOLTAGE_LEVEL}`, + `${CONNECTIVITY}.${VOLTAGE_LEVEL}` as any, { [ID]: preparedVoltageLevel.equipmentId, }, @@ -377,7 +377,7 @@ const LineAttachToVoltageLevelDialog = ({ shouldDirty: true, } ); - setValue(`${CONNECTIVITY}.${BUS_OR_BUSBAR_SECTION}`, null); + setValue(`${CONNECTIVITY}.${BUS_OR_BUSBAR_SECTION}` as any, null); }); }, [currentNodeUuid, studyUuid, newVoltageLevel?.equipmentId, voltageLevelOptions, setValue] diff --git a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-form.tsx b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-form.tsx index adaa5580ba..43e84662c4 100644 --- a/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-form.tsx +++ b/src/components/dialogs/network-modifications/line-attach-to-voltage-level/line-attach-to-voltage-level-form.tsx @@ -26,6 +26,7 @@ import { VoltageLevelConnectivityForm, TextInput, VoltageLevelOption, + LineCreationDto, } from '@gridsuite/commons-ui'; import LineCreationDialog from '../line/creation/line-creation-dialog'; import VoltageLevelCreationDialog from '../voltage-level/creation/voltage-level-creation-dialog'; @@ -37,7 +38,6 @@ import { UUID } from 'node:crypto'; import { CurrentTreeNode } from '../../../graph/tree-node.type'; import { ExtendedVoltageLevelCreationInfo, - LineCreationInfos, VoltageLevelCreationInfo, } from '../../../../services/network-modification-types'; import { FetchStatus } from '../../../../services/utils.type'; @@ -47,8 +47,8 @@ interface LineAttachToVoltageLevelFormProps { studyUuid: UUID; currentNode: CurrentTreeNode; currentRootNetworkUuid: UUID; - onLineCreationDo: ({ lineCreationInfos }: { lineCreationInfos: LineCreationInfos }) => Promise; - lineToEdit?: LineCreationInfos; + onLineCreationDo: ({ lineCreationInfos }: { lineCreationInfos: LineCreationDto }) => Promise; + lineToEdit?: LineCreationDto; onVoltageLevelCreationDo: (voltageLevel: VoltageLevelCreationInfo) => Promise; voltageLevelToEdit?: ExtendedVoltageLevelCreationInfo; onAttachmentPointModificationDo: (voltageLevel: VoltageLevelCreationInfo) => Promise; diff --git a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.ts b/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.ts deleted file mode 100644 index db88e5e53a..0000000000 --- a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane-utils.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2023, 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 { - B1, - B2, - CHARACTERISTICS, - CONNECTIVITY_1, - CONNECTIVITY_2, - G1, - G2, - R, - X, -} from 'components/utils/field-constants'; -import * as yup from 'yup'; -import { LineCharacteristics } from '../modification/line-modification-type'; -import { - Connectivity, - FieldConstants, - getConnectivityWithPositionEmptyFormData, - getConnectivityWithPositionValidationSchema, -} from '@gridsuite/commons-ui'; - -const characteristicsValidationSchema = (id: string, displayConnectivity: boolean, modification: boolean) => ({ - [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, FieldConstants.CONNECTIVITY_1)), - ...(displayConnectivity && getConnectivityWithPositionValidationSchema(false, FieldConstants.CONNECTIVITY_2)), - }), -}); - -export const getCharacteristicsValidationSchema = ( - id: string, - displayConnectivity: boolean, - modification: boolean = false -) => { - return characteristicsValidationSchema(id, displayConnectivity, modification); -}; - -const characteristicsEmptyFormData = (id: string, displayConnectivity: boolean = true) => ({ - [id]: { - [R]: null, - [X]: null, - [B1]: null, - [G1]: null, - [B2]: null, - [G2]: null, - ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, FieldConstants.CONNECTIVITY_1)), - ...(displayConnectivity && getConnectivityWithPositionEmptyFormData(false, FieldConstants.CONNECTIVITY_2)), - }, -}); - -export const getCharacteristicsEmptyFormData = (id: string = CHARACTERISTICS, displayConnectivity: boolean = true) => { - return characteristicsEmptyFormData(id, displayConnectivity); -}; - -export const getCharacteristicsFormData = ( - { - r = null, - x = null, - g1 = null, - b1 = null, - g2 = null, - b2 = null, - connectivity1 = null, - connectivity2 = null, - }: LineCharacteristics & { - connectivity1?: Connectivity | null; - connectivity2?: Connectivity | null; - }, - id = CHARACTERISTICS -) => ({ - [id]: { - [R]: r, - [X]: x, - [G1]: g1, - [B1]: b1, - [G2]: g2, - [B2]: b2, - [CONNECTIVITY_1]: connectivity1, - [CONNECTIVITY_2]: connectivity2, - }, -}); - -export const getCharacteristicsWithOutConnectivityFormData = ( - { r, x, g1, b1, g2, b2 }: LineCharacteristics, - id = CHARACTERISTICS -) => ({ - [id]: { - [R]: r, - [X]: x, - [G1]: g1, - [B1]: b1, - [G2]: g2, - [B2]: b2, - }, -}); diff --git a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane.tsx b/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane.tsx deleted file mode 100644 index 80ca94de0a..0000000000 --- a/src/components/dialogs/network-modifications/line/characteristics-pane/line-characteristics-pane.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright (c) 2023, 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 { Grid2 as Grid } from '@mui/material'; -import { - ConnectivityForm, - convertInputValue, - FieldType, - FloatInput, - MicroSusceptanceAdornment, - OhmAdornment, - PropertiesForm, -} from '@gridsuite/commons-ui'; -import { - B1, - B2, - CHARACTERISTICS, - CONNECTIVITY_1, - CONNECTIVITY_2, - G1, - G2, - R, - X, -} from 'components/utils/field-constants'; -import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos'; -import { GridSection } from '../../../commons/grid-section'; -import { GridItem } from '../../../commons/grid-item'; -import { UUID } from 'node:crypto'; -import { CurrentTreeNode } from '../../../../graph/tree-node.type'; -import { BranchInfos } from '../../../../../services/study/network-map.type'; -import PositionDiagramPane from '../../../../grid-layout/cards/diagrams/singleLineDiagram/positionDiagram/position-diagram-pane'; -import { useCallback } from 'react'; -import { fetchBusesOrBusbarSectionsForVoltageLevel } from '../../../../../services/study/network'; - -const styles = { - h3: { - marginTop: 0, - marginBottom: 0, - }, -}; - -interface LineCharacteristicsPaneProps { - id?: string; - studyUuid: UUID; - currentNode: CurrentTreeNode; - currentRootNetworkUuid: UUID; - displayConnectivity: boolean; - lineToModify?: BranchInfos | null; - clearableFields?: boolean; - isModification?: boolean; -} -const LineCharacteristicsPane = ({ - id = CHARACTERISTICS, - studyUuid, - currentNode, - currentRootNetworkUuid, - displayConnectivity, - lineToModify, - clearableFields = false, - isModification = false, -}: LineCharacteristicsPaneProps) => { - const currentNodeUuid = currentNode.id; - const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNodeUuid, currentRootNetworkUuid); - - const fetchBusesOrBusbarSections = useCallback( - (voltageLevelId: string) => - fetchBusesOrBusbarSectionsForVoltageLevel( - studyUuid, - currentNode.id, - currentRootNetworkUuid, - voltageLevelId - ), - [studyUuid, currentNode.id, currentRootNetworkUuid] - ); - - const seriesResistanceField = ( - - ); - - const seriesReactanceField = ( - - ); - - const shuntConductance1Field = ( - - ); - - const shuntSusceptance1Field = ( - - ); - - const shuntConductance2Field = ( - - ); - - const shuntSusceptance2Field = ( - - ); - - const connectivity1Field = ( - - ); - - const connectivity2Field = ( - - ); - - return ( - <> - {displayConnectivity && ( - <> - - - - {connectivity1Field} - - - - {connectivity2Field} - - - )} - - - {seriesResistanceField} - {seriesReactanceField} - - - - {shuntConductance1Field} - {shuntSusceptance1Field} - - - - {shuntConductance2Field} - {shuntSusceptance2Field} - - - - ); -}; - -export default LineCharacteristicsPane; diff --git a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.ts b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.ts deleted file mode 100644 index f361b5b61d..0000000000 --- a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog-utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2023, 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 { EQUIPMENT_ID, EQUIPMENT_NAME, TAB_HEADER } from 'components/utils/field-constants'; -import * as yup from 'yup'; - -const headerValidationSchema = (id: string) => ({ - [id]: yup.object().shape({ - [EQUIPMENT_ID]: yup.string().required(), - [EQUIPMENT_NAME]: yup.string().nullable(), - }), -}); - -export const getHeaderValidationSchema = (id = TAB_HEADER) => { - return headerValidationSchema(id); -}; - -const headerEmptyFormData = (id: string) => ({ - [id]: { - [EQUIPMENT_ID]: '', - [EQUIPMENT_NAME]: '', - }, -}); - -export const getHeaderEmptyFormData = (id = TAB_HEADER) => { - return headerEmptyFormData(id); -}; - -export const getHeaderFormData = ( - { equipmentId, equipmentName = '' }: { equipmentId: string; equipmentName: string | null }, - id = TAB_HEADER -) => ({ - [id]: { - [EQUIPMENT_ID]: equipmentId, - [EQUIPMENT_NAME]: equipmentName, - }, -}); - -export const LineCreationDialogTab = { - CHARACTERISTICS_TAB: 0, - LIMITS_TAB: 1, -}; diff --git a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.tsx b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.tsx index 220bf10bdf..f78eba0e4c 100644 --- a/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.tsx +++ b/src/components/dialogs/network-modifications/line/creation/line-creation-dialog.tsx @@ -6,114 +6,52 @@ */ import { + ComputedLineCharacteristics, convertInputValue, - convertOutputValue, + convertLimitsToOperationalLimitsGroupFormSchema, + convertToLineSegmentInfos, copyEquipmentPropertiesForCreation, - creationPropertiesSchema, CustomFormProvider, DeepNullable, - emptyProperties, EquipmentType, FieldConstants, FieldType, - filledTextField, + getAllLimitsFormData, getConnectivityFormData, - getPropertiesFromModification, - ModificationType, - sanitizeString, + getLineCharacteristicsFormData, + LineCreationDto, + lineCreationDtoToForm, + lineCreationEmptyFormData, + LineCreationFormData, + lineCreationFormSchema, + lineCreationFormToDto, + LineForm, + LineFormInfos, + LineSegmentsFormData, snackWithFallback, - TextInput, - toModificationProperties, - UNDEFINED_CONNECTION_DIRECTION, useSnackMessage, } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Grid2 as Grid } from '@mui/material'; -import { - B1, - B2, - BUS_OR_BUSBAR_SECTION, - CHARACTERISTICS, - CONNECTED, - CONNECTION_DIRECTION, - CONNECTION_NAME, - CONNECTION_POSITION, - CONNECTIVITY_1, - CONNECTIVITY_2, - EQUIPMENT_ID, - EQUIPMENT_NAME, - FINAL_CURRENT_LIMITS, - G1, - G2, - LIMITS, - LINE_SEGMENTS, - OPERATIONAL_LIMITS_GROUPS, - R, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, - TAB_HEADER, - TOTAL_REACTANCE, - TOTAL_RESISTANCE, - TOTAL_SUSCEPTANCE, - VOLTAGE_LEVEL, - X, -} from 'components/utils/field-constants'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { FieldErrors, useForm } from 'react-hook-form'; +import { useCallback, useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; import { FetchStatus } from '../../../../../services/utils'; import { FORM_LOADING_DELAY } from 'components/network/constants'; -import * as yup from 'yup'; import { ModificationDialog } from '../../../commons/modificationDialog'; -import LineCharacteristicsPane from '../characteristics-pane/line-characteristics-pane'; -import { - getCharacteristicsEmptyFormData, - getCharacteristicsFormData, - getCharacteristicsValidationSchema, -} from '../characteristics-pane/line-characteristics-pane-utils'; -import { - getHeaderEmptyFormData, - getHeaderFormData, - getHeaderValidationSchema, - LineCreationDialogTab, -} from './line-creation-dialog-utils'; -import { - getAllLimitsFormData, - getLimitsEmptyFormData, - getLimitsValidationSchema, - sanitizeLimitsGroups, -} from '../../../limits/limits-pane-utils'; -import LineDialogTabs from '../line-dialog-tabs'; import EquipmentSearchDialog from 'components/dialogs/equipment-search-dialog'; import { useFormSearchCopy } from 'components/dialogs/commons/use-form-search-copy'; -import LineTypeSegmentDialog from '../../../line-types-catalog/line-type-segment-dialog'; import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; import { createLine } from '../../../../../services/study/network-modifications'; -import { GridItem } from '../../../commons/grid-item'; import { formatCompleteCurrentLimit } from '../../../../utils/utils'; -import { LimitsPane } from '../../../limits/limits-pane'; -import { LineCreationInfos } from '../../../../../services/network-modification-types'; -import { LineModificationFormSchema } from '../modification/line-modification-type'; -import { ComputedLineCharacteristics } from '../../../line-types-catalog/line-catalog.type'; -import { LineCreationFormSchema, LineFormInfos } from './line-creation-type'; import { isNodeBuilt } from 'components/graph/util/model-functions'; import { NetworkModificationDialogProps } from '../../../../graph/menus/network-modifications/network-modification-menu.type'; -import { - convertLimitsToOperationalLimitsGroupFormSchema, - convertToLineSegmentInfos, - SegmentFormData, -} from '../../../line-types-catalog/segment-utils'; - -const emptyFormData: any = { - ...getHeaderEmptyFormData(), - ...getCharacteristicsEmptyFormData(), - ...getLimitsEmptyFormData(false), - ...emptyProperties, - [LINE_SEGMENTS]: [], -}; +import PositionDiagramPane from '../../../../grid-layout/cards/diagrams/singleLineDiagram/positionDiagram/position-diagram-pane'; +import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos'; +import { fetchBusesOrBusbarSectionsForVoltageLevel } from '../../../../../services/study/network'; +import LineTypeSegmentDialog from '../../../line-types-catalog/line-type-segment-dialog'; type LineCreationDialogProps = NetworkModificationDialogProps & { - editData?: LineCreationInfos; // contains data when we try to edit an existing hypothesis + editData?: LineCreationDto; onCreateLine: typeof createLine; displayConnectivity?: boolean; }; @@ -125,7 +63,7 @@ type LineCreationDialogProps = NetworkModificationDialogProps & { * @param currentRootNetworkUuid The root network uuid we are currently working on * @param editData the data to edit * @param onCreateLine callback to customize line creation process - * @param displayConnectivity to display connectivity section or not + * @param displayConnectivity to display connectivity tab or not * @param isUpdate check if edition form * @param dialogProps props that are forwarded to the generic ModificationDialog component * @param editDataFetchStatus indicates the status of fetching EditData @@ -143,9 +81,7 @@ const LineCreationDialog = ({ }: Readonly) => { const currentNodeUuid = currentNode?.id; const { snackError } = useSnackMessage(); - - const [tabIndex, setTabIndex] = useState(LineCreationDialogTab.CHARACTERISTICS_TAB); - const [tabIndexesWithError, setTabIndexesWithError] = useState([]); + const voltageLevelOptions = useVoltageLevelsListInfos(studyUuid, currentNode?.id, currentRootNetworkUuid); const [isOpenLineTypesCatalogDialog, setIsOpenLineTypesCatalogDialog] = useState(false); @@ -153,44 +89,20 @@ const LineCreationDialog = ({ setIsOpenLineTypesCatalogDialog(false); }; - const formSchema = yup - .object() - .shape({ - ...getHeaderValidationSchema(), - ...getCharacteristicsValidationSchema(CHARACTERISTICS, displayConnectivity), - ...getLimitsValidationSchema(), - }) - .concat(creationPropertiesSchema) - .required(); - - const formMethods = useForm({ - defaultValues: emptyFormData, - resolver: yupResolver(formSchema), + const formMethods = useForm>({ + defaultValues: lineCreationEmptyFormData, + resolver: yupResolver>(lineCreationFormSchema(displayConnectivity)), }); - const { reset, setValue, watch } = formMethods; - const editSegmentValue = watch(LINE_SEGMENTS); + const { reset, setValue, watch } = formMethods; - const editSegmentsData = useMemo( - () => ({ - [LINE_SEGMENTS]: editSegmentValue ?? [], - }), - [editSegmentValue] - ); + const watchSegments = watch(FieldConstants.LINE_SEGMENTS) as LineSegmentsFormData; const fromSearchCopyToFormValues = (line: LineFormInfos) => { const formData = { - ...getHeaderFormData({ - equipmentId: line.id + '(1)', - equipmentName: line.name ?? '', - }), - ...getCharacteristicsFormData({ - r: line.r, - x: line.x, - g1: convertInputValue(FieldType.G1, line.g1), // this form uses and displays microSiemens - b1: convertInputValue(FieldType.B1, line.b1), - g2: convertInputValue(FieldType.G2, line.g2), - b2: convertInputValue(FieldType.B2, line.b2), + equipmentID: line.id + '(1)', + equipmentName: line.name ?? '', + connectivity: { ...getConnectivityFormData( { voltageLevelId: line.voltageLevelId1, @@ -213,6 +125,14 @@ const LineCreationDialog = ({ }, FieldConstants.CONNECTIVITY_2 ), + }, + ...getLineCharacteristicsFormData({ + r: line.r, + x: line.x, + g1: convertInputValue(FieldType.G1, line.g1), // this form uses and displays microSiemens + b1: convertInputValue(FieldType.B1, line.b1), + g2: convertInputValue(FieldType.G2, line.g2), + b2: convertInputValue(FieldType.B2, line.b2), }), ...getAllLimitsFormData( formatCompleteCurrentLimit(line.currentLimits), @@ -220,61 +140,14 @@ const LineCreationDialog = ({ line.selectedOperationalLimitsGroupId2 ?? null ), ...copyEquipmentPropertiesForCreation(line), - [LINE_SEGMENTS]: [], + [FieldConstants.LINE_SEGMENTS]: [], }; reset(formData, { keepDefaultValues: true }); }; const fromEditDataToFormValues = useCallback( - (line: LineCreationInfos) => { - const formData = { - ...getHeaderFormData({ - equipmentId: line.equipmentId, - equipmentName: line.equipmentName, - }), - ...getCharacteristicsFormData({ - r: line.r, - x: line.x, - g1: convertInputValue(FieldType.G1, line.g1), - b1: convertInputValue(FieldType.B1, line.b1), - g2: convertInputValue(FieldType.G2, line.g2), - b2: convertInputValue(FieldType.B2, line.b2), - ...getConnectivityFormData( - { - busbarSectionId: line.busOrBusbarSectionId1, - connectionDirection: line.connectionDirection1, - connectionName: line.connectionName1, - connectionPosition: line.connectionPosition1, - voltageLevelId: line.voltageLevelId1, - terminalConnected: line.connected1, - }, - FieldConstants.CONNECTIVITY_1 - ), - ...getConnectivityFormData( - { - busbarSectionId: line.busOrBusbarSectionId2, - connectionDirection: line.connectionDirection2, - connectionName: line.connectionName2, - connectionPosition: line.connectionPosition2, - voltageLevelId: line.voltageLevelId2, - terminalConnected: line.connected2, - }, - FieldConstants.CONNECTIVITY_2 - ), - }), - ...getAllLimitsFormData( - line?.operationalLimitsGroups?.map(({ id, ...baseData }) => ({ - ...baseData, - name: id, - id: id + baseData.applicability, - })), - line?.selectedOperationalLimitsGroupId1 ?? null, - line?.selectedOperationalLimitsGroupId2 ?? null - ), - ...getPropertiesFromModification(line.properties), - [LINE_SEGMENTS]: line.lineSegments, - }; - reset(formData, { keepDefaultValues: true }); + (line: LineCreationDto) => { + reset(lineCreationDtoToForm(line)); }, [reset] ); @@ -287,67 +160,42 @@ const LineCreationDialog = ({ } }, [fromEditDataToFormValues, editData]); - const handleLineSegmentsBuildSubmit = ( - data: ComputedLineCharacteristics, - lineSegments: DeepNullable[] - ) => { - setValue(`${CHARACTERISTICS}.${R}`, data[TOTAL_RESISTANCE], { - shouldDirty: true, - }); - setValue(`${CHARACTERISTICS}.${X}`, data[TOTAL_REACTANCE], { - shouldDirty: true, - }); - setValue(`${CHARACTERISTICS}.${B1}`, data[TOTAL_SUSCEPTANCE] / 2, { - shouldDirty: true, - }); - setValue(`${CHARACTERISTICS}.${B2}`, data[TOTAL_SUSCEPTANCE] / 2, { + const handleLineSegmentsBuildSubmit = (data: ComputedLineCharacteristics, lineSegments: LineSegmentsFormData) => { + setValue( + `${FieldConstants.CHARACTERISTICS}.${FieldConstants.R}` as any, + data[FieldConstants.TOTAL_RESISTANCE], + { + shouldDirty: true, + } + ); + setValue(`${FieldConstants.CHARACTERISTICS}.${FieldConstants.X}` as any, data[FieldConstants.TOTAL_REACTANCE], { shouldDirty: true, }); setValue( - `${LIMITS}.${OPERATIONAL_LIMITS_GROUPS}`, - convertLimitsToOperationalLimitsGroupFormSchema(data[FINAL_CURRENT_LIMITS]) + `${FieldConstants.CHARACTERISTICS}.${FieldConstants.B1}` as any, + data[FieldConstants.TOTAL_SUSCEPTANCE] / 2, + { + shouldDirty: true, + } + ); + setValue( + `${FieldConstants.CHARACTERISTICS}.${FieldConstants.B2}` as any, + data[FieldConstants.TOTAL_SUSCEPTANCE] / 2, + { + shouldDirty: true, + } + ); + setValue( + `${FieldConstants.LIMITS}.${FieldConstants.OPERATIONAL_LIMITS_GROUPS}` as any, + convertLimitsToOperationalLimitsGroupFormSchema(data[FieldConstants.FINAL_CURRENT_LIMITS]) ); - setValue(LINE_SEGMENTS, convertToLineSegmentInfos(lineSegments)); + setValue(FieldConstants.LINE_SEGMENTS as any, convertToLineSegmentInfos(lineSegments)); }; const onSubmit = useCallback( - (line: LineCreationFormSchema) => { - const header = line[TAB_HEADER]; - const characteristics = line[CHARACTERISTICS]; - const limits = line[LIMITS]; - const segments = line[LINE_SEGMENTS]; - const lineCreationInfos: LineCreationInfos = { - type: ModificationType.LINE_CREATION, - equipmentId: header[EQUIPMENT_ID], - equipmentName: sanitizeString(header[EQUIPMENT_NAME]), - r: characteristics[R] ?? null, - x: characteristics[X] ?? null, - g1: convertOutputValue(FieldType.G1, characteristics[G1]), - b1: convertOutputValue(FieldType.B1, characteristics[B1]), - g2: convertOutputValue(FieldType.G2, characteristics[G2]), - b2: convertOutputValue(FieldType.B2, characteristics[B2]), - voltageLevelId1: characteristics[CONNECTIVITY_1]?.[VOLTAGE_LEVEL]?.id ?? null, - busOrBusbarSectionId1: characteristics[CONNECTIVITY_1]?.[BUS_OR_BUSBAR_SECTION]?.id ?? null, - voltageLevelId2: characteristics[CONNECTIVITY_2]?.[VOLTAGE_LEVEL]?.id ?? null, - busOrBusbarSectionId2: characteristics[CONNECTIVITY_2]?.[BUS_OR_BUSBAR_SECTION]?.id ?? null, - operationalLimitsGroups: sanitizeLimitsGroups(limits[OPERATIONAL_LIMITS_GROUPS] ?? []), - selectedOperationalLimitsGroupId1: limits[SELECTED_OPERATIONAL_LIMITS_GROUP_ID1] ?? null, - selectedOperationalLimitsGroupId2: limits[SELECTED_OPERATIONAL_LIMITS_GROUP_ID2] ?? null, - connectionName1: sanitizeString(characteristics[CONNECTIVITY_1]?.[CONNECTION_NAME]), - connectionDirection1: - characteristics[CONNECTIVITY_1]?.[CONNECTION_DIRECTION] ?? UNDEFINED_CONNECTION_DIRECTION, - connectionName2: sanitizeString(characteristics[CONNECTIVITY_2]?.[CONNECTION_NAME]), - connectionDirection2: - characteristics[CONNECTIVITY_2]?.[CONNECTION_DIRECTION] ?? UNDEFINED_CONNECTION_DIRECTION, - connectionPosition1: characteristics[CONNECTIVITY_1]?.[CONNECTION_POSITION] ?? null, - connectionPosition2: characteristics[CONNECTIVITY_2]?.[CONNECTION_POSITION] ?? null, - connected1: characteristics[CONNECTIVITY_1]?.[CONNECTED] ?? null, - connected2: characteristics[CONNECTIVITY_2]?.[CONNECTED] ?? null, - properties: toModificationProperties(line), - lineSegments: segments, - } satisfies LineCreationInfos; + (lineForm: LineCreationFormData) => { onCreateLine({ - lineCreationInfos, + lineCreationInfos: lineCreationFormToDto(lineForm), studyUuid: studyUuid, nodeUuid: currentNodeUuid, modificationUuid: editData ? editData.uuid : undefined, @@ -359,57 +207,7 @@ const LineCreationDialog = ({ [onCreateLine, studyUuid, currentNodeUuid, editData, snackError] ); - const onValidationError = (errors: FieldErrors) => { - let tabsInError = []; - if (errors?.[CHARACTERISTICS] !== undefined) { - tabsInError.push(LineCreationDialogTab.CHARACTERISTICS_TAB); - } - - if (errors?.[LIMITS] !== undefined) { - tabsInError.push(LineCreationDialogTab.LIMITS_TAB); - } - - if (tabsInError.includes(tabIndex)) { - // error in current tab => do not change tab systematically but remove current tab in error list - setTabIndexesWithError(tabsInError.filter((errorTabIndex) => errorTabIndex !== tabIndex)); - } else if (tabsInError.length > 0) { - // switch to the first tab in the list then remove the tab in the error list - setTabIndex(tabsInError[0]); - setTabIndexesWithError(tabsInError.filter((errorTabIndex, index, arr) => errorTabIndex !== arr[0])); - } - }; - - const clear = useCallback(() => { - reset(emptyFormData); - }, [reset]); - - const lineIdField = ( - - ); - - const lineNameField = ( - - ); - - const headerAndTabs = ( - - - {lineIdField} - {lineNameField} - - - - ); + const clear = useCallback(() => reset(lineCreationEmptyFormData), [reset]); const open = useOpenShortWaitFetching({ isDataFetched: @@ -417,40 +215,48 @@ const LineCreationDialog = ({ delay: FORM_LOADING_DELAY, }); + const fetchBusesOrBusbarSections = useCallback( + (voltageLevelId: string) => + fetchBusesOrBusbarSectionsForVoltageLevel( + studyUuid, + currentNodeUuid, + currentRootNetworkUuid, + voltageLevelId + ), + [studyUuid, currentNodeUuid, currentRootNetworkUuid] + ); + return ( - + setIsOpenLineTypesCatalogDialog(true)} searchCopy={searchCopy} - PaperProps={{ - sx: { - height: '95vh', // we want the dialog height to be fixed even when switching tabs + onOpenCatalogDialog={() => setIsOpenLineTypesCatalogDialog(true)} + slotProps={{ + paper: { + sx: { + height: '95vh', // we want the dialog height to be fixed even when switching tabs + }, }, }} open={open} isDataFetching={isUpdate && editDataFetchStatus === FetchStatus.RUNNING} {...dialogProps} > - - - - + diff --git a/src/components/dialogs/network-modifications/line/creation/line-creation-type.ts b/src/components/dialogs/network-modifications/line/creation/line-creation-type.ts deleted file mode 100644 index c20c840f30..0000000000 --- a/src/components/dialogs/network-modifications/line/creation/line-creation-type.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2025, 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 { CurrentLimitsData } from '../../../../../services/study/network-map.type'; -import { - CHARACTERISTICS, - CONNECTIVITY_1, - CONNECTIVITY_2, - EQUIPMENT_ID, - EQUIPMENT_NAME, - LIMITS, - LINE_SEGMENTS, - OPERATIONAL_LIMITS_GROUPS, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID1, - SELECTED_OPERATIONAL_LIMITS_GROUP_ID2, - TAB_HEADER, -} from '../../../../utils/field-constants'; -import { OperationalLimitsGroupFormSchema } from '../../../limits/operational-limits-groups-types'; -import { LineCharacteristics } from '../modification/line-modification-type'; -import { ConnectablePositionInfos, Connectivity, FieldConstants, Property } from '@gridsuite/commons-ui'; -import { LineSegmentInfos } from '../../../../../services/network-modification-types'; - -export interface LineCreationFormSchema { - [TAB_HEADER]: { - [EQUIPMENT_ID]: string; - [EQUIPMENT_NAME]?: string | null; - }; - [CHARACTERISTICS]: LineCharacteristics & { - [CONNECTIVITY_1]?: Connectivity; - [CONNECTIVITY_2]?: Connectivity; - }; - [LIMITS]: { - [OPERATIONAL_LIMITS_GROUPS]?: OperationalLimitsGroupFormSchema[]; - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID1]?: string | null; - [SELECTED_OPERATIONAL_LIMITS_GROUP_ID2]?: string | null; - }; - [FieldConstants.ADDITIONAL_PROPERTIES]?: Property[]; - [LINE_SEGMENTS]: LineSegmentInfos[] | undefined; -} - -export interface LineFormInfos { - id: string; - name: string | null; - voltageLevelId1: string; - voltageLevelId2: string; - terminal1Connected: boolean; - terminal2Connected: boolean; - p1: number; - q1: number; - p2: number; - q2: number; - i1: number; - i2: number; - r: number; - x: number; - g1?: number; - b1?: number; - g2?: number; - b2?: number; - busOrBusbarSectionId1: string; - busOrBusbarSectionId2: string; - selectedOperationalLimitsGroupId1: string; - selectedOperationalLimitsGroupId2: string; - connectablePosition1: ConnectablePositionInfos; - connectablePosition2: ConnectablePositionInfos; - currentLimits: CurrentLimitsData[]; - properties: Record; -} diff --git a/src/components/dialogs/network-modifications/line/line-dialog-tabs.tsx b/src/components/dialogs/network-modifications/line/line-dialog-tabs.tsx deleted file mode 100644 index cd2127dc9d..0000000000 --- a/src/components/dialogs/network-modifications/line/line-dialog-tabs.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2023, 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 { Grid2 as Grid, Tab, Tabs } from '@mui/material'; -import { FormattedMessage } from 'react-intl'; -import { getTabIndicatorStyle, getTabStyle } from '../../../utils/tab-utils'; -import { LineCreationDialogTab } from './creation/line-creation-dialog-utils'; -import { LineModificationDialogTab } from './line-utils'; - -interface LineDialogTabsProps { - tabIndex: number; - tabIndexesWithError: number[]; - setTabIndex: (index: number) => void; - isModification?: boolean; -} - -const LineDialogTabs = ({ - tabIndex, - tabIndexesWithError, - setTabIndex, - isModification = false, -}: LineDialogTabsProps) => { - return ( - - setTabIndex(newValue)} - TabIndicatorProps={{ - sx: getTabIndicatorStyle(tabIndexesWithError, tabIndex), - }} - > - {isModification && ( - } - sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.CONNECTIVITY_TAB)} - /> - )} - } - sx={getTabStyle( - tabIndexesWithError, - isModification - ? LineModificationDialogTab.CHARACTERISTICS_TAB - : LineCreationDialogTab.CHARACTERISTICS_TAB - )} - /> - } - sx={getTabStyle( - tabIndexesWithError, - isModification ? LineModificationDialogTab.LIMITS_TAB : LineCreationDialogTab.LIMITS_TAB - )} - /> - {isModification && ( - } - sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.STATE_ESTIMATION_TAB)} - /> - )} - - - ); -}; - -export default LineDialogTabs; diff --git a/src/components/dialogs/network-modifications/line/modification/line-dialog-tabs.tsx b/src/components/dialogs/network-modifications/line/modification/line-dialog-tabs.tsx new file mode 100644 index 0000000000..5a977a49a2 --- /dev/null +++ b/src/components/dialogs/network-modifications/line/modification/line-dialog-tabs.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Grid, Tab, Tabs } from '@mui/material'; +import { FormattedMessage } from 'react-intl'; +import { getTabIndicatorStyle, getTabStyle } from '../../../../utils/tab-utils'; +import { LineModificationDialogTab } from './line-utils'; + +interface LineDialogTabsProps { + tabIndex: number; + tabIndexesWithError: number[]; + setTabIndex: (index: number) => void; +} + +const LineDialogTabs = ({ tabIndex, tabIndexesWithError, setTabIndex }: LineDialogTabsProps) => { + return ( + + setTabIndex(newValue)} + TabIndicatorProps={{ + sx: getTabIndicatorStyle(tabIndexesWithError, tabIndex), + }} + > + } + sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.CONNECTIVITY_TAB)} + /> + } + sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.CHARACTERISTICS_TAB)} + /> + } + sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.LIMITS_TAB)} + /> + } + sx={getTabStyle(tabIndexesWithError, LineModificationDialogTab.STATE_ESTIMATION_TAB)} + /> + + + ); +}; + +export default LineDialogTabs; diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.tsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.tsx index d2d6036148..3212886145 100644 --- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.tsx +++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-header.tsx @@ -7,10 +7,9 @@ import { EQUIPMENT_NAME } from 'components/utils/field-constants'; import { Box, Grid2 as Grid, TextField } from '@mui/material'; -import LineDialogTabs from '../line-dialog-tabs'; -import { filledTextField, TextInput } from '@gridsuite/commons-ui'; +import LineDialogTabs from './line-dialog-tabs'; +import { BranchInfos, filledTextField, TextInput } from '@gridsuite/commons-ui'; import { GridItem } from '../../../commons/grid-item'; -import { BranchInfos } from '../../../../../services/study/network-map.type'; export interface LineModificationDialogHeaderProps { lineToModify: BranchInfos | null; @@ -63,12 +62,7 @@ const LineModificationDialogHeader = ({ {lineIdField} {lineNameField} - + ); }; diff --git a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.tsx b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.tsx index c85d4302ca..e731d3097f 100644 --- a/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.tsx +++ b/src/components/dialogs/network-modifications/line/modification/line-modification-dialog-tabs.tsx @@ -6,14 +6,17 @@ */ import { Box } from '@mui/material'; -import LineCharacteristicsPane from '../characteristics-pane/line-characteristics-pane'; -import { LineModificationDialogTab } from '../line-utils'; -import { LimitsPane } from '../../../limits/limits-pane'; +import { LineModificationDialogTab } from './line-utils'; import type { UUID } from 'node:crypto'; import { CurrentTreeNode } from '../../../../graph/tree-node.type'; -import { BranchInfos } from '../../../../../services/study/network-map.type'; import { JSX, useCallback } from 'react'; -import { BranchActiveReactivePowerMeasurementsForm, BranchConnectivityForm } from '@gridsuite/commons-ui'; +import { + BranchActiveReactivePowerMeasurementsForm, + BranchConnectivityForm, + BranchInfos, + LimitsPane, + LineCharacteristicsPane, +} from '@gridsuite/commons-ui'; import PositionDiagramPane from '../../../../grid-layout/cards/diagrams/singleLineDiagram/positionDiagram/position-diagram-pane'; import useVoltageLevelsListInfos from '../../../../../hooks/use-voltage-levels-list-infos'; import { fetchBusesOrBusbarSectionsForVoltageLevel } from '../../../../../services/study/network'; @@ -57,19 +60,11 @@ const LineModificationDialogTabs = ({ />