From a5b5f69dd180f020c9c58a3e2b0fb1767fcbe991 Mon Sep 17 00:00:00 2001 From: Seb-sti1 <65665540+seb-sti1@users.noreply.github.com> Date: Mon, 6 Nov 2023 20:51:19 +0100 Subject: [PATCH] Main page: Select a road --- .../Components/Conditions/ConditionsMap.tsx | 13 +++- .../src/Components/Map/DetectMapClick.tsx | 24 ++++++++ .../src/Components/Map/ForceMapUpdate.tsx | 3 + frontend/src/Components/Map/Road.tsx | 11 ++-- frontend/src/Components/Map/Roads.tsx | 37 +++++++----- frontend/src/css/navbar.css | 31 +++++----- frontend/src/pages/Main.tsx | 60 ++++++++++++++++--- 7 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 frontend/src/Components/Map/DetectMapClick.tsx diff --git a/frontend/src/Components/Conditions/ConditionsMap.tsx b/frontend/src/Components/Conditions/ConditionsMap.tsx index b23df9b3..1ff61366 100644 --- a/frontend/src/Components/Conditions/ConditionsMap.tsx +++ b/frontend/src/Components/Conditions/ConditionsMap.tsx @@ -12,8 +12,8 @@ import { IRInew, KPI, Mu, - YearMonth, MultiMode, + YearMonth, } from '../../models/conditions'; import { getAllConditions } from '../../queries/conditions'; @@ -56,6 +56,8 @@ const getTypeColor = (type: string): string => { * @param y otherwise the color is yellow if value <= y * @param o otherwise the color is orange if value <= o. Otherwise, the color is red * @returns {string} the color + * + * @author Kerbourc'h */ const getColorForValue = ( value: number, @@ -75,6 +77,13 @@ const getColorForValue = ( : '#FF0000'; }; +/** + * The gradient color for each indicator + * + * @param properties the type and value of the GeoJSON being drawn + * + * @author Kerbourc'h + */ const getConditionColor = (properties: GeoJSON.GeoJsonProperties): string => { if (properties !== null) { const type = properties.type; @@ -111,6 +120,8 @@ interface ConditionsMapProps { /** * Component rendering the map with the conditions + * + * @author Hansen, Kerbourc'h */ const ConditionsMap: FC = ({ children, diff --git a/frontend/src/Components/Map/DetectMapClick.tsx b/frontend/src/Components/Map/DetectMapClick.tsx new file mode 100644 index 00000000..b5028bb1 --- /dev/null +++ b/frontend/src/Components/Map/DetectMapClick.tsx @@ -0,0 +1,24 @@ +import { useMapEvents } from 'react-leaflet'; +import { LatLng } from 'leaflet'; +import { FC } from 'react'; + +interface Props { + /** The callback function */ + onClick: (latLng: LatLng) => void; +} + +/** + * A component that detects a click on the map and calls the callback function + * + * @author Kerbourc'h + */ +const DetectMapClick: FC = ({ onClick }) => { + useMapEvents({ + click: (e) => { + onClick(e.latlng); + }, + }); + return null; +}; + +export default DetectMapClick; diff --git a/frontend/src/Components/Map/ForceMapUpdate.tsx b/frontend/src/Components/Map/ForceMapUpdate.tsx index 91ba5c73..4fd0e898 100644 --- a/frontend/src/Components/Map/ForceMapUpdate.tsx +++ b/frontend/src/Components/Map/ForceMapUpdate.tsx @@ -11,6 +11,8 @@ interface Props { /** * This component is used to force an update of the map. It is used in conjunction with the useMap hook. + * + * @author Kerbourc'h */ const ForceMapUpdate: React.FC = ({ triggerUpdate, position }) => { const map = useMap(); @@ -19,6 +21,7 @@ const ForceMapUpdate: React.FC = ({ triggerUpdate, position }) => { map.invalidateSize(); }, [triggerUpdate]); + // TODO: not working when moving too little useEffect(() => { if (position) { map.flyTo(position); diff --git a/frontend/src/Components/Map/Road.tsx b/frontend/src/Components/Map/Road.tsx index cb96586c..14075377 100644 --- a/frontend/src/Components/Map/Road.tsx +++ b/frontend/src/Components/Map/Road.tsx @@ -1,5 +1,5 @@ import { IRoad } from '../../models/path'; -import { LatLng, LatLngLiteral } from 'leaflet'; +import L, { LatLng, LatLngLiteral } from 'leaflet'; import { Polyline } from 'react-leaflet'; import React, { ReactElement, useEffect } from 'react'; @@ -12,6 +12,8 @@ interface Props { /** * A components that renders a road on the map + * + * @author Kerbourc'h */ const Road: React.FC = ({ road, onClick }) => { const [lines, setLines] = React.useState([]); @@ -47,11 +49,12 @@ const Road: React.FC = ({ road, onClick }) => { pathOptions={{ weight: 5, opacity: opacity }} positions={data} eventHandlers={{ - click: ({ latlng }) => { - if (onClick) onClick(latlng); + click: (e) => { + L.DomEvent.stopPropagation(e); // used to prevent the map to detect the click + if (onClick) onClick(e.latlng); }, mouseover: () => { - setOpacity(0.8); + setOpacity(0.9); }, mouseout: () => { setOpacity(0.2); diff --git a/frontend/src/Components/Map/Roads.tsx b/frontend/src/Components/Map/Roads.tsx index b595c8d3..76d61c4d 100644 --- a/frontend/src/Components/Map/Roads.tsx +++ b/frontend/src/Components/Map/Roads.tsx @@ -2,38 +2,47 @@ import React from 'react'; import { IRoad } from '../../models/path'; import Road from './Road'; import { LatLng } from '../../models/models'; -import { useNavigate } from 'react-router-dom'; interface Props { /** The roads */ roads?: IRoad[]; + /** The selected road index */ + selectedRoadIdx?: number; /** The event handlers, when a road is selected */ - onSelectedRoad?: (road: IRoad, position: LatLng) => void; + onSelectedRoad?: (index: number, road: IRoad, position: LatLng) => void; } /** * A components that renders the roads on the map + * + * @author Kerbourc'h */ -const Roads: React.FC = ({ roads, onSelectedRoad }) => { - const navigate = useNavigate(); - +const Roads: React.FC = ({ roads, selectedRoadIdx, onSelectedRoad }) => { return ( <> - {roads?.map((road) => ( + {selectedRoadIdx === undefined || selectedRoadIdx === -1 || !roads ? ( + roads?.map((road, index) => ( + { + if (onSelectedRoad) { + onSelectedRoad(index, road, position); + } + }} + /> + )) + ) : ( { - console.log(road, position); - // TODO: remove when possible to select a road - navigate('/inspect'); - if (onSelectedRoad) { - onSelectedRoad(road, position); + onSelectedRoad(selectedRoadIdx, roads[selectedRoadIdx], position); } }} /> - ))} + )} ); }; diff --git a/frontend/src/css/navbar.css b/frontend/src/css/navbar.css index cc9b5f1a..0bceb11f 100644 --- a/frontend/src/css/navbar.css +++ b/frontend/src/css/navbar.css @@ -20,29 +20,19 @@ position: relative; width: 15%; min-width: fit-content; - height: 100%; - margin-top: 13px; - margin-left: 0.4%; - align-self: flex-start; } .picker-container { position: relative; width: 15%; min-width: fit-content; - height: 100%; - margin-top: 10px; - align-self: flex-start; + height: 38px; } .filter-container { position: relative; width: 6%; min-width: fit-content; - height: 100%; - margin-top: 13px; - align-self: flex-start; - flex-basis: auto; } .nav-tab { @@ -64,11 +54,20 @@ white-space: nowrap; } -.nav-tab:hover { - color: var(--highlight-text); +.inspect-button-div { + position: relative; + width: 6%; + min-width: fit-content; + margin-left: auto; + margin-right: 20px; } -.nav-tab.active { - color: var(--highlight-text); - background: var(--highlight-background); +.inspect-button { + font-size: inherit; /* Inherit font size */ + color: inherit; /* Inherit text color */ + cursor: pointer; /* Add a pointer cursor on hover */ + text-decoration: none; /* Remove underlines on hover */ + background: #7f6fe9; + border-radius: 2px; + padding: 7px 20px 7px 20px; } diff --git a/frontend/src/pages/Main.tsx b/frontend/src/pages/Main.tsx index f9d35bd5..f8150f84 100644 --- a/frontend/src/pages/Main.tsx +++ b/frontend/src/pages/Main.tsx @@ -9,22 +9,29 @@ import ForceMapUpdate from '../Components/Map/ForceMapUpdate'; import Roads from '../Components/Map/Roads'; import { ConditionTypeOptions, - SeverityOptions, DateRange, - YearMonth, - MultiMode, DefaultMode, + MultiMode, + SeverityOptions, + YearMonth, } from '../models/conditions'; import MonthFilter from '../Components/Map/Inputs/MonthFilter'; import MultiSelector from '../Components/Map/Inputs/MultiSelector'; import '../css/navbar.css'; +import DetectMapClick from '../Components/Map/DetectMapClick'; +import { Link } from 'react-router-dom'; /** * Component rendering the main page + * + * @author Hansen, Kerbourc'h */ const Main: FC = () => { // The roads loaded from the database const [roads, setRoads] = useState(); + // Select road index + const [selectedRoadIdx, setSelectedRoadIdx] = useState(-1); + // The position to move too (used by the Search component) const [moveToPosition, setMoveToPosition] = useState(); // The indicator(s) to display on the map @@ -43,7 +50,7 @@ const Main: FC = () => { function dateChange(date: any) { const YearMonth: YearMonth = { year: date.getFullYear(), - month: date.getMonth() + 1, + month: date.getMonth() + 1, // january = 0 }; return YearMonth; @@ -84,7 +91,7 @@ const Main: FC = () => { const multiModeSet = useCallback((value: string[]) => { const outputMode: MultiMode = { - count: value.length + 0, + count: value.length, mode: value .map((e: any) => e.label) .toString() @@ -108,7 +115,20 @@ const Main: FC = () => {
{ - console.log(value); + const osm_id = value?.properties?.datasource?.raw?.osm_id; + if (osm_id && roads) { + console.debug(osm_id, roads.length); + for (let idx = 0; idx < roads.length; idx++) { + if ( + Object.keys(roads[idx].geometries).includes(String(osm_id)) + ) { + console.debug('Found road', idx); + setSelectedRoadIdx(idx); + break; + } + } + } + const coordinates = value?.geometry?.coordinates; if (coordinates) { const position = { @@ -147,9 +167,35 @@ const Main: FC = () => { />

Start Date → End Date

+
+ + Inspect + +
- + { + // If no road is selected, select the road + if (selectedRoadIdx === -1) { + setSelectedRoadIdx(index); + } + setMoveToPosition(position); + }} + /> + { + if (selectedRoadIdx !== -1) { + setSelectedRoadIdx(-1); + } + }} + />