From 884ae7ee39d29d27d7479c1465d6978e69c068dd Mon Sep 17 00:00:00 2001
From: rbisson <remi.bisson@inrae.fr>
Date: Tue, 17 Dec 2024 16:03:27 +0100
Subject: [PATCH 1/5] [ResultsTableMUI] Corrected bug causing rows to deselect
 on tab change

---
 src/pages/results/ResultsTableMUI.js | 139 ++++++++++++---------------
 1 file changed, 61 insertions(+), 78 deletions(-)

diff --git a/src/pages/results/ResultsTableMUI.js b/src/pages/results/ResultsTableMUI.js
index e3e8887..a918a9d 100644
--- a/src/pages/results/ResultsTableMUI.js
+++ b/src/pages/results/ResultsTableMUI.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
 import { Trans, useTranslation } from 'react-i18next';
 import MUIDataTable from 'mui-datatables';
 import { createTheme, ThemeProvider } from '@mui/material';
@@ -30,113 +30,94 @@ const ResultsTableMUI = ({
   setResourceFlyoutData,
 }) => {
   const { t } = useTranslation('results');
-  const [selectedRows, setSelectedRows] = useState([]);
   const [publicFields, setPublicFields] = useState([]);
-  const [rows, setRows] = useState([]);
-  const [columns, setColumns] = useState([]);
+  const [userFieldsIds, setUserFieldsIds] = useState([]);
   const [isLoading, setIsLoading] = useState(true);
   const [rowsPerPage, setRowsPerPage] = useState(15);
 
-  // On page load, check table rows from selected resources from map
-  useEffect(() => {
-    setSelectedRows(selectedRowsIds.map((id) => getRowIdFromResourceData(id)));
-  }, [selectedRowsIds]);
-
-  // On search results change, fetch all public fields
+  // Fetch public fields and user display settings
   useEffect(() => {
-    fetchPublicFields().then((publicFieldsResults) => {
+    const fetchData = async () => {
+      setIsLoading(true);
+      const publicFieldsResults = await fetchPublicFields();
       setPublicFields(publicFieldsResults);
-    });
-  }, [searchResults]);
-
-  // Build table data (columns and rows)
-  useEffect(() => {
-    if (!searchResults && searchResults.length > 0) {
-      return;
-    }
-    getStdFieldsIds().then((stdFieldsIds) => {
-      const columns = buildColumns(stdFieldsIds);
-      setColumns(columns);
-      setRows(buildRows(searchResults, columns));
-      setIsLoading(false);
-    });
-  }, [publicFields]);
-
-  // Get fields ids settings from user
-  const getStdFieldsIds = async () => {
-    const stdFieldsIds = await fetchUserFieldsDisplaySettings(
-      sessionStorage.getItem('userId')
-    );
-    if (!stdFieldsIds) {
-      const defaultFieldsIds = [1];
+      const userStdFieldsIds = await fetchUserFieldsDisplaySettings(
+        sessionStorage.getItem('userId')
+      );
       // TODO replace hard-coded array by gatekeeper fetch on default settings
-      return defaultFieldsIds;
-    }
-    return stdFieldsIds;
-  };
-
-  // Returns value from JSON obj associated to key string.
-  const getValueByPath = (obj, path) => {
-    return path.split('.').reduce((acc, key) => acc && acc[key], obj);
-  };
+      // If no userStdFields, use system default ones.
+      setUserFieldsIds(userStdFieldsIds || [1]);
+    };
+    fetchData();
+  }, [searchResults]);
 
