Repository: gridsuite/gridstudy-app
Pull request #2872: Feat add diagrams layout
Author: login: sBouzols
Created: 2025-05-19T07:30:57Z | Merged: 2025-05-23T08:38:11Z



Diff:
diff --git a/package-lock.json b/package-lock.json
index b54eb92477..89f081fdc8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,7 +36,7 @@
                 "react": "^18.3.1",
                 "react-csv-downloader": "^3.3.0",
                 "react-dom": "^18.3.1",
-                "react-grid-layout": "^1.5.0",
+                "react-grid-layout": "^1.5.1",
                 "react-hook-form": "^7.54.2",
                 "react-intl": "^7.1.6",
                 "react-papaparse": "^4.4.0",
@@ -17183,9 +17183,9 @@
             }
         },
         "node_modules/react-grid-layout": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.0.tgz",
-            "integrity": "sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w==",
+            "version": "1.5.1",
+            "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.1.tgz",
+            "integrity": "sha512-4Fr+kKMk0+m1HL/BWfHxi/lRuaOmDNNKQDcu7m12+NEYcen20wIuZFo789u3qWCyvUsNUxCiyf0eKq4WiJSNYw==",
             "license": "MIT",
             "dependencies": {
                 "clsx": "^2.0.0",
diff --git a/package.json b/package.json
index 0ac01031fc..5118128309 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
         "react": "^18.3.1",
         "react-csv-downloader": "^3.3.0",
         "react-dom": "^18.3.1",
-        "react-grid-layout": "^1.5.0",
+        "react-grid-layout": "^1.5.1",
         "react-hook-form": "^7.54.2",
         "react-intl": "^7.1.6",
         "react-papaparse": "^4.4.0",
diff --git a/src/components/diagrams/diagram-grid-layout.tsx b/src/components/diagrams/diagram-grid-layout.tsx
new file mode 100644
index 0000000000..b793795493
--- /dev/null
+++ b/src/components/diagrams/diagram-grid-layout.tsx
@@ -0,0 +1,288 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+import { DiagramMetadata, SLDMetadata } from '@powsybl/network-viewer';
+import { DiagramAdditionalMetadata } from './diagram-common';
+import { useParameterState } from 'components/dialogs/parameters/use-parameters-state';
+import { PARAM_DEVELOPER_MODE } from 'utils/config-params';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+// Diagram types to manage here
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    window: {
+        display: 'flex',
+        flexDirection: 'column',
+    },
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // ResponsiveGridLayout will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramGridLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramGridLayout({ studyUuid, showInSpreadsheet, visible }: Readonly<DiagramGridLayoutProps>) {
+    const theme = useTheme();
+    const [enableDeveloperMode] = useParameterState(PARAM_DEVELOPER_MODE);
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+
+    const onAddDiagram = (diagram: Diagram) => {
+        setLayouts((old_layouts) => {
+            const new_lg_layouts = old_layouts.lg.filter((layout) => layout.i !== 'Adder');
+            const layoutItem: Layout = {
+                i: diagram.diagramUuid,
+                x: Infinity,
+                y: 0,
+                w: DEFAULT_WIDTH,
+                h: DEFAULT_HEIGHT,
+                minH: DEFAULT_HEIGHT,
+                minW: DEFAULT_WIDTH,
+            };
+            new_lg_layouts.push(layoutItem);
+            return { lg: new_lg_layouts };
+        });
+    };
+    const { diagrams, removeDiagram, createDiagram } = useDiagramModel({
+        diagramTypes: enableDeveloperMode ? diagramTypes : [],
+        onAddDiagram,
+    });
+
+    const onRemoveItem = useCallback(
+        (diagramUuid: UUID) => {
+            setLayouts((old_layouts) => {
+                const new_lg_layouts = old_layouts.lg.filter((layout: Layout) => layout.i !== diagramUuid);
+                if (new_lg_layouts.length === 0) {
+                    return initialLayouts;
+                }
+                return { lg: new_lg_layouts };
+            });
+            removeDiagram(diagramUuid);
+        },
+        [removeDiagram]
+    );
+
+    const showVoltageLevelDiagram = useCallback(
+        (element: EquipmentInfos) => {
+            if (element.type === EquipmentType.VOLTAGE_LEVEL) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.VOLTAGE_LEVEL,
+                    voltageLevelId: element.voltageLevelId ?? '',
+                };
+                createDiagram(diagram);
+            } else if (element.type === EquipmentType.SUBSTATION) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.SUBSTATION,
+                    substationId: element.id,
+                };
+                createDiagram(diagram);
+            }
+        },
+        [createDiagram]
+    );
+    const renderDiagramAdder = useCallback(() => {
+        if (Object.values(diagrams).length > 0) {
+            return;
+        }
+        return (
+            <div key={'Adder'} style={{ display: 'flex', flexDirection: 'column' }}>
+                <Box sx={styles.header}>
+                    <OverflowableText
+                        className="react-grid-dragHandle"
+                        sx={{ flexGrow: '1' }}
+                        text={'Add a new diagram'}
+                    />
+                </Box>
+                <Box
+                    sx={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        flexGrow: 1,
+                        alignItems: 'center',
+                        justifyContent: 'center',
+                    }}
+                >
+                    <IconButton
+                        onClick={(e) => {
+                            setIsDialogSearchOpen(true);
+                        }}
+                    >
+                        <LibraryAddOutlinedIcon />
+                    </IconButton>
+                </Box>
+            </div>
+        );
+    }, [diagrams]);
+
+    // This function is called by the diagram's contents, when they get their sizes from the backend.
+    const setDiagramSize = useCallback((diagramId: UUID, diagramType: DiagramType, width: number, height: number) => {
+        console.log('TODO setDiagramSize', diagramId, diagramType, width, height);
+        // TODO adapt the layout w and h cnsidering those values
+    }, []);
+
+    const renderDiagrams = useCallback(() => {
+        if (Object.values(diagrams).length === 0) {
+            return;
+        }
+        return Object.values(diagrams).map((diagram) => {
+            if (!diagram) {
+                return null;
+            }
+            return (
+                <Box key={diagram.diagramUuid} sx={styles.window}>
+                    <Box sx={styles.header}>
+                        <OverflowableText
+                            className="react-grid-dragHandle"
+                            sx={{ flexGrow: '1' }}
+                            text={diagram.name}
+                        />
+                        <IconButton
+                            size={'small'}
+                            onClick={(e) => {
+                                onRemoveItem(diagram.diagramUuid);
+                                e.stopPropagation();
+                            }}
+                        >
+                            <CloseIcon fontSize="small" />
+                        </IconButton>
+                    </Box>
+                    <Box sx={styles.diagramContainer}>
+                        {(diagram.type === DiagramType.VOLTAGE_LEVEL || diagram.type === DiagramType.SUBSTATION) && (
+                            <SingleLineDiagramContent
+                                showInSpreadsheet={showInSpreadsheet}
+                                studyUuid={studyUuid}
+                                diagramId={diagram.diagramUuid}
+                                svg={diagram.svg?.svg ?? undefined}
+                                svgType={diagram.type}
+                                svgMetadata={(diagram.svg?.metadata as SLDMetadata) ?? undefined}
+                                loadingState={false} // TODO
+                                diagramSizeSetter={setDiagramSize}
+                                visible={visible}
+                            />
+                        )}
+                        {(diagram.type === DiagramType.NETWORK_AREA_DIAGRAM ||
+                            diagram.type === DiagramType.NAD_FROM_CONFIG) && (
+                            <NetworkAreaDiagramContent
+                                diagramId={diagram.diagramUuid}
+                                svg={diagram.svg?.svg ?? undefined}
+                                svgType={diagram.type}
+                                svgMetadata={(diagram.svg?.metadata as DiagramMetadata) ?? undefined}
+                                svgScalingFactor={
+                                    (diagram.svg?.additionalMetadata as DiagramAdditionalMetadata)?.scalingFactor ??
+                                    undefined
+                                }
+                                svgVoltageLevels={
+                                    (diagram.svg?.additionalMetadata as DiagramAdditionalMetadata)?.voltageLevels
+                                        .map((vl) => vl.id)
+                                        .filter((vlId) => vlId !== undefined) as string[]
+                                }
+                                loadingState={false} // TODO
+                                diagramSizeSetter={setDiagramSize}
+                                visible={visible}
+                            />
+                        )}
+                    </Box>
+                </Box>
+            );
+        });
+    }, [diagrams, onRemoveItem, setDiagramSize, showInSpreadsheet, studyUuid, visible]);
+
+    return (
+        <>
+            <ResponsiveGridLayout
+                className="layout"
+                breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
+                cols={{ lg: 4, md: 2, sm: 2, xs: 1, xxs: 1 }}
+                compactType={'horizontal'}
+                onLayoutChange={(currentLayout, allLayouts) => setLayouts(allLayouts)}
+                layouts={layouts}
+                style={{
+                    backgroundColor:
+                        theme.palette.mode === 'light'
+                            ? darken(theme.palette.background.paper, 0.1)
+                            : theme.reactflow.backgroundColor,
+                    flexGrow: 1,
+                    overflow: 'auto',
+                }}
+                draggableHandle=".react-grid-dragHandle"
+                onDragStart={(layout, oldItem, newItem, placeholder, e, element) => {
+                    if (e.target) {
+                        (e.target as HTMLElement).style.cursor = 'grabbing';
+                    }
+                }}
+                onDragStop={(layout, oldItem, newItem, placeholder, e, element) => {
+                    if (e.target) {
+                        (e.target as HTMLElement).style.cursor = 'default';
+                    }
+                }}
+            >
+                {renderDiagramAdder()}
+                {renderDiagrams()}
+            </ResponsiveGridLayout>
+            <TopBarEquipmentSearchDialog
+                showVoltageLevelDiagram={showVoltageLevelDiagram}
+                isDialogSearchOpen={isDialogSearchOpen}
+                setIsDialogSearchOpen={setIsDialogSearchOpen}
+                disableEventSearch
+            />
+        </>
+    );
+}
+
+export default DiagramGridLayout;
diff --git a/src/components/horizontal-toolbar.jsx b/src/components/horizontal-toolbar.jsx
index 61f2d4d7a9..73a4743653 100644
--- a/src/components/horizontal-toolbar.jsx
+++ b/src/components/horizontal-toolbar.jsx
@@ -11,6 +11,8 @@ import PublicIcon from '@mui/icons-material/Public';
 import IconButton from '@mui/material/IconButton';
 import ListIcon from '@mui/icons-material/List';
 import Tooltip from '@mui/material/Tooltip';
