Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5927d3e
import modification dialog : composite selection
May 25, 2026
cdc7d4d
Merge branch 'main' into composite-selection
souissimai May 26, 2026
96a019c
fixes
May 26, 2026
6c48bc9
adjust css
May 26, 2026
235ddf0
Merge branch 'main' into composite-selection
Mathieu-Deharbe May 27, 2026
74ab417
Merge branch 'main' into composite-selection
souissimai May 27, 2026
7a27fff
fixes
May 28, 2026
5365298
Merge branch 'main' into composite-selection
souissimai May 28, 2026
aaefbcf
resolve conflicts
Jun 1, 2026
55bf819
Merge branch 'main' into composite-selection
souissimai Jun 1, 2026
ec4ea6d
Merge branch 'main' into composite-selection
souissimai Jun 1, 2026
00e32cb
Merge branch 'main' into composite-selection
Mathieu-Deharbe Jun 3, 2026
a095b5a
Merge branch 'main' into composite-selection
Mathieu-Deharbe Jun 3, 2026
f008614
Merge branch 'main' into composite-selection
Mathieu-Deharbe Jun 3, 2026
7ce4492
import shared modification
Mathieu-Deharbe Jun 3, 2026
08b7268
IS_SHARED as constant + cleaning
Mathieu-Deharbe Jun 4, 2026
43f4be6
disable sharing in INSERT mode
Mathieu-Deharbe Jun 4, 2026
7e291d1
InsertNameCell + non editable name when shared
Mathieu-Deharbe Jun 4, 2026
f94fa7e
CompositesToBeInserted
Mathieu-Deharbe Jun 4, 2026
6f66892
updates CompositesToBeInserted
Mathieu-Deharbe Jun 5, 2026
ab43bd0
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 9, 2026
5d77ff6
conflicts
Mathieu-Deharbe Jun 9, 2026
b116ba1
selectionContainsShared
Mathieu-Deharbe Jun 9, 2026
3deb8ac
comment
Mathieu-Deharbe Jun 11, 2026
67ac0fc
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 11, 2026
f25a7ed
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 12, 2026
15019e7
Merge branch 'main' into import-shared-modification
souissimai Jun 17, 2026
d72d30c
temp move back import-shared-modification to import-composite
Mathieu-Deharbe Jun 17, 2026
cecc28b
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 17, 2026
be0981e
Merge branch 'main' into import-shared-modification
souissimai Jun 17, 2026
59ead2c
post merge
Mathieu-Deharbe Jun 17, 2026
0807ad4
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 19, 2026
b73aea2
Merge branch 'main' into import-shared-modification
souissimai Jun 19, 2026
9df75b6
move back component file
Mathieu-Deharbe Jun 19, 2026
d00e737
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jun 19, 2026
c91032a
comments
Mathieu-Deharbe Jun 22, 2026
6011fd5
Merge branch 'main' into import-shared-modification
souissimai Jul 6, 2026
5158dc0
Merge branch 'main' into import-shared-modification
Mathieu-Deharbe Jul 7, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { FormattedMessage, useIntl } from 'react-intl';
import {
CheckboxInput,
CustomFormProvider,
DirectoryItemSelector,
DndColumn,
Expand All @@ -17,45 +18,34 @@ import {
TreeViewFinderNodeProps,
useSnackMessage,
} from '@gridsuite/commons-ui';
import { insertCompositeModifications, type ModificationPair } from '../../services/study';
import { type CompositesToBeInserted, insertCompositeModifications } from '../../../services/study';
import { JSX, useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'redux/reducer.type';
import { CompositeModificationAction } from 'components/graph/menus/network-modifications/network-modification-menu.type';
import { AppState } from '../../../redux/reducer.type';
import { CompositeModificationAction } from '../../graph/menus/network-modifications/network-modification-menu.type';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
FormControl,
FormControlLabel,
FormHelperText,
Radio,
RadioGroup,
Step,
StepLabel,
Stepper,
TextField,
Typography,
} from '@mui/material';

import {
Controller,
useController,
useFieldArray,
useForm,
UseFieldArrayReturn,
useFormContext,
useWatch,
} from 'react-hook-form';
import { useController, useFieldArray, UseFieldArrayReturn, useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { ACTION, SELECTED_MODIFICATIONS } from 'components/utils/field-constants';
import { ACTION, IS_SHARED, SELECTED_MODIFICATIONS } from '../../utils/field-constants';
import * as yup from 'yup';
import { UUID } from 'node:crypto';
import InsertNameCell from './insert-name-cell';

/**
* Dialog to select composite network modifications and append them to the current node.
Expand Down Expand Up @@ -95,44 +85,8 @@ interface SharedCellProps {
}

function SharedCell({ rowIndex }: Readonly<SharedCellProps>) {
const isShared: boolean = useWatch({
name: `${SELECTED_MODIFICATIONS}.${rowIndex}.isShared`,
});

return (
<FormControlLabel
control={<Checkbox size="small" disabled checked={isShared} sx={{ p: 0, ml: 1 }} />}
label={
<Typography variant="body2" color="text.disabled" sx={{ ml: 0.5 }}>
<FormattedMessage id="importComposites.shared" />
</Typography>
}
sx={{ mr: 0, alignItems: 'center' }}
/>
);
}

interface InsertNameCellProps {
rowIndex: number;
}

function InsertNameCell({ rowIndex }: Readonly<InsertNameCellProps>) {
const { control } = useFormContext();
const originalName: string = useWatch({
name: `${SELECTED_MODIFICATIONS}.${rowIndex}.originalName`,
});

return (
<Box sx={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
<Controller
name={`${SELECTED_MODIFICATIONS}.${rowIndex}.name`}
control={control}
render={({ field, fieldState }) => (
<TextField {...field} size="small" fullWidth error={!!fieldState.error} />
)}
/>
<FormHelperText sx={{ px: 1, mt: 0.25 }}> {originalName}</FormHelperText>
</Box>
<CheckboxInput name={`${SELECTED_MODIFICATIONS}.${rowIndex}.${IS_SHARED}`} label={'importComposites.shared'} />
);
}

Expand Down Expand Up @@ -192,8 +146,8 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification

const { field: actionField } = useController({ name: ACTION, control });

const action = watch(ACTION);
const selectedModifications = watch(SELECTED_MODIFICATIONS);
const action: CompositeModificationAction = watch(ACTION);
const selectedModifications: SelectedComposite[] = watch(SELECTED_MODIFICATIONS);
const isInsertMode = action === CompositeModificationAction.INSERT;
const isNextDisabled = selectedModifications.length === 0;

Expand All @@ -207,7 +161,7 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
const sharedColumn: DndColumn = useMemo(
() => ({
label: '',
dataKey: 'isShared',
dataKey: IS_SHARED,
initialValue: false,
editable: true,
width: '25%',
Expand All @@ -217,7 +171,7 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
[]
);

// SPLIT mode — name is read-only, drag-and-drop only
// SPLIT mode — name is read-only, drag-and-drop only, cannot be shared
const splitColumnsDefinition: DndColumn[] = useMemo(
() => [
{
Expand All @@ -228,9 +182,8 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
width: '75%',
type: DndColumnType.TEXT,
},
sharedColumn,
],
[sharedColumn]
[]
);

// INSERT mode — name is editable with original name shown below
Expand Down Expand Up @@ -268,7 +221,7 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
id: e.id as UUID,
name: e.name,
originalName: e.name,
isShared: false,
[IS_SHARED]: false,
}));
setValue(SELECTED_MODIFICATIONS, newRows, {
shouldValidate: true,
Expand All @@ -278,32 +231,6 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
[setValue]
);

const handleSelectModification = useCallback(
(selectedElements: TreeViewFinderNodeProps[]) => {
setIsSelectorOpen(false);

if (!selectedElements.length) {
if (selectedModifications.length === 0) {
onClose();
}
return;
}

const newRows: SelectedComposite[] = selectedElements.map((e) => ({
id: e.id as UUID,
name: e.name,
originalName: e.name,
isShared: false, // currently always false, to be computed later when shared modifications are added
}));

setValue(SELECTED_MODIFICATIONS, newRows, {
shouldValidate: true,
shouldDirty: true,
});
},
[setValue, selectedModifications.length, onClose]
);

const handleNext = useCallback(() => {
if (isNextDisabled) return;
setActiveStep(STEP_ORGANIZATION);
Expand All @@ -324,9 +251,12 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
const handleSave = useCallback(() => {
if (!studyUuid || !currentNode || !isValid) return;

const modificationsToInsert: ModificationPair[] = selectedModifications.map((m) => ({
first: m.id,
second: action === CompositeModificationAction.SPLIT ? m.originalName : m.name,
const modificationsToInsert: CompositesToBeInserted[] = selectedModifications.map((m: SelectedComposite) => ({
id: m.id,
// only inserted non shared composites may be renamed
name: action === CompositeModificationAction.SPLIT || m.isShared ? m.originalName : m.name,
// SPLIT modifications are never shared
isShared: action === CompositeModificationAction.INSERT ? m.isShared : false,
}));

insertCompositeModifications(studyUuid, currentNode.id, modificationsToInsert, action).catch((error) =>
Expand All @@ -338,11 +268,10 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification

return (
<CustomFormProvider validationSchema={formSchema} {...formMethods}>
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth PaperProps={{ sx: { p: 1 } }}>
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle>
<FormattedMessage id="importComposites.title" />
</DialogTitle>

<DialogContent sx={{ display: 'flex', flexDirection: 'column', height: 490 }}>
<Stepper
activeStep={activeStep}
Expand Down Expand Up @@ -378,7 +307,7 @@ const ImportModificationDialog = ({ open, onClose }: Readonly<ImportModification
>
<DirectoryItemSelector
open={isSelectorOpen}
onClose={handleSelectModification}
onClose={() => setIsSelectorOpen(false)}
types={[ElementType.MODIFICATION]}
multiSelect
inline
Expand Down
47 changes: 47 additions & 0 deletions src/components/dialogs/import-composite/insert-name-cell.tsx
Original file line number Diff line number Diff line change
@@ -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 { IS_SHARED, SELECTED_MODIFICATIONS } from '../../utils/field-constants';
Comment thread
Mathieu-Deharbe marked this conversation as resolved.
import { Box, FormHelperText, TextField, Typography } from '@mui/material';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { JSX } from 'react';

interface InsertNameCellProps {
rowIndex: number;
}

const InsertNameCell = ({ rowIndex }: Readonly<InsertNameCellProps>): JSX.Element => {
const { control } = useFormContext();
const originalName: string = useWatch({
name: `${SELECTED_MODIFICATIONS}.${rowIndex}.originalName`,
});
const isShared: boolean = useWatch({
name: `${SELECTED_MODIFICATIONS}.${rowIndex}.${IS_SHARED}`,
});

return (
<Box sx={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{isShared ? (
<Typography m={1} component="span" variant="body1">
{originalName}
</Typography>
) : (
<>
<Controller
name={`${SELECTED_MODIFICATIONS}.${rowIndex}.name`}
control={control}
render={({ field, fieldState }) => (
<TextField {...field} size="small" fullWidth error={!!fieldState.error} />
)}
/>
<FormHelperText sx={{ px: 1, mt: 0.25 }}> {originalName}</FormHelperText>
</>
)}
</Box>
);
};

export default InsertNameCell;
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,16 @@
import { useIsAnyNodeBuilding } from '../../../utils/is-any-node-building-hook';

import { FileUpload, RestoreFromTrash } from '@mui/icons-material';
import ImportModificationDialog from 'components/dialogs/import-modification-dialog';
import ImportModificationDialog from '../../../dialogs/import-composite/import-modification-dialog';
import RestoreModificationDialog from 'components/dialogs/restore-modification-dialog';
import type { UUID } from 'node:crypto';
import { AppState } from 'redux/reducer.type';
import { createCompositeModifications, updateCompositeModifications } from '../../../../services/explore';
import { copyOrMoveModifications } from '../../../../services/study';
import {
assembleModificationsIntoComposite,
fetchExcludedNetworkModifications,
fetchNetworkModifications,
assembleModificationsIntoComposite,
stashModifications,
} from '../../../../services/study/network-modifications';
import {
Expand Down Expand Up @@ -177,6 +177,14 @@
[]
);

// TODO : this is temporary, until copy/paste/save is done for the shared modifications in GRD-4785 :
const selectionContainsShared: boolean = useMemo(() => {
return selectedNetworkModifications.some(
(modification: ComposedModificationMetadata) =>
modification.type === ModificationType.MODIFICATION_REFERENCE

Check failure on line 184 in src/components/graph/menus/network-modifications/network-modification-node-editor.tsx

View workflow job for this annotation

GitHub Actions / build / build

Property 'MODIFICATION_REFERENCE' does not exist on type 'typeof ModificationType'.
);
}, [selectedNetworkModifications]);

const [isDragging, setIsDragging] = useState(false);
const [isAssemblyDepthExceeded, setIsAssemblyDepthExceeded] = useState(false);

Expand Down Expand Up @@ -1314,7 +1322,7 @@
<IconButton
onClick={openCreateCompositeModificationDialog}
size={'small'}
disabled={disabledCompositeExport}
disabled={disabledCompositeExport || selectionContainsShared}
>
<SaveIcon />
</IconButton>
Expand All @@ -1330,7 +1338,8 @@
isAnyNodeBuilding ||
mapDataLoading ||
!currentNode ||
isRootNode
isRootNode ||
selectionContainsShared
}
>
<ContentCutIcon />
Expand All @@ -1346,7 +1355,8 @@
selectedNetworkModifications.length === 0 ||
isAnyNodeBuilding ||
mapDataLoading ||
isRootNode
isRootNode ||
selectionContainsShared
}
>
<ContentCopyIcon />
Expand All @@ -1368,7 +1378,7 @@
<IconButton
onClick={doPasteModifications}
size={'small'}
disabled={isPasteButtonDisabled || isRootNode}
disabled={isPasteButtonDisabled || isRootNode || selectionContainsShared}
>
<ContentPasteIcon />
</IconButton>
Expand Down
2 changes: 1 addition & 1 deletion src/components/utils/field-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,4 @@ export const CONNECTION_SIDE = 'connectionSide';

export const ACTION = 'action';
export const SELECTED_MODIFICATIONS = 'selectedModifications';
export const COMPOSITE_NAMES = 'compositeNames';
export const IS_SHARED = 'isShared';
9 changes: 5 additions & 4 deletions src/services/study/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,16 @@ export function copyOrMoveModifications(
});
}

export interface ModificationPair {
first: UUID;
second: string;
export interface CompositesToBeInserted {
id: UUID;
name: string;
isShared: boolean;
}

export const insertCompositeModifications = (
studyUuid: string,
nodeUuid: string,
modifications: ModificationPair[],
modifications: CompositesToBeInserted[],
action: CompositeModificationAction
): Promise<void> => {
const urlSearchParams = new URLSearchParams({ action });
Expand Down
Loading