-  // Build each row in table from search results data
-  const buildRows = (results, columns) => {
-    let dataRows = [];
-    if (results.length === 0) {
-      return dataRows;
+  // Memoize columns
+  const columns = useMemo(() => {
+    if (publicFields.length === 0) {
+      return [];
     }
-    results.forEach((result) => {
-      let row = {
-        id: result.id,
-      };
-      columns.forEach((columnField) => {
-        const fieldValue = getValueByPath(result, columnField.name);
-        if (typeof fieldValue === 'string') {
-          row[columnField.name] = fieldValue;
-        } else if (typeof fieldValue === 'number') {
-          row[columnField.name] = fieldValue.toString();
-        }
-      });
-      dataRows.push(row);
-    });
-    return dataRows;
-  };
-
-  // Build table columns names (label and name)
-  const buildColumns = (stdFieldsIds) => {
-    let dataColumns = [];
-    dataColumns.push({
-      name: 'id',
-      label: 'ID',
-      options: {
-        display: 'excluded',
+    let dataColumns = [
+      {
+        name: 'id',
+        label: 'ID',
+        options: { display: 'excluded' },
       },
-    });
+    ];
     publicFields.forEach((publicField) => {
       dataColumns.push({
         name: publicField.field_name,
         label: buildFieldName(publicField.field_name),
-        options: {
-          display: stdFieldsIds.includes(publicField.id),
-        },
+        options: { display: userFieldsIds.includes(publicField.id) },
       });
     });
     return dataColumns;
+  }, [publicFields, userFieldsIds]);
+
+  // Returns value from JSON obj associated to key string.
+  const getValueByPath = (obj, path) => {
+    return path.split('.').reduce((acc, key) => acc && acc[key], obj);
   };
 
+  const rows = useMemo(() => {
+    const buildRows = (results, columns) => {
+      if (results.length === 0) {
+        return [];
+      }
+      return results.map((result) => {
+        let row = { id: result.id };
+        columns.forEach((column) => {
+          const value = getValueByPath(result, column.name);
+          row[column.name] = typeof value === 'string' ? value : value?.toString();
+        });
+        return row;
+      });
+    };
+    const rows = buildRows(searchResults, columns);
+    setIsLoading(false);
+    return searchResults && columns.length > 0 ? rows : [];
+  }, [searchResults, columns]);
+
   const getResourceDataFromRowId = (id) => {
     return searchResults.find((resource) => resource.id === id);
   };
 
   const getRowIdFromResourceData = (id) => {
+    if (!rows || rows.length === 0) {
+      return -1;
+    }
     for (let index = 0; index < rows.length; index++) {
       if (rows[index].id === id) {
         return index;
       }
     }
+    return -1;
   };
 
+  // On page load, check table rows from selected resources from map
+  const selectedRows = useMemo(() => {
+    return selectedRowsIds.map((id) => getRowIdFromResourceData(id));
+  }, [rows, selectedRowsIds]);
+
   // Add row to list of selected on checkbox click
   const onRowSelectionCallback = (selectedRow, allSelectedRows) => {
     setSelectedRowsIds(
@@ -218,9 +199,11 @@ const ResultsTableMUI = ({
     textLabels,
   };
 
+  const isTableReady = !isLoading && columns.length > 0 && rows.length > 0;
+
   return (
     <ThemeProvider theme={getMuiTheme()}>
-      {!isLoading && (
+      {isTableReady && (
         <MUIDataTable
           title={<Trans i18nKey={'results:table.title'} components={{ searchQuery }} />}
           data={rows}
-- 
GitLab


From 8489a403d67f603bf4f778d507cbba00f569f793 Mon Sep 17 00:00:00 2001
From: rbisson <remi.bisson@inrae.fr>
Date: Tue, 17 Dec 2024 16:10:26 +0100
Subject: [PATCH 2/5] [Profile] Removed unused imports

---
 src/pages/profile/Profile.js | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/pages/profile/Profile.js b/src/pages/profile/Profile.js
index 313212f..81c0d26 100644
--- a/src/pages/profile/Profile.js
+++ b/src/pages/profile/Profile.js
@@ -1,13 +1,6 @@
 import React, { useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import {
-  EuiPanel,
-  EuiTabbedContent,
-  EuiFlexGroup,
-  EuiCallOut,
-  EuiSpacer,
-  EuiFlexItem,
-} from '@elastic/eui';
+import { EuiPanel, EuiTabbedContent, EuiFlexGroup, EuiCallOut } from '@elastic/eui';
 import UserFieldsDisplaySettings from './UserFieldsDisplaySettings';
 import GroupSettings from './GroupSettings';
 import RoleSettings from './RoleSettings';
-- 
GitLab


From 2b832f36ba9aeb09d0814fd133cc0d4be89710e6 Mon Sep 17 00:00:00 2001
From: rbisson <remi.bisson@inrae.fr>
Date: Wed, 18 Dec 2024 16:20:34 +0100
Subject: [PATCH 3/5] [SearchMap] Added actions to selected resources list ;
 improved styling

---
 public/locales/en/maps.json |   8 +-
 public/locales/fr/maps.json |   8 +-
 src/pages/maps/SearchMap.js | 339 ++++++++++++++++++++++++------------
 src/pages/maps/styles.js    |  26 +--
 4 files changed, 244 insertions(+), 137 deletions(-)

diff --git a/public/locales/en/maps.json b/public/locales/en/maps.json
index 555595d..c71540d 100644
--- a/public/locales/en/maps.json
+++ b/public/locales/en/maps.json
@@ -22,7 +22,11 @@
     }
   },
   "selectedPointsList": {
-    "title": "Selected resources list",
-    "empty": "Select resources to display them here."
+    "title": "Selected resources",
+    "empty": "Select resources to display them here.",
+    "actions": {
+      "openResourceFlyout": "Open resource flyout",
+      "unselectResource": "Unselect resource"
+    }
   }
 }
diff --git a/public/locales/fr/maps.json b/public/locales/fr/maps.json
index fdbcd9b..51f7f8d 100644
--- a/public/locales/fr/maps.json
+++ b/public/locales/fr/maps.json
@@ -22,7 +22,11 @@
     }
   },
   "selectedPointsList": {
-    "title": "Liste des ressources sélectionnées",
-    "empty": "Sélectionnez des ressources pour les afficher ici"
+    "title": "Ressources sélectionnées",
+    "empty": "Sélectionnez des ressources pour les afficher ici",
+    "actions": {
+      "openResourceFlyout": "Ouvrir la fiche de la resource",
+      "unselectResource": "Désélectionner la resource"
+    }
   }
 }
diff --git a/src/pages/maps/SearchMap.js b/src/pages/maps/SearchMap.js
index 6d47330..310e72e 100644
--- a/src/pages/maps/SearchMap.js
+++ b/src/pages/maps/SearchMap.js
@@ -28,8 +28,12 @@ import {
   EuiComboBox,
   EuiProgress,
   EuiSpacer,
-  EuiText,
   EuiTitle,
+  EuiPanel,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiButtonIcon,
+  EuiToolTip,
   htmlIdGenerator,
 } from '@elastic/eui';
 import { updateArrayElement } from '../../Utils.js';
@@ -390,6 +394,10 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
     });
     pointFeature.set('id', pointData.id);
     pointFeature.set('nom', pointData.name);
+    // If point is a 'selected point', link its id to it.
+    if (pointData.linkedPointId) {
+      pointFeature.set('linkedPointId', pointData.linkedPointId);
+    }
     pointFeature.set('isSelected', isSelected);
     pointFeature.setStyle(pointStyle);
     return pointFeature;
@@ -448,6 +456,7 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
                   {
                     id: 0,
                     name: pointName,
+                    linkedPointId: pointId,
                   },
                   coords,
                   5000,
@@ -463,16 +472,13 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
         } else {
           const selectedPointsFeatures = selectedPointsSource.getFeatures();
           if (selectedPointsFeatures.length > 0) {
-            for (let point = 0; point < selectedPointsFeatures.length; point++) {
-              const pointGeom = selectedPointsFeatures[point].getGeometry();
+            for (let i = 0; i < selectedPointsFeatures.length; i++) {
+              const point = selectedPointsFeatures[i];
+              const pointGeom = point.getGeometry();
               const coords = pointGeom.getCenter();
               if (polygonGeometry.intersectsCoordinate(coords)) {
-                // Remove previously selected features
-                selectedPointsSource.removeFeature(selectedPointsFeatures[point]);
-                newSelectedPointsIds = newSelectedPointsIds.splice(
-                  newSelectedPointsIds.indexOf(selectedPointsFeatures[point].get('id')),
-                  1
-                );
+                const pointId = point.get('linkedPointId');
+                unselectPoint(pointId, selectedPointsSource);
               }
             }
           }
@@ -529,6 +535,7 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
               {
                 id: 0,
                 name: pointName,
+                linkedPointId: id,
               },
               proj.fromLonLat([geoPoint.longitude, geoPoint.latitude]),
               5000,
@@ -607,34 +614,127 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
     setMapLayers(updatedLayers);
   };
 
-  // Display selected points names
-  const SelectedPointsList = () => {
+  const getSelectedPoints = () => {
     const selectedPointsLayer = map
       .getLayers()
       .item(getLayerIndex('selectedPointsSource'));
-    let selectedPointsList = <></>;
-    let selectedPoints = 0;
-    if (selectedPointsLayer) {
-      selectedPoints = getSelectedPointsNames(selectedPointsLayer.getSource());
-      if (selectedPoints.length === 0) {
-        selectedPointsList = <p>{t('maps:selectedPointsList.empty')}</p>;
-      } else {
-        selectedPointsList = selectedPoints.map((pointName, index) => {
-          return <p key={index}>{pointName}</p>;
-        });
-      }
+    const selectedPointsSource = selectedPointsLayer.getSource();
+    if (!selectedPointsLayer || !selectedPointsSource) {
+      return [];
+    }
+    const features = selectedPointsSource.getFeatures();
+    if (!features) {
+      return [];
+    }
+    const points = [];
+    features.forEach((feature) => {
+      points.push(feature.getProperties());
+    });
+    return points;
+  };
+
+  // Unselect a single point. Remove it from map display and from selected points ids list.
+  const unselectPoint = (id, source) => {
+    let newSelectedPointsIds = selectedPointsIds;
+    if (!source) {
+      source = map.getLayers().item(getLayerIndex('selectedPointsSource')).getSource();
+    }
+    const selectedPointsFeatures = source.getFeatures();
+    if (!selectedPointsFeatures || selectedPointsFeatures.length === 0) {
+      return;
+    }
+    const featureToRemove = selectedPointsFeatures.find(
+      (feature) => feature.get('linkedPointId') === id
+    );
+    if (!featureToRemove) {
+      return;
+    }
+    source.removeFeature(featureToRemove);
+    const indexOfPointToRemove = newSelectedPointsIds.indexOf(id);
+    if (indexOfPointToRemove >= 0) {
+      newSelectedPointsIds = newSelectedPointsIds.toSpliced(indexOfPointToRemove, 1);
+    }
+    // Remove previously selected features
+    setSelectedPointsIds(newSelectedPointsIds);
+  };
+
+  // Display selected points names
+  const SelectedPointsList = () => {
+    const SelectedPointItem = ({ id, name }) => {
+      const onUncheckButtonClick = () => {
+        unselectPoint(id);
+      };
+
+      const onOpenFlyoutClick = () => {
+        // TODO call a function to open resource flyout, filled with correct data
+      };
+
+      return (
+        <EuiPanel paddingSize="s" hasShadow={false} hasBorder={true}>
+          <EuiFlexGroup alignItems={'center'}>
+            <EuiFlexGroup>
+              <p>{name}</p>
+            </EuiFlexGroup>
+            <EuiToolTip
+              position="top"
+              content={<p>{t('maps:selectedPointsList.actions.openResourceFlyout')}</p>}
+            >
+              <EuiButtonIcon
+                iconType="eye"
+                aria-label={t('maps:selectedPointsList.actions.openResourceFlyout')}
+                onClick={() => onOpenFlyoutClick()}
+                color={'primary'}
+                display={'base'}
+                size={'s'}
+              />
+            </EuiToolTip>
+            <EuiToolTip
+              position="top"
+              content={<p>{t('maps:selectedPointsList.actions.unselectResource')}</p>}
+            >
+              <EuiButtonIcon
+                iconType="cross"
+                aria-label={t('maps:selectedPointsList.actions.unselectResource')}
+                onClick={() => onUncheckButtonClick()}
+                color={'danger'}
+                size={'s'}
+              />
+            </EuiToolTip>
+            <EuiSpacer />
+          </EuiFlexGroup>
+        </EuiPanel>
+      );
+    };
+
+    const buildSelectedPointList = () => {
+      return selectedPoints.map((point, index) => {
+        return (
+          <SelectedPointItem key={index} name={point.nom} id={point.linkedPointId} />
+        );
+      });
+    };
+
+    let selectedPointsList;
+    let selectedPoints = getSelectedPoints();
+    if (selectedPoints.length === 0) {
+      selectedPointsList = <p>{t('maps:selectedPointsList.empty')}</p>;
+    } else {
+      selectedPointsList = buildSelectedPointList();
     }
 
     return (
-      <div style={styles.selectedPointsListContainer}>
-        <EuiTitle size="s">
-          <h3>
-            {t('maps:selectedPointsList.title')} ({selectedPoints.length})
-          </h3>
-        </EuiTitle>
-        <EuiSpacer size="m" />
-        <EuiText style={styles.selectedPointsList}>{selectedPointsList}</EuiText>
-      </div>
+      <EuiPanel hasShadow={false} hasBorder={true}>
+        <EuiFlexGroup direction={'column'}>
+          <EuiTitle size="s">
+            <p>
+              {t('maps:selectedPointsList.title')} ({selectedPoints.length})
+            </p>
+          </EuiTitle>
+          <EuiFlexGroup direction={'column'} style={styles.selectedPointsList}>
+            {selectedPointsList}
+          </EuiFlexGroup>
+        </EuiFlexGroup>
+      </EuiPanel>
     );
   };
 
@@ -657,90 +757,101 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
     setSelectedPointsIds([]);
   };
 
+  const MapTools = () => {
+    return (
+      <EuiPanel paddingSize="l" hasShadow={false} hasBorder={true}>
+        <EuiFlexGroup>
+          <table style={styles.layersTable}>
+            <thead>
+              <tr>
+                <th>{t('maps:layersTableHeaders.cartography')}</th>
+                <th>{t('maps:layersTableHeaders.filters')}</th>
+                <th>{t('maps:layersTableHeaders.tools')}</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr>
+                <td style={styles.layersTableCells}>
+                  <EuiCheckbox
+                    id={htmlIdGenerator()()}
+                    label={t('maps:layersTable.openStreetMap')}
+                    checked={mapLayersVisibility[getLayerIndex('osm-layer')]}
+                    onChange={(e) => setLayerDisplay('osm-layer', e.target.checked)}
+                  />
+                  <EuiCheckbox
+                    id={htmlIdGenerator()()}
+                    label={t('maps:layersTable.bingAerial')}
+                    checked={mapLayersVisibility[getLayerIndex('Bing Aerial')]}
+                    onChange={(e) => setLayerDisplay('Bing Aerial', e.target.checked)}
+                  />
+                  <EuiCheckbox
+                    id={htmlIdGenerator()()}
+                    label={t('maps:layersTable.IGN')}
+                    checked={mapLayersVisibility[getLayerIndex('IGN')]}
+                    onChange={(e) => setLayerDisplay('IGN', e.target.checked)}
+                  />
+                </td>
+                <td style={styles.layersTableCells}>
+                  <EuiCheckbox
+                    id={htmlIdGenerator()()}
+                    label={t('maps:layersTable.queryResults')}
+                    checked={mapLayersVisibility[getLayerIndex('queryResults')]}
+                    onChange={(e) => setLayerDisplay('queryResults', e.target.checked)}
+                  />
+                  <br />
+                  <EuiComboBox
+                    aria-label={t('maps:layersTable.selectFilterOption')}
+                    placeholder={t('maps:layersTable.selectFilterOption')}
+                    singleSelection={{ asPlainText: true }}
+                    options={filterOptions}
+                    selectedOptions={selectedFilterOptions}
+                    onChange={onFilterSelectChange}
+                    styles={styles.filtersSelect}
+                  />
+                </td>
+                <td style={styles.layersTableCells}>
+                  <EuiTitle size="xxs">
+                    <h6>{t('maps:layersTable.selectionTool.title')}</h6>
+                  </EuiTitle>
+                  <EuiButtonGroup
+                    legend={t('maps:layersTable.selectionTool.title')}
+                    options={selectionToolOptions}
+                    onChange={toggleSelectionToolMode}
+                    idSelected={
+                      selectionToolMode
+                        ? 'selectionToolButton__1'
+                        : 'selectionToolButton__0'
+                    }
+                    color={'primary'}
+                    isFullWidth
+                  />
+                  <EuiSpacer size="s" />
+                  <EuiButton
+                    onClick={() => unselectPoints()}
+                    style={styles.unselectAllButton}
+                  >
+                    {t('maps:layersTable.selectionTool.unselectAll')}
+                  </EuiButton>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </EuiFlexGroup>
+      </EuiPanel>
+    );
+  };
+
   return (
-    <>
-      <div style={styles.container}>
-        <div id="map" style={styles.mapContainer}></div>
-        <SelectedPointsList />
-      </div>
-      <div style={styles.loadingContainer}>
-        {isLoading && <EuiProgress size="l" color="accent" />}
-      </div>
-      <EuiSpacer size={'m'} />
-      <table style={styles.layersTable}>
-        <thead>
-          <tr>
-            <th>{t('maps:layersTableHeaders.cartography')}</th>
-            <th>{t('maps:layersTableHeaders.filters')}</th>
-            <th>{t('maps:layersTableHeaders.tools')}</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr>
-            <td style={styles.layersTableCells}>
-              <EuiCheckbox
-                id={htmlIdGenerator()()}
-                label={t('maps:layersTable.openStreetMap')}
-                checked={mapLayersVisibility[getLayerIndex('osm-layer')]}
-                onChange={(e) => setLayerDisplay('osm-layer', e.target.checked)}
-              />
-              <EuiCheckbox
-                id={htmlIdGenerator()()}
-                label={t('maps:layersTable.bingAerial')}
-                checked={mapLayersVisibility[getLayerIndex('Bing Aerial')]}
-                onChange={(e) => setLayerDisplay('Bing Aerial', e.target.checked)}
-              />
-              <EuiCheckbox
-                id={htmlIdGenerator()()}
-                label={t('maps:layersTable.IGN')}
-                checked={mapLayersVisibility[getLayerIndex('IGN')]}
-                onChange={(e) => setLayerDisplay('IGN', e.target.checked)}
-              />
-            </td>
-            <td style={styles.layersTableCells}>
-              <EuiCheckbox
-                id={htmlIdGenerator()()}
-                label={t('maps:layersTable.queryResults')}
-                checked={mapLayersVisibility[getLayerIndex('queryResults')]}
-                onChange={(e) => setLayerDisplay('queryResults', e.target.checked)}
-              />
-              <br />
-              <EuiComboBox
-                aria-label={t('maps:layersTable.selectFilterOption')}
-                placeholder={t('maps:layersTable.selectFilterOption')}
-                singleSelection={{ asPlainText: true }}
-                options={filterOptions}
-                selectedOptions={selectedFilterOptions}
-                onChange={onFilterSelectChange}
-                styles={styles.filtersSelect}
-              />
-            </td>
-            <td style={styles.layersTableCells}>
-              <EuiTitle size="xxs">
-                <h6>{t('maps:layersTable.selectionTool.title')}</h6>
-              </EuiTitle>
-              <EuiButtonGroup
-                legend={t('maps:layersTable.selectionTool.title')}
-                options={selectionToolOptions}
-                onChange={toggleSelectionToolMode}
-                idSelected={
-                  selectionToolMode ? 'selectionToolButton__1' : 'selectionToolButton__0'
-                }
-                color={'primary'}
-                isFullWidth
-              />
-              <EuiSpacer size="s" />
-              <EuiButton
-                onClick={() => unselectPoints()}
-                style={styles.unselectAllButton}
-              >
-                {t('maps:layersTable.selectionTool.unselectAll')}
-              </EuiButton>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </>
+    <EuiFlexGroup>
+      <EuiFlexGroup direction={'column'} style={styles.container}>
+        <EuiFlexItem>
+          <div id="map" style={styles.mapContainer}></div>
+          {isLoading && <EuiProgress size="l" color="accent" />}
+        </EuiFlexItem>
+        <MapTools />
+      </EuiFlexGroup>
+      <SelectedPointsList />
+    </EuiFlexGroup>
   );
 };
 
diff --git a/src/pages/maps/styles.js b/src/pages/maps/styles.js
index f1a31a2..edf2829 100644
--- a/src/pages/maps/styles.js
+++ b/src/pages/maps/styles.js
@@ -21,32 +21,20 @@ const styles = {
     }),
   }),
   container: {
-    width: '100%',
-    display: 'flex',
-    justifyContent: 'start',
-    minHeight: '60vh',
-    maxHeight: '60vh',
+    minWidth: '70vw',
+    maxWidth: '70vw',
   },
   mapContainer: {
-    minWidth: '60vw',
-    minHeight: '100%',
-  },
-  loadingContainer: {
-    maxWidth: '60vw',
-  },
-  selectedPointsListContainer: {
-    display: 'flex',
-    flexDirection: 'column',
-    width: '100%',
-    textAlign: 'center',
-    padding: '10px',
+    minWidth: '70vw',
+    minHeight: '58vh',
+    maxHeight: '58vh',
   },
   selectedPointsList: {
     overflow: 'auto',
-    maxHeight: '100%',
+    maxHeight: '72vh',
   },
   layersTable: {
-    width: '60vw',
+    width: '70vw',
     cursor: 'pointer',
     marginTop: '10px',
   },
-- 
GitLab


From ee4a25ae3af8fea19eefc5bdbb94d704c9cd0c54 Mon Sep 17 00:00:00 2001
From: rbisson <remi.bisson@inrae.fr>
Date: Wed, 18 Dec 2024 16:31:33 +0100
Subject: [PATCH 4/5] [Search] now emptying selected points array after a new
 search

---
 src/pages/search/Search.js | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/pages/search/Search.js b/src/pages/search/Search.js
index 61d1be4..c892f8d 100644
--- a/src/pages/search/Search.js
+++ b/src/pages/search/Search.js
@@ -59,6 +59,11 @@ const Search = () => {
     });
   }, []);
 