+import DashboardCustomizeOutlinedIcon from '@mui/icons-material/DashboardCustomizeOutlined';
+import AutoAwesomeMosaicOutlinedIcon from '@mui/icons-material/AutoAwesomeMosaicOutlined';
 import { useIntl } from 'react-intl';
 import { useDispatch, useSelector } from 'react-redux';
 import Divider from '@mui/material/Divider';
@@ -20,6 +22,7 @@ import OfflineBoltOutlinedIcon from '@mui/icons-material/OfflineBoltOutlined';
 import { PARAM_DEVELOPER_MODE } from '../utils/config-params';
 import { StudyDisplayMode } from './network-modification.type';
 import { useParameterState } from './dialogs/parameters/use-parameters-state';
+import { useEffect } from 'react';
 
 const styles = {
     selected: (theme) => ({
@@ -68,6 +71,25 @@ export function HorizontalToolbar() {
         dispatch(setStudyDisplayMode(StudyDisplayMode.HYBRID));
     }
 
+    function setDiagramGridLayoutDisplay() {
+        dispatch(setStudyDisplayMode(StudyDisplayMode.DIAGRAM_GRID_LAYOUT));
+    }
+    function setDiagramGridLayoutAndTreeDisplay() {
+        dispatch(setStudyDisplayMode(StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE));
+    }
+
+    useEffect(() => {
+        if (!enableDeveloperMode) {
+            if (
+                studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT ||
+                studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
+            ) {
+                dispatch(setStudyDisplayMode(StudyDisplayMode.HYBRID));
+            }
+            dispatch(setEventScenarioDrawerOpen(false));
+        }
+    }, [enableDeveloperMode, dispatch, studyDisplayMode]);
+
     return (
         <List
             style={{
@@ -225,6 +247,76 @@ export function HorizontalToolbar() {
                     <PublicIcon />
                 </IconButton>
             </Tooltip>
+            {enableDeveloperMode && (
+                <Tooltip
+                    title={intl.formatMessage({
+                        id: 'DiagramGridLayout',
+                    })}
+                    placement="right"
+                    arrow
+                    enterDelay={TOOLTIP_DELAY}
+                    enterNextDelay={TOOLTIP_DELAY}
+                    slotProps={{
+                        popper: {
+                            sx: {
+                                '& .MuiTooltip-tooltip': styles.tooltip,
+                            },
+                        },
+                    }}
+                    style={{
+                        marginRight: '8px',
+                    }}
+                >
+                    <span>
+                        <IconButton
+                            size={'small'}
+                            sx={
+                                studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT
+                                    ? styles.selected
+                                    : styles.notSelected
+                            }
+                            onClick={setDiagramGridLayoutDisplay}
+                        >
+                            <DashboardCustomizeOutlinedIcon />
+                        </IconButton>
+                    </span>
+                </Tooltip>
+            )}
+            {enableDeveloperMode && (
+                <Tooltip
+                    title={intl.formatMessage({
+                        id: 'DiagramGridLayoutAndTree',
+                    })}
+                    placement="right"
+                    arrow
+                    enterDelay={TOOLTIP_DELAY}
+                    enterNextDelay={TOOLTIP_DELAY}
+                    slotProps={{
+                        popper: {
+                            sx: {
+                                '& .MuiTooltip-tooltip': styles.tooltip,
+                            },
+                        },
+                    }}
+                    style={{
+                        marginRight: '8px',
+                    }}
+                >
+                    <span>
+                        <IconButton
+                            size={'small'}
+                            sx={
+                                studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
+                                    ? styles.selected
+                                    : styles.notSelected
+                            }
+                            onClick={setDiagramGridLayoutAndTreeDisplay}
+                        >
+                            <AutoAwesomeMosaicOutlinedIcon />
+                        </IconButton>
+                    </span>
+                </Tooltip>
+            )}
         </List>
     );
 }
diff --git a/src/components/map-viewer.jsx b/src/components/map-viewer.jsx
index d1ea26833a..4ca6baead5 100644
--- a/src/components/map-viewer.jsx
+++ b/src/components/map-viewer.jsx
@@ -28,6 +28,7 @@ import KeyboardReturnOutlinedIcon from '@mui/icons-material/KeyboardReturnOutlin
 import { StudyView } from './utils/utils';
 import { DiagramType } from './diagrams/diagram.type';
 import WaitingLoader from './utils/waiting-loader';
+import DiagramGridLayout from './diagrams/diagram-grid-layout';
 
 const styles = {
     map: {
@@ -256,11 +257,17 @@ const MapViewer = ({
                 <Box
                     sx={{
                         display:
-                            studyDisplayMode === StudyDisplayMode.TREE || studyDisplayMode === StudyDisplayMode.HYBRID
+                            studyDisplayMode === StudyDisplayMode.TREE ||
+                            studyDisplayMode === StudyDisplayMode.HYBRID ||
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
                                 ? 'flex'
                                 : 'none',
                         height: '100%',
-                        flexBasis: studyDisplayMode === StudyDisplayMode.HYBRID ? '50%' : '100%',
+                        flexBasis:
+                            studyDisplayMode === StudyDisplayMode.HYBRID ||
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
+                                ? '50%'
+                                : '100%',
                     }}
                 >
                     <ReactFlowProvider>
@@ -271,10 +278,35 @@ const MapViewer = ({
                         />
                     </ReactFlowProvider>
                 </Box>
+                {/* Diagram Grid Layout */}
+                <Box
+                    sx={{
+                        display:
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT ||
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
+                                ? 'flex'
+                                : 'none',
+                        height: '100%',
+                        flexDirection: 'column',
+                        flexBasis: studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE ? '50%' : '100%',
+                    }}
+                >
+                    <DiagramGridLayout
+                        studyUuid={studyUuid}
+                        visible={
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT ||
+                            studyDisplayMode === StudyDisplayMode.DIAGRAM_GRID_LAYOUT_AND_TREE
+                        }
+                        showInSpreadsheet={showInSpreadsheet}
+                    ></DiagramGridLayout>
+                </Box>
                 {/* Map */}
                 <Box
                     sx={{
-                        display: studyDisplayMode !== StudyDisplayMode.TREE ? 'flex' : 'none',
+                        display:
+                            studyDisplayMode === StudyDisplayMode.MAP || studyDisplayMode === StudyDisplayMode.HYBRID
+                                ? 'flex'
+                                : 'none',
                         flexBasis: studyDisplayMode === StudyDisplayMode.HYBRID ? '50%' : '100%',
                         height: '100%',
                     }}
diff --git a/src/components/network-modification.type.ts b/src/components/network-modification.type.ts
index 524a90535c..a8060f5fbd 100644
--- a/src/components/network-modification.type.ts
+++ b/src/components/network-modification.type.ts
@@ -11,6 +11,8 @@ export enum StudyDisplayMode {
     TREE = 'Tree',
     HYBRID = 'Hybrid',
     DRAW = 'Draw',
+    DIAGRAM_GRID_LAYOUT = 'DiagramGridLayout',
+    DIAGRAM_GRID_LAYOUT_AND_TREE = 'DiagramGridLayoutAndTree',
 }
 
 export enum CopyType {
diff --git a/src/components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog.tsx b/src/components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog.tsx
index cca6c6c486..4b5fd199a4 100644
--- a/src/components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog.tsx
+++ b/src/components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog.tsx
@@ -34,10 +34,11 @@ interface TopBarEquipmentSearchDialogProps {
     showVoltageLevelDiagram: (element: EquipmentInfos) => void;
     isDialogSearchOpen: boolean;
     setIsDialogSearchOpen: React.Dispatch<React.SetStateAction<boolean>>;
+    disableEventSearch?: boolean;
 }
 
 export const TopBarEquipmentSearchDialog: FunctionComponent<TopBarEquipmentSearchDialogProps> = (props) => {
-    const { isDialogSearchOpen, setIsDialogSearchOpen, showVoltageLevelDiagram } = props;
+    const { isDialogSearchOpen, setIsDialogSearchOpen, showVoltageLevelDiagram, disableEventSearch } = props;
     const intl = useIntl();
 
     const studyUuid = useSelector((state: AppState) => state.studyUuid);
@@ -58,8 +59,10 @@ export const TopBarEquipmentSearchDialog: FunctionComponent<TopBarEquipmentSearc
     const disabledSearchReason = useDisabledSearchReason();
 
     const enableSearchDialog = useCallback(() => {
-        setIsDialogSearchOpen(true);
-    }, [setIsDialogSearchOpen]);
+        if (!disableEventSearch) {
+            setIsDialogSearchOpen(true);
+        }
+    }, [disableEventSearch, setIsDialogSearchOpen]);
 
     const { snackWarning } = useSnackMessage();
 
diff --git a/src/translations/en.json b/src/translations/en.json
index 5ce1b7702d..7eea777b5d 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1540,5 +1540,7 @@
     "previousStatus": "Previous status",
     "currentStatus": "Modified status",
     "Open": "Open",
-    "Closed": "Closed"
+    "Closed": "Closed",
+    "DiagramGridLayout": "Diagram grid layout",
+    "DiagramGridLayoutAndTree": "Diagram grid layout and tree"
 }
diff --git a/src/translations/fr.json b/src/translations/fr.json
index ea6e9fa7dd..ef1298acb2 100644
--- a/src/translations/fr.json
+++ b/src/translations/fr.json
@@ -1536,5 +1536,7 @@
     "previousStatus": "État précédent",
     "currentStatus": "État modifié",
     "Open": "Ouvert",
-    "Closed": "Fermé"
+    "Closed": "Fermé",
+    "DiagramGridLayout": "Grille des diagrammes",
+    "DiagramGridLayoutAndTree": "Grille des diagrammes et arbre décisionnel"
 }


Reviews:
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:31:37Z
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:32:13Z
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:33:07Z
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:33:33Z
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:35:09Z
user: login: EstherDarkish
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T07:37:48Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T08:40:45Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T08:41:08Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T08:43:07Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T09:10:22Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T09:16:02Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T09:20:39Z
user: login: sBouzols
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T12:50:51Z
user: login: SlimaneAmar
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T07:08:41Z
user: login: EstherDarkish
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T07:23:31Z

Review comments:
diff_hunk: @@ -197,7 +197,6 @@ export function getCommonEquipmentType(equipmentType: EquipmentType): EquipmentT
 
 export interface SldAdditionalMetadata {
     id: string;
-    country: string;
path: src/components/diagrams/diagram-common.ts
user: login: EstherDarkish
body: Why ?
created_at: 2025-05-22T07:31:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
path: src/components/diagrams/diagram-layout.tsx
user: login: EstherDarkish
body: ```suggestion
    // ResponsiveGridLayout will attempt to interpolate the rest of breakpoints based on this one
```
created_at: 2025-05-22T07:32:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
path: src/components/diagrams/diagram-layout.tsx
user: login: EstherDarkish
body: Maybe relative to the mode used ? (full width or half size)
created_at: 2025-05-22T07:33:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+    const onBreakpointChange = (newBreakpoint: string, cols: number) => {
+        setCols(cols);
+    };
+
+    const FindNextXPositionForItem = useCallback(
path: src/components/diagrams/diagram-layout.tsx
user: login: EstherDarkish
body: This function should have a comment to explain its purpose
created_at: 2025-05-22T07:33:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+    const onBreakpointChange = (newBreakpoint: string, cols: number) => {
+        setCols(cols);
+    };
+
+    const FindNextXPositionForItem = useCallback(
+        (layoutItems: Layout[]) => {
+            if (layoutItems.length === 0) {
+                return 0;
+            }
+            let nextXPosition = 0;
+            let lineItems = new Map<number, Layout[]>();
+            layoutItems.forEach((item) => {
+                if (!lineItems.get(item.y)) {
+                    lineItems.set(item.y, [item]);
+                } else {
+                    lineItems.get(item.y)?.push(item);
+                }
+            });
+            for (let [, items] of lineItems) {
+                const itemWithMaxX = items.sort((a, b) => a.x - b.x).slice(-1)[0];
+                const sum = items.reduce((acc, item) => acc + item.w, 0);
+                if (sum + DEFAULT_WIDTH <= cols) {
+                    // then we have the space somewhere
+                    nextXPosition = itemWithMaxX.x + itemWithMaxX.w;
+                    break;
+                }
+            }
+            return nextXPosition;
+        },
+        [cols]
+    );
+
+    const onAddDiagram = (diagram: Diagram) => {
+        setLayouts((old_layouts) => {
+            const new_lg_layouts = old_layouts.lg.filter((layout) => layout.i !== 'Adder');
+            const layoutItem: Layout = {
+                i: diagram.diagramUuid,
+                x: FindNextXPositionForItem(new_lg_layouts),
+                y: Infinity,
+                w: DEFAULT_WIDTH,
+                h: DEFAULT_HEIGHT,
+                minH: DEFAULT_HEIGHT,
+                minW: DEFAULT_WIDTH,
+            };
+            new_lg_layouts.push(layoutItem);
+            return { lg: new_lg_layouts };
+        });
+    };
+    const { diagrams, removeDiagram, createDiagram } = useDiagramModel({ diagramTypes: diagramTypes, onAddDiagram });
+
+    const onRemoveItem = useCallback(
+        (diagramUuid: UUID) => {
+            setLayouts((old_layouts) => {
+                const new_lg_layouts = old_layouts.lg.filter((layout: Layout) => layout.i !== diagramUuid);
+                if (new_lg_layouts.length === 0) {
+                    return initialLayouts;
+                }
+                return { lg: new_lg_layouts };
+            });
+            removeDiagram(diagramUuid);
+        },
+        [removeDiagram]
+    );
+
+    const showVoltageLevelDiagram = useCallback(
+        (element: EquipmentInfos) => {
+            if (element.type === EquipmentType.VOLTAGE_LEVEL) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.VOLTAGE_LEVEL,
+                    voltageLevelId: element.voltageLevelId ?? '',
+                };
+                createDiagram(diagram);
+            } else if (element.type === EquipmentType.SUBSTATION) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.SUBSTATION,
+                    substationId: element.id,
+                };
+                createDiagram(diagram);
+            }
+        },
+        [createDiagram]
+    );
+    const renderDiagramAdder = useCallback(() => {
+        if (Object.values(diagrams).length > 0) {
+            return;
+        }
+        return (
+            <div key={'Adder'} style={{ display: 'flex', flexDirection: 'column' }}>
+                <Box sx={styles.header}>
+                    <OverflowableText
+                        className="react-grid-dragHandle"
+                        sx={{ flexGrow: '1' }}
+                        text={'Add a new diagram'}
+                    />
+                </Box>
+                <Box
+                    sx={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        flexGrow: 1,
+                        alignItems: 'center',
+                        justifyContent: 'center',
+                    }}
+                >
+                    <IconButton
+                        onClick={(e) => {
+                            setIsDialogSearchOpen(true);
+                        }}
+                    >
+                        <LibraryAddOutlinedIcon />
+                    </IconButton>
+                </Box>
+            </div>
+        );
+    }, [diagrams]);
+
+    // This function is called by the diagram's contents, when they get their sizes from the backend.
+    const setDiagramSize = useCallback((diagramId: UUID, diagramType: DiagramType, width: number, height: number) => {
+        console.log('TODO setDiagramSize', diagramId, diagramType, width, height);
+        // TODO adapt the layout w and h cnsidering those values
+    }, []);
+
+    const renderDiagrams = useCallback(() => {
+        if (Object.values(diagrams).length === 0) {
+            return;
+        }
+        return Object.values(diagrams).map((diagram) => {
+            if (!diagram) {
+                return null;
+            }
+            return (
+                <div
+                    key={diagram.diagramUuid}
+                    style={{ display: 'flex', flexDirection: 'column', backgroundColor: 'lightblue' }}
+                >
path: src/components/diagrams/diagram-layout.tsx
user: login: EstherDarkish
body: Shouldn't we use `<Box>` instead of `<div>` ?
created_at: 2025-05-22T07:35:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,19 +72,24 @@ export const useDiagramModel = ({ diagramTypes }: UseDiagramModelProps) => {
         [diagramTypes]
     );
 
-    const createPendingDiagram = useCallback((diagramParams: DiagramParams) => {
-        const pendingDiagram: Diagram = {
-            ...diagramParams,
-            diagramUuid: v4() as UUID,
-            svg: null,
-        };
-        setDiagrams((diagrams) => {
-            const newDiagrams = { ...diagrams };
-            newDiagrams[pendingDiagram.diagramUuid] = pendingDiagram;
-            return newDiagrams;
-        });
-        return pendingDiagram;
-    }, []);
+    const createPendingDiagram = useCallback(
+        (diagramParams: DiagramParams) => {
+            const pendingDiagram: Diagram = {
+                ...diagramParams,
+                diagramUuid: v4() as UUID,
+                name: `loading...`,
path: src/components/diagrams/hooks/use-diagram-model.ts
user: login: EstherDarkish
body: Should be internationalized
created_at: 2025-05-22T07:37:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
path: src/components/diagrams/diagram-layout.tsx
user: login: sBouzols
body: removed
created_at: 2025-05-22T08:40:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+    const onBreakpointChange = (newBreakpoint: string, cols: number) => {
+        setCols(cols);
+    };
+
+    const FindNextXPositionForItem = useCallback(
path: src/components/diagrams/diagram-layout.tsx
user: login: sBouzols
body: remove, restore default placement, will see later if needed
created_at: 2025-05-22T08:41:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -197,7 +197,6 @@ export function getCommonEquipmentType(equipmentType: EquipmentType): EquipmentT
 
 export interface SldAdditionalMetadata {
     id: string;
-    country: string;
path: src/components/diagrams/diagram-common.ts
user: login: sBouzols
body: I didn't find any case where there is a country in the metadata, but maybe it's a mistake ?
created_at: 2025-05-22T08:43:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,19 +72,24 @@ export const useDiagramModel = ({ diagramTypes }: UseDiagramModelProps) => {
         [diagramTypes]
     );
 
-    const createPendingDiagram = useCallback((diagramParams: DiagramParams) => {
-        const pendingDiagram: Diagram = {
-            ...diagramParams,
-            diagramUuid: v4() as UUID,
-            svg: null,
-        };
-        setDiagrams((diagrams) => {
-            const newDiagrams = { ...diagrams };
-            newDiagrams[pendingDiagram.diagramUuid] = pendingDiagram;
-            return newDiagrams;
-        });
-        return pendingDiagram;
-    }, []);
+    const createPendingDiagram = useCallback(
+        (diagramParams: DiagramParams) => {
+            const pendingDiagram: Diagram = {
+                ...diagramParams,
+                diagramUuid: v4() as UUID,
+                name: `loading...`,
path: src/components/diagrams/hooks/use-diagram-model.ts
user: login: sBouzols
body: done
created_at: 2025-05-22T09:10:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -197,7 +197,6 @@ export function getCommonEquipmentType(equipmentType: EquipmentType): EquipmentT
 
 export interface SldAdditionalMetadata {
     id: string;
-    country: string;
path: src/components/diagrams/diagram-common.ts
user: login: sBouzols
body: ok it's present in some cases
created_at: 2025-05-22T09:16:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    itemContainer: (theme: Theme) => ({
+        display: 'flex',
+        flexDirection: 'column',
+        backgroundColor: 'lightblue',
+    }),
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // RGL will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: DiagramLayoutProps) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [cols, setCols] = useState<number>(4);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+    const onBreakpointChange = (newBreakpoint: string, cols: number) => {
+        setCols(cols);
+    };
+
+    const FindNextXPositionForItem = useCallback(
+        (layoutItems: Layout[]) => {
+            if (layoutItems.length === 0) {
+                return 0;
+            }
+            let nextXPosition = 0;
+            let lineItems = new Map<number, Layout[]>();
+            layoutItems.forEach((item) => {
+                if (!lineItems.get(item.y)) {
+                    lineItems.set(item.y, [item]);
+                } else {
+                    lineItems.get(item.y)?.push(item);
+                }
+            });
+            for (let [, items] of lineItems) {
+                const itemWithMaxX = items.sort((a, b) => a.x - b.x).slice(-1)[0];
+                const sum = items.reduce((acc, item) => acc + item.w, 0);
+                if (sum + DEFAULT_WIDTH <= cols) {
+                    // then we have the space somewhere
+                    nextXPosition = itemWithMaxX.x + itemWithMaxX.w;
+                    break;
+                }
+            }
+            return nextXPosition;
+        },
+        [cols]
+    );
+
+    const onAddDiagram = (diagram: Diagram) => {
+        setLayouts((old_layouts) => {
+            const new_lg_layouts = old_layouts.lg.filter((layout) => layout.i !== 'Adder');
+            const layoutItem: Layout = {
+                i: diagram.diagramUuid,
+                x: FindNextXPositionForItem(new_lg_layouts),
+                y: Infinity,
+                w: DEFAULT_WIDTH,
+                h: DEFAULT_HEIGHT,
+                minH: DEFAULT_HEIGHT,
+                minW: DEFAULT_WIDTH,
+            };
+            new_lg_layouts.push(layoutItem);
+            return { lg: new_lg_layouts };
+        });
+    };
+    const { diagrams, removeDiagram, createDiagram } = useDiagramModel({ diagramTypes: diagramTypes, onAddDiagram });
+
+    const onRemoveItem = useCallback(
+        (diagramUuid: UUID) => {
+            setLayouts((old_layouts) => {
+                const new_lg_layouts = old_layouts.lg.filter((layout: Layout) => layout.i !== diagramUuid);
+                if (new_lg_layouts.length === 0) {
+                    return initialLayouts;
+                }
+                return { lg: new_lg_layouts };
+            });
+            removeDiagram(diagramUuid);
+        },
+        [removeDiagram]
+    );
+
+    const showVoltageLevelDiagram = useCallback(
+        (element: EquipmentInfos) => {
+            if (element.type === EquipmentType.VOLTAGE_LEVEL) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.VOLTAGE_LEVEL,
+                    voltageLevelId: element.voltageLevelId ?? '',
+                };
+                createDiagram(diagram);
+            } else if (element.type === EquipmentType.SUBSTATION) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.SUBSTATION,
+                    substationId: element.id,
+                };
+                createDiagram(diagram);
+            }
+        },
+        [createDiagram]
+    );
+    const renderDiagramAdder = useCallback(() => {
+        if (Object.values(diagrams).length > 0) {
+            return;
+        }
+        return (
+            <div key={'Adder'} style={{ display: 'flex', flexDirection: 'column' }}>
+                <Box sx={styles.header}>
+                    <OverflowableText
+                        className="react-grid-dragHandle"
+                        sx={{ flexGrow: '1' }}
+                        text={'Add a new diagram'}
+                    />
+                </Box>
+                <Box
+                    sx={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        flexGrow: 1,
+                        alignItems: 'center',
+                        justifyContent: 'center',
+                    }}
+                >
+                    <IconButton
+                        onClick={(e) => {
+                            setIsDialogSearchOpen(true);
+                        }}
+                    >
+                        <LibraryAddOutlinedIcon />
+                    </IconButton>
+                </Box>
+            </div>
+        );
+    }, [diagrams]);
+
+    // This function is called by the diagram's contents, when they get their sizes from the backend.
+    const setDiagramSize = useCallback((diagramId: UUID, diagramType: DiagramType, width: number, height: number) => {
+        console.log('TODO setDiagramSize', diagramId, diagramType, width, height);
+        // TODO adapt the layout w and h cnsidering those values
+    }, []);
+
+    const renderDiagrams = useCallback(() => {
+        if (Object.values(diagrams).length === 0) {
+            return;
+        }
+        return Object.values(diagrams).map((diagram) => {
+            if (!diagram) {
+                return null;
+            }
+            return (
+                <div
+                    key={diagram.diagramUuid}
+                    style={{ display: 'flex', flexDirection: 'column', backgroundColor: 'lightblue' }}
+                >
path: src/components/diagrams/diagram-layout.tsx
user: login: sBouzols
body: true
created_at: 2025-05-22T09:20:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+/**
+ * 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 { useCallback, useState } from 'react';
+import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
+import { useDiagramModel } from './hooks/use-diagram-model';
+import { Diagram, DiagramParams, DiagramType } from './diagram.type';
+import { Box, darken, IconButton, Theme, useTheme } from '@mui/material';
+import { EquipmentInfos, EquipmentType, OverflowableText } from '@gridsuite/commons-ui';
+import CloseIcon from '@mui/icons-material/Close';
+import LibraryAddOutlinedIcon from '@mui/icons-material/LibraryAddOutlined';
+import { UUID } from 'crypto';
+import { TopBarEquipmentSearchDialog } from 'components/top-bar-equipment-seach-dialog/top-bar-equipment-search-dialog';
+import SingleLineDiagramContent from './singleLineDiagram/single-line-diagram-content';
+import NetworkAreaDiagramContent from './networkAreaDiagram/network-area-diagram-content';
+import { DiagramMetadata, SLDMetadata } from '@powsybl/network-viewer';
+import { DiagramAdditionalMetadata } from './diagram-common';
+const ResponsiveGridLayout = WidthProvider(Responsive);
+
+// Diagram types to manage here
+const diagramTypes = [
+    DiagramType.VOLTAGE_LEVEL,
+    DiagramType.SUBSTATION,
+    DiagramType.NETWORK_AREA_DIAGRAM,
+    DiagramType.NAD_FROM_CONFIG,
+];
+
+const styles = {
+    window: {
+        display: 'flex',
+        flexDirection: 'column',
+    },
+    diagramContainer: (theme: Theme) => ({
+        flexGrow: 1,
+        overflow: 'hidden',
+        position: 'relative',
+        backgroundColor:
+            theme.palette.mode === 'light'
+                ? theme.palette.background.paper
+                : theme.networkModificationPanel.backgroundColor,
+    }),
+    header: (theme: Theme) => ({
+        padding: theme.spacing(0.5),
+        display: 'flex',
+        alignItems: 'center',
+        backgroundColor: theme.palette.background.default,
+        borderBottom: 'solid 1px',
+        borderBottomColor: theme.palette.mode === 'light' ? theme.palette.action.selected : 'transparent',
+    }),
+};
+
+const DEFAULT_WIDTH = 1;
+const DEFAULT_HEIGHT = 2;
+
+const initialLayouts = {
+    // ResponsiveGridLayout will attempt to interpolate the rest of breakpoints based on this one
+    lg: [
+        {
+            i: 'Adder',
+            x: 0,
+            y: 0,
+            w: 1,
+            h: 1,
+        },
+    ],
+};
+
+interface DiagramLayoutProps {
+    studyUuid: UUID;
+    showInSpreadsheet: (equipment: { equipmentId: string | null; equipmentType: EquipmentType | null }) => void;
+    visible: boolean;
+}
+
+function DiagramLayout({ studyUuid, showInSpreadsheet, visible }: Readonly<DiagramLayoutProps>) {
+    const theme = useTheme();
+    const [layouts, setLayouts] = useState<Layouts>(initialLayouts);
+    const [isDialogSearchOpen, setIsDialogSearchOpen] = useState(false);
+
+    const onAddDiagram = (diagram: Diagram) => {
+        setLayouts((old_layouts) => {
+            const new_lg_layouts = old_layouts.lg.filter((layout) => layout.i !== 'Adder');
+            const layoutItem: Layout = {
+                i: diagram.diagramUuid,
+                x: Infinity,
+                y: 0,
+                w: DEFAULT_WIDTH,
+                h: DEFAULT_HEIGHT,
+                minH: DEFAULT_HEIGHT,
+                minW: DEFAULT_WIDTH,
+            };
+            new_lg_layouts.push(layoutItem);
+            return { lg: new_lg_layouts };
+        });
+    };
+    const { diagrams, removeDiagram, createDiagram } = useDiagramModel({ diagramTypes: diagramTypes, onAddDiagram });
+
+    const onRemoveItem = useCallback(
+        (diagramUuid: UUID) => {
+            setLayouts((old_layouts) => {
+                const new_lg_layouts = old_layouts.lg.filter((layout: Layout) => layout.i !== diagramUuid);
+                if (new_lg_layouts.length === 0) {
+                    return initialLayouts;
+                }
+                return { lg: new_lg_layouts };
+            });
+            removeDiagram(diagramUuid);
+        },
+        [removeDiagram]
+    );
+
+    const showVoltageLevelDiagram = useCallback(
+        (element: EquipmentInfos) => {
+            if (element.type === EquipmentType.VOLTAGE_LEVEL) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.VOLTAGE_LEVEL,
+                    voltageLevelId: element.voltageLevelId ?? '',
+                };
+                createDiagram(diagram);
+            } else if (element.type === EquipmentType.SUBSTATION) {
+                const diagram: DiagramParams = {
+                    type: DiagramType.SUBSTATION,
+                    substationId: element.id,
+                };
+                createDiagram(diagram);
+            }
+        },
+        [createDiagram]
+    );
+    const renderDiagramAdder = useCallback(() => {
+        if (Object.values(diagrams).length > 0) {
+            return;
+        }
+        return (
+            <div key={'Adder'} style={{ display: 'flex', flexDirection: 'column' }}>
+                <Box sx={styles.header}>
+                    <OverflowableText
+                        className="react-grid-dragHandle"
+                        sx={{ flexGrow: '1' }}
+                        text={'Add a new diagram'}
+                    />
+                </Box>
+                <Box
+                    sx={{
+                        display: 'flex',
+                        flexDirection: 'column',
+                        flexGrow: 1,
+                        alignItems: 'center',
+                        justifyContent: 'center',
+                    }}
+                >
+                    <IconButton
+                        onClick={(e) => {
+                            setIsDialogSearchOpen(true);
+                        }}
+                    >
+                        <LibraryAddOutlinedIcon />
+                    </IconButton>
+                </Box>
+            </div>
+        );
+    }, [diagrams]);
+
+    // This function is called by the diagram's contents, when they get their sizes from the backend.
+    const setDiagramSize = useCallback((diagramId: UUID, diagramType: DiagramType, width: number, height: number) => {
+        console.log('TODO setDiagramSize', diagramId, diagramType, width, height);
+        // TODO adapt the layout w and h cnsidering those values
+    }, []);
+
+    const renderDiagrams = useCallback(() => {
+        if (Object.values(diagrams).length === 0) {
+            return;
+        }
+        return Object.values(diagrams).map((diagram) => {
+            if (!diagram) {
+                return null;
+            }
+            return (
+                <Box key={diagram.diagramUuid} sx={styles.window}>
+                    <Box sx={styles.header}>
+                        <OverflowableText
+                            className="react-grid-dragHandle"
+                            sx={{ flexGrow: '1' }}
+                            text={diagram.name}
+                        />
+                        <IconButton
+                            size={'small'}
+                            onClick={(e) => {
+                                onRemoveItem(diagram.diagramUuid);
+                                e.stopPropagation();
+                            }}
+                        >
+                            <CloseIcon fontSize="small" />
+                        </IconButton>
+                    </Box>
+                    <Box sx={styles.diagramContainer}>
+                        {(diagram.type === DiagramType.VOLTAGE_LEVEL || diagram.type === DiagramType.SUBSTATION) && (
+                            <SingleLineDiagramContent
+                                showInSpreadsheet={showInSpreadsheet}
+                                studyUuid={studyUuid}
+                                diagramId={diagram.diagramUuid}
+                                svg={diagram.svg?.svg ?? undefined}
+                                svgType={diagram.type}
+                                svgMetadata={(diagram.svg?.metadata as SLDMetadata) ?? undefined}
+                                loadingState={false} // TODO
+                                diagramSizeSetter={setDiagramSize}
+                                visible={visible}
+                            />
+                        )}
+                        {(diagram.type === DiagramType.NETWORK_AREA_DIAGRAM ||
+                            diagram.type === DiagramType.NAD_FROM_CONFIG) && (
+                            <NetworkAreaDiagramContent
+                                diagramId={diagram.diagramUuid}
+                                svg={diagram.svg?.svg ?? undefined}
+                                svgType={diagram.type}
+                                svgMetadata={(diagram.svg?.metadata as DiagramMetadata) ?? undefined}
+                                svgScalingFactor={
+                                    (diagram.svg?.additionalMetadata as DiagramAdditionalMetadata)?.scalingFactor ??
+                                    undefined
+                                }
+                                svgVoltageLevels={
+                                    (diagram.svg?.additionalMetadata as DiagramAdditionalMetadata)?.voltageLevels
+                                        .map((vl) => vl.id)
+                                        .filter((vlId) => vlId !== undefined) as string[]
+                                }
+                                loadingState={false} // TODO
+                                diagramSizeSetter={setDiagramSize}
+                                visible={visible}
+                            />
+                        )}
+                    </Box>
+                </Box>
+            );
+        });
+    }, [diagrams, onRemoveItem, setDiagramSize, showInSpreadsheet, studyUuid, visible]);
+
+    return (
+        <>
+            <ResponsiveGridLayout
path: src/components/diagrams/diagram-layout.tsx
user: login: sBouzols
body: rename component `DiagramGridLayout`
created_at: 2025-05-22T12:50:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-23T08:20:16Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=gridsuite_gridstudy-app&pullRequest=2872) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8 New issues](https://sonarcloud.io/project/issues?id=gridsuite_gridstudy-app&pullRequest=2872&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=gridsuite_gridstudy-app&pullRequest=2872&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=gridsuite_gridstudy-app&pullRequest=2872&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=gridsuite_gridstudy-app&pullRequest=2872&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=gridsuite_gridstudy-app&pullRequest=2872&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=gridsuite_gridstudy-app&pullRequest=2872)


author_association: NONE