Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/ui/reactHookForm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@

export const NAME = 'name';
export const DESCRIPTION = 'description';
export const DIRECTORY_ITEM = 'directoryItem';
export const DIRECTORY_ITEM_ID = 'directoryItemId';
export const DIRECTORY_ITEM_FULL_PATH = 'directoryItemFullPath';
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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 { Box, Button, FormHelperText, Grid2 as Grid, Stack, Tooltip, Typography } from '@mui/material';
import { FormattedMessage, useIntl } from 'react-intl';
import { useCallback, useMemo, useState } from 'react';
import { useController } from 'react-hook-form';
import { UUID } from 'node:crypto';
import { FolderOutlined } from '@mui/icons-material';
import { DIRECTORY_ITEM_FULL_PATH, DIRECTORY_ITEM_ID } from '../constants';
import { DirectoryItemSchema, getAbsenceLabelKeyFromType } from './directory-item-utils';
import { DirectoryItemSelector, DirectoryItemSelectorProps } from '../../directoryItemSelector';
import { TreeViewFinderNodeProps } from '../../treeViewFinder';

export interface DirectoryItemSelectorInputProps extends Omit<DirectoryItemSelectorProps, 'onClose' | 'open'> {
name: string;
}

export function DirectoryItemInput({ name, types, ...props }: Readonly<DirectoryItemSelectorInputProps>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

be sure to clean in study

@thangqp thangqp Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be done in Sprint 38

const [isOpen, setIsOpen] = useState<boolean>(false);

const {
field: { onChange, value },
fieldState: { error },
} = useController({ name });

const nodeInfos: DirectoryItemSchema | undefined | null = value;
const intl = useIntl();

const breadcrumb = useMemo(() => {
return nodeInfos?.[DIRECTORY_ITEM_FULL_PATH] ? nodeInfos[DIRECTORY_ITEM_FULL_PATH] : undefined;
}, [nodeInfos]);

const onNodeChanged = useCallback(
(nodes: TreeViewFinderNodeProps[]) => {
if (nodes.length > 0) {
const fullPath = nodes[0]?.name;
const nodeId: UUID | null = nodes[0]?.id;
if (nodeId) {
const newNodeInfos = {
[DIRECTORY_ITEM_ID]: nodeId,
[DIRECTORY_ITEM_FULL_PATH]: fullPath,
};
onChange(newNodeInfos);
Comment on lines +40 to +48

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to build the full path, as demanded by our PO, the full path shoud be built by a specific maner to avoid cases in which path is so long.. This wil be done later..

}
}
setIsOpen(false);
},
[onChange]
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return (
<Box>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Grid container alignItems="center">
<Grid paddingTop={1}>
<FolderOutlined />
</Grid>
<Grid paddingTop={1} paddingLeft={1}>
<Tooltip
title={nodeInfos?.[DIRECTORY_ITEM_FULL_PATH] ?? ''}
slotProps={{
tooltip: {
sx: {
maxWidth: 'none', // to override the background of text is auto cut
},
},
}}
>
<Typography fontWeight={breadcrumb ? undefined : 'bold'} noWrap>
{breadcrumb || <FormattedMessage id={getAbsenceLabelKeyFromType(types?.[0])} />}
</Typography>
</Tooltip>
</Grid>
<Grid paddingTop={1} paddingLeft={1}>
{error?.message && (
<FormHelperText error>{intl.formatMessage({ id: error?.message })}</FormHelperText>
)}
</Grid>
</Grid>
<Button onClick={() => setIsOpen(true)} variant="contained" color="primary" component="label">
<FormattedMessage id={breadcrumb ? 'edit' : 'Select'} />
</Button>
</Stack>

<DirectoryItemSelector open={isOpen} onClose={onNodeChanged} types={types} {...props} />
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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 * as yup from 'yup';
import { DIRECTORY_ITEM_FULL_PATH, DIRECTORY_ITEM_ID } from '../constants';
import { ElementType } from '../../../../utils';

export function getAbsenceLabelKeyFromType(elementType: string) {
switch (elementType) {
case ElementType.DIRECTORY:
return 'NoFolder';
case ElementType.CASE:
return 'NoCase';
case ElementType.STUDY:
return 'NoStudy';
default:
return 'NoItem';
}
}

export const directoryItemSchema = yup.object().shape({
[DIRECTORY_ITEM_ID]: yup.string().required(),
[DIRECTORY_ITEM_FULL_PATH]: yup.string().required(),
});

export type DirectoryItemSchema = yup.InferType<typeof directoryItemSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +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 http://mozilla.org/MPL/2.0/.
*/
export * from './directory-item-utils';
export * from './directory-item-input';
1 change: 1 addition & 0 deletions src/components/ui/reactHookForm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export * from './constants';
export * from './expandableInput';
export * from './CountrySelectionInput';
export * from './CheckboxNullableInput';
export * from './directory-item-input';
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type DirectoryItemsInputLineProps = {
name: string;
equipmentTypes?: string[];
elementType: string;
hideErrorMessage: boolean;
hideErrorMessage?: boolean;
allowMultiSelect?: boolean;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/

import React, { forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { alpha, Checkbox, styled, SxProps, Theme, useTheme } from '@mui/material';
import { alpha, Checkbox, styled, SxProps, Theme } from '@mui/material';
import { SimpleTreeView, TreeItem, treeItemClasses } from '@mui/x-tree-view';

const BorderedTreeItem = styled(TreeItem)(({ theme, root }: { theme: Theme; root: boolean }) => {
const BorderedTreeItem = styled(TreeItem)<{ root: boolean }>(({ theme, root }: { theme: Theme; root: boolean }) => {
const border = `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`;
return {
position: 'relative',
Expand Down Expand Up @@ -65,8 +65,6 @@ function CheckboxTreeview<TData extends ItemData>(
{ data: items, checkAll, onSelectionChanged, getLabel, sx }: Readonly<CheckBoxTreeViewProps<TData>>,
ref: Ref<CheckboxTreeviewApi<TData>>
) {
const theme = useTheme();

const initialItemStates = useMemo<Record<string, CheckState>>(
() => Object.fromEntries(items.map((elem) => [elem.id, checkAll ? CheckState.CHECKED : CheckState.UNCHECKED])),
[items, checkAll]
Expand Down Expand Up @@ -202,7 +200,6 @@ function CheckboxTreeview<TData extends ItemData>(

return itemsToRender.map((elem) => (
<BorderedTreeItem
theme={theme}
key={elem.id}
itemId={elem.id}
onClick={handleExpand}
Expand All @@ -222,7 +219,7 @@ function CheckboxTreeview<TData extends ItemData>(
</BorderedTreeItem>
));
},
[itemsByParent, theme, handleExpand, itemStates, getLabel, handleItemSelect]
[itemsByParent, handleExpand, itemStates, getLabel, handleItemSelect]
);

return <SimpleTreeView sx={sx}>{renderTree(null)}</SimpleTreeView>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { ExpertFilter, IdentifiableAttributes } from '../../../../components/com
import { Curve as CurveType } from './common/curve.type';
import { type MuiStyles } from '../../../../utils/styles';
import { CustomAGGrid } from '../../../../components/composite/customAGGrid';
import { isEmpty } from '../../../../utils/functions';
import { IdName } from '../../../../utils';

const styles = {
grid: {
Expand Down Expand Up @@ -156,8 +156,8 @@ function CurveParameters({

// config fetchers based on the mapping and studyUuid
const modelsFetcher = useCallback(() => {
const mapping = getValues(mappingPath);
return isEmpty(mapping) ? undefined : fetchDynamicSimulationModels(mapping);
const mapping = (getValues(mappingPath) as IdName[] | undefined)?.[0]; /* array of one element */
return mapping?.id ? fetchDynamicSimulationModels(mapping) : undefined;
}, [getValues, mappingPath]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { useCallback, useEffect, useState } from 'react';
import { FieldErrors, FieldValues } from 'react-hook-form';
import { Grid } from '@mui/material';
import { ElementType, mergeSx, snackWithFallback, VoltageLevelInfos } from '../../../utils';
import {
ElementType,
mapDynamicSimulationParameters,
mergeSx,
snackWithFallback,
VoltageLevelInfos,
} from '../../../utils';
import { UseParametersBackendReturnProps } from '../../../utils/types/parameters.type';
import { ComputingType, CreateParameterDialog, LabelledButton } from '../common';

Expand All @@ -21,7 +27,7 @@ import { DirectoryItemSelector } from '../../../components/ui/directoryItemSelec
import { PopupConfirmationDialog } from '../../../components/ui/dialogs';
import {
toFormValues,
toParamsInfos,
toParamsEnriched,
useDynamicSimulationParametersForm,
} from './use-dynamic-simulation-parameters-form';
import { DynamicSimulationForm } from './dynamic-simulation-parameters-form';
Expand Down Expand Up @@ -83,7 +89,7 @@ export function DynamicSimulationInline({
const onSubmit = useCallback(
(formData: FieldValues) => {
// update params after convert form representation to dto representation
updateParameters(toParamsInfos(formData, getDefaultParams()));
updateParameters(toParamsEnriched(formData, getDefaultParams()));
},
Comment on lines 89 to 93
[updateParameters, getDefaultParams]
);
Expand Down Expand Up @@ -142,7 +148,9 @@ export function DynamicSimulationInline({
open={openCreateParameterDialog}
onClose={() => setOpenCreateParameterDialog(false)}
parameterValues={getValues}
parameterFormatter={(formData) => toParamsInfos(formData, getDefaultParams())}
parameterFormatter={(formData) =>
mapDynamicSimulationParameters(toParamsEnriched(formData, getDefaultParams()))
}
parameterType={ElementType.DYNAMIC_SIMULATION_PARAMETERS}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@
*/
import * as yup from 'yup';
import { MAPPING } from './mapping-parameters-constants';
import { ID, NAME } from '../../common/parameter-table-field';
import { YUP_REQUIRED } from '../../../../utils';

export const mappingFormSchema = yup.object().shape({
[MAPPING]: yup.string().required(),
[MAPPING]: yup
.array()
.of(
yup
.object()
.shape({
[ID]: yup.string().required(),
[NAME]: yup.string().required(),
})
.required()
)
.min(1, YUP_REQUIRED),
});

export const mappingEmptyFormData = {
[MAPPING]: '',
[MAPPING]: [],
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,23 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Grid } from '@mui/material';
import { useEffect, useMemo, useState } from 'react';
import { ParameterType, SpecificParameterInfos } from '../../../../utils/types/parameters.type';
import { MappingInfos } from '../../../../utils/types/dynamic-simulation.type';
import ParameterField from '../../common/parameter-field';
import { MAPPING } from './mapping-parameters-constants';
import { getDynamicMappings } from '../../../../services';
import { snackWithFallback } from '../../../../utils';
import { useSnackMessage } from '../../../../hooks';
import { ElementType } from '../../../../utils';
import { ParameterLineDirectoryItemsInput } from '../../common';

interface MappingParametersProps {
path: string;
}

export function MappingParameters({ path }: Readonly<MappingParametersProps>) {
const { snackError } = useSnackMessage();
const [mappings, setMappings] = useState<MappingInfos[]>([]);

useEffect(() => {
getDynamicMappings()
.then((_mappings) => {
setMappings(_mappings);
})
.catch((error: Error) => {
snackWithFallback(snackError, error, {
headerId: `DynamicSimulationMappingsError`,
});
});
}, [setMappings, snackError]);

const mappingOptions = useMemo(() => {
return mappings?.map((elem) => elem.name) ?? [];
}, [mappings]);

const params: SpecificParameterInfos[] = useMemo(
() => [
{
name: MAPPING,
type: ParameterType.STRING,
label: 'DynamicSimulationMapping',
possibleValues: mappingOptions,
sx: { width: '100%' },
},
],
[mappingOptions]
);

return (
<Grid container>
{params.map((param: SpecificParameterInfos) => {
const { name, type, ...otherParams } = param;
return (
<ParameterField key={param.name} id={path} name={param.name} type={param.type} {...otherParams} />
);
})}
<ParameterLineDirectoryItemsInput
name={`${path}.${MAPPING}`}
elementType={ElementType.DYNAMIC_MAPPING}
label="DynamicSimulationMapping"
allowMultiSelect={false}
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</Grid>
);
}
Loading
Loading