From 716cdce9a72aa257887ab7aa9499f31bb29adf3f Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 23 Jan 2026 10:37:47 +0100 Subject: [PATCH 01/34] wip Signed-off-by: David BRAQUART --- .../customAGGrid/customAggrid.style.ts | 2 +- src/components/customAGGrid/customAggrid.tsx | 4 +- src/components/dialogs/dialog-utils.ts | 141 +++++++++++++ .../elementSaveDialog/ElementSaveDialog.tsx | 3 +- src/components/dialogs/index.ts | 1 + src/components/index.ts | 1 + .../reactHookForm/country-selection-input.tsx | 26 +++ .../expandableInput/deletable-row.tsx | 55 ++++++ .../expandableInput/expandable-input.tsx | 92 +++++++++ .../reactHookForm/expandableInput/index.ts | 8 + src/components/inputs/reactHookForm/index.ts | 2 + .../network-modifications/common/index.ts | 8 + .../common/modification-dialog/index.ts | 8 + .../modification-dialog-content.tsx | 105 ++++++++++ .../modificationDialog.tsx | 91 +++++++++ .../common/properties/index.ts | 10 + .../common/properties/properties-form.tsx | 111 +++++++++++ .../common/properties/property-form.tsx | 97 +++++++++ .../common/properties/property-types.ts | 19 ++ .../common/properties/property-utils.ts | 185 ++++++++++++++++++ src/components/network-modifications/index.ts | 8 + .../substation/creation/index.ts | 8 + .../creation/substation-creation-dialog.tsx | 182 +++++++++++++++++ .../creation/substation-creation-form.tsx | 38 ++++ .../network-modifications/substation/index.ts | 8 + .../substation/substation-dialog.type.ts | 13 ++ src/components/parameters/common/constant.ts | 11 -- .../voltage-init/general-parameters.tsx | 3 +- .../voltage-limits-parameters.tsx | 2 +- src/hooks/index.ts | 4 + src/hooks/use-button-with-tooltip.tsx | 43 ++++ src/hooks/use-csv-picker.tsx | 97 +++++++++ src/hooks/use-form-search-copy.ts | 79 ++++++++ src/hooks/use-simple-text-value.tsx | 41 ++++ src/hooks/useModificationLabelComputer.tsx | 4 +- src/services/index.ts | 2 + src/services/network-modification-types.ts | 23 +++ src/services/network-modification.ts | 21 ++ src/services/security-analysis.ts | 6 +- src/services/sensitivity-analysis.ts | 3 +- src/services/study.ts | 99 +++++++++- src/services/utils.ts | 4 + src/utils/constants/fieldConstants.ts | 7 + src/utils/constants/uiConstants.ts | 2 + src/utils/index.ts | 3 +- src/utils/ts-utils.ts | 18 ++ src/utils/types/equipmentType.ts | 11 +- 47 files changed, 1675 insertions(+), 34 deletions(-) create mode 100644 src/components/dialogs/dialog-utils.ts create mode 100644 src/components/inputs/reactHookForm/country-selection-input.tsx create mode 100644 src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx create mode 100644 src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx create mode 100644 src/components/inputs/reactHookForm/expandableInput/index.ts create mode 100644 src/components/network-modifications/common/index.ts create mode 100644 src/components/network-modifications/common/modification-dialog/index.ts create mode 100644 src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx create mode 100644 src/components/network-modifications/common/modification-dialog/modificationDialog.tsx create mode 100644 src/components/network-modifications/common/properties/index.ts create mode 100644 src/components/network-modifications/common/properties/properties-form.tsx create mode 100644 src/components/network-modifications/common/properties/property-form.tsx create mode 100644 src/components/network-modifications/common/properties/property-types.ts create mode 100644 src/components/network-modifications/common/properties/property-utils.ts create mode 100644 src/components/network-modifications/index.ts create mode 100644 src/components/network-modifications/substation/creation/index.ts create mode 100644 src/components/network-modifications/substation/creation/substation-creation-dialog.tsx create mode 100644 src/components/network-modifications/substation/creation/substation-creation-form.tsx create mode 100644 src/components/network-modifications/substation/index.ts create mode 100644 src/components/network-modifications/substation/substation-dialog.type.ts create mode 100644 src/hooks/use-button-with-tooltip.tsx create mode 100644 src/hooks/use-csv-picker.tsx create mode 100644 src/hooks/use-form-search-copy.ts create mode 100644 src/hooks/use-simple-text-value.tsx create mode 100644 src/services/network-modification-types.ts create mode 100644 src/services/network-modification.ts create mode 100644 src/utils/ts-utils.ts diff --git a/src/components/customAGGrid/customAggrid.style.ts b/src/components/customAGGrid/customAggrid.style.ts index 74a7fada4..13c7fdb71 100644 --- a/src/components/customAGGrid/customAggrid.style.ts +++ b/src/components/customAGGrid/customAggrid.style.ts @@ -8,7 +8,7 @@ import type { MuiStyles } from '../../utils/styles'; export const CUSTOM_AGGRID_THEME = 'custom-aggrid-theme'; -export const styles = { +export const agGridStyles = { grid: (theme) => ({ width: 'auto', height: '100%', diff --git a/src/components/customAGGrid/customAggrid.tsx b/src/components/customAGGrid/customAggrid.tsx index 1df51e84c..ef2c12bfb 100644 --- a/src/components/customAGGrid/customAggrid.tsx +++ b/src/components/customAGGrid/customAggrid.tsx @@ -14,7 +14,7 @@ import { AG_GRID_LOCALE_EN, AG_GRID_LOCALE_FR } from '@ag-grid-community/locale' import { useIntl } from 'react-intl'; import { Box, type BoxProps, useTheme } from '@mui/material'; import { mergeSx } from '../../utils/styles'; -import { CUSTOM_AGGRID_THEME, styles } from './customAggrid.style'; +import { agGridStyles, CUSTOM_AGGRID_THEME } from './customAggrid.style'; import { type GsLangUser, LANG_ENGLISH, LANG_FRENCH } from '../../utils/langs'; export type AgGridLocale = Partial>; // using EN for keyof because it's the only who has more keys, so more complete @@ -60,7 +60,7 @@ export const CustomAGGrid = forwardRef( return ( ({ + justifyContent: 'flex-start', + fontSize: 'small', + marginTop: theme.spacing(1), + }), + paddingButton: (theme) => ({ + paddingLeft: theme.spacing(2), + }), + formDirectoryElements1: { + display: 'flex', + gap: '8px', + flexWrap: 'wrap', + flexDirection: 'row', + border: '2px solid lightgray', + padding: '4px', + borderRadius: '4px', + overflow: 'hidden', + }, + formDirectoryElementsError: (theme) => ({ + borderColor: theme.palette.error.main, + }), + formDirectoryElements2: { + display: 'flex', + gap: '8px', + flexWrap: 'wrap', + flexDirection: 'row', + marginTop: 0, + padding: '4px', + overflow: 'hidden', + }, + labelDirectoryElements: { + marginTop: '-10px', + }, + addDirectoryElements: { + marginTop: '-5px', + }, +} as const satisfies MuiStyles; + +export const MicroSusceptanceAdornment = { + position: 'end', + text: MICRO_SIEMENS, +}; + +export const SusceptanceAdornment = { + position: 'end', + text: SIEMENS, +}; +export const OhmAdornment = { + position: 'end', + text: OHM, +}; +export const AmpereAdornment = { + position: 'end', + text: AMPERE, +}; + +export const KiloAmpereAdornment = { + position: 'end', + text: KILO_AMPERE, +}; + +export const ActivePowerAdornment = { + position: 'end', + text: MEGA_WATT, +}; +export const ReactivePowerAdornment = { + position: 'end', + text: MEGA_VAR, +}; +export const MVAPowerAdornment = { + position: 'end', + text: MEGA_VOLT_AMPERE, +}; +export const VoltageAdornment = { + position: 'end', + text: KILO_VOLT, +}; +export const KilometerAdornment = { + position: 'end', + text: KILO_METER, +}; +export const filledTextField: FilledTextFieldProps = { + variant: 'filled', +}; + +export const standardTextField: StandardTextFieldProps = { + variant: 'standard', +}; + +export const italicFontTextField = { + style: { fontStyle: 'italic' }, +}; + +export const percentageTextField = { + position: 'end', + text: PERCENTAGE, +}; + +export function parseIntData(val: string | number, defaultValue: string | number) { + const intValue = Number.parseInt(String(val), 10); + return Number.isNaN(intValue) ? defaultValue : intValue; +} + +export function sanitizeString(val: string | null | undefined): string | null { + const trimmedValue = val?.trim(); + return trimmedValue === undefined || trimmedValue === '' ? null : trimmedValue; +} + +export const getIdOrSelf = (e: any) => e?.id ?? e; diff --git a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx index 04a44d87f..2308b6f4c 100644 --- a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx +++ b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx @@ -13,7 +13,8 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import yup from '../../../utils/yupConfig'; import { TreeViewFinderNodeProps } from '../../treeViewFinder'; -import { DescriptionField, RadioInput, UniqueNameInput } from '../../inputs'; +import { DescriptionField, UniqueNameInput } from '../../inputs/reactHookForm/text'; +import { RadioInput } from '../../inputs/reactHookForm/booleans'; import { DirectoryItemSelector } from '../../directoryItemSelector'; import { CustomMuiDialog } from '../customMuiDialog/CustomMuiDialog'; import { ElementAttributes, ElementType, FieldConstants, MAX_CHAR_DESCRIPTION } from '../../../utils'; diff --git a/src/components/dialogs/index.ts b/src/components/dialogs/index.ts index 276676961..ee62e48f7 100644 --- a/src/components/dialogs/index.ts +++ b/src/components/dialogs/index.ts @@ -9,3 +9,4 @@ export * from './descriptionModificationDialog'; export * from './elementSaveDialog'; export * from './modifyElementSelection'; export * from './popupConfirmationDialog'; +export * from './dialog-utils'; diff --git a/src/components/index.ts b/src/components/index.ts index a5f3a83f9..a60c49a3b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -29,3 +29,4 @@ export * from './parameters'; export * from './menus'; export * from './muiTable'; export * from './resizablePanels'; +export * from './network-modifications'; diff --git a/src/components/inputs/reactHookForm/country-selection-input.tsx b/src/components/inputs/reactHookForm/country-selection-input.tsx new file mode 100644 index 000000000..3bbaf4108 --- /dev/null +++ b/src/components/inputs/reactHookForm/country-selection-input.tsx @@ -0,0 +1,26 @@ +/** + * 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 { AutocompleteInput, AutocompleteInputProps } from './autocompleteInputs'; +import { useLocalizedCountries } from '../../../hooks'; +import { useCustomFormContext } from './provider'; + +interface CountrySelectionInputProps extends Omit {} + +export function CountrySelectionInput(props: CountrySelectionInputProps) { + const { language } = useCustomFormContext(); + const { translate, countryCodes } = useLocalizedCountries(language!); + + return ( + translate(countryCode as string)} + {...props} + /> + ); +} diff --git a/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx b/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx new file mode 100644 index 000000000..c6e870056 --- /dev/null +++ b/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { PropsWithChildren, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { Grid, IconButton, Tooltip } from '@mui/material'; +import RestoreFromTrashIcon from '@mui/icons-material/RestoreFromTrash'; +import DeleteIcon from '@mui/icons-material/Delete'; + +export interface DeletableRowProps extends PropsWithChildren { + alignItems: string; + onClick: () => void; + deletionMark?: boolean | null; + disabledDeletion?: boolean | null; +} + +export function DeletableRow({ + alignItems, + onClick, + deletionMark, + disabledDeletion, + children, +}: Readonly) { + const intl = useIntl(); + const [isMouseHover, setIsMouseHover] = useState(false); + + return ( + setIsMouseHover(true)} + onMouseLeave={() => setIsMouseHover(false)} + > + {children} + + {isMouseHover && !disabledDeletion && ( + + + {deletionMark ? : } + + + )} + + + ); +} diff --git a/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx b/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx new file mode 100644 index 000000000..adfe8d458 --- /dev/null +++ b/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx @@ -0,0 +1,92 @@ +/** + * 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 { useFieldArray } from 'react-hook-form'; +import { Button, Grid } from '@mui/material'; +import AddIcon from '@mui/icons-material/ControlPoint'; +import { FormattedMessage } from 'react-intl'; +import { DeletableRow } from './deletable-row'; +import { ErrorInput, MidFormError } from '../errorManagement'; +import { mergeSx } from '../../../../utils'; +import { dialogStyles } from '../../../dialogs'; + +export interface ExpandableInputProps { + name: string; + Field: React.ComponentType; + fieldProps?: any; + addButtonLabel?: string; + initialValue?: any; + getDeletionMark?: (idx: number) => boolean; + deleteCallback?: (idx: number) => boolean; + alignItems?: string; + watchProps?: boolean; + disabled?: boolean; + disabledDeletion?: (idx: number) => boolean; +} + +// This component is used to display Array of objects. +// We can manage 2 states for deletion: +// - only 1 state and 1 delete icon that removes the current line +// - a second state "mark for deletion" with a second icon: the line is not removed +// and we can cancel this mark to go back to normal state. +export function ExpandableInput({ + name, + Field, // Used to display each object of an array + fieldProps, // Props to pass to Field + addButtonLabel, + initialValue, // Initial value to display when we add a new entry to array + getDeletionMark, + deleteCallback, + alignItems = 'stretch', // default value for a flex container + watchProps = true, + disabled = false, + disabledDeletion, +}: Readonly) { + const { + fields: values, + append, + remove, + } = useFieldArray({ + name, + }); + + return ( + + + + + {watchProps && + values.map((value, idx) => ( + { + const shouldRemove = deleteCallback ? deleteCallback(idx) : true; + if (shouldRemove) { + remove(idx); + } + }} + deletionMark={getDeletionMark?.(idx)} + disabledDeletion={disabledDeletion?.(idx)} + > + + + ))} + + + + + ); +} diff --git a/src/components/inputs/reactHookForm/expandableInput/index.ts b/src/components/inputs/reactHookForm/expandableInput/index.ts new file mode 100644 index 000000000..bb1bb5cc3 --- /dev/null +++ b/src/components/inputs/reactHookForm/expandableInput/index.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export * from './expandable-input'; diff --git a/src/components/inputs/reactHookForm/index.ts b/src/components/inputs/reactHookForm/index.ts index b22d04f3c..b13f60d7d 100644 --- a/src/components/inputs/reactHookForm/index.ts +++ b/src/components/inputs/reactHookForm/index.ts @@ -21,3 +21,5 @@ export * from './tableInputs'; export * from './text'; export * from './utils'; export * from './constants'; +export * from './expandableInput'; +export * from './country-selection-input'; diff --git a/src/components/network-modifications/common/index.ts b/src/components/network-modifications/common/index.ts new file mode 100644 index 000000000..636755c11 --- /dev/null +++ b/src/components/network-modifications/common/index.ts @@ -0,0 +1,8 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './modification-dialog'; +export * from './properties'; diff --git a/src/components/network-modifications/common/modification-dialog/index.ts b/src/components/network-modifications/common/modification-dialog/index.ts new file mode 100644 index 000000000..6beb260a6 --- /dev/null +++ b/src/components/network-modifications/common/modification-dialog/index.ts @@ -0,0 +1,8 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './modification-dialog-content'; +export * from './modificationDialog'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx new file mode 100644 index 000000000..073c7014e --- /dev/null +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx @@ -0,0 +1,105 @@ +/** + * 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, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress, DialogProps } from '@mui/material'; +import FindInPageIcon from '@mui/icons-material/FindInPage'; +import AutoStoriesOutlinedIcon from '@mui/icons-material/AutoStoriesOutlined'; +import React, { ReactNode } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { CancelButton } from '../../../inputs'; +import { useButtonWithTooltip, UseFormSearchCopy } from '../../../../hooks'; + +/** + * Common parts for the Modification Dialog + * @param {String} titleId id for title translation + * @param {Object} onOpenCatalogDialog Object managing catalog + * @param {Object} searchCopy Object managing search equipments for copy + * @param {ReactElement} subtitle subtitle component to put inside DialogTitle + * @param {Boolean} isDataFetching props to display loading + * @param {ReactElement} submitButton submitButton to put in the dialog's footer + * @param {CallbackEvent} closeAndClear callback when the dialog needs to be closed and cleared + * @param {Array} dialogProps props that are forwarded to the MUI Dialog component + */ + +export type ModificationDialogContentProps = Omit & { + closeAndClear: () => void; + isDataFetching?: boolean; + titleId: string; + onOpenCatalogDialog?: () => void; + searchCopy?: UseFormSearchCopy; + submitButton: ReactNode; + subtitle?: ReactNode; +}; + +export function ModificationDialogContent({ + closeAndClear, + isDataFetching = false, + titleId, + onOpenCatalogDialog, + searchCopy, + submitButton, + subtitle, + ...dialogProps +}: Readonly) { + const catalogButton = useButtonWithTooltip({ + label: 'CatalogButtonTooltip', + handleClick: onOpenCatalogDialog ?? (() => {}), + icon: , + }); + const copyEquipmentButton = useButtonWithTooltip({ + label: 'CopyFromExisting', + handleClick: searchCopy?.handleOpenSearchDialog ?? (() => {}), + icon: , + }); + + const handleClose = (_event: React.MouseEvent, reason: string) => { + // don't close the dialog for outside click + if (reason !== 'backdropClick') { + closeAndClear(); + } + }; + + const handleCancel = () => { + closeAndClear(); + }; + + return ( + + {isDataFetching && } + + + + + + + + {onOpenCatalogDialog && ( + + {catalogButton} + + )} + {searchCopy && ( + + {copyEquipmentButton} + + )} + + {subtitle && ( + + {subtitle} + + )} + + + {dialogProps.children} + + + {submitButton} + + + ); +} diff --git a/src/components/network-modifications/common/modification-dialog/modificationDialog.tsx b/src/components/network-modifications/common/modification-dialog/modificationDialog.tsx new file mode 100644 index 000000000..781efcafc --- /dev/null +++ b/src/components/network-modifications/common/modification-dialog/modificationDialog.tsx @@ -0,0 +1,91 @@ +/** + * 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 { useCallback } from 'react'; +import { FieldErrors, FieldValues, useFormContext } from 'react-hook-form'; +import { ModificationDialogContent, ModificationDialogContentProps } from './modification-dialog-content'; +import { SubmitButton } from '../../../inputs'; + +/** + * Generic Modification Dialog which manage basic common behaviors with react + * hook form validation. + * @param {CallbackEvent} onClear callback when the dialog needs to be cleared + * @param {CallbackEvent} onSave callback when saving the modification + * @param {Boolean} disabledSave to control disabled prop of the validate button + * @param {CallbackEvent} onValidated callback when validation is successful + * @param {CallbackEvent} onValidationError callback when validation failed + * @param {Array} dialogProps props that are forwarded to the MUI Dialog component + */ + +export type ModificationDialogProps = Omit< + ModificationDialogContentProps, + 'closeAndClear' | 'submitButton' +> & { + disabledSave?: boolean; + onClear: () => void; + onClose?: () => void; + onSave: (modificationData: TFieldValues) => void; + onValidated?: () => void; + onValidationError?: (errors: FieldErrors) => void; +}; + +export function ModificationDialog({ + disabledSave = false, + onClear, + onClose, + onSave, + onValidated, + onValidationError, + ...dialogProps +}: Readonly>) { + const { handleSubmit } = useFormContext(); + + const closeAndClear = () => { + onClear(); + onClose?.(); + }; + + const handleValidate = (data: TFieldValues) => { + onValidated?.(); + onSave(data); + // do not wait fetch response and close dialog, errors will be shown in snackbar. + closeAndClear(); + }; + + const handleScrollWhenError = useCallback(() => { + // When scrolling to the field with focus, you can end up with the label not completely displayed + // We ensure that field with focus is displayed in the middle of the dialog + // Delay focusing to ensure it happens after the validation. + // Without timeout, document.activeElement will return the validation button. + const timeoutId = setTimeout(() => { + const focusedElement = document.activeElement; + + if (focusedElement instanceof HTMLElement) { + focusedElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + } + }, 0); // Delay of 0 milliseconds, effectively running at the next opportunity + + return () => clearTimeout(timeoutId); + }, []); + + const handleValidationError = (errors: FieldErrors) => { + onValidationError?.(errors); + handleScrollWhenError(); + }; + + const submitButton = ( + + ); + return ; +} diff --git a/src/components/network-modifications/common/properties/index.ts b/src/components/network-modifications/common/properties/index.ts new file mode 100644 index 000000000..003ceb6be --- /dev/null +++ b/src/components/network-modifications/common/properties/index.ts @@ -0,0 +1,10 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './properties-form'; +export * from './property-form'; +export * from './property-types'; +export * from './property-utils'; diff --git a/src/components/network-modifications/common/properties/properties-form.tsx b/src/components/network-modifications/common/properties/properties-form.tsx new file mode 100644 index 000000000..0bb3377cd --- /dev/null +++ b/src/components/network-modifications/common/properties/properties-form.tsx @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { Grid } from '@mui/material'; +import { useCallback, useEffect, useState } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { fetchPredefinedProperties, initializedProperty } from './property-utils'; +import { PropertyForm } from './property-form'; +import GridSection from '../../../grid/grid-section'; +import { FieldConstants, PredefinedProperties } from '../../../../utils'; +import { ExpandableInput } from '../../../inputs'; + +type PropertiesFormProps = { + id?: string; + networkElementType?: string; + isModification?: boolean; +}; + +export function PropertiesForm({ id, networkElementType, isModification = false }: Readonly) { + const additionalProperties = id + ? `${id}.${FieldConstants.ADDITIONAL_PROPERTIES}` + : FieldConstants.ADDITIONAL_PROPERTIES; + const watchProps = useWatch({ + name: additionalProperties, + }); + const { getValues, setValue } = useFormContext(); + const [predefinedProperties, setPredefinedProperties] = useState({}); + + useEffect(() => { + if (networkElementType) { + fetchPredefinedProperties(networkElementType).then((res) => { + if (res) { + setPredefinedProperties(res); + } + }); + } + }, [networkElementType]); + + const getDeletionMark = useCallback( + (idx: number) => { + const properties = getValues(`${additionalProperties}`); + if (properties && typeof properties[idx] !== 'undefined') { + return watchProps && properties[idx][FieldConstants.DELETION_MARK]; + } + return false; + }, + [getValues, watchProps, additionalProperties] + ); + + const deleteCallback = useCallback( + (idx: number) => { + let markedForDeletion = false; + const properties = getValues(`${additionalProperties}`); + if (properties && typeof properties[idx] !== 'undefined') { + markedForDeletion = properties[idx][FieldConstants.DELETION_MARK]; + } else { + return false; + } + + let canRemoveLine = true; + if (markedForDeletion) { + // just unmark + setValue(`${additionalProperties}.${idx}.${FieldConstants.DELETION_MARK}`, false, { + shouldDirty: true, + }); + canRemoveLine = false; + } else if ( + properties[idx][FieldConstants.PREVIOUS_VALUE] && + properties[idx][FieldConstants.ADDED] === false + ) { + // we should mark for deletion a property that actually exists in the network and not delete the property line straight away + setValue(`${additionalProperties}.${idx}.${FieldConstants.DELETION_MARK}`, true, { + shouldDirty: true, + }); + canRemoveLine = false; + } + // otherwise just delete the line + return canRemoveLine; + }, + [getValues, setValue, additionalProperties] + ); + + const modificationProperties = isModification + ? { + getDeletionMark, + deleteCallback, + watchProps, + } + : {}; + + const additionalProps = ( + + ); + + return ( + + + {additionalProps} + + ); +} diff --git a/src/components/network-modifications/common/properties/property-form.tsx b/src/components/network-modifications/common/properties/property-form.tsx new file mode 100644 index 000000000..38097ba67 --- /dev/null +++ b/src/components/network-modifications/common/properties/property-form.tsx @@ -0,0 +1,97 @@ +/** + * 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 { useMemo } from 'react'; + +import { useWatch } from 'react-hook-form'; +import { FieldConstants, PredefinedProperties } from '../../../../utils'; +import GridItem from '../../../grid/grid-item'; +import { AutocompleteInput, TextInput } from '../../../inputs'; +import { italicFontTextField } from '../../../dialogs'; + +type PropertyFormProps = { + name: string; + index: string; + predefinedProperties: PredefinedProperties; +}; + +export const PropertyForm = ({ name, index, predefinedProperties }: PropertyFormProps) => { + const watchPropertyName = useWatch({ name: `${name}.${index}.${FieldConstants.NAME}` }); + const watchPropertyPreviousValue = useWatch({ + name: `${name}.${index}.${FieldConstants.PREVIOUS_VALUE}`, + }); + const watchPropertyDeletionMark = useWatch({ + name: `${name}.${index}.${FieldConstants.DELETION_MARK}`, + }); + const watchPropertyAdded = useWatch({ + name: `${name}.${index}.${FieldConstants.ADDED}`, + }); + + const predefinedNames = useMemo(() => { + return Object.keys(predefinedProperties ?? {}).sort(); + }, [predefinedProperties]); + + const predefinedValues = useMemo(() => { + return predefinedProperties?.[watchPropertyName]?.sort() ?? []; + }, [watchPropertyName, predefinedProperties]); + + const nameField = ( + + ); + + const nameReadOnlyField = ( + + ); + + const valueField = ( + + ); + + const valueReadOnlyField = ( + + ); + + function renderPropertyLine() { + return ( + <> + {watchPropertyDeletionMark || (watchPropertyAdded === false && watchPropertyPreviousValue) ? ( + {nameReadOnlyField} + ) : ( + {nameField} + )} + {watchPropertyDeletionMark ? ( + {valueReadOnlyField} + ) : ( + {valueField} + )} + + ); + } + + return renderPropertyLine(); +}; diff --git a/src/components/network-modifications/common/properties/property-types.ts b/src/components/network-modifications/common/properties/property-types.ts new file mode 100644 index 000000000..b338f3b5a --- /dev/null +++ b/src/components/network-modifications/common/properties/property-types.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { FieldConstants } from '../../../../utils'; + +export type Property = { + [FieldConstants.NAME]: string; + [FieldConstants.VALUE]?: string | null; + [FieldConstants.PREVIOUS_VALUE]?: string | null; + [FieldConstants.DELETION_MARK]: boolean; + [FieldConstants.ADDED]: boolean; +}; + +export type Properties = { + [FieldConstants.ADDITIONAL_PROPERTIES]?: Property[]; +}; diff --git a/src/components/network-modifications/common/properties/property-utils.ts b/src/components/network-modifications/common/properties/property-utils.ts new file mode 100644 index 000000000..226d6a4f6 --- /dev/null +++ b/src/components/network-modifications/common/properties/property-utils.ts @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { FieldConstants, isBlankOrEmpty, PredefinedProperties } from '../../../../utils'; +import yup from '../../../../utils/yupConfig'; +import { fetchStudyMetadata } from '../../../../services'; +import { Properties, Property } from './property-types'; + +export type EquipmentWithProperties = { + properties?: Record; +}; + +export const fetchPredefinedProperties = (networkElementType: string): Promise => { + return fetchStudyMetadata().then((studyMetadata) => { + return studyMetadata.predefinedEquipmentProperties?.[networkElementType]; + }); +}; + +export const emptyProperties: Properties = { + [FieldConstants.ADDITIONAL_PROPERTIES]: [] as Property[], +}; + +export const createPropertyModification = (name: string, value: string | null): Property => { + return { + [FieldConstants.NAME]: name, + [FieldConstants.VALUE]: value, + [FieldConstants.PREVIOUS_VALUE]: value, + [FieldConstants.DELETION_MARK]: false, + [FieldConstants.ADDED]: true, + }; +}; + +export const initializedProperty = (): Property => { + return createPropertyModification('', null); +}; + +export const getPropertiesFromModification = (properties: Property[] | undefined | null): Properties => { + return { + [FieldConstants.ADDITIONAL_PROPERTIES]: properties + ? properties.map((p) => { + return { + [FieldConstants.NAME]: p[FieldConstants.NAME], + [FieldConstants.VALUE]: p[FieldConstants.VALUE], + [FieldConstants.PREVIOUS_VALUE]: p[FieldConstants.PREVIOUS_VALUE], + [FieldConstants.ADDED]: p[FieldConstants.ADDED], + [FieldConstants.DELETION_MARK]: p[FieldConstants.DELETION_MARK], + }; + }) + : [], + }; +}; + +export const copyEquipmentPropertiesForCreation = (equipmentInfos: EquipmentWithProperties): Properties => { + return { + [FieldConstants.ADDITIONAL_PROPERTIES]: equipmentInfos.properties + ? Object.entries(equipmentInfos.properties).map(([name, value]) => { + return { + [FieldConstants.NAME]: name, + [FieldConstants.VALUE]: value, + [FieldConstants.PREVIOUS_VALUE]: null, + [FieldConstants.DELETION_MARK]: false, + [FieldConstants.ADDED]: true, + }; + }) + : [], + }; +}; + +/* + We first load modification properties (empty at creation but could be filled later on), then we load properties + already present on the equipment (network). If one of the equipment properties key is present in the modification + we update the previousValue of this one, it means the modification change the network property value. + If not we add it as an unmodified property. We will be able to delete it or modify its value, but not it's name. + */ +export const mergeModificationAndEquipmentProperties = ( + modificationProperties: Property[], + equipment: EquipmentWithProperties +): Property[] => { + const newModificationProperties = new Map(); + modificationProperties.forEach((property) => { + if (property.name !== null) { + newModificationProperties.set(property.name, property); + } + }); + if (equipment.properties !== undefined) { + Object.entries(equipment.properties).forEach(([name, value]) => { + if (name !== null) { + let propertyToAdd; + // If the property is present in the modification and in the equipment + if (newModificationProperties.has(name)) { + const modProperty = newModificationProperties.get(name)!; + propertyToAdd = { + ...modProperty, + previousValue: value, // We set previous value of the modification to the equipment value + }; + } else { + propertyToAdd = { + [FieldConstants.NAME]: name, + [FieldConstants.VALUE]: null, + [FieldConstants.PREVIOUS_VALUE]: value, + [FieldConstants.DELETION_MARK]: false, + [FieldConstants.ADDED]: false, + }; + } + newModificationProperties.set(name, propertyToAdd); + } + }); + } + return Array.from(newModificationProperties.values()); +}; + +export function getConcatenatedProperties( + equipment: EquipmentWithProperties, + getValues: (name: string) => any, + id?: string +): any { + // ex: current Array [ {Object { name: "p1", value: "v2", previousValue: undefined, added: true, deletionMark: false } }, {...} ] + + const path = id ? `${id}.${FieldConstants.ADDITIONAL_PROPERTIES}` : `${FieldConstants.ADDITIONAL_PROPERTIES}`; + const modificationProperties = getValues(path); + return mergeModificationAndEquipmentProperties(modificationProperties, equipment); +} + +export const toModificationProperties = (properties: Properties) => { + const filteredProperties = properties[FieldConstants.ADDITIONAL_PROPERTIES]?.filter( + (p: Property) => !isBlankOrEmpty(p.value) || p[FieldConstants.DELETION_MARK] + ); + return filteredProperties === undefined || filteredProperties?.length === 0 ? null : filteredProperties; +}; + +const checkUniquePropertyNames = ( + properties: + | { + name: string; + }[] + | undefined +) => { + if (properties === undefined) { + return true; + } + const validValues = properties.filter((v) => v.name); + return validValues.length === new Set(validValues.map((v) => v.name)).size; +}; + +export const creationPropertiesSchema = yup.object({ + [FieldConstants.ADDITIONAL_PROPERTIES]: yup + .array() + .of( + yup.object().shape({ + [FieldConstants.NAME]: yup.string().required(), + [FieldConstants.VALUE]: yup.string().required(), + [FieldConstants.PREVIOUS_VALUE]: yup.string().nullable(), + [FieldConstants.DELETION_MARK]: yup.boolean().required(), + [FieldConstants.ADDED]: yup.boolean().required(), + }) + ) + .test('checkUniqueProperties', 'DuplicatedPropsError', (values) => checkUniquePropertyNames(values)), +}); + +export const modificationPropertiesSchema = yup.object({ + [FieldConstants.ADDITIONAL_PROPERTIES]: yup + .array() + .of( + yup.object().shape({ + [FieldConstants.NAME]: yup.string().required(), + [FieldConstants.VALUE]: yup + .string() + .nullable() + .when([FieldConstants.ADDED], { + is: (added: boolean) => added, + then: (schema) => schema.required(), + }), + [FieldConstants.PREVIOUS_VALUE]: yup.string().nullable(), + [FieldConstants.DELETION_MARK]: yup.boolean().required(), + [FieldConstants.ADDED]: yup.boolean().required(), + }) + ) + .test('checkUniqueProperties', 'DuplicatedPropsError', (values) => checkUniquePropertyNames(values)), +}); + +export const getPropertyValue = (properties: Record | undefined, keyName: string): string | undefined => + properties?.[keyName]; diff --git a/src/components/network-modifications/index.ts b/src/components/network-modifications/index.ts new file mode 100644 index 000000000..c0c6cdf07 --- /dev/null +++ b/src/components/network-modifications/index.ts @@ -0,0 +1,8 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './common'; +export * from './substation'; diff --git a/src/components/network-modifications/substation/creation/index.ts b/src/components/network-modifications/substation/creation/index.ts new file mode 100644 index 000000000..32dc2337d --- /dev/null +++ b/src/components/network-modifications/substation/creation/index.ts @@ -0,0 +1,8 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './substation-creation-dialog'; +export * from './substation-creation-form'; diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx new file mode 100644 index 000000000..eb89fa701 --- /dev/null +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -0,0 +1,182 @@ +/** + * 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 { useForm } from 'react-hook-form'; +import { useCallback, useEffect } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { UUID } from 'node:crypto'; +import { SubstationCreationForm } from './substation-creation-form'; +// import { useOpenShortWaitFetching } from 'components/dialogs/commons/handle-modification-form'; +// import { FetchStatus } from '../../../../../services/utils'; +import { useFormSearchCopy, useSnackMessage } from '../../../../hooks'; +import { DeepNullable, EquipmentType, FieldConstants, snackWithFallback } from '../../../../utils'; +import { CustomFormProvider } from '../../../inputs'; +import yup from '../../../../utils/yupConfig'; +import { + copyEquipmentPropertiesForCreation, + creationPropertiesSchema, + getPropertiesFromModification, + ModificationDialog, + Property, + toModificationProperties, +} from '../../common'; +import { sanitizeString } from '../../../dialogs'; +import { createSubstation, fetchDefaultCountry, StudyContext } from '../../../../services'; +import { SubstationInfos } from '../substation-dialog.type'; + +const formSchema = yup + .object() + .shape({ + [FieldConstants.EQUIPMENT_ID]: yup.string().required(), + [FieldConstants.EQUIPMENT_NAME]: yup.string().nullable(), + [FieldConstants.COUNTRY]: yup.string().nullable(), + }) + .concat(creationPropertiesSchema); + +export type SubstationCreationFormData = yup.InferType; + +const emptyFormData: SubstationCreationFormData = { + [FieldConstants.EQUIPMENT_ID]: '', + [FieldConstants.EQUIPMENT_NAME]: '', + [FieldConstants.COUNTRY]: null, + [FieldConstants.ADDITIONAL_PROPERTIES]: [], +}; + +interface SubstationCreationEditData { + uuid?: UUID; + equipmentId: string; + equipmentName?: string; + country: string | null; + properties?: Property[] | null; +} + +interface SubstationCreationDialogProps { + editData?: SubstationCreationEditData; + isUpdate: boolean; + studyContext?: StudyContext; + onClose?: () => void; + editDataFetchStatus?: string; +} + +/** + * Dialog to create a substation in the network + * @param editData the data to edit + * @param isUpdate check if edition form + * @param studyContext the current tree node context in case of using the modification in a study + * @param editDataFetchStatus indicates the status of fetching EditData + * @param dialogProps props that are forwarded to the generic ModificationDialog component + */ +export function SubstationCreationDialog({ + editData, + isUpdate, + studyContext, + onClose, + editDataFetchStatus, + ...dialogProps +}: Readonly) { + const { snackError } = useSnackMessage(); + + const formMethods = useForm>({ + defaultValues: emptyFormData, + resolver: yupResolver>(formSchema), + }); + + const { reset, getValues } = formMethods; + + const fromSearchCopyToFormValues = (substation: SubstationInfos) => { + reset( + { + [FieldConstants.EQUIPMENT_ID]: `${substation.id}(1)`, + [FieldConstants.EQUIPMENT_NAME]: substation.name ?? '', + [FieldConstants.COUNTRY]: substation.country, + ...copyEquipmentPropertiesForCreation(substation), + }, + { keepDefaultValues: true } + ); + }; + + const searchCopy = useFormSearchCopy(fromSearchCopyToFormValues, EquipmentType.SUBSTATION); + + useEffect(() => { + if (editData) { + reset({ + [FieldConstants.EQUIPMENT_ID]: editData.equipmentId, + [FieldConstants.EQUIPMENT_NAME]: editData.equipmentName ?? '', + [FieldConstants.COUNTRY]: editData.country, + ...getPropertiesFromModification(editData.properties), + }); + } + }, [reset, editData]); + + // We set the default country only in creation mode + useEffect(() => { + if (!isUpdate) { + fetchDefaultCountry().then((country) => { + if (country) { + reset({ + ...getValues(), + [FieldConstants.COUNTRY]: country, + }); + } + }); + } + }, [reset, getValues, isUpdate]); + + const clear = useCallback(() => { + reset(emptyFormData); + }, [reset]); + + const onSubmit = useCallback( + (substation: SubstationCreationFormData) => { + if (studyContext) { + // create inside the study + createSubstation({ + studyId: studyContext.studyId, + nodeId: studyContext.nodeId, + substationId: substation[FieldConstants.EQUIPMENT_ID], + substationName: sanitizeString(substation[FieldConstants.EQUIPMENT_NAME]), + country: substation[FieldConstants.COUNTRY] ?? null, + isUpdate: !!editData, + modificationUuid: editData ? editData.uuid : undefined, + properties: toModificationProperties(substation), + }).catch((error: Error) => { + snackWithFallback(snackError, error, { headerId: 'SubstationCreationError' }); + }); + } else { + // TODO DBR create directly in modification-server + } + }, + [editData, snackError, studyContext] + ); + + // const open = useOpenShortWaitFetching({ + // isDataFetched: + // !isUpdate || editDataFetchStatus === FetchStatus.SUCCEED || editDataFetchStatus === FetchStatus.FAILED, + // delay: FORM_LOADING_DELAY, + // }); + // isDataFetching={isUpdate && editDataFetchStatus === FetchStatus.RUNNING} + // open={true} + + return ( + + + + + + ); +} diff --git a/src/components/network-modifications/substation/creation/substation-creation-form.tsx b/src/components/network-modifications/substation/creation/substation-creation-form.tsx new file mode 100644 index 000000000..8b435b7f9 --- /dev/null +++ b/src/components/network-modifications/substation/creation/substation-creation-form.tsx @@ -0,0 +1,38 @@ +/** + * 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 } from '@mui/material'; +import GridItem from '../../../grid/grid-item'; +import { TextInput } from '../../../inputs'; +import { CountrySelectionInput } from '../../../inputs/reactHookForm/country-selection-input'; +import { FieldConstants } from '../../../../utils'; +import { filledTextField } from '../../../dialogs'; +import { PropertiesForm } from '../../common/properties/properties-form'; + +export function SubstationCreationForm() { + const substationIdField = ; + + const substationNameField = ( + + ); + + const substationCountryField = ( + + ); + + // + + return ( + <> + + {substationIdField} + {substationNameField} + {substationCountryField} + + + ); +} diff --git a/src/components/network-modifications/substation/index.ts b/src/components/network-modifications/substation/index.ts new file mode 100644 index 000000000..22c23ccda --- /dev/null +++ b/src/components/network-modifications/substation/index.ts @@ -0,0 +1,8 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export * from './creation'; +export * from './substation-dialog.type'; diff --git a/src/components/network-modifications/substation/substation-dialog.type.ts b/src/components/network-modifications/substation/substation-dialog.type.ts new file mode 100644 index 000000000..29e962cfe --- /dev/null +++ b/src/components/network-modifications/substation/substation-dialog.type.ts @@ -0,0 +1,13 @@ +/** + * 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 interface SubstationInfos { + id: string; + name?: string; + country: string | null; + properties?: Record; +} diff --git a/src/components/parameters/common/constant.ts b/src/components/parameters/common/constant.ts index 9fdd93594..c5ba92230 100644 --- a/src/components/parameters/common/constant.ts +++ b/src/components/parameters/common/constant.ts @@ -5,8 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { KILO_VOLT, MEGA_VAR } from '../../../utils/constants/unitsConstants'; - export const PROVIDER = 'provider'; export const VOLTAGE_LEVEL = 'voltageLevel'; @@ -17,15 +15,6 @@ export const PARAM_SA_LOW_VOLTAGE_ABSOLUTE_THRESHOLD = 'lowVoltageAbsoluteThresh export const PARAM_SA_HIGH_VOLTAGE_PROPORTIONAL_THRESHOLD = 'highVoltageProportionalThreshold'; export const PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD = 'highVoltageAbsoluteThreshold'; -export const ReactivePowerAdornment = { - position: 'end', - text: MEGA_VAR, -}; -export const VoltageAdornment = { - position: 'end', - text: KILO_VOLT, -}; - export const VERSION_PARAMETER = 'version'; export const COMMON_PARAMETERS = 'commonParameters'; export const SPECIFIC_PARAMETERS = 'specificParametersPerProvider'; diff --git a/src/components/parameters/voltage-init/general-parameters.tsx b/src/components/parameters/voltage-init/general-parameters.tsx index c0643a22f..9eacb6702 100644 --- a/src/components/parameters/voltage-init/general-parameters.tsx +++ b/src/components/parameters/voltage-init/general-parameters.tsx @@ -15,8 +15,9 @@ import { SHUNT_COMPENSATOR_ACTIVATION_THRESHOLD, UPDATE_BUS_VOLTAGE, } from './constants'; -import { LineSeparator, ParameterFloat, ParameterSwitch, ReactivePowerAdornment } from '../common'; +import { LineSeparator, ParameterFloat, ParameterSwitch } from '../common'; import { parametersStyles } from '../parameters-style'; +import { ReactivePowerAdornment } from '../../dialogs'; export interface GeneralParametersProps { withApplyModifications: boolean; diff --git a/src/components/parameters/voltage-init/voltage-limits-parameters.tsx b/src/components/parameters/voltage-init/voltage-limits-parameters.tsx index 35263d39a..a7a1b3efc 100644 --- a/src/components/parameters/voltage-init/voltage-limits-parameters.tsx +++ b/src/components/parameters/voltage-init/voltage-limits-parameters.tsx @@ -17,9 +17,9 @@ import { VOLTAGE_LIMITS_MODIFICATION, } from './constants'; import { ElementType, EquipmentType } from '../../../utils'; -import { VoltageAdornment } from '../common'; import { DndColumn, DndColumnType, DndTable, SELECTED } from '../../dnd-table'; import { FILTERS } from '../../../utils/constants/filterConstant'; +import { VoltageAdornment } from '../../dialogs'; export function VoltageLimitsParameters() { const intl = useIntl(); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f9d9af44f..c0ec097dc 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,13 +6,17 @@ */ export * from './customStates'; +export * from './use-button-with-tooltip'; +export * from './use-csv-picker'; export * from './useModificationLabelComputer'; export * from './useConfidentialityWarning'; export * from './useDebounce'; export * from './useIntlRef'; +export * from './use-form-search-copy'; export * from './useLocalizedCountries'; export * from './usePredefinedProperties'; export * from './usePrevious'; +export * from './use-simple-text-value'; export * from './useSnackMessage'; export * from './useFormatLabelWithUnit'; export * from './useSelectAppearance'; diff --git a/src/hooks/use-button-with-tooltip.tsx b/src/hooks/use-button-with-tooltip.tsx new file mode 100644 index 000000000..de844c2f2 --- /dev/null +++ b/src/hooks/use-button-with-tooltip.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import React, { ReactNode, useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Tooltip, IconButton } from '@mui/material'; +import { dialogStyles } from '../components/dialogs/dialog-utils'; +import { TOOLTIP_DELAY } from '../utils'; + +interface UseButtonWithTooltipProps { + handleClick: React.MouseEventHandler; + label: string; + icon: ReactNode; +} + +export const useButtonWithTooltip = ({ handleClick, label, icon }: UseButtonWithTooltipProps) => { + return useMemo(() => { + return ( + } + placement="top" + arrow + enterDelay={TOOLTIP_DELAY} + enterNextDelay={TOOLTIP_DELAY} + slotProps={{ + popper: { + sx: { + '& .MuiTooltip-tooltip': dialogStyles.tooltip, + }, + }, + }} + > + + {icon} + + + ); + }, [label, handleClick, icon]); +}; diff --git a/src/hooks/use-csv-picker.tsx b/src/hooks/use-csv-picker.tsx new file mode 100644 index 000000000..522db6acb --- /dev/null +++ b/src/hooks/use-csv-picker.tsx @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { Button, Grid } from '@mui/material'; +import { useCSVReader } from 'react-papaparse'; +import { LANG_FRENCH } from '../utils'; + +interface UseCSVPickerProps { + label: string; + header: string[]; + resetTrigger: boolean; + maxTapNumber?: number; + disabled?: boolean; + language: string; +} + +export const useCSVPicker = ({ + label, + header, + resetTrigger, + maxTapNumber, + disabled = false, + language, +}: UseCSVPickerProps) => { + const intl = useIntl(); + + const { CSVReader } = useCSVReader(); + const [selectedFile, setSelectedFile] = useState(); + const [fileError, setFileError] = useState(); + + const equals = (a: string[], b: string[]) => b.every((item) => a.includes(item)); + + useEffect(() => { + setSelectedFile(undefined); + setFileError(undefined); + }, [resetTrigger]); + + // Expose a reset function to allow clearing the file manually + const resetFile = useCallback(() => { + setSelectedFile(undefined); + setFileError(undefined); + }, []); + + const field = useMemo(() => { + return ( + { + setSelectedFile(acceptedFile); + if (results?.data.length > 0 && equals(header, results.data[0])) { + setFileError(undefined); + } else { + setFileError( + intl.formatMessage({ + id: 'InvalidRuleHeader', + }) + ); + } + + if (maxTapNumber && results.data.length > maxTapNumber) { + setFileError(intl.formatMessage({ id: 'TapPositionValueError' }, { value: maxTapNumber })); + } + }} + > + {({ getRootProps }: { getRootProps: () => any }) => ( + + + + {selectedFile + ? selectedFile.name + : intl.formatMessage({ + id: 'uploadMessage', + })} + + + )} + + ); + }, [selectedFile, disabled, header, intl, label, maxTapNumber, CSVReader, language]); + + return [selectedFile, field, fileError, setSelectedFile, resetFile] as const; +}; diff --git a/src/hooks/use-form-search-copy.ts b/src/hooks/use-form-search-copy.ts new file mode 100644 index 000000000..1a77c9899 --- /dev/null +++ b/src/hooks/use-form-search-copy.ts @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useIntl } from 'react-intl'; +import { useCallback, useState } from 'react'; +import { UUID } from 'node:crypto'; +import { EquipmentInfos, EquipmentInfosTypes, EquipmentType, ExtendedEquipmentType, snackWithFallback } from '../utils'; +import { useSnackMessage } from './useSnackMessage'; +import { fetchNetworkElementInfos, StudyContext } from '../services'; + +// TODO fetchNetworkElementInfos has no type +type FetchResponse = Awaited>; + +export interface UseFormSearchCopy { + isDialogSearchOpen: boolean; + handleOpenSearchDialog: () => void; + handleSelectionChange: (element: EquipmentInfos) => void; + handleCloseSearchDialog: () => void; +} + +export function useFormSearchCopy( + setFormValues: (response: FetchResponse) => void, + elementType: EquipmentType | ExtendedEquipmentType, + studyContext?: StudyContext +): UseFormSearchCopy { + const intl = useIntl(); + const { snackInfo, snackError } = useSnackMessage(); + const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false); + + const handleCloseSearchDialog = useCallback(() => { + setIsDialogSearchOpen(false); + }, []); + + const handleOpenSearchDialog = useCallback(() => { + setIsDialogSearchOpen(true); + }, []); + + const handleSelectionChange = useCallback( + (element: EquipmentInfos) => { + if (!studyContext) { + return; + } + fetchNetworkElementInfos( + studyContext.studyId, + studyContext.nodeId, + studyContext.rootNetworkId, + elementType, + EquipmentInfosTypes.FORM.type, + element.id as UUID, + true + ) + .then((response) => { + setFormValues(response); + snackInfo({ + messageTxt: intl.formatMessage({ id: 'EquipmentCopied' }, { equipmentId: element.id }), + }); + }) + .catch((error: Error) => { + snackWithFallback(snackError, error, { + headerId: 'EquipmentCopyFailed', + headerValues: { equipmentId: element.id }, + }); + }) + .finally(() => handleCloseSearchDialog()); + }, + [studyContext, elementType, handleCloseSearchDialog, intl, setFormValues, snackError, snackInfo] + ); + + return { + isDialogSearchOpen, + handleOpenSearchDialog, + handleSelectionChange, + handleCloseSearchDialog, + }; +} diff --git a/src/hooks/use-simple-text-value.tsx b/src/hooks/use-simple-text-value.tsx new file mode 100644 index 000000000..fb5d0bb43 --- /dev/null +++ b/src/hooks/use-simple-text-value.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { TextField, TextFieldProps } from '@mui/material'; + +interface UseSimpleTextValueProps { + defaultValue: string; + adornment: TextFieldProps['InputProps']; + error: boolean; + triggerReset: boolean; +} + +export const useSimpleTextValue = ({ defaultValue, adornment, error, triggerReset }: UseSimpleTextValueProps) => { + const [value, setValue] = useState(defaultValue); + + const handleChangeValue = useCallback((event: React.ChangeEvent) => { + setValue(event.target.value); + }, []); + + const field = useMemo(() => { + return ( + + ); + }, [value, handleChangeValue, adornment, error]); + + useEffect(() => setValue(defaultValue), [defaultValue, triggerReset]); + + return [value, field] as const; +}; diff --git a/src/hooks/useModificationLabelComputer.tsx b/src/hooks/useModificationLabelComputer.tsx index a805281c2..34144521f 100644 --- a/src/hooks/useModificationLabelComputer.tsx +++ b/src/hooks/useModificationLabelComputer.tsx @@ -8,11 +8,11 @@ import { useIntl } from 'react-intl'; import { useCallback } from 'react'; import type { UUID } from 'node:crypto'; -import { MODIFICATION_TYPES, EquipmentType } from '../utils'; +import { MODIFICATION_TYPES, EquipmentType, ModificationType } from '../utils'; export interface NetworkModificationMetadata { uuid: UUID; - type: string; + type: ModificationType; date: Date; stashed: boolean; activated: boolean; diff --git a/src/services/index.ts b/src/services/index.ts index 12f8d1be1..621486d41 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -17,3 +17,5 @@ export * from './userAdmin'; export * from './utils'; export * from './voltage-init'; export * from './short-circuit-analysis'; +export * from './network-modification'; +export * from './network-modification-types'; diff --git a/src/services/network-modification-types.ts b/src/services/network-modification-types.ts new file mode 100644 index 000000000..15cfbb3d8 --- /dev/null +++ b/src/services/network-modification-types.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2026, 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 { UUID } from 'node:crypto'; +import { Property } from '../components/network-modifications/common/properties/property-types'; + +export interface StudyContext { + studyId: UUID; + nodeId: UUID; + rootNetworkId?: UUID; +} + +export interface SubstationCreationInfo extends StudyContext { + substationId: string; + substationName: string | null; + country: string | null; + isUpdate: boolean; + modificationUuid?: UUID; + properties: Property[] | null; +} diff --git a/src/services/network-modification.ts b/src/services/network-modification.ts new file mode 100644 index 000000000..83cd4dd55 --- /dev/null +++ b/src/services/network-modification.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2026, 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 { UUID } from 'node:crypto'; +import { backendFetch } from './utils'; + +const PREFIX_NETWORK_MODIFICATION_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/network-modification`; + +function getUrl() { + return `${PREFIX_NETWORK_MODIFICATION_QUERIES}/v1/network-modifications`; +} + +export function fetchNetworkModification(modificationUuid: UUID) { + const modificationFetchUrl = `${getUrl()}/${modificationUuid}`; + console.debug(modificationFetchUrl); + return backendFetch(modificationFetchUrl); +} diff --git a/src/services/security-analysis.ts b/src/services/security-analysis.ts index 751007d0b..2dc3da8ac 100644 --- a/src/services/security-analysis.ts +++ b/src/services/security-analysis.ts @@ -6,13 +6,9 @@ */ import type { UUID } from 'node:crypto'; -import { backendFetch, backendFetchJson, backendFetchText } from './utils'; +import { backendFetch, backendFetchJson, backendFetchText, safeEncodeURIComponent } from './utils'; import { PREFIX_STUDY_QUERIES } from './loadflow'; -export function safeEncodeURIComponent(value: string | null | undefined): string { - return value != null ? encodeURIComponent(value) : ''; -} - const PREFIX_SECURITY_ANALYSIS_SERVER_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/security-analysis`; export const getStudyUrl = (studyUuid: UUID | null) => diff --git a/src/services/sensitivity-analysis.ts b/src/services/sensitivity-analysis.ts index 3fecaeac0..f39a1d3fd 100644 --- a/src/services/sensitivity-analysis.ts +++ b/src/services/sensitivity-analysis.ts @@ -5,8 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { UUID } from 'node:crypto'; -import { backendFetch, backendFetchJson, backendFetchText } from './utils'; -import { safeEncodeURIComponent } from './security-analysis'; +import { backendFetch, backendFetchJson, backendFetchText, safeEncodeURIComponent } from './utils'; import { FactorsCount, SensitivityAnalysisParametersInfos } from '../utils'; import { PREFIX_STUDY_QUERIES } from './loadflow'; diff --git a/src/services/study.ts b/src/services/study.ts index 0b97def65..f67e943cf 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -6,17 +6,38 @@ */ import type { UUID } from 'node:crypto'; -import { backendFetch, backendFetchJson } from './utils'; +import { backendFetch, backendFetchJson, backendFetchText, safeEncodeURIComponent } from './utils'; import { NetworkVisualizationParameters } from '../components/parameters/network-visualizations/network-visualizations.types'; import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; +import { EquipmentType, ExtendedEquipmentType, ModificationType } from '../utils'; +import { SubstationCreationInfo } from './network-modification-types'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; +const getStudyUrl = (studyUuid: UUID | null) => + `${PREFIX_STUDY_QUERIES}/v1/studies/${safeEncodeURIComponent(studyUuid)}`; + +const getStudyUrlWithNodeUuid = (studyUuid: UUID | null | undefined, nodeUuid: UUID | undefined) => + `${PREFIX_STUDY_QUERIES}/v1/studies/${safeEncodeURIComponent(studyUuid)}/nodes/${safeEncodeURIComponent(nodeUuid)}`; + +const getStudyUrlWithNodeUuidAndRootNetworkUuid = ( + studyUuid: string | null | undefined, + nodeUuid: string | undefined, + rootNetworkUuid: string | undefined | null +) => + `${PREFIX_STUDY_QUERIES}/v1/studies/${safeEncodeURIComponent(studyUuid)}/root-networks/${safeEncodeURIComponent( + rootNetworkUuid + )}/nodes/${safeEncodeURIComponent(nodeUuid)}`; + +function getNetworkModificationUrl(studyUuid: UUID | null | undefined, nodeUuid: UUID | undefined) { + return `${getStudyUrlWithNodeUuid(studyUuid, nodeUuid)}/network-modifications`; +} + export function exportFilter(studyUuid: UUID, filterUuid?: UUID, token?: string) { console.info('get filter export on study root node'); return backendFetchJson( - `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, + `${getStudyUrl(studyUuid)}/filters/${filterUuid}/elements`, { method: 'get', headers: { 'Content-Type': 'application/json' }, @@ -32,12 +53,12 @@ export function getAvailableComponentLibraries(): Promise { export function getStudyNetworkVisualizationsParameters(studyUuid: UUID): Promise { console.info('get study network visualization parameters'); - return backendFetchJson(`${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/network-visualizations/parameters`); + return backendFetchJson(`${getStudyUrl(studyUuid)}/network-visualizations/parameters`); } export function setStudyNetworkVisualizationParameters(studyUuid: UUID, newParams: Record) { console.info('set study network visualization parameters'); - return backendFetch(`${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/network-visualizations/parameters`, { + return backendFetch(`${getStudyUrl(studyUuid)}/network-visualizations/parameters`, { method: 'POST', headers: { Accept: 'application/json', @@ -49,12 +70,12 @@ export function setStudyNetworkVisualizationParameters(studyUuid: UUID, newParam export function getStudyShortCircuitParameters(studyUuid: UUID): Promise { console.info('get study short-circuit parameters'); - return backendFetchJson(`${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/short-circuit-analysis/parameters`); + return backendFetchJson(`${getStudyUrl(studyUuid)}/short-circuit-analysis/parameters`); } export function updateVoltageInitParameters(studyUuid: UUID | null, newParams: VoltageInitStudyParameters) { console.info('set study voltage init parameters'); - const url = `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/voltage-init/parameters`; + const url = `${getStudyUrl(studyUuid)}/voltage-init/parameters`; console.debug(url); return backendFetch(url, { method: 'POST', @@ -65,3 +86,69 @@ export function updateVoltageInitParameters(studyUuid: UUID | null, newParams: V body: JSON.stringify(newParams), }); } + +export function fetchNetworkElementInfos( + studyUuid: UUID | undefined | null, + currentNodeUuid: UUID | undefined, + currentRootNetworkUuid: UUID | undefined | null, + elementType: EquipmentType | ExtendedEquipmentType, + infoType: string, + elementId: UUID, + inUpstreamBuiltParentNode: boolean +) { + console.info( + `Fetching specific network element '${elementId}' of type '${elementType}' of study '${studyUuid}' on root network '${currentRootNetworkUuid}' and node '${currentNodeUuid}' ...` + ); + const urlSearchParams = new URLSearchParams(); + if (inUpstreamBuiltParentNode !== undefined) { + urlSearchParams.append('inUpstreamBuiltParentNode', String(inUpstreamBuiltParentNode)); + } + urlSearchParams.append('elementType', elementType); + urlSearchParams.append('infoType', infoType); + + const fetchElementsUrl = `${getStudyUrlWithNodeUuidAndRootNetworkUuid( + studyUuid, + currentNodeUuid, + currentRootNetworkUuid + )}/network/elements/${encodeURIComponent(elementId)}?${urlSearchParams.toString()}`; + console.debug(fetchElementsUrl); + + return backendFetchJson(fetchElementsUrl); +} + +export function createSubstation({ + studyId, + nodeId, + substationId, + substationName, + country, + isUpdate = false, + modificationUuid, + properties, +}: SubstationCreationInfo) { + let url = getNetworkModificationUrl(studyId, nodeId); + + const body = JSON.stringify({ + type: ModificationType.SUBSTATION_CREATION, + equipmentId: substationId, + equipmentName: substationName, + country: country === '' ? null : country, + properties, + }); + + if (modificationUuid) { + url += `/${encodeURIComponent(modificationUuid)}`; + console.info('Updating substation creation', { url, body }); + } else { + console.info('Creating substation creation', { url, body }); + } + + return backendFetchText(url, { + method: isUpdate ? 'PUT' : 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body, + }); +} diff --git a/src/services/utils.ts b/src/services/utils.ts index 061bbc011..6afba3000 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -151,3 +151,7 @@ export const backendFetchFile = (url: string, init: RequestInit, token?: string) export const getRequestParamFromList = (paramName: string, params: string[] = []) => { return new URLSearchParams(params.map((param) => [paramName, param])); }; + +export function safeEncodeURIComponent(value: string | null | undefined): string { + return value != null ? encodeURIComponent(value) : ''; +} diff --git a/src/utils/constants/fieldConstants.ts b/src/utils/constants/fieldConstants.ts index 337fa6cda..8953e17a5 100644 --- a/src/utils/constants/fieldConstants.ts +++ b/src/utils/constants/fieldConstants.ts @@ -6,6 +6,8 @@ */ export enum FieldConstants { + ADDED = 'added', + ADDITIONAL_PROPERTIES = 'AdditionalProperties', AG_GRID_ROW_UUID = 'agGridRowUuid', API_CALL = 'apiCall', CASE_FILE = 'caseFile', @@ -17,11 +19,14 @@ export enum FieldConstants { COUNTRIES_1 = 'countries1', COUNTRIES_2 = 'countries2', COUNTRIES = 'countries', + COUNTRY = 'country', CURRENT_PARAMETERS = 'currentParameters', + DELETION_MARK = 'deletionMark', DESCRIPTION = 'description', DIRECTORY = 'directory', ENERGY_SOURCE = 'energySource', EQUIPMENT_ID = 'equipmentID', + EQUIPMENT_NAME = 'equipmentName', EQUIPMENT_IDS = 'equipmentIDs', EQUIPMENT_TABLE = 'equipmentTable', EQUIPMENT_TYPE = 'equipmentType', @@ -38,12 +43,14 @@ export enum FieldConstants { NOMINAL_VOLTAGE = 'nominalVoltage', OPERATION_TYPE = 'type', TYPE = 'type', + PREVIOUS_VALUE = 'previousValue', PROPERTY_NAME = 'propertyName', PROPERTY_OPERATOR = 'propertyOperator', PROPERTY = 'PROPERTY', PROPERTY_VALUES = 'propertyValues', SCRIPT = 'script', STUDY_NAME = 'studyName', + VALUE = 'value', VALUE_1 = 'value1', VALUE_2 = 'value2', } diff --git a/src/utils/constants/uiConstants.ts b/src/utils/constants/uiConstants.ts index 8d373de85..a9bc059fd 100644 --- a/src/utils/constants/uiConstants.ts +++ b/src/utils/constants/uiConstants.ts @@ -6,3 +6,5 @@ */ export const MAX_CHAR_DESCRIPTION = 500; +export const TOOLTIP_DELAY = 1000; +export const FORM_LOADING_DELAY = 200; diff --git a/src/utils/index.ts b/src/utils/index.ts index decfdf2e0..9436b0781 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,12 +9,13 @@ export * from './constants'; export * from './conversionUtils'; export * from './error'; export * from './functions'; +export * from './labelUtils'; export * from './langs'; export * from './mapper'; export * from './navigator-clipboard'; export * from './constants/notificationsProvider'; export * from './styles'; export * from './types'; +export * from './ts-utils'; export * from './validation-functions'; -export * from './labelUtils'; export { default as yupConfig } from './yupConfig'; diff --git a/src/utils/ts-utils.ts b/src/utils/ts-utils.ts new file mode 100644 index 000000000..22926c9de --- /dev/null +++ b/src/utils/ts-utils.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +export type Nullable = { [K in keyof T]: T[K] | null }; +export type DeepNullable = { + [K in keyof T]: DeepNullable | null; +}; + +export function notUndefined(value: T | undefined): value is T { + return value !== undefined; +} + +export function notNull(value: T | null): value is T { + return value !== null; +} diff --git a/src/utils/types/equipmentType.ts b/src/utils/types/equipmentType.ts index 0fe31364d..b65d1c2fb 100644 --- a/src/utils/types/equipmentType.ts +++ b/src/utils/types/equipmentType.ts @@ -75,7 +75,6 @@ export enum EquipmentType { BREAKER = 'BREAKER', } -// TODO move into powsybl-network-viewer export enum HvdcType { LCC = 'LCC', VSC = 'VSC', @@ -293,3 +292,13 @@ export function getEquipmentsInfosForSearchBar(equipmentsInfos: Equipment[], get })) ?? []); }); } + +type EquipmentInfosTypesStruct = { type: T }; +export const EquipmentInfosTypes: Record = { + LIST: { type: 'LIST' }, + MAP: { type: 'MAP' }, + FORM: { type: 'FORM' }, + TAB: { type: 'TAB' }, + TOOLTIP: { type: 'TOOLTIP' }, + OPERATING_STATUS: { type: 'OPERATING_STATUS' }, +}; From fe5e9b52c57fcf415f3e3a4e2ea9054ee93c904a Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 23 Jan 2026 19:36:28 +0100 Subject: [PATCH 02/34] update rest calls and typing Signed-off-by: David BRAQUART --- .../common/modification-dialog/index.ts | 3 +- .../modification-dialog-content.tsx | 17 ++----- .../modification-dialog-types.tsx | 47 +++++++++++++++++++ ...tionDialog.tsx => modification-dialog.tsx} | 15 +----- .../creation/substation-creation-dialog.tsx | 44 ++++++++++------- .../creation/substation-creation-form.tsx | 3 +- src/services/network-modification-types.ts | 4 +- src/services/network-modification.ts | 36 +++++++++++++- src/services/study.ts | 42 ++--------------- 9 files changed, 125 insertions(+), 86 deletions(-) create mode 100644 src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx rename src/components/network-modifications/common/modification-dialog/{modificationDialog.tsx => modification-dialog.tsx} (85%) diff --git a/src/components/network-modifications/common/modification-dialog/index.ts b/src/components/network-modifications/common/modification-dialog/index.ts index 6beb260a6..0f8031473 100644 --- a/src/components/network-modifications/common/modification-dialog/index.ts +++ b/src/components/network-modifications/common/modification-dialog/index.ts @@ -5,4 +5,5 @@ */ export * from './modification-dialog-content'; -export * from './modificationDialog'; +export * from './modification-dialog'; +export * from './modification-dialog-types'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx index 073c7014e..8a384ac0d 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx @@ -5,13 +5,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Grid, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress, DialogProps } from '@mui/material'; +import { Grid, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress } from '@mui/material'; import FindInPageIcon from '@mui/icons-material/FindInPage'; import AutoStoriesOutlinedIcon from '@mui/icons-material/AutoStoriesOutlined'; -import React, { ReactNode } from 'react'; +import React from 'react'; import { FormattedMessage } from 'react-intl'; import { CancelButton } from '../../../inputs'; -import { useButtonWithTooltip, UseFormSearchCopy } from '../../../../hooks'; +import { useButtonWithTooltip } from '../../../../hooks'; +import { ModificationDialogContentProps } from './modification-dialog-types'; /** * Common parts for the Modification Dialog @@ -25,16 +26,6 @@ import { useButtonWithTooltip, UseFormSearchCopy } from '../../../../hooks'; * @param {Array} dialogProps props that are forwarded to the MUI Dialog component */ -export type ModificationDialogContentProps = Omit & { - closeAndClear: () => void; - isDataFetching?: boolean; - titleId: string; - onOpenCatalogDialog?: () => void; - searchCopy?: UseFormSearchCopy; - submitButton: ReactNode; - subtitle?: ReactNode; -}; - export function ModificationDialogContent({ closeAndClear, isDataFetching = false, diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx new file mode 100644 index 000000000..035b00e94 --- /dev/null +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2026, 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 { ReactNode } from 'react'; +import { DialogProps } from '@mui/material'; +import { UUID } from 'node:crypto'; +import { UseFormSearchCopy } from '../../../../hooks'; +import { FieldErrors, FieldValues } from 'react-hook-form'; +import { StudyContext } from '../../../../services'; + +export type ModificationDialogContentProps = Omit & { + closeAndClear: () => void; + isDataFetching?: boolean; + titleId: string; + onOpenCatalogDialog?: () => void; + searchCopy?: UseFormSearchCopy; + submitButton: ReactNode; + subtitle?: ReactNode; +}; + +export type ModificationDialogProps = Omit< + ModificationDialogContentProps, + 'closeAndClear' | 'submitButton' +> & { + disabledSave?: boolean; + onClear: () => void; + onClose?: () => void; + onSave: (modificationData: TFieldValues) => void; + onValidated?: () => void; + onValidationError?: (errors: FieldErrors) => void; +}; + +export type NetworkModificationDialogProps = { + studyContext?: StudyContext; + isUpdate: boolean; + editDataFetchStatus?: string; + onValidated?: () => void; + onClose?: () => void; +}; + +export type EquipmentModificationDialogProps = NetworkModificationDialogProps & { + defaultIdValue: UUID; +}; diff --git a/src/components/network-modifications/common/modification-dialog/modificationDialog.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx similarity index 85% rename from src/components/network-modifications/common/modification-dialog/modificationDialog.tsx rename to src/components/network-modifications/common/modification-dialog/modification-dialog.tsx index 781efcafc..27725fe0f 100644 --- a/src/components/network-modifications/common/modification-dialog/modificationDialog.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx @@ -7,8 +7,9 @@ import { useCallback } from 'react'; import { FieldErrors, FieldValues, useFormContext } from 'react-hook-form'; -import { ModificationDialogContent, ModificationDialogContentProps } from './modification-dialog-content'; +import { ModificationDialogContent } from './modification-dialog-content'; import { SubmitButton } from '../../../inputs'; +import { ModificationDialogProps } from './modification-dialog-types'; /** * Generic Modification Dialog which manage basic common behaviors with react @@ -21,18 +22,6 @@ import { SubmitButton } from '../../../inputs'; * @param {Array} dialogProps props that are forwarded to the MUI Dialog component */ -export type ModificationDialogProps = Omit< - ModificationDialogContentProps, - 'closeAndClear' | 'submitButton' -> & { - disabledSave?: boolean; - onClear: () => void; - onClose?: () => void; - onSave: (modificationData: TFieldValues) => void; - onValidated?: () => void; - onValidationError?: (errors: FieldErrors) => void; -}; - export function ModificationDialog({ disabledSave = false, onClear, diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx index eb89fa701..87dd3370c 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -21,11 +21,17 @@ import { creationPropertiesSchema, getPropertiesFromModification, ModificationDialog, + NetworkModificationDialogProps, Property, toModificationProperties, } from '../../common'; import { sanitizeString } from '../../../dialogs'; -import { createSubstation, fetchDefaultCountry, StudyContext } from '../../../../services'; +import { + createSubstation, + createSubstationInNode, + fetchDefaultCountry, + SubstationCreationDto, +} from '../../../../services'; import { SubstationInfos } from '../substation-dialog.type'; const formSchema = yup @@ -54,12 +60,8 @@ interface SubstationCreationEditData { properties?: Property[] | null; } -interface SubstationCreationDialogProps { +interface SubstationCreationDialogProps extends NetworkModificationDialogProps { editData?: SubstationCreationEditData; - isUpdate: boolean; - studyContext?: StudyContext; - onClose?: () => void; - editDataFetchStatus?: string; } /** @@ -75,6 +77,7 @@ export function SubstationCreationDialog({ isUpdate, studyContext, onClose, + onValidated, editDataFetchStatus, ...dialogProps }: Readonly) { @@ -132,23 +135,27 @@ export function SubstationCreationDialog({ const onSubmit = useCallback( (substation: SubstationCreationFormData) => { + const dto: SubstationCreationDto = { + substationId: substation[FieldConstants.EQUIPMENT_ID], + substationName: sanitizeString(substation[FieldConstants.EQUIPMENT_NAME]), + country: substation[FieldConstants.COUNTRY] ?? null, + isUpdate: !!editData, + modificationUuid: editData ? editData.uuid : undefined, + properties: toModificationProperties(substation), + }; + let promise: Promise; if (studyContext) { - // create inside the study - createSubstation({ + promise = createSubstationInNode({ studyId: studyContext.studyId, nodeId: studyContext.nodeId, - substationId: substation[FieldConstants.EQUIPMENT_ID], - substationName: sanitizeString(substation[FieldConstants.EQUIPMENT_NAME]), - country: substation[FieldConstants.COUNTRY] ?? null, - isUpdate: !!editData, - modificationUuid: editData ? editData.uuid : undefined, - properties: toModificationProperties(substation), - }).catch((error: Error) => { - snackWithFallback(snackError, error, { headerId: 'SubstationCreationError' }); + ...dto, }); } else { - // TODO DBR create directly in modification-server + promise = createSubstation(dto); } + promise.catch((error: Error) => { + snackWithFallback(snackError, error, { headerId: 'SubstationCreationError' }); + }); }, [editData, snackError, studyContext] ); @@ -168,10 +175,11 @@ export function SubstationCreationDialog({ onClear={clear} onSave={onSubmit} onClose={onClose} + onValidated={onValidated} maxWidth="md" titleId="CreateSubstation" searchCopy={studyContext ? searchCopy : undefined} - open={true} + open isDataFetching={false} {...dialogProps} > diff --git a/src/components/network-modifications/substation/creation/substation-creation-form.tsx b/src/components/network-modifications/substation/creation/substation-creation-form.tsx index 8b435b7f9..e9b1255d7 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-form.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-form.tsx @@ -24,8 +24,6 @@ export function SubstationCreationForm() { ); - // - return ( <> @@ -33,6 +31,7 @@ export function SubstationCreationForm() { {substationNameField} {substationCountryField} + ); } diff --git a/src/services/network-modification-types.ts b/src/services/network-modification-types.ts index 15cfbb3d8..570da6bf5 100644 --- a/src/services/network-modification-types.ts +++ b/src/services/network-modification-types.ts @@ -13,7 +13,7 @@ export interface StudyContext { rootNetworkId?: UUID; } -export interface SubstationCreationInfo extends StudyContext { +export interface SubstationCreationDto { substationId: string; substationName: string | null; country: string | null; @@ -21,3 +21,5 @@ export interface SubstationCreationInfo extends StudyContext { modificationUuid?: UUID; properties: Property[] | null; } + +export interface SubstationCreationInfo extends StudyContext, SubstationCreationDto {} diff --git a/src/services/network-modification.ts b/src/services/network-modification.ts index 83cd4dd55..189e8d7bd 100644 --- a/src/services/network-modification.ts +++ b/src/services/network-modification.ts @@ -6,7 +6,9 @@ */ import type { UUID } from 'node:crypto'; -import { backendFetch } from './utils'; +import { backendFetch, backendFetchText } from './utils'; +import { SubstationCreationDto } from './network-modification-types'; +import { ModificationType } from '../utils'; const PREFIX_NETWORK_MODIFICATION_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/network-modification`; @@ -19,3 +21,35 @@ export function fetchNetworkModification(modificationUuid: UUID) { console.debug(modificationFetchUrl); return backendFetch(modificationFetchUrl); } + +export function createSubstationPromise( + { substationId, substationName, country, isUpdate, modificationUuid, properties }: SubstationCreationDto, + baseUrl: string +) { + const body = JSON.stringify({ + type: ModificationType.SUBSTATION_CREATION, + equipmentId: substationId, + equipmentName: substationName, + country: country === '' ? null : country, + properties, + }); + let url = baseUrl; + if (modificationUuid) { + url += `/${encodeURIComponent(modificationUuid)}`; + console.info('Updating substation creation', { url, body }); + } else { + console.info('Creating substation creation', { url, body }); + } + return backendFetchText(url, { + method: isUpdate ? 'PUT' : 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body, + }); +} + +export function createSubstation(dto: SubstationCreationDto) { + return createSubstationPromise(dto, getUrl()); +} diff --git a/src/services/study.ts b/src/services/study.ts index f67e943cf..3a0e97f96 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -6,12 +6,13 @@ */ import type { UUID } from 'node:crypto'; -import { backendFetch, backendFetchJson, backendFetchText, safeEncodeURIComponent } from './utils'; +import { backendFetch, backendFetchJson, safeEncodeURIComponent } from './utils'; import { NetworkVisualizationParameters } from '../components/parameters/network-visualizations/network-visualizations.types'; import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; -import { EquipmentType, ExtendedEquipmentType, ModificationType } from '../utils'; +import { EquipmentType, ExtendedEquipmentType } from '../utils'; import { SubstationCreationInfo } from './network-modification-types'; +import { createSubstationPromise } from './network-modification'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; @@ -116,39 +117,6 @@ export function fetchNetworkElementInfos( return backendFetchJson(fetchElementsUrl); } -export function createSubstation({ - studyId, - nodeId, - substationId, - substationName, - country, - isUpdate = false, - modificationUuid, - properties, -}: SubstationCreationInfo) { - let url = getNetworkModificationUrl(studyId, nodeId); - - const body = JSON.stringify({ - type: ModificationType.SUBSTATION_CREATION, - equipmentId: substationId, - equipmentName: substationName, - country: country === '' ? null : country, - properties, - }); - - if (modificationUuid) { - url += `/${encodeURIComponent(modificationUuid)}`; - console.info('Updating substation creation', { url, body }); - } else { - console.info('Creating substation creation', { url, body }); - } - - return backendFetchText(url, { - method: isUpdate ? 'PUT' : 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body, - }); +export function createSubstationInNode(info: SubstationCreationInfo) { + return createSubstationPromise(info, getNetworkModificationUrl(info.studyId, info.nodeId)); } From 29c999d9cd61f8f8ebbdcc68542573b9f1c4ec3e Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 26 Jan 2026 11:35:54 +0100 Subject: [PATCH 03/34] mv translations Signed-off-by: David BRAQUART --- src/translations/en/commonButtonEn.ts | 1 + src/translations/en/index.ts | 1 + src/translations/en/networkModificationsEn.ts | 12 ++++++++++++ src/translations/en/use-csv-picker-en.ts | 12 ++++++++++++ src/translations/fr/commonButtonFr.ts | 1 + src/translations/fr/index.ts | 1 + src/translations/fr/networkModificationsFr.ts | 12 ++++++++++++ src/translations/fr/use-csv-picker-fr.ts | 12 ++++++++++++ 8 files changed, 52 insertions(+) create mode 100644 src/translations/en/use-csv-picker-en.ts create mode 100644 src/translations/fr/use-csv-picker-fr.ts diff --git a/src/translations/en/commonButtonEn.ts b/src/translations/en/commonButtonEn.ts index 839a024d4..c629bdda7 100644 --- a/src/translations/en/commonButtonEn.ts +++ b/src/translations/en/commonButtonEn.ts @@ -10,4 +10,5 @@ export const commonButtonEn = { validate: 'Validate', 'button.changeType': 'Change', 'button.changeOperator': 'Change', + 'button.restore': 'Restore', }; diff --git a/src/translations/en/index.ts b/src/translations/en/index.ts index 8e0cdb36f..84d8137f2 100644 --- a/src/translations/en/index.ts +++ b/src/translations/en/index.ts @@ -33,3 +33,4 @@ export * from './external/importParamsEn'; export * from './componentsEn'; export * from './parameters'; export * from './use-unique-name-validation-en'; +export * from './use-csv-picker-en'; diff --git a/src/translations/en/networkModificationsEn.ts b/src/translations/en/networkModificationsEn.ts index 8f4ace6f8..f54787cd7 100644 --- a/src/translations/en/networkModificationsEn.ts +++ b/src/translations/en/networkModificationsEn.ts @@ -76,4 +76,16 @@ export const networkModificationsEn = { 'network_modifications.CREATE_VOLTAGE_LEVEL_TOPOLOGY': 'Creating a busbar in voltage level {computedLabel}', 'network_modifications.CREATE_VOLTAGE_LEVEL_SECTION': 'Adding busbar section to voltage level {computedLabel}', 'network_modifications.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS': 'Moving feeder bays in voltage level {computedLabel}', + CreateSubstation: 'Create substation', + SubstationCreationError: 'Error while creating substation', + AdditionalInformation: 'Additional information', + AddProperty: 'Add property', + PropertyName: 'Property name', + PropertyValue: 'Property value', + Name: 'Name', + Country: 'Country', + CatalogButtonTooltip: 'Open catalog', + CopyFromExisting: 'Copy from existing equipment', + EquipmentCopied: 'Data successfully copied from equipment {equipmentId}', + EquipmentCopyFailed: 'Equipment copy failed', }; diff --git a/src/translations/en/use-csv-picker-en.ts b/src/translations/en/use-csv-picker-en.ts new file mode 100644 index 000000000..8c9e24f8b --- /dev/null +++ b/src/translations/en/use-csv-picker-en.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2026, 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 const useCsvPickerEn = { + 'use-csv-picker/InvalidRuleHeader': 'The file has an invalid header, please refer yourself to the template', + 'use-csv-picker/uploadMessage': ' No file selected', + 'use-csv-picker/TapPositionValueError': 'The number of taps must not exceed the value {value}', +}; diff --git a/src/translations/fr/commonButtonFr.ts b/src/translations/fr/commonButtonFr.ts index 47f4a3afd..9c153de7b 100644 --- a/src/translations/fr/commonButtonFr.ts +++ b/src/translations/fr/commonButtonFr.ts @@ -10,4 +10,5 @@ export const commonButtonFr = { validate: 'Valider', 'button.changeType': 'Modifier', 'button.changeOperator': 'Modifier', + 'button.restore': 'Restaurer', }; diff --git a/src/translations/fr/index.ts b/src/translations/fr/index.ts index cfff22fca..2f5aa2fbf 100644 --- a/src/translations/fr/index.ts +++ b/src/translations/fr/index.ts @@ -33,3 +33,4 @@ export * from './external/importParamsFr'; export * from './componentsFr'; export * from './parameters'; export * from './use-unique-name-validation-fr'; +export * from './use-csv-picker-fr'; diff --git a/src/translations/fr/networkModificationsFr.ts b/src/translations/fr/networkModificationsFr.ts index 08fd2ae91..12f0d3802 100644 --- a/src/translations/fr/networkModificationsFr.ts +++ b/src/translations/fr/networkModificationsFr.ts @@ -80,4 +80,16 @@ export const networkModificationsFr = { 'network_modifications.CREATE_VOLTAGE_LEVEL_TOPOLOGY': "Ajout d'un jeu de barre dans le poste {computedLabel}", 'network_modifications.CREATE_VOLTAGE_LEVEL_SECTION': 'Ajout de section / tronçon dans le poste {computedLabel}', 'network_modifications.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS': 'Déplacements de départs dans le poste {computedLabel}', + CreateSubstation: 'Créer un site', + SubstationCreationError: "Erreur lors de la création d'un site'", + AdditionalInformation: 'Informations complémentaires', + AddProperty: 'Ajouter une propriété', + PropertyName: 'Nom de la propriété', + PropertyValue: 'Valeur de la propriété', + Name: 'Nom', + Country: 'Pays', + CatalogButtonTooltip: 'Ouvrir le catalogue', + CopyFromExisting: 'Copier depuis un ouvrage existant', + EquipmentCopied: "Données copiées depuis l'ouvrage {equipmentId}", + EquipmentCopyFailed: "Erreur lors de la copie de l'ouvrage", }; diff --git a/src/translations/fr/use-csv-picker-fr.ts b/src/translations/fr/use-csv-picker-fr.ts new file mode 100644 index 000000000..4032d2287 --- /dev/null +++ b/src/translations/fr/use-csv-picker-fr.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2026, 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 const useCsvPickerFr = { + 'use-csv-picker/InvalidRuleHeader': "Le fichier n'a pas le bon en-tête, veuillez vous référer au modèle", + 'use-csv-picker/uploadMessage': ' Aucun fichier sélectionné', + 'use-csv-picker/TapPositionValueError': 'Le nombre de prises ne doit pas dépasser la valeur {value}', +}; From 28de2e493a2024da51b4ffc6af6b5cb4664806de Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 26 Jan 2026 13:10:22 +0100 Subject: [PATCH 04/34] use FetchStatus (with same values than grid-study) Signed-off-by: David BRAQUART --- .../expert/ExpertFilterEditionDialog.tsx | 10 ++--- .../ExplicitNamingFilterEditionDialog.tsx | 10 ++--- .../creation/substation-creation-dialog.tsx | 24 +++++------ src/hooks/index.ts | 3 +- src/hooks/use-open-short-wait-fetching.ts | 43 +++++++++++++++++++ src/services/network-modification.ts | 2 +- src/utils/constants/fetchStatus.ts | 6 +-- 7 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 src/hooks/use-open-short-wait-fetching.ts diff --git a/src/components/filter/expert/ExpertFilterEditionDialog.tsx b/src/components/filter/expert/ExpertFilterEditionDialog.tsx index 9ac073c9b..5cecbb533 100644 --- a/src/components/filter/expert/ExpertFilterEditionDialog.tsx +++ b/src/components/filter/expert/ExpertFilterEditionDialog.tsx @@ -65,10 +65,10 @@ export function ExpertFilterEditionDialog({ // Fetch the filter data from back-end if necessary and fill the form with it useEffect(() => { if (id && open) { - setDataFetchStatus(FetchStatus.FETCHING); + setDataFetchStatus(FetchStatus.RUNNING); getFilterById(id) .then((response) => { - setDataFetchStatus(FetchStatus.FETCH_SUCCESS); + setDataFetchStatus(FetchStatus.SUCCEED); reset({ [FieldConstants.NAME]: name, [FieldConstants.DESCRIPTION]: description, @@ -77,7 +77,7 @@ export function ExpertFilterEditionDialog({ }); }) .catch((error: unknown) => { - setDataFetchStatus(FetchStatus.FETCH_ERROR); + setDataFetchStatus(FetchStatus.FAILED); snackWithFallback(snackError, error, { headerId: 'cannotRetrieveFilter' }); }); } @@ -106,7 +106,7 @@ export function ExpertFilterEditionDialog({ [broadcastChannel, id, onClose, itemSelectionForCopy.sourceItemUuid, snackError, setItemSelectionForCopy] ); - const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; + const isDataReady = dataFetchStatus === FetchStatus.SUCCEED; return ( { if (id && open) { - setDataFetchStatus(FetchStatus.FETCHING); + setDataFetchStatus(FetchStatus.RUNNING); getFilterById(id) .then((response) => { - setDataFetchStatus(FetchStatus.FETCH_SUCCESS); + setDataFetchStatus(FetchStatus.SUCCEED); reset({ [FieldConstants.NAME]: name, [FieldConstants.DESCRIPTION]: description, @@ -83,7 +83,7 @@ export function ExplicitNamingFilterEditionDialog({ }); }) .catch((error) => { - setDataFetchStatus(FetchStatus.FETCH_ERROR); + setDataFetchStatus(FetchStatus.FAILED); snackWithFallback(snackError, error, { headerId: 'cannotRetrieveFilter' }); }); } @@ -111,7 +111,7 @@ export function ExplicitNamingFilterEditionDialog({ [broadcastChannel, id, itemSelectionForCopy, onClose, snackError, setItemSelectionForCopy] ); - const isDataReady = dataFetchStatus === FetchStatus.FETCH_SUCCESS; + const isDataReady = dataFetchStatus === FetchStatus.SUCCEED; return ( @@ -179,8 +177,8 @@ export function SubstationCreationDialog({ maxWidth="md" titleId="CreateSubstation" searchCopy={studyContext ? searchCopy : undefined} - open - isDataFetching={false} + open={open} + isDataFetching={isUpdate && editDataFetchStatus === FetchStatus.RUNNING} {...dialogProps} > diff --git a/src/hooks/index.ts b/src/hooks/index.ts index c0ec097dc..6618ec37b 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,7 +14,8 @@ export * from './useDebounce'; export * from './useIntlRef'; export * from './use-form-search-copy'; export * from './useLocalizedCountries'; -export * from './usePredefinedProperties'; +export * from './useLocalizedCountries'; +export * from './use-open-short-wait-fetching'; export * from './usePrevious'; export * from './use-simple-text-value'; export * from './useSnackMessage'; diff --git a/src/hooks/use-open-short-wait-fetching.ts b/src/hooks/use-open-short-wait-fetching.ts new file mode 100644 index 000000000..5b216b2a1 --- /dev/null +++ b/src/hooks/use-open-short-wait-fetching.ts @@ -0,0 +1,43 @@ +/** + * 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 { useEffect, useState } from 'react'; + +/** + * A hook that returns a boolean indicating whether a form should be opened after a short delay. + * + * @param {boolean} isDataFetched - Whether data is fetched. + * @param {number} delay - The delay before opening the form, in milliseconds. + * + * @returns {boolean} A boolean indicating whether the form should be opened. + */ + +export const useOpenShortWaitFetching: ({ + isDataFetched, + delay, +}: { + isDataFetched: boolean; + delay: number; +}) => boolean = ({ isDataFetched, delay }) => { + // State to track whether the form should be opened or not. + const [shouldOpen, setShouldOpen] = useState(false); + + useEffect(() => { + let timeout: NodeJS.Timeout; + // If data is already available, open the form immediately. + if (isDataFetched) { + setShouldOpen(true); + } else { + // Otherwise, wait for a short delay before opening the form. + timeout = setTimeout(() => setShouldOpen(true), delay); + } + // Return a cleanup function to cancel the timeout if the data arrives before the end of the delay. + return () => clearTimeout(timeout); + }, [delay, isDataFetched]); + + return shouldOpen; +}; diff --git a/src/services/network-modification.ts b/src/services/network-modification.ts index 189e8d7bd..9ee8a9177 100644 --- a/src/services/network-modification.ts +++ b/src/services/network-modification.ts @@ -23,7 +23,7 @@ export function fetchNetworkModification(modificationUuid: UUID) { } export function createSubstationPromise( - { substationId, substationName, country, isUpdate, modificationUuid, properties }: SubstationCreationDto, + { substationId, substationName, country, properties, isUpdate, modificationUuid }: SubstationCreationDto, baseUrl: string ) { const body = JSON.stringify({ diff --git a/src/utils/constants/fetchStatus.ts b/src/utils/constants/fetchStatus.ts index 7337391f4..0bc917f85 100644 --- a/src/utils/constants/fetchStatus.ts +++ b/src/utils/constants/fetchStatus.ts @@ -7,7 +7,7 @@ export const FetchStatus = { IDLE: 'IDLE', - FETCHING: 'FETCHING', - FETCH_SUCCESS: 'FETCH_SUCCESS', - FETCH_ERROR: 'FETCH_ERROR', + RUNNING: 'RUNNING', + SUCCEED: 'SUCCEED', + FAILED: 'FAILED', }; From 9fd917c56ac06d4da39b715707ec287d2aa11e25 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 26 Jan 2026 13:57:18 +0100 Subject: [PATCH 05/34] fix linter Signed-off-by: David BRAQUART --- .../modification-dialog-types.tsx | 2 +- .../creation/substation-creation-dialog.tsx | 17 ++++++++++++----- src/hooks/index.ts | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx index 035b00e94..eaf03670d 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx @@ -8,8 +8,8 @@ import { ReactNode } from 'react'; import { DialogProps } from '@mui/material'; import { UUID } from 'node:crypto'; -import { UseFormSearchCopy } from '../../../../hooks'; import { FieldErrors, FieldValues } from 'react-hook-form'; +import { UseFormSearchCopy } from '../../../../hooks'; import { StudyContext } from '../../../../services'; export type ModificationDialogContentProps = Omit & { diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx index 237d2369f..685b30cfb 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -11,7 +11,14 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { UUID } from 'node:crypto'; import { SubstationCreationForm } from './substation-creation-form'; import { useFormSearchCopy, useOpenShortWaitFetching, useSnackMessage } from '../../../../hooks'; -import { DeepNullable, EquipmentType, FetchStatus, FieldConstants, FORM_LOADING_DELAY, snackWithFallback } from '../../../../utils'; +import { + DeepNullable, + EquipmentType, + FetchStatus, + FieldConstants, + FORM_LOADING_DELAY, + snackWithFallback, +} from '../../../../utils'; import { CustomFormProvider } from '../../../inputs'; import yup from '../../../../utils/yupConfig'; import { @@ -161,10 +168,10 @@ export function SubstationCreationDialog({ ); const open = useOpenShortWaitFetching({ - isDataFetched: - !isUpdate || editDataFetchStatus === FetchStatus.SUCCEED || editDataFetchStatus === FetchStatus.FAILED, - delay: FORM_LOADING_DELAY, - }); + isDataFetched: + !isUpdate || editDataFetchStatus === FetchStatus.SUCCEED || editDataFetchStatus === FetchStatus.FAILED, + delay: FORM_LOADING_DELAY, + }); return ( diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 6618ec37b..4f0e2edb6 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,7 +14,7 @@ export * from './useDebounce'; export * from './useIntlRef'; export * from './use-form-search-copy'; export * from './useLocalizedCountries'; -export * from './useLocalizedCountries'; +export * from './usePredefinedProperties'; export * from './use-open-short-wait-fetching'; export * from './usePrevious'; export * from './use-simple-text-value'; From ef8338a2863f23186a7b6bdf7528e83f769df3ba Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 26 Jan 2026 14:15:03 +0100 Subject: [PATCH 06/34] fix quality gate Signed-off-by: David BRAQUART --- .../common/properties/property-form.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/network-modifications/common/properties/property-form.tsx b/src/components/network-modifications/common/properties/property-form.tsx index 38097ba67..89730cdf2 100644 --- a/src/components/network-modifications/common/properties/property-form.tsx +++ b/src/components/network-modifications/common/properties/property-form.tsx @@ -31,11 +31,13 @@ export const PropertyForm = ({ name, index, predefinedProperties }: PropertyForm }); const predefinedNames = useMemo(() => { - return Object.keys(predefinedProperties ?? {}).sort(); + const keys = Object.keys(predefinedProperties ?? {}); + return keys.sort((a, b) => a.localeCompare(b)); }, [predefinedProperties]); const predefinedValues = useMemo(() => { - return predefinedProperties?.[watchPropertyName]?.sort() ?? []; + const values = predefinedProperties?.[watchPropertyName] ?? []; + return values.sort((a, b) => a.localeCompare(b)); }, [watchPropertyName, predefinedProperties]); const nameField = ( From 27be2c316f3a7885a837c467acc28e3006fb807d Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Mon, 26 Jan 2026 16:00:06 +0100 Subject: [PATCH 07/34] change Icon imports (to avoid crash with npm start) Signed-off-by: David BRAQUART --- .../inputs/reactHookForm/expandableInput/deletable-row.tsx | 3 +-- .../inputs/reactHookForm/expandableInput/expandable-input.tsx | 2 +- .../common/modification-dialog/modification-dialog-content.tsx | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx b/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx index c6e870056..774bf92d9 100644 --- a/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx +++ b/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx @@ -7,8 +7,7 @@ import { PropsWithChildren, useState } from 'react'; import { useIntl } from 'react-intl'; import { Grid, IconButton, Tooltip } from '@mui/material'; -import RestoreFromTrashIcon from '@mui/icons-material/RestoreFromTrash'; -import DeleteIcon from '@mui/icons-material/Delete'; +import { Delete as DeleteIcon, RestoreFromTrash as RestoreFromTrashIcon } from '@mui/icons-material'; export interface DeletableRowProps extends PropsWithChildren { alignItems: string; diff --git a/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx b/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx index adfe8d458..482d62767 100644 --- a/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx +++ b/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx @@ -7,7 +7,7 @@ import { useFieldArray } from 'react-hook-form'; import { Button, Grid } from '@mui/material'; -import AddIcon from '@mui/icons-material/ControlPoint'; +import { ControlPoint as AddIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; import { DeletableRow } from './deletable-row'; import { ErrorInput, MidFormError } from '../errorManagement'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx index 8a384ac0d..049d02851 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx @@ -6,8 +6,7 @@ */ import { Grid, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress } from '@mui/material'; -import FindInPageIcon from '@mui/icons-material/FindInPage'; -import AutoStoriesOutlinedIcon from '@mui/icons-material/AutoStoriesOutlined'; +import { AutoStoriesOutlined as AutoStoriesOutlinedIcon, FindInPage as FindInPageIcon } from '@mui/icons-material'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { CancelButton } from '../../../inputs'; From a61a50fbe7b1d0d0dd5d4c21c4c9e790f6b63230 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 27 Jan 2026 14:21:07 +0100 Subject: [PATCH 08/34] add search and copy Signed-off-by: David BRAQUART --- demo/src/app.jsx | 4 +- demo/src/equipment-search.tsx | 4 +- .../common/equipment-search-dialog.tsx | 94 +++++++++++++++++++ .../network-modifications/common/index.ts | 2 + .../modification-dialog-types.tsx | 2 +- .../common/modificationsUtils.ts | 23 +++++ .../creation/substation-creation-dialog.tsx | 15 ++- src/hooks/index.ts | 1 + src/hooks/use-form-search-copy.ts | 11 ++- src/hooks/use-search-matching-equipments.tsx | 76 +++++++++++++++ src/services/network-modification-types.ts | 11 ++- src/services/study.ts | 28 +++++- src/utils/labelUtils.ts | 19 +++- src/utils/types/types.ts | 7 ++ 14 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 src/components/network-modifications/common/equipment-search-dialog.tsx create mode 100644 src/components/network-modifications/common/modificationsUtils.ts create mode 100644 src/hooks/use-search-matching-equipments.tsx diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 1ae82c8a5..d2f00884c 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -44,7 +44,7 @@ import { import searchEquipments from '../data/EquipmentSearchBar'; import FlatParametersTab from './FlatParametersTab'; import InputsTab from './InputsTab'; -import { EquipmentSearchDialog } from './equipment-search'; +import { EquipmentSearchDemoDialog } from './equipment-search'; import { InlineSearch } from './inline-search'; import { AuthenticationRouter, @@ -914,7 +914,7 @@ function AppContent({ language, onLanguageClick }) { dense > - +
{ }); }; -export function EquipmentSearchDialog() { +export function EquipmentSearchDemoDialog() { const [isSearchOpen, setIsSearchOpen] = useState(false); const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ @@ -93,4 +93,4 @@ export function EquipmentSearchDialog() { ); } -export default EquipmentSearchDialog; +export default EquipmentSearchDemoDialog; diff --git a/src/components/network-modifications/common/equipment-search-dialog.tsx b/src/components/network-modifications/common/equipment-search-dialog.tsx new file mode 100644 index 000000000..6c6a1edd9 --- /dev/null +++ b/src/components/network-modifications/common/equipment-search-dialog.tsx @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useIntl } from 'react-intl'; +import type { UUID } from 'node:crypto'; +import { TextField } from '@mui/material'; +import { Search } from '@mui/icons-material'; +import { ElementSearchDialog, EquipmentItem } from '../../elementSearch'; +import { EquipmentInfos, equipmentStyles, EquipmentType, ExtendedEquipmentType } from '../../../utils'; +import { useSearchMatchingEquipments } from '../../../hooks'; + +interface EquipmentSearchDialogProps { + open: boolean; + onClose: () => void; + onSelectionChange: (equipment: EquipmentInfos) => void; + equipmentType: EquipmentType | ExtendedEquipmentType; + studyUuid: UUID; + currentNodeUuid: UUID; + currentRootNetworkUuid: UUID; + useName: boolean; +} + +/** + * Dialog to search equipment with a given type + * @param {Boolean} open: Is the dialog open ? + * @param {Function} onClose: callback to call when closing the dialog + * @param {Function} onSelectionChange: callback when the selection changes + * @param {String} equipmentType: the type of equipment we want to search + * @param {String} studyUuid: the current study + * @param {String} currentNodeUuid: the node selected + * @param {String} currentRootNetworkUuid: the root network UUID + * @param {Boolean} useName: equipment naming (name vs id) + */ +export function EquipmentSearchDialog({ + open, + onClose, + onSelectionChange, + equipmentType, + studyUuid, + currentNodeUuid, + currentRootNetworkUuid, + useName, +}: Readonly) { + const intl = useIntl(); + const { searchTerm, updateSearchTerm, equipmentsFound, isLoading } = useSearchMatchingEquipments({ + studyUuid: studyUuid, + nodeUuid: currentNodeUuid, + currentRootNetworkUuid: currentRootNetworkUuid, + inUpstreamBuiltParentNode: true, + equipmentType: equipmentType, + useName, + }); + + return ( + { + updateSearchTerm(''); + onSelectionChange(element); + }} + elementsFound={equipmentsFound} + renderElement={(props) => } + loading={isLoading} + getOptionLabel={(equipment) => equipment.label} + isOptionEqualToValue={(equipment1, equipment2) => equipment1.id === equipment2.id} + renderInput={(displayedValue, params) => ( + + + {params.InputProps.startAdornment} + + ), + }} + value={displayedValue} + /> + )} + /> + ); +} diff --git a/src/components/network-modifications/common/index.ts b/src/components/network-modifications/common/index.ts index 636755c11..fed3a1327 100644 --- a/src/components/network-modifications/common/index.ts +++ b/src/components/network-modifications/common/index.ts @@ -6,3 +6,5 @@ export * from './modification-dialog'; export * from './properties'; +export * from './equipment-search-dialog'; +export * from './modificationsUtils'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx index eaf03670d..7acadfdb3 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx @@ -10,7 +10,7 @@ import { DialogProps } from '@mui/material'; import { UUID } from 'node:crypto'; import { FieldErrors, FieldValues } from 'react-hook-form'; import { UseFormSearchCopy } from '../../../../hooks'; -import { StudyContext } from '../../../../services'; +import { StudyContext } from '../../../../utils'; export type ModificationDialogContentProps = Omit & { closeAndClear: () => void; diff --git a/src/components/network-modifications/common/modificationsUtils.ts b/src/components/network-modifications/common/modificationsUtils.ts new file mode 100644 index 000000000..b5b80dd67 --- /dev/null +++ b/src/components/network-modifications/common/modificationsUtils.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { NetworkModificationData } from '../../../services'; + +export const removeNullFields = (data: NetworkModificationData) => { + let dataTemp = data; + if (dataTemp) { + Object.keys(dataTemp).forEach((key) => { + if (dataTemp[key] && dataTemp[key] !== null && typeof dataTemp[key] === 'object') { + dataTemp[key] = removeNullFields(dataTemp[key]); + } + + if (dataTemp[key] === null) { + delete dataTemp[key]; + } + }); + } + return dataTemp; +}; diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx index 685b30cfb..87f8fcb1a 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -24,6 +24,7 @@ import yup from '../../../../utils/yupConfig'; import { copyEquipmentPropertiesForCreation, creationPropertiesSchema, + EquipmentSearchDialog, getPropertiesFromModification, ModificationDialog, NetworkModificationDialogProps, @@ -109,7 +110,7 @@ export function SubstationCreationDialog({ ); }; - const searchCopy = useFormSearchCopy(fromSearchCopyToFormValues, EquipmentType.SUBSTATION); + const searchCopy = useFormSearchCopy(fromSearchCopyToFormValues, EquipmentType.SUBSTATION, studyContext); useEffect(() => { if (editData) { @@ -189,6 +190,18 @@ export function SubstationCreationDialog({ {...dialogProps} > + {studyContext && ( + + )} ); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 4f0e2edb6..490e57a84 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -23,3 +23,4 @@ export * from './useFormatLabelWithUnit'; export * from './useSelectAppearance'; export * from './use-parameters-backend'; export * from './use-create-row-data-sensi'; +export * from './use-search-matching-equipments'; diff --git a/src/hooks/use-form-search-copy.ts b/src/hooks/use-form-search-copy.ts index 1a77c9899..12cf8144c 100644 --- a/src/hooks/use-form-search-copy.ts +++ b/src/hooks/use-form-search-copy.ts @@ -8,9 +8,16 @@ import { useIntl } from 'react-intl'; import { useCallback, useState } from 'react'; import { UUID } from 'node:crypto'; -import { EquipmentInfos, EquipmentInfosTypes, EquipmentType, ExtendedEquipmentType, snackWithFallback } from '../utils'; +import { + EquipmentInfos, + EquipmentInfosTypes, + EquipmentType, + ExtendedEquipmentType, + snackWithFallback, + StudyContext, +} from '../utils'; import { useSnackMessage } from './useSnackMessage'; -import { fetchNetworkElementInfos, StudyContext } from '../services'; +import { fetchNetworkElementInfos } from '../services'; // TODO fetchNetworkElementInfos has no type type FetchResponse = Awaited>; diff --git a/src/hooks/use-search-matching-equipments.tsx b/src/hooks/use-search-matching-equipments.tsx new file mode 100644 index 000000000..d1641f9ed --- /dev/null +++ b/src/hooks/use-search-matching-equipments.tsx @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useCallback, useEffect, useMemo } from 'react'; +import type { UUID } from 'node:crypto'; +import { + Equipment, + EquipmentType, + ExtendedEquipmentType, + getEquipmentsInfosForSearchBar, getIdentifiableNameOrId, getUseNameKey, + Identifiable, +} from '../utils'; +import { useElementSearch } from '../components'; +import { searchEquipmentsInfos } from '../services'; + +interface UseSearchMatchingEquipmentsProps { + studyUuid: UUID; + nodeUuid: UUID; + currentRootNetworkUuid: UUID; + useName: boolean; + inUpstreamBuiltParentNode?: boolean; + equipmentType?: EquipmentType | ExtendedEquipmentType; +} + +export const useSearchMatchingEquipments = (props: UseSearchMatchingEquipmentsProps) => { + const { studyUuid, nodeUuid, currentRootNetworkUuid, inUpstreamBuiltParentNode, equipmentType, useName } = props; + + const getNameOrId = useCallback( + (infos?: Identifiable | null) => { + return getIdentifiableNameOrId(useName, infos); + }, + [useName] + ); + + const getUseNameParameterKey = useCallback(() => { + return getUseNameKey(useName); + }, [useName]); + + const fetchElements: (newSearchTerm: string) => Promise = useCallback( + (newSearchTerm) => + searchEquipmentsInfos( + studyUuid, + nodeUuid, + currentRootNetworkUuid, + newSearchTerm, + getUseNameParameterKey, + inUpstreamBuiltParentNode, + equipmentType + ), + [equipmentType, getUseNameParameterKey, inUpstreamBuiltParentNode, nodeUuid, studyUuid, currentRootNetworkUuid] + ); + + const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ + fetchElements, + }); + + const equipmentsFound = useMemo( + () => getEquipmentsInfosForSearchBar(elementsFound, getNameOrId), + [elementsFound, getNameOrId] + ); + + useEffect(() => { + updateSearchTerm(searchTerm?.trim()); + }, [searchTerm, equipmentType, updateSearchTerm]); + + return { + searchTerm, + updateSearchTerm, + equipmentsFound, + isLoading, + }; +}; diff --git a/src/services/network-modification-types.ts b/src/services/network-modification-types.ts index 570da6bf5..5b24be287 100644 --- a/src/services/network-modification-types.ts +++ b/src/services/network-modification-types.ts @@ -7,10 +7,15 @@ import type { UUID } from 'node:crypto'; import { Property } from '../components/network-modifications/common/properties/property-types'; -export interface StudyContext { +export interface NetworkModificationData { + uuid: UUID; + type: string; + [key: string]: any; +} + +export interface StudyNodeInfo { studyId: UUID; nodeId: UUID; - rootNetworkId?: UUID; } export interface SubstationCreationDto { @@ -22,4 +27,4 @@ export interface SubstationCreationDto { properties: Property[] | null; } -export interface SubstationCreationInfo extends StudyContext, SubstationCreationDto {} +export interface SubstationCreationInfo extends StudyNodeInfo, SubstationCreationDto {} diff --git a/src/services/study.ts b/src/services/study.ts index 3a0e97f96..25b52f140 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -10,7 +10,7 @@ import { backendFetch, backendFetchJson, safeEncodeURIComponent } from './utils' import { NetworkVisualizationParameters } from '../components/parameters/network-visualizations/network-visualizations.types'; import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; -import { EquipmentType, ExtendedEquipmentType } from '../utils'; +import { EquipmentType, ExtendedEquipmentType, namingConventionValues } from '../utils'; import { SubstationCreationInfo } from './network-modification-types'; import { createSubstationPromise } from './network-modification'; @@ -117,6 +117,32 @@ export function fetchNetworkElementInfos( return backendFetchJson(fetchElementsUrl); } +export function searchEquipmentsInfos( + studyUuid: UUID, + nodeUuid: UUID, + currentRootNetworkUuid: UUID, + searchTerm: string, + getUseNameParameterKey: () => namingConventionValues, + inUpstreamBuiltParentNode?: boolean, + equipmentType?: EquipmentType | ExtendedEquipmentType +) { + console.info("Fetching equipments infos matching with '%s' term ... ", searchTerm); + let urlSearchParams = new URLSearchParams(); + urlSearchParams.append('userInput', searchTerm); + urlSearchParams.append('fieldSelector', getUseNameParameterKey()); + if (inUpstreamBuiltParentNode !== undefined) { + urlSearchParams.append('inUpstreamBuiltParentNode', inUpstreamBuiltParentNode.toString()); + } + if (equipmentType !== undefined) { + urlSearchParams.append('equipmentType', equipmentType); + } + return backendFetchJson( + getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, nodeUuid, currentRootNetworkUuid) + + '/search?' + + urlSearchParams.toString() + ); +} + export function createSubstationInNode(info: SubstationCreationInfo) { return createSubstationPromise(info, getNetworkModificationUrl(info.studyId, info.nodeId)); } diff --git a/src/utils/labelUtils.ts b/src/utils/labelUtils.ts index 294b1abb9..50efe2938 100644 --- a/src/utils/labelUtils.ts +++ b/src/utils/labelUtils.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ALL_EQUIPMENTS } from './types'; +import { ALL_EQUIPMENTS, Identifiable } from './types'; export function getEquipmentTypeShortLabel(equipmentType: string | undefined): string { if (!equipmentType) { @@ -20,3 +20,20 @@ export function getEquipmentTypeTagLabel(equipmentType: string | undefined): str } return ALL_EQUIPMENTS[equipmentType as keyof typeof ALL_EQUIPMENTS]?.tagLabel ?? ''; } + +export function getIdentifiableNameOrId(useName: boolean, infos?: Identifiable | null): string { + if (infos) { + const name = infos.name; + return useName && name != null && name.trim() !== '' ? name : infos?.id; + } + return ''; +} + +export type namingConventionValues = 'name' | 'id'; + +export function getUseNameKey(useName: boolean): namingConventionValues { + if (useName) { + return 'name'; + } + return 'id'; +} diff --git a/src/utils/types/types.ts b/src/utils/types/types.ts index 43276a921..69808754b 100644 --- a/src/utils/types/types.ts +++ b/src/utils/types/types.ts @@ -77,3 +77,10 @@ export enum ArrayAction { ADD = 'ADD', REMOVE = 'REMOVE', } + +export interface StudyContext { + studyId: UUID; + nodeId: UUID; + rootNetworkId: UUID; + useNameParam: boolean; +} From cfa6a45dc1e72250c82834b871720ce5e57945c9 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 27 Jan 2026 16:14:15 +0100 Subject: [PATCH 09/34] add language (to translate countries) Signed-off-by: David BRAQUART --- .../common/modification-dialog/modification-dialog-types.tsx | 3 ++- .../substation/creation/substation-creation-dialog.tsx | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx index 7acadfdb3..609158964 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx @@ -10,7 +10,7 @@ import { DialogProps } from '@mui/material'; import { UUID } from 'node:crypto'; import { FieldErrors, FieldValues } from 'react-hook-form'; import { UseFormSearchCopy } from '../../../../hooks'; -import { StudyContext } from '../../../../utils'; +import { GsLang, StudyContext } from '../../../../utils'; export type ModificationDialogContentProps = Omit & { closeAndClear: () => void; @@ -40,6 +40,7 @@ export type NetworkModificationDialogProps = { editDataFetchStatus?: string; onValidated?: () => void; onClose?: () => void; + language: GsLang; }; export type EquipmentModificationDialogProps = NetworkModificationDialogProps & { diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx index 87f8fcb1a..2da715920 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -78,6 +78,7 @@ interface SubstationCreationDialogProps extends NetworkModificationDialogProps { * @param onClose callback when we close the dialog * @param onValidated callback when we validate the dialog * @param editDataFetchStatus indicates the status of fetching EditData + * @param language app language to ensure translations * @param dialogProps props that are forwarded to the generic ModificationDialog component */ export function SubstationCreationDialog({ @@ -87,6 +88,7 @@ export function SubstationCreationDialog({ onClose, onValidated, editDataFetchStatus, + language, ...dialogProps }: Readonly) { const { snackError } = useSnackMessage(); @@ -175,7 +177,7 @@ export function SubstationCreationDialog({ }); return ( - + Date: Tue, 27 Jan 2026 17:52:11 +0100 Subject: [PATCH 10/34] fix linter Signed-off-by: David BRAQUART --- .../common/equipment-search-dialog.tsx | 8 ++++---- .../network-modifications/common/modificationsUtils.ts | 2 +- src/hooks/use-search-matching-equipments.tsx | 4 +++- src/services/study.ts | 10 ++++------ src/utils/labelUtils.ts | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/network-modifications/common/equipment-search-dialog.tsx b/src/components/network-modifications/common/equipment-search-dialog.tsx index 6c6a1edd9..3dc86b516 100644 --- a/src/components/network-modifications/common/equipment-search-dialog.tsx +++ b/src/components/network-modifications/common/equipment-search-dialog.tsx @@ -47,11 +47,11 @@ export function EquipmentSearchDialog({ }: Readonly) { const intl = useIntl(); const { searchTerm, updateSearchTerm, equipmentsFound, isLoading } = useSearchMatchingEquipments({ - studyUuid: studyUuid, + studyUuid, nodeUuid: currentNodeUuid, - currentRootNetworkUuid: currentRootNetworkUuid, + currentRootNetworkUuid, inUpstreamBuiltParentNode: true, - equipmentType: equipmentType, + equipmentType, useName, }); @@ -72,7 +72,7 @@ export function EquipmentSearchDialog({ isOptionEqualToValue={(equipment1, equipment2) => equipment1.id === equipment2.id} renderInput={(displayedValue, params) => ( { - let dataTemp = data; + const dataTemp = data; if (dataTemp) { Object.keys(dataTemp).forEach((key) => { if (dataTemp[key] && dataTemp[key] !== null && typeof dataTemp[key] === 'object') { diff --git a/src/hooks/use-search-matching-equipments.tsx b/src/hooks/use-search-matching-equipments.tsx index d1641f9ed..09a7bc430 100644 --- a/src/hooks/use-search-matching-equipments.tsx +++ b/src/hooks/use-search-matching-equipments.tsx @@ -11,7 +11,9 @@ import { Equipment, EquipmentType, ExtendedEquipmentType, - getEquipmentsInfosForSearchBar, getIdentifiableNameOrId, getUseNameKey, + getEquipmentsInfosForSearchBar, + getIdentifiableNameOrId, + getUseNameKey, Identifiable, } from '../utils'; import { useElementSearch } from '../components'; diff --git a/src/services/study.ts b/src/services/study.ts index 25b52f140..a53c8403f 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -10,7 +10,7 @@ import { backendFetch, backendFetchJson, safeEncodeURIComponent } from './utils' import { NetworkVisualizationParameters } from '../components/parameters/network-visualizations/network-visualizations.types'; import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; -import { EquipmentType, ExtendedEquipmentType, namingConventionValues } from '../utils'; +import { EquipmentType, ExtendedEquipmentType, NamingConventionValues } from '../utils'; import { SubstationCreationInfo } from './network-modification-types'; import { createSubstationPromise } from './network-modification'; @@ -122,12 +122,12 @@ export function searchEquipmentsInfos( nodeUuid: UUID, currentRootNetworkUuid: UUID, searchTerm: string, - getUseNameParameterKey: () => namingConventionValues, + getUseNameParameterKey: () => NamingConventionValues, inUpstreamBuiltParentNode?: boolean, equipmentType?: EquipmentType | ExtendedEquipmentType ) { console.info("Fetching equipments infos matching with '%s' term ... ", searchTerm); - let urlSearchParams = new URLSearchParams(); + const urlSearchParams = new URLSearchParams(); urlSearchParams.append('userInput', searchTerm); urlSearchParams.append('fieldSelector', getUseNameParameterKey()); if (inUpstreamBuiltParentNode !== undefined) { @@ -137,9 +137,7 @@ export function searchEquipmentsInfos( urlSearchParams.append('equipmentType', equipmentType); } return backendFetchJson( - getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, nodeUuid, currentRootNetworkUuid) + - '/search?' + - urlSearchParams.toString() + `${getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, nodeUuid, currentRootNetworkUuid)}/search?${urlSearchParams.toString()}` ); } diff --git a/src/utils/labelUtils.ts b/src/utils/labelUtils.ts index 50efe2938..e735a903c 100644 --- a/src/utils/labelUtils.ts +++ b/src/utils/labelUtils.ts @@ -23,15 +23,15 @@ export function getEquipmentTypeTagLabel(equipmentType: string | undefined): str export function getIdentifiableNameOrId(useName: boolean, infos?: Identifiable | null): string { if (infos) { - const name = infos.name; - return useName && name != null && name.trim() !== '' ? name : infos?.id; + const { name, id } = infos; + return useName && name != null && name.trim() !== '' ? name : id; } return ''; } -export type namingConventionValues = 'name' | 'id'; +export type NamingConventionValues = 'name' | 'id'; -export function getUseNameKey(useName: boolean): namingConventionValues { +export function getUseNameKey(useName: boolean): NamingConventionValues { if (useName) { return 'name'; } From 885738ced88bd71bf882267ce6f56ee555f29039 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 27 Jan 2026 18:34:25 +0100 Subject: [PATCH 11/34] fix circ dep Signed-off-by: David BRAQUART --- demo/src/equipment-search.tsx | 4 +--- src/components/elementSearch/hooks/index.ts | 7 ------- src/components/elementSearch/index.ts | 1 - src/hooks/index.ts | 1 + src/hooks/use-search-matching-equipments.tsx | 2 +- .../elementSearch => }/hooks/useElementSearch.tsx | 6 +++--- 6 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 src/components/elementSearch/hooks/index.ts rename src/{components/elementSearch => }/hooks/useElementSearch.tsx (95%) diff --git a/demo/src/equipment-search.tsx b/demo/src/equipment-search.tsx index f95e30347..f4f951b13 100644 --- a/demo/src/equipment-search.tsx +++ b/demo/src/equipment-search.tsx @@ -15,7 +15,7 @@ import { equipmentStyles, EquipmentType, useElementSearch, -} from '../../src/index'; +} from '../../src'; interface AnyElementInterface { id: string; @@ -92,5 +92,3 @@ export function EquipmentSearchDemoDialog() { ); } - -export default EquipmentSearchDemoDialog; diff --git a/src/components/elementSearch/hooks/index.ts b/src/components/elementSearch/hooks/index.ts deleted file mode 100644 index cc1afe6be..000000000 --- a/src/components/elementSearch/hooks/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -export * from './useElementSearch'; diff --git a/src/components/elementSearch/index.ts b/src/components/elementSearch/index.ts index c8b714c71..528b1c1b4 100644 --- a/src/components/elementSearch/index.ts +++ b/src/components/elementSearch/index.ts @@ -7,5 +7,4 @@ export * from './elementItem'; export * from './elementSearchDialog'; export * from './elementSearchInput'; -export * from './hooks'; export * from './tagRenderer'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 490e57a84..353469684 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -24,3 +24,4 @@ export * from './useSelectAppearance'; export * from './use-parameters-backend'; export * from './use-create-row-data-sensi'; export * from './use-search-matching-equipments'; +export * from './useElementSearch'; diff --git a/src/hooks/use-search-matching-equipments.tsx b/src/hooks/use-search-matching-equipments.tsx index 09a7bc430..3eed9d329 100644 --- a/src/hooks/use-search-matching-equipments.tsx +++ b/src/hooks/use-search-matching-equipments.tsx @@ -16,8 +16,8 @@ import { getUseNameKey, Identifiable, } from '../utils'; -import { useElementSearch } from '../components'; import { searchEquipmentsInfos } from '../services'; +import { useElementSearch } from './useElementSearch'; interface UseSearchMatchingEquipmentsProps { studyUuid: UUID; diff --git a/src/components/elementSearch/hooks/useElementSearch.tsx b/src/hooks/useElementSearch.tsx similarity index 95% rename from src/components/elementSearch/hooks/useElementSearch.tsx rename to src/hooks/useElementSearch.tsx index 97c7d3812..bcfbfa027 100644 --- a/src/components/elementSearch/hooks/useElementSearch.tsx +++ b/src/hooks/useElementSearch.tsx @@ -6,9 +6,9 @@ */ import { useCallback, useRef, useState } from 'react'; -import { useDebounce } from '../../../hooks/useDebounce'; -import { useSnackMessage } from '../../../hooks/useSnackMessage'; -import { snackWithFallback } from '../../../utils/error'; +import { useDebounce } from './useDebounce'; +import { useSnackMessage } from './useSnackMessage'; +import { snackWithFallback } from '../utils/error'; const SEARCH_FETCH_TIMEOUT_MILLIS = 1000; From b959169509611a34f8bb46bf7dcc5f58165e9c0d Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 27 Jan 2026 18:38:24 +0100 Subject: [PATCH 12/34] use enum FetchStatus Signed-off-by: David BRAQUART --- src/utils/constants/fetchStatus.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/constants/fetchStatus.ts b/src/utils/constants/fetchStatus.ts index 0bc917f85..416595da7 100644 --- a/src/utils/constants/fetchStatus.ts +++ b/src/utils/constants/fetchStatus.ts @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export const FetchStatus = { - IDLE: 'IDLE', - RUNNING: 'RUNNING', - SUCCEED: 'SUCCEED', - FAILED: 'FAILED', -}; +export enum FetchStatus { + SUCCEED = 'SUCCEED', + FAILED = 'FAILED', + IDLE = 'IDLE', + RUNNING = 'RUNNING', +} From a350ba9702a93674d175c3432496fb9d329945fb Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 28 Jan 2026 19:24:24 +0100 Subject: [PATCH 13/34] forgot to move this one (unused actually) Signed-off-by: David BRAQUART --- src/utils/types/equipmentType.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/types/equipmentType.ts b/src/utils/types/equipmentType.ts index b65d1c2fb..55e14f5e1 100644 --- a/src/utils/types/equipmentType.ts +++ b/src/utils/types/equipmentType.ts @@ -302,3 +302,6 @@ export const EquipmentInfosTypes: Record = { TOOLTIP: { type: 'TOOLTIP' }, OPERATING_STATUS: { type: 'OPERATING_STATUS' }, }; +export type EquipmentInfosTypes = EquipmentInfosTypesStruct< + 'LIST' | 'MAP' | 'FORM' | 'TAB' | 'TOOLTIP' | 'OPERATING_STATUS' +>; From 10a1451108e4ee4678a50a752dafbd80abff3827 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Thu, 29 Jan 2026 11:27:32 +0100 Subject: [PATCH 14/34] use more StudyContext type Signed-off-by: David BRAQUART --- .../common/equipment-search-dialog.tsx | 23 ++++------------- .../creation/substation-creation-dialog.tsx | 5 +--- src/hooks/use-search-matching-equipments.tsx | 25 ++++++++----------- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/components/network-modifications/common/equipment-search-dialog.tsx b/src/components/network-modifications/common/equipment-search-dialog.tsx index 3dc86b516..644625674 100644 --- a/src/components/network-modifications/common/equipment-search-dialog.tsx +++ b/src/components/network-modifications/common/equipment-search-dialog.tsx @@ -6,11 +6,10 @@ */ import { useIntl } from 'react-intl'; -import type { UUID } from 'node:crypto'; import { TextField } from '@mui/material'; import { Search } from '@mui/icons-material'; import { ElementSearchDialog, EquipmentItem } from '../../elementSearch'; -import { EquipmentInfos, equipmentStyles, EquipmentType, ExtendedEquipmentType } from '../../../utils'; +import { EquipmentInfos, equipmentStyles, EquipmentType, ExtendedEquipmentType, StudyContext } from '../../../utils'; import { useSearchMatchingEquipments } from '../../../hooks'; interface EquipmentSearchDialogProps { @@ -18,10 +17,7 @@ interface EquipmentSearchDialogProps { onClose: () => void; onSelectionChange: (equipment: EquipmentInfos) => void; equipmentType: EquipmentType | ExtendedEquipmentType; - studyUuid: UUID; - currentNodeUuid: UUID; - currentRootNetworkUuid: UUID; - useName: boolean; + studyContext: StudyContext; } /** @@ -30,29 +26,20 @@ interface EquipmentSearchDialogProps { * @param {Function} onClose: callback to call when closing the dialog * @param {Function} onSelectionChange: callback when the selection changes * @param {String} equipmentType: the type of equipment we want to search - * @param {String} studyUuid: the current study - * @param {String} currentNodeUuid: the node selected - * @param {String} currentRootNetworkUuid: the root network UUID - * @param {Boolean} useName: equipment naming (name vs id) + * @param studyContext the current tree node context (study case) */ export function EquipmentSearchDialog({ open, onClose, onSelectionChange, equipmentType, - studyUuid, - currentNodeUuid, - currentRootNetworkUuid, - useName, + studyContext, }: Readonly) { const intl = useIntl(); const { searchTerm, updateSearchTerm, equipmentsFound, isLoading } = useSearchMatchingEquipments({ - studyUuid, - nodeUuid: currentNodeUuid, - currentRootNetworkUuid, inUpstreamBuiltParentNode: true, equipmentType, - useName, + studyContext, }); return ( diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx index 2da715920..8187871ee 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx @@ -198,10 +198,7 @@ export function SubstationCreationDialog({ onClose={searchCopy.handleCloseSearchDialog} equipmentType={EquipmentType.SUBSTATION} onSelectionChange={searchCopy.handleSelectionChange} - studyUuid={studyContext.studyId} - currentNodeUuid={studyContext.nodeId} - currentRootNetworkUuid={studyContext.rootNetworkId} - useName={studyContext.useNameParam} + studyContext={studyContext} /> )} diff --git a/src/hooks/use-search-matching-equipments.tsx b/src/hooks/use-search-matching-equipments.tsx index 3eed9d329..fc04e27f9 100644 --- a/src/hooks/use-search-matching-equipments.tsx +++ b/src/hooks/use-search-matching-equipments.tsx @@ -6,7 +6,6 @@ */ import { useCallback, useEffect, useMemo } from 'react'; -import type { UUID } from 'node:crypto'; import { Equipment, EquipmentType, @@ -15,45 +14,43 @@ import { getIdentifiableNameOrId, getUseNameKey, Identifiable, + StudyContext, } from '../utils'; import { searchEquipmentsInfos } from '../services'; import { useElementSearch } from './useElementSearch'; interface UseSearchMatchingEquipmentsProps { - studyUuid: UUID; - nodeUuid: UUID; - currentRootNetworkUuid: UUID; - useName: boolean; inUpstreamBuiltParentNode?: boolean; equipmentType?: EquipmentType | ExtendedEquipmentType; + studyContext: StudyContext; } export const useSearchMatchingEquipments = (props: UseSearchMatchingEquipmentsProps) => { - const { studyUuid, nodeUuid, currentRootNetworkUuid, inUpstreamBuiltParentNode, equipmentType, useName } = props; + const { studyContext, inUpstreamBuiltParentNode, equipmentType } = props; const getNameOrId = useCallback( (infos?: Identifiable | null) => { - return getIdentifiableNameOrId(useName, infos); + return getIdentifiableNameOrId(studyContext.useNameParam, infos); }, - [useName] + [studyContext.useNameParam] ); const getUseNameParameterKey = useCallback(() => { - return getUseNameKey(useName); - }, [useName]); + return getUseNameKey(studyContext.useNameParam); + }, [studyContext.useNameParam]); const fetchElements: (newSearchTerm: string) => Promise = useCallback( (newSearchTerm) => searchEquipmentsInfos( - studyUuid, - nodeUuid, - currentRootNetworkUuid, + studyContext.studyId, + studyContext.nodeId, + studyContext.rootNetworkId, newSearchTerm, getUseNameParameterKey, inUpstreamBuiltParentNode, equipmentType ), - [equipmentType, getUseNameParameterKey, inUpstreamBuiltParentNode, nodeUuid, studyUuid, currentRootNetworkUuid] + [equipmentType, getUseNameParameterKey, inUpstreamBuiltParentNode, studyContext] ); const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ From f393ccba62599676275ee984c0bb2405d73974db Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Thu, 29 Jan 2026 18:40:00 +0100 Subject: [PATCH 15/34] no need csv-picker translations Signed-off-by: David BRAQUART --- src/translations/en/csvEn.ts | 1 + src/translations/en/index.ts | 1 - src/translations/en/networkModificationsEn.ts | 1 + src/translations/en/use-csv-picker-en.ts | 12 ------------ src/translations/fr/csvFr.ts | 1 + src/translations/fr/index.ts | 1 - src/translations/fr/networkModificationsFr.ts | 1 + src/translations/fr/use-csv-picker-fr.ts | 12 ------------ 8 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 src/translations/en/use-csv-picker-en.ts delete mode 100644 src/translations/fr/use-csv-picker-fr.ts diff --git a/src/translations/en/csvEn.ts b/src/translations/en/csvEn.ts index 11c9263a6..c8d03ae77 100644 --- a/src/translations/en/csvEn.ts +++ b/src/translations/en/csvEn.ts @@ -14,4 +14,5 @@ export const csvEn = { GenerateCSV: 'Generate CSV template', UploadCSV: 'Upload CSV', uploadMessage: ' No file selected', + InvalidRuleHeader: 'The file has an invalid header, please refer yourself to the template', }; diff --git a/src/translations/en/index.ts b/src/translations/en/index.ts index 84d8137f2..8e0cdb36f 100644 --- a/src/translations/en/index.ts +++ b/src/translations/en/index.ts @@ -33,4 +33,3 @@ export * from './external/importParamsEn'; export * from './componentsEn'; export * from './parameters'; export * from './use-unique-name-validation-en'; -export * from './use-csv-picker-en'; diff --git a/src/translations/en/networkModificationsEn.ts b/src/translations/en/networkModificationsEn.ts index f54787cd7..9887e6d1f 100644 --- a/src/translations/en/networkModificationsEn.ts +++ b/src/translations/en/networkModificationsEn.ts @@ -88,4 +88,5 @@ export const networkModificationsEn = { CopyFromExisting: 'Copy from existing equipment', EquipmentCopied: 'Data successfully copied from equipment {equipmentId}', EquipmentCopyFailed: 'Equipment copy failed', + TapPositionValueError: 'The number of taps must not exceed the value {value}', }; diff --git a/src/translations/en/use-csv-picker-en.ts b/src/translations/en/use-csv-picker-en.ts deleted file mode 100644 index 8c9e24f8b..000000000 --- a/src/translations/en/use-csv-picker-en.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2026, 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 const useCsvPickerEn = { - 'use-csv-picker/InvalidRuleHeader': 'The file has an invalid header, please refer yourself to the template', - 'use-csv-picker/uploadMessage': ' No file selected', - 'use-csv-picker/TapPositionValueError': 'The number of taps must not exceed the value {value}', -}; diff --git a/src/translations/fr/csvFr.ts b/src/translations/fr/csvFr.ts index 665bcf70c..4bf567b79 100644 --- a/src/translations/fr/csvFr.ts +++ b/src/translations/fr/csvFr.ts @@ -14,4 +14,5 @@ export const csvFr = { GenerateCSV: 'Générer le modèle CSV', UploadCSV: 'Télécharger le CSV', uploadMessage: ' Aucun fichier sélectionné', + InvalidRuleHeader: "Le fichier n'a pas le bon en-tête, veuillez vous référer au modèle", }; diff --git a/src/translations/fr/index.ts b/src/translations/fr/index.ts index 2f5aa2fbf..cfff22fca 100644 --- a/src/translations/fr/index.ts +++ b/src/translations/fr/index.ts @@ -33,4 +33,3 @@ export * from './external/importParamsFr'; export * from './componentsFr'; export * from './parameters'; export * from './use-unique-name-validation-fr'; -export * from './use-csv-picker-fr'; diff --git a/src/translations/fr/networkModificationsFr.ts b/src/translations/fr/networkModificationsFr.ts index 12f0d3802..a78ce5129 100644 --- a/src/translations/fr/networkModificationsFr.ts +++ b/src/translations/fr/networkModificationsFr.ts @@ -92,4 +92,5 @@ export const networkModificationsFr = { CopyFromExisting: 'Copier depuis un ouvrage existant', EquipmentCopied: "Données copiées depuis l'ouvrage {equipmentId}", EquipmentCopyFailed: "Erreur lors de la copie de l'ouvrage", + TapPositionValueError: 'Le nombre de prises ne doit pas dépasser la valeur {value}' }; diff --git a/src/translations/fr/use-csv-picker-fr.ts b/src/translations/fr/use-csv-picker-fr.ts deleted file mode 100644 index 4032d2287..000000000 --- a/src/translations/fr/use-csv-picker-fr.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2026, 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 const useCsvPickerFr = { - 'use-csv-picker/InvalidRuleHeader': "Le fichier n'a pas le bon en-tête, veuillez vous référer au modèle", - 'use-csv-picker/uploadMessage': ' Aucun fichier sélectionné', - 'use-csv-picker/TapPositionValueError': 'Le nombre de prises ne doit pas dépasser la valeur {value}', -}; From 0b75373d171840ddf8c7d41686699ed078ec9ab0 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Fri, 30 Jan 2026 19:18:19 +0100 Subject: [PATCH 16/34] report from grid-study "Add modification submit button data test id (#3703)" Signed-off-by: David BRAQUART --- .../common/modification-dialog/modification-dialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx b/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx index 27725fe0f..b4fb22aa4 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx +++ b/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx @@ -71,6 +71,7 @@ export function ModificationDialog({ const submitButton = ( Date: Tue, 3 Feb 2026 12:41:47 +0100 Subject: [PATCH 17/34] simplify EquipmentInfosTypes as enum Signed-off-by: David BRAQUART --- src/hooks/use-form-search-copy.ts | 2 +- src/utils/types/equipmentType.ts | 21 +++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/hooks/use-form-search-copy.ts b/src/hooks/use-form-search-copy.ts index 12cf8144c..cc2b5cc30 100644 --- a/src/hooks/use-form-search-copy.ts +++ b/src/hooks/use-form-search-copy.ts @@ -56,7 +56,7 @@ export function useFormSearchCopy( studyContext.nodeId, studyContext.rootNetworkId, elementType, - EquipmentInfosTypes.FORM.type, + EquipmentInfosTypes.FORM, element.id as UUID, true ) diff --git a/src/utils/types/equipmentType.ts b/src/utils/types/equipmentType.ts index 55e14f5e1..02365959d 100644 --- a/src/utils/types/equipmentType.ts +++ b/src/utils/types/equipmentType.ts @@ -293,15 +293,12 @@ export function getEquipmentsInfosForSearchBar(equipmentsInfos: Equipment[], get }); } -type EquipmentInfosTypesStruct = { type: T }; -export const EquipmentInfosTypes: Record = { - LIST: { type: 'LIST' }, - MAP: { type: 'MAP' }, - FORM: { type: 'FORM' }, - TAB: { type: 'TAB' }, - TOOLTIP: { type: 'TOOLTIP' }, - OPERATING_STATUS: { type: 'OPERATING_STATUS' }, -}; -export type EquipmentInfosTypes = EquipmentInfosTypesStruct< - 'LIST' | 'MAP' | 'FORM' | 'TAB' | 'TOOLTIP' | 'OPERATING_STATUS' ->; +// cf ElementInfos.InfoType from map-server +export enum EquipmentInfosTypes { + LIST = 'LIST', + MAP = 'MAP', + FORM = 'FORM', + TAB = 'TAB', + TOOLTIP = 'TOOLTIP', + OPERATING_STATUS = 'OPERATING_STATUS', +} From 2539d07290bc967881caebf5b4c651396f9b2387 Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 3 Feb 2026 13:18:39 +0100 Subject: [PATCH 18/34] rev remark: add copyright in headers Signed-off-by: David BRAQUART --- src/components/network-modifications/common/index.ts | 5 +++-- .../common/modification-dialog/index.ts | 5 +++-- .../network-modifications/common/properties/index.ts | 5 +++-- src/components/network-modifications/index.ts | 5 +++-- src/components/network-modifications/substation/index.ts | 5 +++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/network-modifications/common/index.ts b/src/components/network-modifications/common/index.ts index fed3a1327..ad4c22cfa 100644 --- a/src/components/network-modifications/common/index.ts +++ b/src/components/network-modifications/common/index.ts @@ -1,7 +1,8 @@ -/* +/** + * Copyright (c) 2026, 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 https://mozilla.org/MPL/2.0/. + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './modification-dialog'; diff --git a/src/components/network-modifications/common/modification-dialog/index.ts b/src/components/network-modifications/common/modification-dialog/index.ts index 0f8031473..fd03d7037 100644 --- a/src/components/network-modifications/common/modification-dialog/index.ts +++ b/src/components/network-modifications/common/modification-dialog/index.ts @@ -1,7 +1,8 @@ -/* +/** + * Copyright (c) 2026, 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 https://mozilla.org/MPL/2.0/. + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './modification-dialog-content'; diff --git a/src/components/network-modifications/common/properties/index.ts b/src/components/network-modifications/common/properties/index.ts index 003ceb6be..1aa6a80e4 100644 --- a/src/components/network-modifications/common/properties/index.ts +++ b/src/components/network-modifications/common/properties/index.ts @@ -1,7 +1,8 @@ -/* +/** + * Copyright (c) 2026, 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 https://mozilla.org/MPL/2.0/. + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './properties-form'; diff --git a/src/components/network-modifications/index.ts b/src/components/network-modifications/index.ts index c0c6cdf07..a55b30f6b 100644 --- a/src/components/network-modifications/index.ts +++ b/src/components/network-modifications/index.ts @@ -1,7 +1,8 @@ -/* +/** + * Copyright (c) 2026, 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 https://mozilla.org/MPL/2.0/. + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './common'; diff --git a/src/components/network-modifications/substation/index.ts b/src/components/network-modifications/substation/index.ts index 22c23ccda..fbc880f99 100644 --- a/src/components/network-modifications/substation/index.ts +++ b/src/components/network-modifications/substation/index.ts @@ -1,7 +1,8 @@ -/* +/** + * Copyright (c) 2026, 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 https://mozilla.org/MPL/2.0/. + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './creation'; From 15f6e3a96202760da0aaf2f9c2c08b121b370d4b Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 3 Feb 2026 15:29:04 +0100 Subject: [PATCH 19/34] rev remark: use camelCase for new files Signed-off-by: David BRAQUART --- .../dialogs/{dialog-utils.ts => dialogUtils.ts} | 0 src/components/dialogs/index.ts | 2 +- ...ction-input.tsx => CountrySelectionInput.tsx} | 0 .../{deletable-row.tsx => DeletableRow.tsx} | 0 ...{expandable-input.tsx => ExpandableInput.tsx} | 2 +- .../reactHookForm/expandableInput/index.ts | 2 +- src/components/inputs/reactHookForm/index.ts | 2 +- .../reactHookForm/text/UniqueNameInput.tsx | 2 +- ...arch-dialog.tsx => EquipmentSearchDialog.tsx} | 0 .../network-modifications/common/index.ts | 4 ++-- .../ModificationDialog.tsx} | 4 ++-- .../ModificationDialogContent.tsx} | 2 +- .../index.ts | 6 +++--- .../modificationDialog.type.ts} | 0 .../{properties-form.tsx => PropertiesForm.tsx} | 4 ++-- .../{property-form.tsx => PropertyForm.tsx} | 0 .../common/properties/index.ts | 8 ++++---- .../{property-types.ts => properties.type.ts} | 0 .../{property-utils.ts => propertyUtils.ts} | 2 +- ...n-dialog.tsx => SubstationCreationDialog.tsx} | 4 ++-- ...ation-form.tsx => SubstationCreationForm.tsx} | 4 ++-- .../substation/creation/index.ts | 4 ++-- .../network-modifications/substation/index.ts | 2 +- ...n-dialog.type.ts => substationDialog.type.ts} | 0 .../sensi/sensitivity-parameters-selector.tsx | 2 +- src/hooks/index.ts | 16 ++++++++-------- ...with-tooltip.tsx => useButtonWithTooltip.tsx} | 2 +- .../{use-csv-picker.tsx => useCSVPicker.tsx} | 0 ...ow-data-sensi.ts => useCreateRowDataSensi.ts} | 0 ...-form-search-copy.ts => useFormSearchCopy.ts} | 0 ...t-fetching.ts => useOpenShortWaitFetching.ts} | 0 ...meters-backend.ts => useParametersBackend.ts} | 0 ...ments.tsx => useSearchMatchingEquipments.tsx} | 0 ...ple-text-value.tsx => useSimpleTextValue.tsx} | 0 ...-validation.ts => useUniqueNameValidation.ts} | 0 src/services/index.ts | 4 ++-- ...rk-modification.ts => networkModification.ts} | 2 +- ...tion-types.ts => networkModification.type.ts} | 2 +- src/services/study.ts | 4 ++-- src/translations/fr/networkModificationsFr.ts | 2 +- 40 files changed, 44 insertions(+), 44 deletions(-) rename src/components/dialogs/{dialog-utils.ts => dialogUtils.ts} (100%) rename src/components/inputs/reactHookForm/{country-selection-input.tsx => CountrySelectionInput.tsx} (100%) rename src/components/inputs/reactHookForm/expandableInput/{deletable-row.tsx => DeletableRow.tsx} (100%) rename src/components/inputs/reactHookForm/expandableInput/{expandable-input.tsx => ExpandableInput.tsx} (98%) rename src/components/network-modifications/common/{equipment-search-dialog.tsx => EquipmentSearchDialog.tsx} (100%) rename src/components/network-modifications/common/{modification-dialog/modification-dialog.tsx => modificationDialog/ModificationDialog.tsx} (95%) rename src/components/network-modifications/common/{modification-dialog/modification-dialog-content.tsx => modificationDialog/ModificationDialogContent.tsx} (97%) rename src/components/network-modifications/common/{modification-dialog => modificationDialog}/index.ts (67%) rename src/components/network-modifications/common/{modification-dialog/modification-dialog-types.tsx => modificationDialog/modificationDialog.type.ts} (100%) rename src/components/network-modifications/common/properties/{properties-form.tsx => PropertiesForm.tsx} (98%) rename src/components/network-modifications/common/properties/{property-form.tsx => PropertyForm.tsx} (100%) rename src/components/network-modifications/common/properties/{property-types.ts => properties.type.ts} (100%) rename src/components/network-modifications/common/properties/{property-utils.ts => propertyUtils.ts} (99%) rename src/components/network-modifications/substation/creation/{substation-creation-dialog.tsx => SubstationCreationDialog.tsx} (98%) rename src/components/network-modifications/substation/creation/{substation-creation-form.tsx => SubstationCreationForm.tsx} (93%) rename src/components/network-modifications/substation/{substation-dialog.type.ts => substationDialog.type.ts} (100%) rename src/hooks/{use-button-with-tooltip.tsx => useButtonWithTooltip.tsx} (95%) rename src/hooks/{use-csv-picker.tsx => useCSVPicker.tsx} (100%) rename src/hooks/{use-create-row-data-sensi.ts => useCreateRowDataSensi.ts} (100%) rename src/hooks/{use-form-search-copy.ts => useFormSearchCopy.ts} (100%) rename src/hooks/{use-open-short-wait-fetching.ts => useOpenShortWaitFetching.ts} (100%) rename src/hooks/{use-parameters-backend.ts => useParametersBackend.ts} (100%) rename src/hooks/{use-search-matching-equipments.tsx => useSearchMatchingEquipments.tsx} (100%) rename src/hooks/{use-simple-text-value.tsx => useSimpleTextValue.tsx} (100%) rename src/hooks/{use-unique-name-validation.ts => useUniqueNameValidation.ts} (100%) rename src/services/{network-modification.ts => networkModification.ts} (96%) rename src/services/{network-modification-types.ts => networkModification.type.ts} (96%) diff --git a/src/components/dialogs/dialog-utils.ts b/src/components/dialogs/dialogUtils.ts similarity index 100% rename from src/components/dialogs/dialog-utils.ts rename to src/components/dialogs/dialogUtils.ts diff --git a/src/components/dialogs/index.ts b/src/components/dialogs/index.ts index ee62e48f7..0665e44a1 100644 --- a/src/components/dialogs/index.ts +++ b/src/components/dialogs/index.ts @@ -9,4 +9,4 @@ export * from './descriptionModificationDialog'; export * from './elementSaveDialog'; export * from './modifyElementSelection'; export * from './popupConfirmationDialog'; -export * from './dialog-utils'; +export * from './dialogUtils'; diff --git a/src/components/inputs/reactHookForm/country-selection-input.tsx b/src/components/inputs/reactHookForm/CountrySelectionInput.tsx similarity index 100% rename from src/components/inputs/reactHookForm/country-selection-input.tsx rename to src/components/inputs/reactHookForm/CountrySelectionInput.tsx diff --git a/src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx b/src/components/inputs/reactHookForm/expandableInput/DeletableRow.tsx similarity index 100% rename from src/components/inputs/reactHookForm/expandableInput/deletable-row.tsx rename to src/components/inputs/reactHookForm/expandableInput/DeletableRow.tsx diff --git a/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx b/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx similarity index 98% rename from src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx rename to src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx index 482d62767..f64e31118 100644 --- a/src/components/inputs/reactHookForm/expandableInput/expandable-input.tsx +++ b/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx @@ -9,7 +9,7 @@ import { useFieldArray } from 'react-hook-form'; import { Button, Grid } from '@mui/material'; import { ControlPoint as AddIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; -import { DeletableRow } from './deletable-row'; +import { DeletableRow } from './DeletableRow'; import { ErrorInput, MidFormError } from '../errorManagement'; import { mergeSx } from '../../../../utils'; import { dialogStyles } from '../../../dialogs'; diff --git a/src/components/inputs/reactHookForm/expandableInput/index.ts b/src/components/inputs/reactHookForm/expandableInput/index.ts index bb1bb5cc3..17f7df8d5 100644 --- a/src/components/inputs/reactHookForm/expandableInput/index.ts +++ b/src/components/inputs/reactHookForm/expandableInput/index.ts @@ -5,4 +5,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './expandable-input'; +export * from './ExpandableInput'; diff --git a/src/components/inputs/reactHookForm/index.ts b/src/components/inputs/reactHookForm/index.ts index b13f60d7d..8895de408 100644 --- a/src/components/inputs/reactHookForm/index.ts +++ b/src/components/inputs/reactHookForm/index.ts @@ -22,4 +22,4 @@ export * from './text'; export * from './utils'; export * from './constants'; export * from './expandableInput'; -export * from './country-selection-input'; +export * from './CountrySelectionInput'; diff --git a/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx b/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx index eba870275..26ae0cc4f 100644 --- a/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx +++ b/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx @@ -13,7 +13,7 @@ import { useController } from 'react-hook-form'; import type { UUID } from 'node:crypto'; import type { ElementType } from '../../../../utils'; import type { SxStyle } from '../../../../utils/styles'; -import { useUniqueNameValidation } from '../../../../hooks/use-unique-name-validation'; +import { useUniqueNameValidation } from '../../../../hooks/useUniqueNameValidation'; export interface UniqueNameInputProps { name: string; diff --git a/src/components/network-modifications/common/equipment-search-dialog.tsx b/src/components/network-modifications/common/EquipmentSearchDialog.tsx similarity index 100% rename from src/components/network-modifications/common/equipment-search-dialog.tsx rename to src/components/network-modifications/common/EquipmentSearchDialog.tsx diff --git a/src/components/network-modifications/common/index.ts b/src/components/network-modifications/common/index.ts index ad4c22cfa..1348db66b 100644 --- a/src/components/network-modifications/common/index.ts +++ b/src/components/network-modifications/common/index.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './modification-dialog'; +export * from './modificationDialog'; export * from './properties'; -export * from './equipment-search-dialog'; +export * from './EquipmentSearchDialog'; export * from './modificationsUtils'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx b/src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx similarity index 95% rename from src/components/network-modifications/common/modification-dialog/modification-dialog.tsx rename to src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx index b4fb22aa4..92e2ada61 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog.tsx +++ b/src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx @@ -7,9 +7,9 @@ import { useCallback } from 'react'; import { FieldErrors, FieldValues, useFormContext } from 'react-hook-form'; -import { ModificationDialogContent } from './modification-dialog-content'; +import { ModificationDialogContent } from './ModificationDialogContent'; import { SubmitButton } from '../../../inputs'; -import { ModificationDialogProps } from './modification-dialog-types'; +import { ModificationDialogProps } from './modificationDialog.type'; /** * Generic Modification Dialog which manage basic common behaviors with react diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx b/src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx similarity index 97% rename from src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx rename to src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx index 049d02851..04d1e0c5a 100644 --- a/src/components/network-modifications/common/modification-dialog/modification-dialog-content.tsx +++ b/src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { CancelButton } from '../../../inputs'; import { useButtonWithTooltip } from '../../../../hooks'; -import { ModificationDialogContentProps } from './modification-dialog-types'; +import { ModificationDialogContentProps } from './modificationDialog.type'; /** * Common parts for the Modification Dialog diff --git a/src/components/network-modifications/common/modification-dialog/index.ts b/src/components/network-modifications/common/modificationDialog/index.ts similarity index 67% rename from src/components/network-modifications/common/modification-dialog/index.ts rename to src/components/network-modifications/common/modificationDialog/index.ts index fd03d7037..e05523f5f 100644 --- a/src/components/network-modifications/common/modification-dialog/index.ts +++ b/src/components/network-modifications/common/modificationDialog/index.ts @@ -5,6 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './modification-dialog-content'; -export * from './modification-dialog'; -export * from './modification-dialog-types'; +export * from './ModificationDialogContent'; +export * from './ModificationDialog'; +export * from './modificationDialog.type'; diff --git a/src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx b/src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts similarity index 100% rename from src/components/network-modifications/common/modification-dialog/modification-dialog-types.tsx rename to src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts diff --git a/src/components/network-modifications/common/properties/properties-form.tsx b/src/components/network-modifications/common/properties/PropertiesForm.tsx similarity index 98% rename from src/components/network-modifications/common/properties/properties-form.tsx rename to src/components/network-modifications/common/properties/PropertiesForm.tsx index 0bb3377cd..e479ff49c 100644 --- a/src/components/network-modifications/common/properties/properties-form.tsx +++ b/src/components/network-modifications/common/properties/PropertiesForm.tsx @@ -7,8 +7,8 @@ import { Grid } from '@mui/material'; import { useCallback, useEffect, useState } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; -import { fetchPredefinedProperties, initializedProperty } from './property-utils'; -import { PropertyForm } from './property-form'; +import { fetchPredefinedProperties, initializedProperty } from './propertyUtils'; +import { PropertyForm } from './PropertyForm'; import GridSection from '../../../grid/grid-section'; import { FieldConstants, PredefinedProperties } from '../../../../utils'; import { ExpandableInput } from '../../../inputs'; diff --git a/src/components/network-modifications/common/properties/property-form.tsx b/src/components/network-modifications/common/properties/PropertyForm.tsx similarity index 100% rename from src/components/network-modifications/common/properties/property-form.tsx rename to src/components/network-modifications/common/properties/PropertyForm.tsx diff --git a/src/components/network-modifications/common/properties/index.ts b/src/components/network-modifications/common/properties/index.ts index 1aa6a80e4..78373ff41 100644 --- a/src/components/network-modifications/common/properties/index.ts +++ b/src/components/network-modifications/common/properties/index.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './properties-form'; -export * from './property-form'; -export * from './property-types'; -export * from './property-utils'; +export * from './PropertiesForm'; +export * from './PropertyForm'; +export * from './properties.type'; +export * from './propertyUtils'; diff --git a/src/components/network-modifications/common/properties/property-types.ts b/src/components/network-modifications/common/properties/properties.type.ts similarity index 100% rename from src/components/network-modifications/common/properties/property-types.ts rename to src/components/network-modifications/common/properties/properties.type.ts diff --git a/src/components/network-modifications/common/properties/property-utils.ts b/src/components/network-modifications/common/properties/propertyUtils.ts similarity index 99% rename from src/components/network-modifications/common/properties/property-utils.ts rename to src/components/network-modifications/common/properties/propertyUtils.ts index 226d6a4f6..076052059 100644 --- a/src/components/network-modifications/common/properties/property-utils.ts +++ b/src/components/network-modifications/common/properties/propertyUtils.ts @@ -7,7 +7,7 @@ import { FieldConstants, isBlankOrEmpty, PredefinedProperties } from '../../../../utils'; import yup from '../../../../utils/yupConfig'; import { fetchStudyMetadata } from '../../../../services'; -import { Properties, Property } from './property-types'; +import { Properties, Property } from './properties.type'; export type EquipmentWithProperties = { properties?: Record; diff --git a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx b/src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx similarity index 98% rename from src/components/network-modifications/substation/creation/substation-creation-dialog.tsx rename to src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx index 8187871ee..a6f64fd0d 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-dialog.tsx +++ b/src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx @@ -9,7 +9,7 @@ import { useForm } from 'react-hook-form'; import { useCallback, useEffect } from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; import { UUID } from 'node:crypto'; -import { SubstationCreationForm } from './substation-creation-form'; +import { SubstationCreationForm } from './SubstationCreationForm'; import { useFormSearchCopy, useOpenShortWaitFetching, useSnackMessage } from '../../../../hooks'; import { DeepNullable, @@ -38,7 +38,7 @@ import { fetchDefaultCountry, SubstationCreationDto, } from '../../../../services'; -import { SubstationInfos } from '../substation-dialog.type'; +import { SubstationInfos } from '../substationDialog.type'; const formSchema = yup .object() diff --git a/src/components/network-modifications/substation/creation/substation-creation-form.tsx b/src/components/network-modifications/substation/creation/SubstationCreationForm.tsx similarity index 93% rename from src/components/network-modifications/substation/creation/substation-creation-form.tsx rename to src/components/network-modifications/substation/creation/SubstationCreationForm.tsx index e9b1255d7..d9d5eca5d 100644 --- a/src/components/network-modifications/substation/creation/substation-creation-form.tsx +++ b/src/components/network-modifications/substation/creation/SubstationCreationForm.tsx @@ -8,10 +8,10 @@ import { Grid } from '@mui/material'; import GridItem from '../../../grid/grid-item'; import { TextInput } from '../../../inputs'; -import { CountrySelectionInput } from '../../../inputs/reactHookForm/country-selection-input'; +import { CountrySelectionInput } from '../../../inputs/reactHookForm/CountrySelectionInput'; import { FieldConstants } from '../../../../utils'; import { filledTextField } from '../../../dialogs'; -import { PropertiesForm } from '../../common/properties/properties-form'; +import { PropertiesForm } from '../../common/properties/PropertiesForm'; export function SubstationCreationForm() { const substationIdField = ; diff --git a/src/components/network-modifications/substation/creation/index.ts b/src/components/network-modifications/substation/creation/index.ts index 32dc2337d..518fe8c9b 100644 --- a/src/components/network-modifications/substation/creation/index.ts +++ b/src/components/network-modifications/substation/creation/index.ts @@ -4,5 +4,5 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -export * from './substation-creation-dialog'; -export * from './substation-creation-form'; +export * from './SubstationCreationDialog'; +export * from './SubstationCreationForm'; diff --git a/src/components/network-modifications/substation/index.ts b/src/components/network-modifications/substation/index.ts index fbc880f99..8ec568af5 100644 --- a/src/components/network-modifications/substation/index.ts +++ b/src/components/network-modifications/substation/index.ts @@ -6,4 +6,4 @@ */ export * from './creation'; -export * from './substation-dialog.type'; +export * from './substationDialog.type'; diff --git a/src/components/network-modifications/substation/substation-dialog.type.ts b/src/components/network-modifications/substation/substationDialog.type.ts similarity index 100% rename from src/components/network-modifications/substation/substation-dialog.type.ts rename to src/components/network-modifications/substation/substationDialog.type.ts diff --git a/src/components/parameters/sensi/sensitivity-parameters-selector.tsx b/src/components/parameters/sensi/sensitivity-parameters-selector.tsx index fb9021797..72b02e2c6 100644 --- a/src/components/parameters/sensi/sensitivity-parameters-selector.tsx +++ b/src/components/parameters/sensi/sensitivity-parameters-selector.tsx @@ -21,7 +21,7 @@ import { } from './columns-definitions'; import { SensitivityTable } from './sensitivity-table'; import { TabPanel } from '../common'; -import { useCreateRowDataSensi } from '../../../hooks/use-create-row-data-sensi'; +import { useCreateRowDataSensi } from '../../../hooks/useCreateRowDataSensi'; import type { MuiStyles } from '../../../utils/styles'; import { SensitivityAnalysisParametersFactorCount } from './sensitivity-analysis-parameters-factor-count'; import { MAX_RESULTS_COUNT, MAX_VARIABLES_COUNT } from './constants'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 353469684..225330182 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,22 +6,22 @@ */ export * from './customStates'; -export * from './use-button-with-tooltip'; -export * from './use-csv-picker'; +export * from './useButtonWithTooltip'; +export * from './useCSVPicker'; export * from './useModificationLabelComputer'; export * from './useConfidentialityWarning'; export * from './useDebounce'; export * from './useIntlRef'; -export * from './use-form-search-copy'; +export * from './useFormSearchCopy'; export * from './useLocalizedCountries'; export * from './usePredefinedProperties'; -export * from './use-open-short-wait-fetching'; +export * from './useOpenShortWaitFetching'; export * from './usePrevious'; -export * from './use-simple-text-value'; +export * from './useSimpleTextValue'; export * from './useSnackMessage'; export * from './useFormatLabelWithUnit'; export * from './useSelectAppearance'; -export * from './use-parameters-backend'; -export * from './use-create-row-data-sensi'; -export * from './use-search-matching-equipments'; +export * from './useParametersBackend'; +export * from './useCreateRowDataSensi'; +export * from './useSearchMatchingEquipments'; export * from './useElementSearch'; diff --git a/src/hooks/use-button-with-tooltip.tsx b/src/hooks/useButtonWithTooltip.tsx similarity index 95% rename from src/hooks/use-button-with-tooltip.tsx rename to src/hooks/useButtonWithTooltip.tsx index de844c2f2..cac09c9dc 100644 --- a/src/hooks/use-button-with-tooltip.tsx +++ b/src/hooks/useButtonWithTooltip.tsx @@ -8,7 +8,7 @@ import React, { ReactNode, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { Tooltip, IconButton } from '@mui/material'; -import { dialogStyles } from '../components/dialogs/dialog-utils'; +import { dialogStyles } from '../components/dialogs/dialogUtils'; import { TOOLTIP_DELAY } from '../utils'; interface UseButtonWithTooltipProps { diff --git a/src/hooks/use-csv-picker.tsx b/src/hooks/useCSVPicker.tsx similarity index 100% rename from src/hooks/use-csv-picker.tsx rename to src/hooks/useCSVPicker.tsx diff --git a/src/hooks/use-create-row-data-sensi.ts b/src/hooks/useCreateRowDataSensi.ts similarity index 100% rename from src/hooks/use-create-row-data-sensi.ts rename to src/hooks/useCreateRowDataSensi.ts diff --git a/src/hooks/use-form-search-copy.ts b/src/hooks/useFormSearchCopy.ts similarity index 100% rename from src/hooks/use-form-search-copy.ts rename to src/hooks/useFormSearchCopy.ts diff --git a/src/hooks/use-open-short-wait-fetching.ts b/src/hooks/useOpenShortWaitFetching.ts similarity index 100% rename from src/hooks/use-open-short-wait-fetching.ts rename to src/hooks/useOpenShortWaitFetching.ts diff --git a/src/hooks/use-parameters-backend.ts b/src/hooks/useParametersBackend.ts similarity index 100% rename from src/hooks/use-parameters-backend.ts rename to src/hooks/useParametersBackend.ts diff --git a/src/hooks/use-search-matching-equipments.tsx b/src/hooks/useSearchMatchingEquipments.tsx similarity index 100% rename from src/hooks/use-search-matching-equipments.tsx rename to src/hooks/useSearchMatchingEquipments.tsx diff --git a/src/hooks/use-simple-text-value.tsx b/src/hooks/useSimpleTextValue.tsx similarity index 100% rename from src/hooks/use-simple-text-value.tsx rename to src/hooks/useSimpleTextValue.tsx diff --git a/src/hooks/use-unique-name-validation.ts b/src/hooks/useUniqueNameValidation.ts similarity index 100% rename from src/hooks/use-unique-name-validation.ts rename to src/hooks/useUniqueNameValidation.ts diff --git a/src/services/index.ts b/src/services/index.ts index 621486d41..9dd5e130c 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -17,5 +17,5 @@ export * from './userAdmin'; export * from './utils'; export * from './voltage-init'; export * from './short-circuit-analysis'; -export * from './network-modification'; -export * from './network-modification-types'; +export * from './networkModification'; +export * from './networkModification.type'; diff --git a/src/services/network-modification.ts b/src/services/networkModification.ts similarity index 96% rename from src/services/network-modification.ts rename to src/services/networkModification.ts index 9ee8a9177..dbf7a9d93 100644 --- a/src/services/network-modification.ts +++ b/src/services/networkModification.ts @@ -7,7 +7,7 @@ import type { UUID } from 'node:crypto'; import { backendFetch, backendFetchText } from './utils'; -import { SubstationCreationDto } from './network-modification-types'; +import { SubstationCreationDto } from './networkModification.type'; import { ModificationType } from '../utils'; const PREFIX_NETWORK_MODIFICATION_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/network-modification`; diff --git a/src/services/network-modification-types.ts b/src/services/networkModification.type.ts similarity index 96% rename from src/services/network-modification-types.ts rename to src/services/networkModification.type.ts index 5b24be287..3b003e38c 100644 --- a/src/services/network-modification-types.ts +++ b/src/services/networkModification.type.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { UUID } from 'node:crypto'; -import { Property } from '../components/network-modifications/common/properties/property-types'; +import { Property } from '../components/network-modifications/common/properties/properties.type'; export interface NetworkModificationData { uuid: UUID; diff --git a/src/services/study.ts b/src/services/study.ts index a53c8403f..4fa843898 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -11,8 +11,8 @@ import { NetworkVisualizationParameters } from '../components/parameters/network import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; import { EquipmentType, ExtendedEquipmentType, NamingConventionValues } from '../utils'; -import { SubstationCreationInfo } from './network-modification-types'; -import { createSubstationPromise } from './network-modification'; +import { SubstationCreationInfo } from './networkModification.type'; +import { createSubstationPromise } from './networkModification'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; diff --git a/src/translations/fr/networkModificationsFr.ts b/src/translations/fr/networkModificationsFr.ts index a78ce5129..762aef9e1 100644 --- a/src/translations/fr/networkModificationsFr.ts +++ b/src/translations/fr/networkModificationsFr.ts @@ -92,5 +92,5 @@ export const networkModificationsFr = { CopyFromExisting: 'Copier depuis un ouvrage existant', EquipmentCopied: "Données copiées depuis l'ouvrage {equipmentId}", EquipmentCopyFailed: "Erreur lors de la copie de l'ouvrage", - TapPositionValueError: 'Le nombre de prises ne doit pas dépasser la valeur {value}' + TapPositionValueError: 'Le nombre de prises ne doit pas dépasser la valeur {value}', }; From 71ff052f0b20e36341851d34fb2f6268e4cc380e Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Tue, 3 Feb 2026 18:22:32 +0100 Subject: [PATCH 20/34] rev remark: add ModificationReadError header msg Signed-off-by: David BRAQUART --- src/translations/en/networkModificationsEn.ts | 1 + src/translations/fr/networkModificationsFr.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/translations/en/networkModificationsEn.ts b/src/translations/en/networkModificationsEn.ts index 9887e6d1f..475097dc1 100644 --- a/src/translations/en/networkModificationsEn.ts +++ b/src/translations/en/networkModificationsEn.ts @@ -76,6 +76,7 @@ export const networkModificationsEn = { 'network_modifications.CREATE_VOLTAGE_LEVEL_TOPOLOGY': 'Creating a busbar in voltage level {computedLabel}', 'network_modifications.CREATE_VOLTAGE_LEVEL_SECTION': 'Adding busbar section to voltage level {computedLabel}', 'network_modifications.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS': 'Moving feeder bays in voltage level {computedLabel}', + ModificationReadError: 'An error occurred while fetching the modification', CreateSubstation: 'Create substation', SubstationCreationError: 'Error while creating substation', AdditionalInformation: 'Additional information', diff --git a/src/translations/fr/networkModificationsFr.ts b/src/translations/fr/networkModificationsFr.ts index 762aef9e1..16bce0337 100644 --- a/src/translations/fr/networkModificationsFr.ts +++ b/src/translations/fr/networkModificationsFr.ts @@ -80,6 +80,7 @@ export const networkModificationsFr = { 'network_modifications.CREATE_VOLTAGE_LEVEL_TOPOLOGY': "Ajout d'un jeu de barre dans le poste {computedLabel}", 'network_modifications.CREATE_VOLTAGE_LEVEL_SECTION': 'Ajout de section / tronçon dans le poste {computedLabel}', 'network_modifications.MOVE_VOLTAGE_LEVEL_FEEDER_BAYS': 'Déplacements de départs dans le poste {computedLabel}', + ModificationReadError: 'Une erreur est survenue lors de la récupération de la modification', CreateSubstation: 'Créer un site', SubstationCreationError: "Erreur lors de la création d'un site'", AdditionalInformation: 'Informations complémentaires', From 58594264644fa9bc3b87838002a3a2657fc1a1e2 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Thu, 5 Feb 2026 11:51:07 +0100 Subject: [PATCH 21/34] fix: import only useful parts from study Signed-off-by: Joris Mancini --- .../network-modifications/common/index.ts | 1 - .../common/modificationsUtils.ts | 23 -- .../common/properties/properties.type.ts | 4 + .../common/properties/propertyUtils.ts | 18 +- .../creation/SubstationCreationDialog.tsx | 207 ------------------ .../substation/creation/index.ts | 3 +- .../creation/substationCreation.types.ts | 9 + .../creation/substationCreation.utils.ts | 46 ++++ .../network-modifications/substation/index.ts | 1 - .../substation/substationDialog.type.ts | 13 -- src/services/index.ts | 1 - src/services/networkModification.ts | 31 +-- src/services/networkModification.type.ts | 30 --- src/services/study.ts | 13 -- src/translations/en/networkModificationsEn.ts | 1 + src/translations/fr/networkModificationsFr.ts | 1 + src/utils/ts-utils.ts | 21 ++ 17 files changed, 107 insertions(+), 316 deletions(-) delete mode 100644 src/components/network-modifications/common/modificationsUtils.ts delete mode 100644 src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx create mode 100644 src/components/network-modifications/substation/creation/substationCreation.types.ts create mode 100644 src/components/network-modifications/substation/creation/substationCreation.utils.ts delete mode 100644 src/components/network-modifications/substation/substationDialog.type.ts delete mode 100644 src/services/networkModification.type.ts diff --git a/src/components/network-modifications/common/index.ts b/src/components/network-modifications/common/index.ts index 1348db66b..6d7b7ad37 100644 --- a/src/components/network-modifications/common/index.ts +++ b/src/components/network-modifications/common/index.ts @@ -8,4 +8,3 @@ export * from './modificationDialog'; export * from './properties'; export * from './EquipmentSearchDialog'; -export * from './modificationsUtils'; diff --git a/src/components/network-modifications/common/modificationsUtils.ts b/src/components/network-modifications/common/modificationsUtils.ts deleted file mode 100644 index 9b4dcce06..000000000 --- a/src/components/network-modifications/common/modificationsUtils.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import { NetworkModificationData } from '../../../services'; - -export const removeNullFields = (data: NetworkModificationData) => { - const dataTemp = data; - if (dataTemp) { - Object.keys(dataTemp).forEach((key) => { - if (dataTemp[key] && dataTemp[key] !== null && typeof dataTemp[key] === 'object') { - dataTemp[key] = removeNullFields(dataTemp[key]); - } - - if (dataTemp[key] === null) { - delete dataTemp[key]; - } - }); - } - return dataTemp; -}; diff --git a/src/components/network-modifications/common/properties/properties.type.ts b/src/components/network-modifications/common/properties/properties.type.ts index b338f3b5a..30589abe2 100644 --- a/src/components/network-modifications/common/properties/properties.type.ts +++ b/src/components/network-modifications/common/properties/properties.type.ts @@ -6,6 +6,10 @@ */ import { FieldConstants } from '../../../../utils'; +export type FilledProperty = Omit & { + [FieldConstants.VALUE]: string; +}; + export type Property = { [FieldConstants.NAME]: string; [FieldConstants.VALUE]?: string | null; diff --git a/src/components/network-modifications/common/properties/propertyUtils.ts b/src/components/network-modifications/common/properties/propertyUtils.ts index 076052059..5f21db0aa 100644 --- a/src/components/network-modifications/common/properties/propertyUtils.ts +++ b/src/components/network-modifications/common/properties/propertyUtils.ts @@ -7,7 +7,7 @@ import { FieldConstants, isBlankOrEmpty, PredefinedProperties } from '../../../../utils'; import yup from '../../../../utils/yupConfig'; import { fetchStudyMetadata } from '../../../../services'; -import { Properties, Property } from './properties.type'; +import { FilledProperty, Properties, Property } from './properties.type'; export type EquipmentWithProperties = { properties?: Record; @@ -37,6 +37,22 @@ export const initializedProperty = (): Property => { return createPropertyModification('', null); }; +export const getFilledPropertiesFromModification = (properties: Property[] | undefined | null): FilledProperty[] => { + return ( + properties + ?.filter((p) => p[FieldConstants.VALUE] != null) + .map((p) => { + return { + [FieldConstants.NAME]: p[FieldConstants.NAME], + [FieldConstants.VALUE]: p[FieldConstants.VALUE] as string, + [FieldConstants.PREVIOUS_VALUE]: p[FieldConstants.PREVIOUS_VALUE], + [FieldConstants.ADDED]: p[FieldConstants.ADDED], + [FieldConstants.DELETION_MARK]: p[FieldConstants.DELETION_MARK], + }; + }) ?? [] + ); +}; + export const getPropertiesFromModification = (properties: Property[] | undefined | null): Properties => { return { [FieldConstants.ADDITIONAL_PROPERTIES]: properties diff --git a/src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx b/src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx deleted file mode 100644 index a6f64fd0d..000000000 --- a/src/components/network-modifications/substation/creation/SubstationCreationDialog.tsx +++ /dev/null @@ -1,207 +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 { useForm } from 'react-hook-form'; -import { useCallback, useEffect } from 'react'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { UUID } from 'node:crypto'; -import { SubstationCreationForm } from './SubstationCreationForm'; -import { useFormSearchCopy, useOpenShortWaitFetching, useSnackMessage } from '../../../../hooks'; -import { - DeepNullable, - EquipmentType, - FetchStatus, - FieldConstants, - FORM_LOADING_DELAY, - snackWithFallback, -} from '../../../../utils'; -import { CustomFormProvider } from '../../../inputs'; -import yup from '../../../../utils/yupConfig'; -import { - copyEquipmentPropertiesForCreation, - creationPropertiesSchema, - EquipmentSearchDialog, - getPropertiesFromModification, - ModificationDialog, - NetworkModificationDialogProps, - Property, - toModificationProperties, -} from '../../common'; -import { sanitizeString } from '../../../dialogs'; -import { - createSubstation, - createSubstationInNode, - fetchDefaultCountry, - SubstationCreationDto, -} from '../../../../services'; -import { SubstationInfos } from '../substationDialog.type'; - -const formSchema = yup - .object() - .shape({ - [FieldConstants.EQUIPMENT_ID]: yup.string().required(), - [FieldConstants.EQUIPMENT_NAME]: yup.string().nullable(), - [FieldConstants.COUNTRY]: yup.string().nullable(), - }) - .concat(creationPropertiesSchema); - -export type SubstationCreationFormData = yup.InferType; - -const emptyFormData: SubstationCreationFormData = { - [FieldConstants.EQUIPMENT_ID]: '', - [FieldConstants.EQUIPMENT_NAME]: '', - [FieldConstants.COUNTRY]: null, - [FieldConstants.ADDITIONAL_PROPERTIES]: [], -}; - -interface SubstationCreationEditData { - uuid?: UUID; - equipmentId: string; - equipmentName?: string; - country: string | null; - properties?: Property[] | null; -} - -interface SubstationCreationDialogProps extends NetworkModificationDialogProps { - editData?: SubstationCreationEditData; -} - -/** - * Dialog to create a substation in the network - * @param editData the data to edit - * @param isUpdate check if edition form - * @param studyContext the current tree node context in case of using the modification in a study - * @param onClose callback when we close the dialog - * @param onValidated callback when we validate the dialog - * @param editDataFetchStatus indicates the status of fetching EditData - * @param language app language to ensure translations - * @param dialogProps props that are forwarded to the generic ModificationDialog component - */ -export function SubstationCreationDialog({ - editData, - isUpdate, - studyContext, - onClose, - onValidated, - editDataFetchStatus, - language, - ...dialogProps -}: Readonly) { - const { snackError } = useSnackMessage(); - - const formMethods = useForm>({ - defaultValues: emptyFormData, - resolver: yupResolver>(formSchema), - }); - - const { reset, getValues } = formMethods; - - const fromSearchCopyToFormValues = (substation: SubstationInfos) => { - reset( - { - [FieldConstants.EQUIPMENT_ID]: `${substation.id}(1)`, - [FieldConstants.EQUIPMENT_NAME]: substation.name ?? '', - [FieldConstants.COUNTRY]: substation.country, - ...copyEquipmentPropertiesForCreation(substation), - }, - { keepDefaultValues: true } - ); - }; - - const searchCopy = useFormSearchCopy(fromSearchCopyToFormValues, EquipmentType.SUBSTATION, studyContext); - - useEffect(() => { - if (editData) { - reset({ - [FieldConstants.EQUIPMENT_ID]: editData.equipmentId, - [FieldConstants.EQUIPMENT_NAME]: editData.equipmentName ?? '', - [FieldConstants.COUNTRY]: editData.country, - ...getPropertiesFromModification(editData.properties), - }); - } - }, [reset, editData]); - - // We set the default country only in creation mode - useEffect(() => { - if (!isUpdate) { - fetchDefaultCountry().then((country) => { - if (country) { - reset({ - ...getValues(), - [FieldConstants.COUNTRY]: country, - }); - } - }); - } - }, [reset, getValues, isUpdate]); - - const clear = useCallback(() => { - reset(emptyFormData); - }, [reset]); - - const onSubmit = useCallback( - (substation: SubstationCreationFormData) => { - const dto: SubstationCreationDto = { - substationId: substation[FieldConstants.EQUIPMENT_ID], - substationName: sanitizeString(substation[FieldConstants.EQUIPMENT_NAME]), - country: substation[FieldConstants.COUNTRY] ?? null, - isUpdate: !!editData, - modificationUuid: editData ? editData.uuid : undefined, - properties: toModificationProperties(substation), - }; - let promise: Promise; - if (studyContext) { - promise = createSubstationInNode({ - studyId: studyContext.studyId, - nodeId: studyContext.nodeId, - ...dto, - }); - } else { - promise = createSubstation(dto); - } - promise.catch((error: Error) => { - snackWithFallback(snackError, error, { headerId: 'SubstationCreationError' }); - }); - }, - [editData, snackError, studyContext] - ); - - const open = useOpenShortWaitFetching({ - isDataFetched: - !isUpdate || editDataFetchStatus === FetchStatus.SUCCEED || editDataFetchStatus === FetchStatus.FAILED, - delay: FORM_LOADING_DELAY, - }); - - return ( - - - - {studyContext && ( - - )} - - - ); -} diff --git a/src/components/network-modifications/substation/creation/index.ts b/src/components/network-modifications/substation/creation/index.ts index 518fe8c9b..0ea078438 100644 --- a/src/components/network-modifications/substation/creation/index.ts +++ b/src/components/network-modifications/substation/creation/index.ts @@ -4,5 +4,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -export * from './SubstationCreationDialog'; export * from './SubstationCreationForm'; +export * from './substationCreation.types'; +export * from './substationCreation.utils'; diff --git a/src/components/network-modifications/substation/creation/substationCreation.types.ts b/src/components/network-modifications/substation/creation/substationCreation.types.ts new file mode 100644 index 000000000..2ec377bad --- /dev/null +++ b/src/components/network-modifications/substation/creation/substationCreation.types.ts @@ -0,0 +1,9 @@ +import { Property } from '../../common'; + +export type SubstationCreationDto = { + type: string; + equipmentId: string; + equipmentName: string | null; + country: string | null; + properties: Property[] | null; +}; diff --git a/src/components/network-modifications/substation/creation/substationCreation.utils.ts b/src/components/network-modifications/substation/creation/substationCreation.utils.ts new file mode 100644 index 000000000..23eedd455 --- /dev/null +++ b/src/components/network-modifications/substation/creation/substationCreation.utils.ts @@ -0,0 +1,46 @@ +import { sanitizeString } from '../../../dialogs'; +import { + creationPropertiesSchema, + getFilledPropertiesFromModification, + toModificationProperties, +} from '../../common/properties/propertyUtils'; +import yup from '../../../../utils/yupConfig'; +import { FieldConstants } from '../../../../utils'; +import { SubstationCreationDto } from './substationCreation.types'; + +export const substationCreationFormToDto = (substationForm: SubstationCreationFormData): SubstationCreationDto => { + return { + type: 'SUBSTATION_CREATION', + equipmentId: substationForm[FieldConstants.EQUIPMENT_ID], + equipmentName: sanitizeString(substationForm[FieldConstants.EQUIPMENT_NAME]), + country: substationForm[FieldConstants.COUNTRY] ?? null, + properties: toModificationProperties(substationForm), + } satisfies SubstationCreationDto; +}; + +export const substationCreationDtoToForm = (substationDto: SubstationCreationDto): SubstationCreationFormData => { + return { + [FieldConstants.EQUIPMENT_ID]: substationDto.equipmentId, + [FieldConstants.EQUIPMENT_NAME]: substationDto.equipmentName ?? '', + [FieldConstants.COUNTRY]: substationDto.country, + [FieldConstants.ADDITIONAL_PROPERTIES]: getFilledPropertiesFromModification(substationDto.properties), + } satisfies SubstationCreationFormData; +}; + +export const substationCreationFormSchema = yup + .object() + .shape({ + [FieldConstants.EQUIPMENT_ID]: yup.string().required(), + [FieldConstants.EQUIPMENT_NAME]: yup.string().nullable(), + [FieldConstants.COUNTRY]: yup.string().nullable(), + }) + .concat(creationPropertiesSchema); + +export type SubstationCreationFormData = yup.InferType; + +export const substationCreationEmptyFormData: SubstationCreationFormData = { + [FieldConstants.EQUIPMENT_ID]: '', + [FieldConstants.EQUIPMENT_NAME]: '', + [FieldConstants.COUNTRY]: null, + [FieldConstants.ADDITIONAL_PROPERTIES]: [], +}; diff --git a/src/components/network-modifications/substation/index.ts b/src/components/network-modifications/substation/index.ts index 8ec568af5..2836a5fdc 100644 --- a/src/components/network-modifications/substation/index.ts +++ b/src/components/network-modifications/substation/index.ts @@ -6,4 +6,3 @@ */ export * from './creation'; -export * from './substationDialog.type'; diff --git a/src/components/network-modifications/substation/substationDialog.type.ts b/src/components/network-modifications/substation/substationDialog.type.ts deleted file mode 100644 index 29e962cfe..000000000 --- a/src/components/network-modifications/substation/substationDialog.type.ts +++ /dev/null @@ -1,13 +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 interface SubstationInfos { - id: string; - name?: string; - country: string | null; - properties?: Record; -} diff --git a/src/services/index.ts b/src/services/index.ts index 9dd5e130c..a617a3fa3 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -18,4 +18,3 @@ export * from './utils'; export * from './voltage-init'; export * from './short-circuit-analysis'; export * from './networkModification'; -export * from './networkModification.type'; diff --git a/src/services/networkModification.ts b/src/services/networkModification.ts index dbf7a9d93..b2a773bb5 100644 --- a/src/services/networkModification.ts +++ b/src/services/networkModification.ts @@ -7,8 +7,6 @@ import type { UUID } from 'node:crypto'; import { backendFetch, backendFetchText } from './utils'; -import { SubstationCreationDto } from './networkModification.type'; -import { ModificationType } from '../utils'; const PREFIX_NETWORK_MODIFICATION_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/network-modification`; @@ -22,26 +20,13 @@ export function fetchNetworkModification(modificationUuid: UUID) { return backendFetch(modificationFetchUrl); } -export function createSubstationPromise( - { substationId, substationName, country, properties, isUpdate, modificationUuid }: SubstationCreationDto, - baseUrl: string -) { - const body = JSON.stringify({ - type: ModificationType.SUBSTATION_CREATION, - equipmentId: substationId, - equipmentName: substationName, - country: country === '' ? null : country, - properties, - }); - let url = baseUrl; - if (modificationUuid) { - url += `/${encodeURIComponent(modificationUuid)}`; - console.info('Updating substation creation', { url, body }); - } else { - console.info('Creating substation creation', { url, body }); - } +export function updateModification({ modificationUuid, body }: { modificationUuid: UUID; body: string }) { + const url = `${getUrl()}/${encodeURIComponent(modificationUuid)}`; + + console.info('Updating modification', { url, body }); + return backendFetchText(url, { - method: isUpdate ? 'PUT' : 'POST', + method: 'PUT', headers: { Accept: 'application/json', 'Content-Type': 'application/json', @@ -49,7 +34,3 @@ export function createSubstationPromise( body, }); } - -export function createSubstation(dto: SubstationCreationDto) { - return createSubstationPromise(dto, getUrl()); -} diff --git a/src/services/networkModification.type.ts b/src/services/networkModification.type.ts deleted file mode 100644 index 3b003e38c..000000000 --- a/src/services/networkModification.type.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026, 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 { UUID } from 'node:crypto'; -import { Property } from '../components/network-modifications/common/properties/properties.type'; - -export interface NetworkModificationData { - uuid: UUID; - type: string; - [key: string]: any; -} - -export interface StudyNodeInfo { - studyId: UUID; - nodeId: UUID; -} - -export interface SubstationCreationDto { - substationId: string; - substationName: string | null; - country: string | null; - isUpdate: boolean; - modificationUuid?: UUID; - properties: Property[] | null; -} - -export interface SubstationCreationInfo extends StudyNodeInfo, SubstationCreationDto {} diff --git a/src/services/study.ts b/src/services/study.ts index 4fa843898..4d5a45b56 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -11,17 +11,12 @@ import { NetworkVisualizationParameters } from '../components/parameters/network import { type ShortCircuitParametersInfos } from '../components/parameters/short-circuit/short-circuit-parameters.type'; import { VoltageInitStudyParameters } from '../components/parameters/voltage-init/voltage-init.type'; import { EquipmentType, ExtendedEquipmentType, NamingConventionValues } from '../utils'; -import { SubstationCreationInfo } from './networkModification.type'; -import { createSubstationPromise } from './networkModification'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; const getStudyUrl = (studyUuid: UUID | null) => `${PREFIX_STUDY_QUERIES}/v1/studies/${safeEncodeURIComponent(studyUuid)}`; -const getStudyUrlWithNodeUuid = (studyUuid: UUID | null | undefined, nodeUuid: UUID | undefined) => - `${PREFIX_STUDY_QUERIES}/v1/studies/${safeEncodeURIComponent(studyUuid)}/nodes/${safeEncodeURIComponent(nodeUuid)}`; - const getStudyUrlWithNodeUuidAndRootNetworkUuid = ( studyUuid: string | null | undefined, nodeUuid: string | undefined, @@ -31,10 +26,6 @@ const getStudyUrlWithNodeUuidAndRootNetworkUuid = ( rootNetworkUuid )}/nodes/${safeEncodeURIComponent(nodeUuid)}`; -function getNetworkModificationUrl(studyUuid: UUID | null | undefined, nodeUuid: UUID | undefined) { - return `${getStudyUrlWithNodeUuid(studyUuid, nodeUuid)}/network-modifications`; -} - export function exportFilter(studyUuid: UUID, filterUuid?: UUID, token?: string) { console.info('get filter export on study root node'); return backendFetchJson( @@ -140,7 +131,3 @@ export function searchEquipmentsInfos( `${getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, nodeUuid, currentRootNetworkUuid)}/search?${urlSearchParams.toString()}` ); } - -export function createSubstationInNode(info: SubstationCreationInfo) { - return createSubstationPromise(info, getNetworkModificationUrl(info.studyId, info.nodeId)); -} diff --git a/src/translations/en/networkModificationsEn.ts b/src/translations/en/networkModificationsEn.ts index 475097dc1..33eec0641 100644 --- a/src/translations/en/networkModificationsEn.ts +++ b/src/translations/en/networkModificationsEn.ts @@ -90,4 +90,5 @@ export const networkModificationsEn = { EquipmentCopied: 'Data successfully copied from equipment {equipmentId}', EquipmentCopyFailed: 'Equipment copy failed', TapPositionValueError: 'The number of taps must not exceed the value {value}', + DuplicatedPropsError: 'Duplicated properties: each property must be unique', }; diff --git a/src/translations/fr/networkModificationsFr.ts b/src/translations/fr/networkModificationsFr.ts index 16bce0337..f1069e259 100644 --- a/src/translations/fr/networkModificationsFr.ts +++ b/src/translations/fr/networkModificationsFr.ts @@ -94,4 +94,5 @@ export const networkModificationsFr = { EquipmentCopied: "Données copiées depuis l'ouvrage {equipmentId}", EquipmentCopyFailed: "Erreur lors de la copie de l'ouvrage", TapPositionValueError: 'Le nombre de prises ne doit pas dépasser la valeur {value}', + DuplicatedPropsError: 'Propriétés dupliquées : chaque propriété doit être unique', }; diff --git a/src/utils/ts-utils.ts b/src/utils/ts-utils.ts index 22926c9de..7bab50b77 100644 --- a/src/utils/ts-utils.ts +++ b/src/utils/ts-utils.ts @@ -16,3 +16,24 @@ export function notUndefined(value: T | undefined): value is T { export function notNull(value: T | null): value is T { return value !== null; } + +export const removeNullFields = >(data: T): T => { + const result = Object.entries(data).reduce>((acc, [key, value]) => { + if (value === null) { + return acc; + } + + if (typeof value === 'object' && !Array.isArray(value)) { + const cleaned = removeNullFields(value); + if (Object.keys(cleaned).length > 0) { + acc[key] = cleaned; + } + return acc; + } + + acc[key] = value; + return acc; + }, {}); + + return result as T; +}; From f155bddfbd690584a5e34571e4fd13886dcd3e0b Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Thu, 5 Feb 2026 14:53:49 +0100 Subject: [PATCH 22/34] fix(rhf-inputs): re-render issues on form update Signed-off-by: Joris Mancini --- .../autocompleteInputs/AutocompleteInput.tsx | 13 ++++++------- .../inputs/reactHookForm/text/TextInput.tsx | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx index b31003640..5cc629731 100644 --- a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx +++ b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx @@ -12,12 +12,11 @@ import { genHelperError, identity, isFieldRequired, FieldLabel, HelperPreviousVa import { useCustomFormContext } from '../provider'; import { Option } from '../../../../utils'; -export interface AutocompleteInputProps - extends Omit< - AutocompleteProps, - // we already defined them in our custom Autocomplete - 'value' | 'onChange' | 'renderInput' - > { +export interface AutocompleteInputProps extends Omit< + AutocompleteProps, + // we already defined them in our custom Autocomplete + 'value' | 'onChange' | 'renderInput' +> { name: string; options: Option[]; label?: string; @@ -85,7 +84,7 @@ export function AutocompleteInput({ return ( handleChange(data as Option)} {...(allowNewValue && { freeSolo: true, diff --git a/src/components/inputs/reactHookForm/text/TextInput.tsx b/src/components/inputs/reactHookForm/text/TextInput.tsx index bed687be5..907d6733a 100644 --- a/src/components/inputs/reactHookForm/text/TextInput.tsx +++ b/src/components/inputs/reactHookForm/text/TextInput.tsx @@ -99,7 +99,7 @@ export function TextInput({ fullWidth id={id ?? label} label={fieldLabel} - value={transformedValue} + value={transformedValue ?? ''} onChange={handleValueChanged} disabled={disabled} InputProps={{ From 4173e48f8f30b968df241909d0580dc81b66934b Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Thu, 5 Feb 2026 15:12:06 +0100 Subject: [PATCH 23/34] fix: reduce diffs Signed-off-by: Joris Mancini --- demo/src/app.jsx | 4 +- demo/src/equipment-search.tsx | 6 +- .../customAGGrid/customAggrid.style.ts | 2 +- src/components/customAGGrid/customAggrid.tsx | 4 +- .../elementSaveDialog/ElementSaveDialog.tsx | 3 +- .../reactHookForm/text/UniqueNameInput.tsx | 2 +- .../common/EquipmentSearchDialog.tsx | 81 ---------------- .../modificationDialog/ModificationDialog.tsx | 81 ---------------- .../ModificationDialogContent.tsx | 95 ------------------ .../common/modificationDialog/index.ts | 10 -- .../modificationDialog.type.ts | 48 --------- .../sensi/sensitivity-parameters-selector.tsx | 2 +- src/hooks/index.ts | 10 +- ...aSensi.ts => use-create-row-data-sensi.ts} | 0 ...rsBackend.ts => use-parameters-backend.ts} | 0 ...ation.ts => use-unique-name-validation.ts} | 0 src/hooks/useButtonWithTooltip.tsx | 43 -------- src/hooks/useCSVPicker.tsx | 97 ------------------- src/hooks/useFormSearchCopy.ts | 86 ---------------- src/hooks/useOpenShortWaitFetching.ts | 43 -------- src/hooks/useSearchMatchingEquipments.tsx | 75 -------------- src/hooks/useSimpleTextValue.tsx | 41 -------- src/services/study.ts | 24 ----- src/utils/constants/uiConstants.ts | 2 - 24 files changed, 14 insertions(+), 745 deletions(-) delete mode 100644 src/components/network-modifications/common/EquipmentSearchDialog.tsx delete mode 100644 src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx delete mode 100644 src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx delete mode 100644 src/components/network-modifications/common/modificationDialog/index.ts delete mode 100644 src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts rename src/hooks/{useCreateRowDataSensi.ts => use-create-row-data-sensi.ts} (100%) rename src/hooks/{useParametersBackend.ts => use-parameters-backend.ts} (100%) rename src/hooks/{useUniqueNameValidation.ts => use-unique-name-validation.ts} (100%) delete mode 100644 src/hooks/useButtonWithTooltip.tsx delete mode 100644 src/hooks/useCSVPicker.tsx delete mode 100644 src/hooks/useFormSearchCopy.ts delete mode 100644 src/hooks/useOpenShortWaitFetching.ts delete mode 100644 src/hooks/useSearchMatchingEquipments.tsx delete mode 100644 src/hooks/useSimpleTextValue.tsx diff --git a/demo/src/app.jsx b/demo/src/app.jsx index d2f00884c..1ae82c8a5 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -44,7 +44,7 @@ import { import searchEquipments from '../data/EquipmentSearchBar'; import FlatParametersTab from './FlatParametersTab'; import InputsTab from './InputsTab'; -import { EquipmentSearchDemoDialog } from './equipment-search'; +import { EquipmentSearchDialog } from './equipment-search'; import { InlineSearch } from './inline-search'; import { AuthenticationRouter, @@ -914,7 +914,7 @@ function AppContent({ language, onLanguageClick }) { dense > - +
{ }); }; -export function EquipmentSearchDemoDialog() { +export function EquipmentSearchDialog() { const [isSearchOpen, setIsSearchOpen] = useState(false); const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ @@ -92,3 +92,5 @@ export function EquipmentSearchDemoDialog() { ); } + +export default EquipmentSearchDialog; diff --git a/src/components/customAGGrid/customAggrid.style.ts b/src/components/customAGGrid/customAggrid.style.ts index 13c7fdb71..74a7fada4 100644 --- a/src/components/customAGGrid/customAggrid.style.ts +++ b/src/components/customAGGrid/customAggrid.style.ts @@ -8,7 +8,7 @@ import type { MuiStyles } from '../../utils/styles'; export const CUSTOM_AGGRID_THEME = 'custom-aggrid-theme'; -export const agGridStyles = { +export const styles = { grid: (theme) => ({ width: 'auto', height: '100%', diff --git a/src/components/customAGGrid/customAggrid.tsx b/src/components/customAGGrid/customAggrid.tsx index ef2c12bfb..1df51e84c 100644 --- a/src/components/customAGGrid/customAggrid.tsx +++ b/src/components/customAGGrid/customAggrid.tsx @@ -14,7 +14,7 @@ import { AG_GRID_LOCALE_EN, AG_GRID_LOCALE_FR } from '@ag-grid-community/locale' import { useIntl } from 'react-intl'; import { Box, type BoxProps, useTheme } from '@mui/material'; import { mergeSx } from '../../utils/styles'; -import { agGridStyles, CUSTOM_AGGRID_THEME } from './customAggrid.style'; +import { CUSTOM_AGGRID_THEME, styles } from './customAggrid.style'; import { type GsLangUser, LANG_ENGLISH, LANG_FRENCH } from '../../utils/langs'; export type AgGridLocale = Partial>; // using EN for keyof because it's the only who has more keys, so more complete @@ -60,7 +60,7 @@ export const CustomAGGrid = forwardRef( return ( void; - onSelectionChange: (equipment: EquipmentInfos) => void; - equipmentType: EquipmentType | ExtendedEquipmentType; - studyContext: StudyContext; -} - -/** - * Dialog to search equipment with a given type - * @param {Boolean} open: Is the dialog open ? - * @param {Function} onClose: callback to call when closing the dialog - * @param {Function} onSelectionChange: callback when the selection changes - * @param {String} equipmentType: the type of equipment we want to search - * @param studyContext the current tree node context (study case) - */ -export function EquipmentSearchDialog({ - open, - onClose, - onSelectionChange, - equipmentType, - studyContext, -}: Readonly) { - const intl = useIntl(); - const { searchTerm, updateSearchTerm, equipmentsFound, isLoading } = useSearchMatchingEquipments({ - inUpstreamBuiltParentNode: true, - equipmentType, - studyContext, - }); - - return ( - { - updateSearchTerm(''); - onSelectionChange(element); - }} - elementsFound={equipmentsFound} - renderElement={(props) => } - loading={isLoading} - getOptionLabel={(equipment) => equipment.label} - isOptionEqualToValue={(equipment1, equipment2) => equipment1.id === equipment2.id} - renderInput={(displayedValue, params) => ( - - - {params.InputProps.startAdornment} - - ), - }} - value={displayedValue} - /> - )} - /> - ); -} diff --git a/src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx b/src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx deleted file mode 100644 index 92e2ada61..000000000 --- a/src/components/network-modifications/common/modificationDialog/ModificationDialog.tsx +++ /dev/null @@ -1,81 +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 { useCallback } from 'react'; -import { FieldErrors, FieldValues, useFormContext } from 'react-hook-form'; -import { ModificationDialogContent } from './ModificationDialogContent'; -import { SubmitButton } from '../../../inputs'; -import { ModificationDialogProps } from './modificationDialog.type'; - -/** - * Generic Modification Dialog which manage basic common behaviors with react - * hook form validation. - * @param {CallbackEvent} onClear callback when the dialog needs to be cleared - * @param {CallbackEvent} onSave callback when saving the modification - * @param {Boolean} disabledSave to control disabled prop of the validate button - * @param {CallbackEvent} onValidated callback when validation is successful - * @param {CallbackEvent} onValidationError callback when validation failed - * @param {Array} dialogProps props that are forwarded to the MUI Dialog component - */ - -export function ModificationDialog({ - disabledSave = false, - onClear, - onClose, - onSave, - onValidated, - onValidationError, - ...dialogProps -}: Readonly>) { - const { handleSubmit } = useFormContext(); - - const closeAndClear = () => { - onClear(); - onClose?.(); - }; - - const handleValidate = (data: TFieldValues) => { - onValidated?.(); - onSave(data); - // do not wait fetch response and close dialog, errors will be shown in snackbar. - closeAndClear(); - }; - - const handleScrollWhenError = useCallback(() => { - // When scrolling to the field with focus, you can end up with the label not completely displayed - // We ensure that field with focus is displayed in the middle of the dialog - // Delay focusing to ensure it happens after the validation. - // Without timeout, document.activeElement will return the validation button. - const timeoutId = setTimeout(() => { - const focusedElement = document.activeElement; - - if (focusedElement instanceof HTMLElement) { - focusedElement.scrollIntoView({ - behavior: 'smooth', - block: 'center', - }); - } - }, 0); // Delay of 0 milliseconds, effectively running at the next opportunity - - return () => clearTimeout(timeoutId); - }, []); - - const handleValidationError = (errors: FieldErrors) => { - onValidationError?.(errors); - handleScrollWhenError(); - }; - - const submitButton = ( - - ); - return ; -} diff --git a/src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx b/src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx deleted file mode 100644 index 04d1e0c5a..000000000 --- a/src/components/network-modifications/common/modificationDialog/ModificationDialogContent.tsx +++ /dev/null @@ -1,95 +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 { Grid, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress } from '@mui/material'; -import { AutoStoriesOutlined as AutoStoriesOutlinedIcon, FindInPage as FindInPageIcon } from '@mui/icons-material'; -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { CancelButton } from '../../../inputs'; -import { useButtonWithTooltip } from '../../../../hooks'; -import { ModificationDialogContentProps } from './modificationDialog.type'; - -/** - * Common parts for the Modification Dialog - * @param {String} titleId id for title translation - * @param {Object} onOpenCatalogDialog Object managing catalog - * @param {Object} searchCopy Object managing search equipments for copy - * @param {ReactElement} subtitle subtitle component to put inside DialogTitle - * @param {Boolean} isDataFetching props to display loading - * @param {ReactElement} submitButton submitButton to put in the dialog's footer - * @param {CallbackEvent} closeAndClear callback when the dialog needs to be closed and cleared - * @param {Array} dialogProps props that are forwarded to the MUI Dialog component - */ - -export function ModificationDialogContent({ - closeAndClear, - isDataFetching = false, - titleId, - onOpenCatalogDialog, - searchCopy, - submitButton, - subtitle, - ...dialogProps -}: Readonly) { - const catalogButton = useButtonWithTooltip({ - label: 'CatalogButtonTooltip', - handleClick: onOpenCatalogDialog ?? (() => {}), - icon: , - }); - const copyEquipmentButton = useButtonWithTooltip({ - label: 'CopyFromExisting', - handleClick: searchCopy?.handleOpenSearchDialog ?? (() => {}), - icon: , - }); - - const handleClose = (_event: React.MouseEvent, reason: string) => { - // don't close the dialog for outside click - if (reason !== 'backdropClick') { - closeAndClear(); - } - }; - - const handleCancel = () => { - closeAndClear(); - }; - - return ( - - {isDataFetching && } - - - - - - - - {onOpenCatalogDialog && ( - - {catalogButton} - - )} - {searchCopy && ( - - {copyEquipmentButton} - - )} - - {subtitle && ( - - {subtitle} - - )} - - - {dialogProps.children} - - - {submitButton} - - - ); -} diff --git a/src/components/network-modifications/common/modificationDialog/index.ts b/src/components/network-modifications/common/modificationDialog/index.ts deleted file mode 100644 index e05523f5f..000000000 --- a/src/components/network-modifications/common/modificationDialog/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) 2026, 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 * from './ModificationDialogContent'; -export * from './ModificationDialog'; -export * from './modificationDialog.type'; diff --git a/src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts b/src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts deleted file mode 100644 index 609158964..000000000 --- a/src/components/network-modifications/common/modificationDialog/modificationDialog.type.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2026, 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 { ReactNode } from 'react'; -import { DialogProps } from '@mui/material'; -import { UUID } from 'node:crypto'; -import { FieldErrors, FieldValues } from 'react-hook-form'; -import { UseFormSearchCopy } from '../../../../hooks'; -import { GsLang, StudyContext } from '../../../../utils'; - -export type ModificationDialogContentProps = Omit & { - closeAndClear: () => void; - isDataFetching?: boolean; - titleId: string; - onOpenCatalogDialog?: () => void; - searchCopy?: UseFormSearchCopy; - submitButton: ReactNode; - subtitle?: ReactNode; -}; - -export type ModificationDialogProps = Omit< - ModificationDialogContentProps, - 'closeAndClear' | 'submitButton' -> & { - disabledSave?: boolean; - onClear: () => void; - onClose?: () => void; - onSave: (modificationData: TFieldValues) => void; - onValidated?: () => void; - onValidationError?: (errors: FieldErrors) => void; -}; - -export type NetworkModificationDialogProps = { - studyContext?: StudyContext; - isUpdate: boolean; - editDataFetchStatus?: string; - onValidated?: () => void; - onClose?: () => void; - language: GsLang; -}; - -export type EquipmentModificationDialogProps = NetworkModificationDialogProps & { - defaultIdValue: UUID; -}; diff --git a/src/components/parameters/sensi/sensitivity-parameters-selector.tsx b/src/components/parameters/sensi/sensitivity-parameters-selector.tsx index 72b02e2c6..fb9021797 100644 --- a/src/components/parameters/sensi/sensitivity-parameters-selector.tsx +++ b/src/components/parameters/sensi/sensitivity-parameters-selector.tsx @@ -21,7 +21,7 @@ import { } from './columns-definitions'; import { SensitivityTable } from './sensitivity-table'; import { TabPanel } from '../common'; -import { useCreateRowDataSensi } from '../../../hooks/useCreateRowDataSensi'; +import { useCreateRowDataSensi } from '../../../hooks/use-create-row-data-sensi'; import type { MuiStyles } from '../../../utils/styles'; import { SensitivityAnalysisParametersFactorCount } from './sensitivity-analysis-parameters-factor-count'; import { MAX_RESULTS_COUNT, MAX_VARIABLES_COUNT } from './constants'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 225330182..de0e422b3 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,22 +6,16 @@ */ export * from './customStates'; -export * from './useButtonWithTooltip'; -export * from './useCSVPicker'; export * from './useModificationLabelComputer'; export * from './useConfidentialityWarning'; export * from './useDebounce'; export * from './useIntlRef'; -export * from './useFormSearchCopy'; export * from './useLocalizedCountries'; export * from './usePredefinedProperties'; -export * from './useOpenShortWaitFetching'; export * from './usePrevious'; -export * from './useSimpleTextValue'; export * from './useSnackMessage'; export * from './useFormatLabelWithUnit'; export * from './useSelectAppearance'; -export * from './useParametersBackend'; -export * from './useCreateRowDataSensi'; -export * from './useSearchMatchingEquipments'; +export * from './use-parameters-backend'; +export * from './use-create-row-data-sensi'; export * from './useElementSearch'; diff --git a/src/hooks/useCreateRowDataSensi.ts b/src/hooks/use-create-row-data-sensi.ts similarity index 100% rename from src/hooks/useCreateRowDataSensi.ts rename to src/hooks/use-create-row-data-sensi.ts diff --git a/src/hooks/useParametersBackend.ts b/src/hooks/use-parameters-backend.ts similarity index 100% rename from src/hooks/useParametersBackend.ts rename to src/hooks/use-parameters-backend.ts diff --git a/src/hooks/useUniqueNameValidation.ts b/src/hooks/use-unique-name-validation.ts similarity index 100% rename from src/hooks/useUniqueNameValidation.ts rename to src/hooks/use-unique-name-validation.ts diff --git a/src/hooks/useButtonWithTooltip.tsx b/src/hooks/useButtonWithTooltip.tsx deleted file mode 100644 index cac09c9dc..000000000 --- a/src/hooks/useButtonWithTooltip.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import React, { ReactNode, useMemo } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Tooltip, IconButton } from '@mui/material'; -import { dialogStyles } from '../components/dialogs/dialogUtils'; -import { TOOLTIP_DELAY } from '../utils'; - -interface UseButtonWithTooltipProps { - handleClick: React.MouseEventHandler; - label: string; - icon: ReactNode; -} - -export const useButtonWithTooltip = ({ handleClick, label, icon }: UseButtonWithTooltipProps) => { - return useMemo(() => { - return ( - } - placement="top" - arrow - enterDelay={TOOLTIP_DELAY} - enterNextDelay={TOOLTIP_DELAY} - slotProps={{ - popper: { - sx: { - '& .MuiTooltip-tooltip': dialogStyles.tooltip, - }, - }, - }} - > - - {icon} - - - ); - }, [label, handleClick, icon]); -}; diff --git a/src/hooks/useCSVPicker.tsx b/src/hooks/useCSVPicker.tsx deleted file mode 100644 index 522db6acb..000000000 --- a/src/hooks/useCSVPicker.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { Button, Grid } from '@mui/material'; -import { useCSVReader } from 'react-papaparse'; -import { LANG_FRENCH } from '../utils'; - -interface UseCSVPickerProps { - label: string; - header: string[]; - resetTrigger: boolean; - maxTapNumber?: number; - disabled?: boolean; - language: string; -} - -export const useCSVPicker = ({ - label, - header, - resetTrigger, - maxTapNumber, - disabled = false, - language, -}: UseCSVPickerProps) => { - const intl = useIntl(); - - const { CSVReader } = useCSVReader(); - const [selectedFile, setSelectedFile] = useState(); - const [fileError, setFileError] = useState(); - - const equals = (a: string[], b: string[]) => b.every((item) => a.includes(item)); - - useEffect(() => { - setSelectedFile(undefined); - setFileError(undefined); - }, [resetTrigger]); - - // Expose a reset function to allow clearing the file manually - const resetFile = useCallback(() => { - setSelectedFile(undefined); - setFileError(undefined); - }, []); - - const field = useMemo(() => { - return ( - { - setSelectedFile(acceptedFile); - if (results?.data.length > 0 && equals(header, results.data[0])) { - setFileError(undefined); - } else { - setFileError( - intl.formatMessage({ - id: 'InvalidRuleHeader', - }) - ); - } - - if (maxTapNumber && results.data.length > maxTapNumber) { - setFileError(intl.formatMessage({ id: 'TapPositionValueError' }, { value: maxTapNumber })); - } - }} - > - {({ getRootProps }: { getRootProps: () => any }) => ( - - - - {selectedFile - ? selectedFile.name - : intl.formatMessage({ - id: 'uploadMessage', - })} - - - )} - - ); - }, [selectedFile, disabled, header, intl, label, maxTapNumber, CSVReader, language]); - - return [selectedFile, field, fileError, setSelectedFile, resetFile] as const; -}; diff --git a/src/hooks/useFormSearchCopy.ts b/src/hooks/useFormSearchCopy.ts deleted file mode 100644 index cc2b5cc30..000000000 --- a/src/hooks/useFormSearchCopy.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { useIntl } from 'react-intl'; -import { useCallback, useState } from 'react'; -import { UUID } from 'node:crypto'; -import { - EquipmentInfos, - EquipmentInfosTypes, - EquipmentType, - ExtendedEquipmentType, - snackWithFallback, - StudyContext, -} from '../utils'; -import { useSnackMessage } from './useSnackMessage'; -import { fetchNetworkElementInfos } from '../services'; - -// TODO fetchNetworkElementInfos has no type -type FetchResponse = Awaited>; - -export interface UseFormSearchCopy { - isDialogSearchOpen: boolean; - handleOpenSearchDialog: () => void; - handleSelectionChange: (element: EquipmentInfos) => void; - handleCloseSearchDialog: () => void; -} - -export function useFormSearchCopy( - setFormValues: (response: FetchResponse) => void, - elementType: EquipmentType | ExtendedEquipmentType, - studyContext?: StudyContext -): UseFormSearchCopy { - const intl = useIntl(); - const { snackInfo, snackError } = useSnackMessage(); - const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false); - - const handleCloseSearchDialog = useCallback(() => { - setIsDialogSearchOpen(false); - }, []); - - const handleOpenSearchDialog = useCallback(() => { - setIsDialogSearchOpen(true); - }, []); - - const handleSelectionChange = useCallback( - (element: EquipmentInfos) => { - if (!studyContext) { - return; - } - fetchNetworkElementInfos( - studyContext.studyId, - studyContext.nodeId, - studyContext.rootNetworkId, - elementType, - EquipmentInfosTypes.FORM, - element.id as UUID, - true - ) - .then((response) => { - setFormValues(response); - snackInfo({ - messageTxt: intl.formatMessage({ id: 'EquipmentCopied' }, { equipmentId: element.id }), - }); - }) - .catch((error: Error) => { - snackWithFallback(snackError, error, { - headerId: 'EquipmentCopyFailed', - headerValues: { equipmentId: element.id }, - }); - }) - .finally(() => handleCloseSearchDialog()); - }, - [studyContext, elementType, handleCloseSearchDialog, intl, setFormValues, snackError, snackInfo] - ); - - return { - isDialogSearchOpen, - handleOpenSearchDialog, - handleSelectionChange, - handleCloseSearchDialog, - }; -} diff --git a/src/hooks/useOpenShortWaitFetching.ts b/src/hooks/useOpenShortWaitFetching.ts deleted file mode 100644 index 5b216b2a1..000000000 --- a/src/hooks/useOpenShortWaitFetching.ts +++ /dev/null @@ -1,43 +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 { useEffect, useState } from 'react'; - -/** - * A hook that returns a boolean indicating whether a form should be opened after a short delay. - * - * @param {boolean} isDataFetched - Whether data is fetched. - * @param {number} delay - The delay before opening the form, in milliseconds. - * - * @returns {boolean} A boolean indicating whether the form should be opened. - */ - -export const useOpenShortWaitFetching: ({ - isDataFetched, - delay, -}: { - isDataFetched: boolean; - delay: number; -}) => boolean = ({ isDataFetched, delay }) => { - // State to track whether the form should be opened or not. - const [shouldOpen, setShouldOpen] = useState(false); - - useEffect(() => { - let timeout: NodeJS.Timeout; - // If data is already available, open the form immediately. - if (isDataFetched) { - setShouldOpen(true); - } else { - // Otherwise, wait for a short delay before opening the form. - timeout = setTimeout(() => setShouldOpen(true), delay); - } - // Return a cleanup function to cancel the timeout if the data arrives before the end of the delay. - return () => clearTimeout(timeout); - }, [delay, isDataFetched]); - - return shouldOpen; -}; diff --git a/src/hooks/useSearchMatchingEquipments.tsx b/src/hooks/useSearchMatchingEquipments.tsx deleted file mode 100644 index fc04e27f9..000000000 --- a/src/hooks/useSearchMatchingEquipments.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { useCallback, useEffect, useMemo } from 'react'; -import { - Equipment, - EquipmentType, - ExtendedEquipmentType, - getEquipmentsInfosForSearchBar, - getIdentifiableNameOrId, - getUseNameKey, - Identifiable, - StudyContext, -} from '../utils'; -import { searchEquipmentsInfos } from '../services'; -import { useElementSearch } from './useElementSearch'; - -interface UseSearchMatchingEquipmentsProps { - inUpstreamBuiltParentNode?: boolean; - equipmentType?: EquipmentType | ExtendedEquipmentType; - studyContext: StudyContext; -} - -export const useSearchMatchingEquipments = (props: UseSearchMatchingEquipmentsProps) => { - const { studyContext, inUpstreamBuiltParentNode, equipmentType } = props; - - const getNameOrId = useCallback( - (infos?: Identifiable | null) => { - return getIdentifiableNameOrId(studyContext.useNameParam, infos); - }, - [studyContext.useNameParam] - ); - - const getUseNameParameterKey = useCallback(() => { - return getUseNameKey(studyContext.useNameParam); - }, [studyContext.useNameParam]); - - const fetchElements: (newSearchTerm: string) => Promise = useCallback( - (newSearchTerm) => - searchEquipmentsInfos( - studyContext.studyId, - studyContext.nodeId, - studyContext.rootNetworkId, - newSearchTerm, - getUseNameParameterKey, - inUpstreamBuiltParentNode, - equipmentType - ), - [equipmentType, getUseNameParameterKey, inUpstreamBuiltParentNode, studyContext] - ); - - const { elementsFound, isLoading, searchTerm, updateSearchTerm } = useElementSearch({ - fetchElements, - }); - - const equipmentsFound = useMemo( - () => getEquipmentsInfosForSearchBar(elementsFound, getNameOrId), - [elementsFound, getNameOrId] - ); - - useEffect(() => { - updateSearchTerm(searchTerm?.trim()); - }, [searchTerm, equipmentType, updateSearchTerm]); - - return { - searchTerm, - updateSearchTerm, - equipmentsFound, - isLoading, - }; -}; diff --git a/src/hooks/useSimpleTextValue.tsx b/src/hooks/useSimpleTextValue.tsx deleted file mode 100644 index fb5d0bb43..000000000 --- a/src/hooks/useSimpleTextValue.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2022, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { TextField, TextFieldProps } from '@mui/material'; - -interface UseSimpleTextValueProps { - defaultValue: string; - adornment: TextFieldProps['InputProps']; - error: boolean; - triggerReset: boolean; -} - -export const useSimpleTextValue = ({ defaultValue, adornment, error, triggerReset }: UseSimpleTextValueProps) => { - const [value, setValue] = useState(defaultValue); - - const handleChangeValue = useCallback((event: React.ChangeEvent) => { - setValue(event.target.value); - }, []); - - const field = useMemo(() => { - return ( - - ); - }, [value, handleChangeValue, adornment, error]); - - useEffect(() => setValue(defaultValue), [defaultValue, triggerReset]); - - return [value, field] as const; -}; diff --git a/src/services/study.ts b/src/services/study.ts index 4d5a45b56..73c846af3 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -107,27 +107,3 @@ export function fetchNetworkElementInfos( return backendFetchJson(fetchElementsUrl); } - -export function searchEquipmentsInfos( - studyUuid: UUID, - nodeUuid: UUID, - currentRootNetworkUuid: UUID, - searchTerm: string, - getUseNameParameterKey: () => NamingConventionValues, - inUpstreamBuiltParentNode?: boolean, - equipmentType?: EquipmentType | ExtendedEquipmentType -) { - console.info("Fetching equipments infos matching with '%s' term ... ", searchTerm); - const urlSearchParams = new URLSearchParams(); - urlSearchParams.append('userInput', searchTerm); - urlSearchParams.append('fieldSelector', getUseNameParameterKey()); - if (inUpstreamBuiltParentNode !== undefined) { - urlSearchParams.append('inUpstreamBuiltParentNode', inUpstreamBuiltParentNode.toString()); - } - if (equipmentType !== undefined) { - urlSearchParams.append('equipmentType', equipmentType); - } - return backendFetchJson( - `${getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, nodeUuid, currentRootNetworkUuid)}/search?${urlSearchParams.toString()}` - ); -} diff --git a/src/utils/constants/uiConstants.ts b/src/utils/constants/uiConstants.ts index a9bc059fd..8d373de85 100644 --- a/src/utils/constants/uiConstants.ts +++ b/src/utils/constants/uiConstants.ts @@ -6,5 +6,3 @@ */ export const MAX_CHAR_DESCRIPTION = 500; -export const TOOLTIP_DELAY = 1000; -export const FORM_LOADING_DELAY = 200; From 31c031a61464ccf3a60a0937d142a38f62c24efa Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Thu, 5 Feb 2026 15:29:10 +0100 Subject: [PATCH 24/34] fix: eslint Signed-off-by: Joris Mancini --- src/components/dialogs/dialogUtils.ts | 48 ------------------- .../autocompleteInputs/AutocompleteInput.tsx | 13 ++--- .../expandableInput/ExpandableInput.tsx | 16 +++++-- .../network-modifications/common/index.ts | 2 - src/services/study.ts | 2 +- 5 files changed, 21 insertions(+), 60 deletions(-) diff --git a/src/components/dialogs/dialogUtils.ts b/src/components/dialogs/dialogUtils.ts index 9d526ce24..badb2187f 100644 --- a/src/components/dialogs/dialogUtils.ts +++ b/src/components/dialogs/dialogUtils.ts @@ -15,59 +15,11 @@ import { MEGA_VOLT_AMPERE, MEGA_WATT, MICRO_SIEMENS, - type MuiStyles, OHM, PERCENTAGE, SIEMENS, } from '../../utils'; -export const dialogStyles = { - helperText: { - margin: 0, - marginTop: '4px', - }, - tooltip: { - fontSize: 18, - maxWidth: 'none', - }, - button: (theme) => ({ - justifyContent: 'flex-start', - fontSize: 'small', - marginTop: theme.spacing(1), - }), - paddingButton: (theme) => ({ - paddingLeft: theme.spacing(2), - }), - formDirectoryElements1: { - display: 'flex', - gap: '8px', - flexWrap: 'wrap', - flexDirection: 'row', - border: '2px solid lightgray', - padding: '4px', - borderRadius: '4px', - overflow: 'hidden', - }, - formDirectoryElementsError: (theme) => ({ - borderColor: theme.palette.error.main, - }), - formDirectoryElements2: { - display: 'flex', - gap: '8px', - flexWrap: 'wrap', - flexDirection: 'row', - marginTop: 0, - padding: '4px', - overflow: 'hidden', - }, - labelDirectoryElements: { - marginTop: '-10px', - }, - addDirectoryElements: { - marginTop: '-5px', - }, -} as const satisfies MuiStyles; - export const MicroSusceptanceAdornment = { position: 'end', text: MICRO_SIEMENS, diff --git a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx index 5cc629731..38830672d 100644 --- a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx +++ b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx @@ -8,15 +8,16 @@ import { useMemo } from 'react'; import { Autocomplete, AutocompleteProps, TextField, TextFieldProps } from '@mui/material'; import { useController } from 'react-hook-form'; -import { genHelperError, identity, isFieldRequired, FieldLabel, HelperPreviousValue } from '../utils'; +import { FieldLabel, genHelperError, HelperPreviousValue, identity, isFieldRequired } from '../utils'; import { useCustomFormContext } from '../provider'; import { Option } from '../../../../utils'; -export interface AutocompleteInputProps extends Omit< - AutocompleteProps, - // we already defined them in our custom Autocomplete - 'value' | 'onChange' | 'renderInput' -> { +export interface AutocompleteInputProps + extends Omit< + AutocompleteProps, + // we already defined them in our custom Autocomplete + 'value' | 'onChange' | 'renderInput' + > { name: string; options: Option[]; label?: string; diff --git a/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx b/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx index f64e31118..56e7518ee 100644 --- a/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx +++ b/src/components/inputs/reactHookForm/expandableInput/ExpandableInput.tsx @@ -11,8 +11,18 @@ import { ControlPoint as AddIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; import { DeletableRow } from './DeletableRow'; import { ErrorInput, MidFormError } from '../errorManagement'; -import { mergeSx } from '../../../../utils'; -import { dialogStyles } from '../../../dialogs'; +import { mergeSx, type MuiStyles } from '../../../../utils'; + +const styles = { + button: (theme) => ({ + justifyContent: 'flex-start', + fontSize: 'small', + marginTop: theme.spacing(1), + }), + paddingButton: (theme) => ({ + paddingLeft: theme.spacing(2), + }), +} as const satisfies MuiStyles; export interface ExpandableInputProps { name: string; @@ -80,7 +90,7 @@ export function ExpandableInput({