+  // On new search, reset selected rows
+  useEffect(() => {
+    setSelectedResultsRowsIds([]);
+  }, [searchResults]);
+
   const tabsContent = [
     {
       id: 'tab1',
-- 
GitLab


From bf13f9c639029738fe1aaf368d88e8f44c89be0d Mon Sep 17 00:00:00 2001
From: rbisson <remi.bisson@inrae.fr>
Date: Wed, 18 Dec 2024 16:50:08 +0100
Subject: [PATCH 5/5] [Search] now flyout is in parent component and can be
 opened from id [SearchMap] eye button opens flyout now

---
 src/pages/maps/SearchMap.js          | 11 ++++++--
 src/pages/results/Results.js         | 22 +++++++--------
 src/pages/results/ResultsTableMUI.js | 10 ++-----
 src/pages/search/Search.js           | 41 +++++++++++++++++++++++-----
 4 files changed, 55 insertions(+), 29 deletions(-)

diff --git a/src/pages/maps/SearchMap.js b/src/pages/maps/SearchMap.js
index 310e72e..7a3a913 100644
--- a/src/pages/maps/SearchMap.js
+++ b/src/pages/maps/SearchMap.js
@@ -92,7 +92,13 @@ const pointBaseStyle = new Style({
   }),
 });
 
