diff --git a/package-lock.json b/package-lock.json index 772a07a430..e5da475fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@gridsuite/commons-ui": "0.240.0", + "@gridsuite/commons-ui": "0.241.0", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.1.3", "@mui/icons-material": "^6.5.0", @@ -3318,9 +3318,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.240.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.240.0.tgz", - "integrity": "sha512-sGP5jDrxpPje7aCYs4SwLqVxPldqpcLIPTeQL9zArWHocHYdTQGU6Fa6JGQV3YWZa4D6tVNa3iYtJKuUpqKkFg==", + "version": "0.241.0", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.241.0.tgz", + "integrity": "sha512-bCVh1o+vkhD0tDJt2skpZarQD7BI1K1iW4DHi6lcbVxUMUf3SSz1FiM//DFBtGkX5MOWTa19MWycwsEIQeeP6g==", "license": "MPL-2.0", "dependencies": { "@ag-grid-community/locale": "^35.3.1", diff --git a/package.json b/package.json index 851c2b5cad..25c326f9f3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@gridsuite/commons-ui": "0.240.0", + "@gridsuite/commons-ui": "0.241.0", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^4.1.3", "@mui/icons-material": "^6.5.0", diff --git a/src/components/dialogs/parameters/state-estimation/state-estimation-general-parameters.tsx b/src/components/dialogs/parameters/state-estimation/state-estimation-general-parameters.tsx index e8d09f979a..8b3d03acff 100644 --- a/src/components/dialogs/parameters/state-estimation/state-estimation-general-parameters.tsx +++ b/src/components/dialogs/parameters/state-estimation/state-estimation-general-parameters.tsx @@ -17,49 +17,53 @@ import { parametersStyles } from '../util/styles'; export const StateEstimationGeneralParameters = () => { return ( - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + ); }; diff --git a/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-form.tsx b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-form.tsx new file mode 100644 index 0000000000..496a989210 --- /dev/null +++ b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters-form.tsx @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { SyntheticEvent } from 'react'; +import { TabPanel } from '@gridsuite/commons-ui'; +import { Grid2 as Grid, Stack, Tab, Tabs } from '@mui/material'; +import { FormattedMessage } from 'react-intl'; +import { TabValue } from './state-estimation-parameters-utils'; +import { StateEstimationGeneralParameters } from './state-estimation-general-parameters'; +import { StateEstimationWeightsParameters } from './state-estimation-weights-parameters'; +import { StateEstimationQualityParameters } from './state-estimation-quality-parameters'; +import { StateEstimationLoadboundsParameters } from './state-estimation-loadbounds-parameters'; +import { getTabStyle } from '../../../utils/tab-utils'; + +interface StateEstimationParametersFormProps { + tabValue: TabValue; + handleTabChange: (event: SyntheticEvent, newValue: TabValue) => void; + tabIndexesWithError: TabValue[]; +} + +export const StateEstimationParametersForm = ({ + tabValue, + handleTabChange, + tabIndexesWithError, +}: StateEstimationParametersFormProps) => { + return ( + + + } + value={TabValue.GENERAL} + sx={getTabStyle(tabIndexesWithError, TabValue.GENERAL)} + /> + } + value={TabValue.WEIGHTS} + sx={getTabStyle(tabIndexesWithError, TabValue.WEIGHTS)} + /> + } + value={TabValue.QUALITY} + sx={getTabStyle(tabIndexesWithError, TabValue.QUALITY)} + /> + } + value={TabValue.LOADBOUNDS} + sx={getTabStyle(tabIndexesWithError, TabValue.LOADBOUNDS)} + /> + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/dialogs/parameters/state-estimation/state-estimation-parameters.tsx b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters.tsx index 9df32336e9..3645870bd4 100644 --- a/src/components/dialogs/parameters/state-estimation/state-estimation-parameters.tsx +++ b/src/components/dialogs/parameters/state-estimation/state-estimation-parameters.tsx @@ -5,18 +5,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react'; + import { CustomFormProvider, - mergeSx, - PopupConfirmationDialog, snackWithFallback, - SubmitButton, - TabPanel, useSnackMessage, + ParameterLayout, + ElementType, } from '@gridsuite/commons-ui'; -import { Button, DialogActions, Grid2 as Grid, Stack, Tab, Tabs } from '@mui/material'; -import { getTabIndicatorStyle, getTabStyle } from '../../../utils/tab-utils'; -import { FormattedMessage } from 'react-intl'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { useSelector } from 'react-redux'; @@ -24,17 +20,13 @@ import { AppState } from '../../../../redux/reducer.type'; import { fromStateEstimationParametersFormToParamValues, fromStateEstimationParametersParamToFormValues, - StateEstimationParametersForm, + StateEstimationParametersForm as StateEstimationFormType, stateEstimationParametersFormSchema, TabValue, } from './state-estimation-parameters-utils'; -import { StateEstimationGeneralParameters } from './state-estimation-general-parameters'; -import { StateEstimationWeightsParameters } from './state-estimation-weights-parameters'; -import { StateEstimationQualityParameters } from './state-estimation-quality-parameters'; -import { StateEstimationLoadboundsParameters } from './state-estimation-loadbounds-parameters'; +import { StateEstimationParametersForm } from './state-estimation-parameters-form'; import { updateStateEstimationParameters } from '../../../../services/study/state-estimation'; import { UseGetStateEstimationParametersProps } from './use-get-state-estimation-parameters'; -import { parametersStyles } from '../util/styles'; export const StateEstimationParameters = ({ useStateEstimationParameters, @@ -45,7 +37,6 @@ export const StateEstimationParameters = ({ }) => { const studyUuid = useSelector((state: AppState) => state.studyUuid); const [stateEstimationParams, setStateEstimationParams] = useStateEstimationParameters; - const [openResetConfirmation, setOpenResetConfirmation] = useState(false); const initialFormValues = useMemo( () => @@ -55,7 +46,7 @@ export const StateEstimationParameters = ({ [stateEstimationParams] ); - const formMethods = useForm({ + const formMethods = useForm({ defaultValues: initialFormValues, resolver: yupResolver(stateEstimationParametersFormSchema), }); @@ -98,19 +89,10 @@ export const StateEstimationParameters = ({ const clear = useCallback(() => { resetStateEstimationParameters(); onValidationError(); - setOpenResetConfirmation(false); }, [resetStateEstimationParameters, onValidationError]); - const handleResetClick = useCallback(() => { - setOpenResetConfirmation(true); - }, []); - - const handleCancelReset = useCallback(() => { - setOpenResetConfirmation(false); - }, []); - const onSubmit = useCallback( - (newParams: StateEstimationParametersForm) => { + (newParams: StateEstimationFormType) => { updateStateEstimationParameters(studyUuid, fromStateEstimationParametersFormToParamValues(newParams)) .then(() => { setStateEstimationParams(fromStateEstimationParametersFormToParamValues(newParams)); @@ -135,92 +117,19 @@ export const StateEstimationParameters = ({ return ( - - - - - } - value={TabValue.GENERAL} - sx={getTabStyle(tabIndexesWithError, TabValue.GENERAL)} - /> - } - value={TabValue.WEIGHTS} - sx={getTabStyle(tabIndexesWithError, TabValue.WEIGHTS)} - /> - } - value={TabValue.QUALITY} - sx={getTabStyle(tabIndexesWithError, TabValue.QUALITY)} - /> - } - value={TabValue.LOADBOUNDS} - sx={getTabStyle(tabIndexesWithError, TabValue.LOADBOUNDS)} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* Reset Confirmation Dialog */} - {openResetConfirmation && ( - - )} + ); }; diff --git a/src/components/dialogs/parameters/util/styles.ts b/src/components/dialogs/parameters/util/styles.ts index 70feffddbc..3bb201e2fb 100644 --- a/src/components/dialogs/parameters/util/styles.ts +++ b/src/components/dialogs/parameters/util/styles.ts @@ -14,7 +14,6 @@ export const parametersStyles = { scrollableGrid: (theme) => ({ overflowY: 'auto', overflowX: 'hidden', - maxHeight: '85%', // TODO This needs to be refactored paddingRight: theme.spacing(2), paddingTop: theme.spacing(2), paddingBottom: theme.spacing(1), diff --git a/src/components/parameters-tabs.tsx b/src/components/parameters-tabs.tsx index de8d99da8b..ca1084b7da 100644 --- a/src/components/parameters-tabs.tsx +++ b/src/components/parameters-tabs.tsx @@ -5,10 +5,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; +import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { Box, DialogContentText, Divider, Grid2 as Grid, Stack, Tab, Tabs, Typography } from '@mui/material'; +import { + Box, + DialogContentText, + Divider, + Grid2 as Grid, + MenuItem, + Select, + Stack, + Tab, + Tabs, + Typography, +} from '@mui/material'; import { useOptionalServiceStatus } from 'hooks/use-optional-service-status'; import { OptionalServicesNames, OptionalServicesStatus } from './utils/optional-services'; import { AppState } from 'redux/reducer.type'; @@ -40,15 +51,16 @@ import { fetchSecurityAnalysisProviders, getSecurityAnalysisDefaultLimitReductions, getSecurityAnalysisParameters, - setSecurityAnalysisParameters, getSensitivityAnalysisParameters, LoadFlowParametersInline, NetworkVisualizationParametersInline, PARAM_DEVELOPER_MODE, PARAM_LANGUAGE, + ParameterLayoutProvider, PccMinParametersInLine, SecurityAnalysisParametersInline, SensitivityAnalysisParametersInline, + setSecurityAnalysisParameters, ShortCircuitParametersInLine, useParametersBackend, VoltageInitParametersInLine, @@ -480,104 +492,166 @@ const ParametersTabs: FunctionComponent = () => { networkVisualizationsParameters, ]); + const { ref, isXsScreen } = useContainerXs(600); + + const tabOptions = useMemo(() => { + const options: { value: string; labelId: string; disabled?: boolean }[] = [ + { + value: TAB_VALUES.lfParamsTabValue, + labelId: 'LoadFlow', + disabled: computationStatus === RunningStatus.RUNNING && tabValue === TAB_VALUES.lfParamsTabValue, + }, + { + value: TAB_VALUES.securityAnalysisParamsTabValue, + labelId: 'SecurityAnalysis', + disabled: securityAnalysisAvailability !== OptionalServicesStatus.Up, + }, + { + value: TAB_VALUES.sensitivityAnalysisParamsTabValue, + labelId: 'SensitivityAnalysis', + disabled: sensitivityAnalysisAvailability !== OptionalServicesStatus.Up, + }, + { + value: TAB_VALUES.shortCircuitParamsTabValue, + labelId: 'ShortCircuit', + disabled: shortCircuitAvailability !== OptionalServicesStatus.Up, + }, + { + value: TAB_VALUES.pccMinTabValue, + labelId: 'PccMin', + disabled: pccMinAvailability !== OptionalServicesStatus.Up, + }, + ...(isDeveloperMode + ? [ + { + value: TAB_VALUES.dynamicSimulationParamsTabValue, + labelId: 'DynamicSimulation', + disabled: dynamicSimulationAvailability !== OptionalServicesStatus.Up, + }, + { + value: TAB_VALUES.dynamicSecurityAnalysisParamsTabValue, + labelId: 'DynamicSecurityAnalysis', + disabled: dynamicSecurityAnalysisAvailability !== OptionalServicesStatus.Up, + }, + { + value: TAB_VALUES.dynamicMarginCalculationParamsTabValue, + labelId: 'DynamicMarginCalculation', + disabled: dynamicMarginCalculationAvailability !== OptionalServicesStatus.Up, + }, + ] + : []), + { + value: TAB_VALUES.voltageInitParamsTabValue, + labelId: 'VoltageInit', + disabled: voltageInitAvailability !== OptionalServicesStatus.Up, + }, + ...(isDeveloperMode + ? [ + { + value: TAB_VALUES.stateEstimationTabValue, + labelId: 'StateEstimation', + disabled: stateEstimationAvailability === OptionalServicesStatus.Up, + }, + ] + : []), + { value: 'divider', labelId: '' }, // visual separator — rendered as Divider in Tabs, hidden in Select + { + value: TAB_VALUES.networkVisualizationsParams, + labelId: 'NetworkVisualizations', + }, + ]; + return options; + }, [ + computationStatus, + tabValue, + securityAnalysisAvailability, + sensitivityAnalysisAvailability, + shortCircuitAvailability, + pccMinAvailability, + voltageInitAvailability, + stateEstimationAvailability, + dynamicSimulationAvailability, + dynamicSecurityAnalysisAvailability, + dynamicMarginCalculationAvailability, + isDeveloperMode, + ]); + return ( <> - - - - - - - - - - + {isXsScreen ? ( + + + + + + ) : ( + + + + + + + + + handleChangeTab(newValue)} + aria-label="parameters" + orientation={isXsScreen ? 'horizontal' : 'vertical'} + sx={tabStyles.listDisplay} + > + {tabOptions.map((opt) => + opt.value === 'divider' ? ( + } + disabled + /> + ) : ( + } + value={opt.value} + disabled={opt.disabled} + /> + ) + )} + + + + + )} + - {displayTab()} + + {displayTab()} + @@ -597,4 +671,23 @@ const ParametersTabs: FunctionComponent = () => { ); }; +const useContainerXs = (threshold = 600) => { + const ref = useRef(null); + const [isXsScreen, setIsXsScreen] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const observer = new ResizeObserver(([entry]) => { + setIsXsScreen(entry.contentRect.width < threshold); + }); + + observer.observe(el); + return () => observer.disconnect(); + }, [threshold]); + + return { ref, isXsScreen }; +}; + export default ParametersTabs; diff --git a/src/components/utils/tab-utils.ts b/src/components/utils/tab-utils.ts index beaf501603..f1fe40958e 100644 --- a/src/components/utils/tab-utils.ts +++ b/src/components/utils/tab-utils.ts @@ -45,6 +45,9 @@ export const stylesLayout = { } as const satisfies MuiStyles; export const tabStyles = { + menuSelect: (theme) => ({ + backgroundColor: theme.palette.mode === 'light' ? 'white' : 'none', + }), listTitleDisplay: (theme) => ({ paddingTop: 1, paddingBottom: 1, @@ -73,6 +76,7 @@ export const tabStyles = { '.MuiTabScrollButton-root:nth-of-type(1)': { height: '30px', //40px by default }, + height: '100%', }), parametersBox: (theme) => ({ backgroundColor: @@ -86,9 +90,11 @@ export const tabStyles = { contentBox: (theme) => ({ paddingTop: theme.spacing(2), paddingBottom: theme.spacing(2), - paddingLeft: theme.spacing(8), - paddingRight: theme.spacing(8), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), height: '100%', + display: 'flex', + flexDirection: 'column', }), dividerTab: (theme) => ({ padding: 0,