-const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) => {
+const SearchMap = ({
+  searchResults,
+  selectedPointsIds,
+  setSelectedPointsIds,
+  setResourceFlyoutDataFromId,
+  setIsResourceFlyoutOpen,
+}) => {
   const { t } = useTranslation('maps');
   // ref for handling zoomHelperText display
   const timerRef = useRef(null);
@@ -666,7 +672,8 @@ const SearchMap = ({ searchResults, selectedPointsIds, setSelectedPointsIds }) =
       };
 
       const onOpenFlyoutClick = () => {
-        // TODO call a function to open resource flyout, filled with correct data
+        setResourceFlyoutDataFromId(id);
+        setIsResourceFlyoutOpen(true);
       };
 
       return (
diff --git a/src/pages/results/Results.js b/src/pages/results/Results.js
index 6d274a1..5ebb6f4 100644
--- a/src/pages/results/Results.js
+++ b/src/pages/results/Results.js
@@ -1,23 +1,21 @@
-import React, { useState } from 'react';
+import React from 'react';
 import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
 import { useTranslation } from 'react-i18next';
 import ResultsTableMUI from './ResultsTableMUI';
-import ResourceFlyout from './ResourceFlyout/ResourceFlyout';
 import ResultsDownload from './ResultsDownload';
 
-const Results = ({ searchResults, searchQuery, selectedRowsIds, setSelectedRowsIds }) => {
+const Results = ({
+  searchResults,
+  searchQuery,
+  selectedRowsIds,
+  setSelectedRowsIds,
+  setResourceFlyoutDataFromId,
+  setIsResourceFlyoutOpen,
+}) => {
   const { t } = useTranslation('results');
-  const [isResourceFlyoutOpen, setIsResourceFlyoutOpen] = useState(false);
-  const [resourceFlyoutData, setResourceFlyoutData] = useState({});
 
   return (
     <>
-      <ResourceFlyout
-        resourceFlyoutData={resourceFlyoutData}
-        setResourceFlyoutData={setResourceFlyoutData}
-        isResourceFlyoutOpen={isResourceFlyoutOpen}
-        setIsResourceFlyoutOpen={setIsResourceFlyoutOpen}
-      />
       <EuiFlexGroup>
         <EuiFlexItem>
           <EuiCallOut size="s" title={t('results:clickOnRowTip')} iconType="search" />
@@ -36,7 +34,7 @@ const Results = ({ searchResults, searchQuery, selectedRowsIds, setSelectedRowsI
         searchQuery={searchQuery}
         selectedRowsIds={selectedRowsIds}
         setSelectedRowsIds={setSelectedRowsIds}
-        setResourceFlyoutData={setResourceFlyoutData}
+        setResourceFlyoutDataFromId={setResourceFlyoutDataFromId}
         setIsResourceFlyoutOpen={setIsResourceFlyoutOpen}
       />
     </>
diff --git a/src/pages/results/ResultsTableMUI.js b/src/pages/results/ResultsTableMUI.js
index a918a9d..f3eb2c7 100644
--- a/src/pages/results/ResultsTableMUI.js
+++ b/src/pages/results/ResultsTableMUI.js
@@ -27,7 +27,7 @@ const ResultsTableMUI = ({
   selectedRowsIds,
   setSelectedRowsIds,
   setIsResourceFlyoutOpen,
-  setResourceFlyoutData,
+  setResourceFlyoutDataFromId,
 }) => {
   const { t } = useTranslation('results');
   const [publicFields, setPublicFields] = useState([]);
@@ -97,10 +97,6 @@ const ResultsTableMUI = ({
     return searchResults && columns.length > 0 ? rows : [];
   }, [searchResults, columns]);
 
-  const getResourceDataFromRowId = (id) => {
-    return searchResults.find((resource) => resource.id === id);
-  };
-
   const getRowIdFromResourceData = (id) => {
     if (!rows || rows.length === 0) {
       return -1;
@@ -132,9 +128,7 @@ const ResultsTableMUI = ({
     if (!searchResults || searchResults.length === 0) {
       return;
     }
-    const resourceData = getResourceDataFromRowId(rows[cellState.dataIndex].id);
-    // Extract all values except for id to avoid displaying it to user
-    setResourceFlyoutData((({ id, ...rest }) => rest)(resourceData));
+    setResourceFlyoutDataFromId(rows[cellState.dataIndex].id);
     setIsResourceFlyoutOpen(true);
   };
 
diff --git a/src/pages/search/Search.js b/src/pages/search/Search.js
index c892f8d..6f31278 100644
--- a/src/pages/search/Search.js
+++ b/src/pages/search/Search.js
@@ -11,6 +11,7 @@ import {
 import { useTranslation } from 'react-i18next';
 import AdvancedSearch from './AdvancedSearch/AdvancedSearch';
 import BasicSearch from './BasicSearch/BasicSearch';
+import ResourceFlyout from '../results/ResourceFlyout/ResourceFlyout';
 
 const Search = () => {
   const { t } = useTranslation('search');
@@ -24,6 +25,8 @@ const Search = () => {
   const [standardFields, setStandardFields] = useState([]);
   const [searchResults, setSearchResults] = useState([]);
   const [selectedResultsRowsIds, setSelectedResultsRowsIds] = useState([]);
+  const [isResourceFlyoutOpen, setIsResourceFlyoutOpen] = useState(false);
+  const [resourceFlyoutData, setResourceFlyoutData] = useState({});
 
   useEffect(() => {
     fetchPublicFields().then((resultStdFields) => {
@@ -64,6 +67,18 @@ const Search = () => {
     setSelectedResultsRowsIds([]);
   }, [searchResults]);
 
+  // Returns all data from a resource from its id. Used to display resource data in flyout
+  const getResourceDataFromRowId = (id) => {
+    return searchResults.find((resource) => resource.id === id);
+  };
+
+  // Add data to resource flyout, so it can be displayed.
+  const setResourceFlyoutDataFromId = (id) => {
+    const resourceData = getResourceDataFromRowId(id);
+    // Extract all values except for id to avoid displaying it to user
+    setResourceFlyoutData((({ id, ...rest }) => rest)(resourceData));
+  };
+
   const tabsContent = [
     {
       id: 'tab1',
@@ -117,6 +132,8 @@ const Search = () => {
               searchQuery={isAdvancedSearch ? search : basicSearch}
               selectedRowsIds={selectedResultsRowsIds}
               setSelectedRowsIds={setSelectedResultsRowsIds}
+              setResourceFlyoutDataFromId={setResourceFlyoutDataFromId}
+              setIsResourceFlyoutOpen={setIsResourceFlyoutOpen}
             />
           </EuiFlexItem>
         </EuiFlexGroup>
@@ -133,6 +150,8 @@ const Search = () => {
               searchResults={searchResults}
               selectedPointsIds={selectedResultsRowsIds}
               setSelectedPointsIds={setSelectedResultsRowsIds}
+              setResourceFlyoutDataFromId={setResourceFlyoutDataFromId}
+              setIsResourceFlyoutOpen={setIsResourceFlyoutOpen}
             />
           </EuiFlexItem>
         </EuiFlexGroup>
@@ -141,13 +160,21 @@ const Search = () => {
   ];
 
   return (
-    <EuiTabbedContent
-      tabs={tabsContent}
-      selectedTab={tabsContent[selectedTabNumber]}
-      onTabClick={(tab) => {
-        setSelectedTabNumber(tabsContent.indexOf(tab));
-      }}
-    />
+    <>
+      <ResourceFlyout
+        resourceFlyoutData={resourceFlyoutData}
+        setResourceFlyoutData={setResourceFlyoutData}
+        isResourceFlyoutOpen={isResourceFlyoutOpen}
+        setIsResourceFlyoutOpen={setIsResourceFlyoutOpen}
+      />
+      <EuiTabbedContent
+        tabs={tabsContent}
+        selectedTab={tabsContent[selectedTabNumber]}
+        onTabClick={(tab) => {
+          setSelectedTabNumber(tabsContent.indexOf(tab));
+        }}
+      />
+    </>
   );
 };
 
-- 
GitLab