diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx index 66659ad5..f3a8252e 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx @@ -16,13 +16,16 @@ const GetCoordinatesOnClick = ({ useEffect(() => { if (!map || !isMapLoaded) return () => {}; map.getCanvas().style.cursor = 'crosshair'; - map.on('click', e => { + + const handleClick = (e: any) => { const latLng = e.lngLat; getCoordinates(latLng); - }); + }; + map.on('click', handleClick); return () => { map.getCanvas().style.cursor = ''; + map.off('click', handleClick); }; }, [map, isMapLoaded, getCoordinates]); return null; diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx index 5666af81..9d12e1ab 100644 --- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx +++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx @@ -12,7 +12,10 @@ import { GeojsonType } from '@Components/common/MapLibreComponents/types'; import { Button } from '@Components/RadixComponents/Button'; import { postTaskWaypoint } from '@Services/tasks'; import { toggleModal } from '@Store/actions/common'; -import { setSelectedTakeOffPoint } from '@Store/actions/droneOperatorTask'; +import { + setSelectedTakeOffPoint, + setSelectedTakeOffPointOption, +} from '@Store/actions/droneOperatorTask'; import { useTypedSelector } from '@Store/hooks'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import getBbox from '@turf/bbox'; @@ -81,6 +84,7 @@ const MapSection = ({ className }: { className?: string }) => { return data; }); dispatch(setSelectedTakeOffPoint(null)); + dispatch(setSelectedTakeOffPointOption('current_location')); }, onError: (err: any) => { toast.error(err?.response?.data?.detail || err.message); @@ -158,6 +162,7 @@ const MapSection = ({ className }: { className?: string }) => { useEffect( () => () => { dispatch(setSelectedTakeOffPoint(null)); + dispatch(setSelectedTakeOffPointOption('current_location')); }, [dispatch], ); diff --git a/src/frontend/src/components/IndividualProject/MapSection/index.tsx b/src/frontend/src/components/IndividualProject/MapSection/index.tsx index f6e4bcdb..7e907844 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/index.tsx @@ -1,30 +1,29 @@ /* eslint-disable no-nested-ternary */ /* eslint-disable no-unused-vars */ -import { useNavigate, useParams } from 'react-router-dom'; -import { useCallback, useEffect, useState } from 'react'; -import { useTypedSelector, useTypedDispatch } from '@Store/hooks'; -import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; -import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer'; -import MapContainer from '@Components/common/MapLibreComponents/MapContainer'; -import { GeojsonType } from '@Components/common/MapLibreComponents/types'; -import AsyncPopup from '@Components/common/MapLibreComponents/AsyncPopup'; -import getBbox from '@turf/bbox'; -import { FeatureCollection } from 'geojson'; -import { GeolocateControl, LngLatBoundsLike, Map } from 'maplibre-gl'; -import { setProjectState } from '@Store/actions/project'; import { useGetProjectsDetailQuery, useGetTaskStatesQuery, useGetUserDetailsQuery, } from '@Api/projects'; import lock from '@Assets/images/lock.png'; +import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher'; +import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; +import AsyncPopup from '@Components/common/MapLibreComponents/AsyncPopup'; +import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer'; +import LocateUser from '@Components/common/MapLibreComponents/LocateUser'; +import MapContainer from '@Components/common/MapLibreComponents/MapContainer'; +import { GeojsonType } from '@Components/common/MapLibreComponents/types'; import { postTaskStatus } from '@Services/project'; +import { setProjectState } from '@Store/actions/project'; +import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; import { useMutation } from '@tanstack/react-query'; -import { toast } from 'react-toastify'; +import getBbox from '@turf/bbox'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; -import baseLayersData from '@Components/common/MapLibreComponents/BaseLayerSwitcher/baseLayers'; -import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher'; -import LocateUser from '@Components/common/MapLibreComponents/LocateUser'; +import { FeatureCollection } from 'geojson'; +import { LngLatBoundsLike, Map } from 'maplibre-gl'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { toast } from 'react-toastify'; import Legend from './Legend'; const MapSection = () => { @@ -85,6 +84,20 @@ const MapSection = () => { }, }); + const { mutate: unLockTask } = useMutation({ + mutationFn: postTaskStatus, + onSuccess: (res: any) => { + setTaskStatusObj({ + ...taskStatusObj, + [res.data.task_id]: 'UNLOCKED_TO_MAP', + }); + toast.success('Task Unlocked Successfully'); + }, + onError: (err: any) => { + toast.error(err.message); + }, + }); + useEffect(() => { if (!map || !taskStates) return; // @ts-ignore @@ -149,6 +162,14 @@ const MapSection = () => { }); }; + const handleTaskUnLockClick = () => { + unLockTask({ + projectId: id, + taskId: selectedTaskId, + data: { event: 'unlock' }, + }); + }; + return ( { ? handleTaskLockClick() : navigate(`/projects/${id}/tasks/${selectedTaskId}`) } + hasSecondaryButton={ + taskStatusObj?.[selectedTaskId] === 'LOCKED_FOR_MAPPING' && + lockedUser?.id === userDetails?.id + } + secondaryButtonText="Unlock Task" + handleSecondaryBtnClick={() => handleTaskUnLockClick()} /> diff --git a/src/frontend/src/components/Projects/MapSection/index.tsx b/src/frontend/src/components/Projects/MapSection/index.tsx index 81c2b14d..3009db9f 100644 --- a/src/frontend/src/components/Projects/MapSection/index.tsx +++ b/src/frontend/src/components/Projects/MapSection/index.tsx @@ -1,15 +1,15 @@ +import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; -import MapContainer from '@Components/common/MapLibreComponents/MapContainer'; -import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher'; -import { useGetProjectsListQuery } from '@Api/projects'; -import hasErrorBoundary from '@Utils/hasErrorBoundary'; -import centroid from '@turf/centroid'; +import { LngLatBoundsLike, Map } from 'maplibre-gl'; import getBbox from '@turf/bbox'; -import { useCallback, useEffect, useState } from 'react'; +import centroid from '@turf/centroid'; import { FeatureCollection } from 'geojson'; +import { useGetProjectsListQuery } from '@Api/projects'; +import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; import AsyncPopup from '@Components/common/MapLibreComponents/AsyncPopup'; -import { LngLatBoundsLike, Map } from 'maplibre-gl'; +import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher'; +import MapContainer from '@Components/common/MapLibreComponents/MapContainer'; +import hasErrorBoundary from '@Utils/hasErrorBoundary'; import VectorLayerWithCluster from './VectorLayerWithCluster'; const ProjectsMapSection = () => { @@ -78,6 +78,7 @@ const ProjectsMapSection = () => { style={{ width: '100%', height: '100%', + borderRadius: '8px', }} > diff --git a/src/frontend/src/components/common/BaseLayerSwitcher/index.tsx b/src/frontend/src/components/common/BaseLayerSwitcher/index.tsx index 7ca166a2..e54b5685 100644 --- a/src/frontend/src/components/common/BaseLayerSwitcher/index.tsx +++ b/src/frontend/src/components/common/BaseLayerSwitcher/index.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; import useOutsideClick from '@Hooks/useOutsideClick'; +import { useState } from 'react'; import BaseLayerSwitcher from '../MapLibreComponents/BaseLayerSwitcher'; import baseLayersData from '../MapLibreComponents/BaseLayerSwitcher/baseLayers'; import { MapInstanceType } from '../MapLibreComponents/types'; @@ -27,6 +27,7 @@ const BaseLayerSwitcherUI = ({ handleToggle(); }} role="presentation" + title="Layer Switcher" > layers diff --git a/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx b/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx index be22f596..f65bf3a4 100644 --- a/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx +++ b/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx @@ -1,13 +1,13 @@ /* eslint-disable no-unused-vars */ /* eslint-disable react/no-danger */ -import { useEffect, useRef, useState } from 'react'; -import { renderToString } from 'react-dom/server'; -import { Popup } from 'maplibre-gl'; -import type { MapMouseEvent } from 'maplibre-gl'; -import 'maplibre-gl/dist/maplibre-gl.css'; import '@Components/common/MapLibreComponents/map.css'; import { Button } from '@Components/RadixComponents/Button'; import Skeleton from '@Components/RadixComponents/Skeleton'; +import type { MapMouseEvent } from 'maplibre-gl'; +import { Popup } from 'maplibre-gl'; +import 'maplibre-gl/dist/maplibre-gl.css'; +import { useEffect, useRef, useState } from 'react'; +import { renderToString } from 'react-dom/server'; import { IAsyncPopup } from '../types'; const popup = new Popup({ @@ -26,6 +26,9 @@ export default function AsyncPopup({ buttonText = 'View More', hideButton = false, getCoordOnProperties = false, + hasSecondaryButton = false, + secondaryButtonText = '', + handleSecondaryBtnClick, showPopup = (_clickedFeature: Record) => true, }: IAsyncPopup) { const [properties, setProperties] = useState | null>( @@ -106,14 +109,27 @@ export default function AsyncPopup({
{!isLoading && !hideButton && ( -
- +
+
+ {hasSecondaryButton && ( + + )} + + +
)}
diff --git a/src/frontend/src/components/common/MapLibreComponents/types/index.ts b/src/frontend/src/components/common/MapLibreComponents/types/index.ts index 4155a0af..538e1b48 100644 --- a/src/frontend/src/components/common/MapLibreComponents/types/index.ts +++ b/src/frontend/src/components/common/MapLibreComponents/types/index.ts @@ -1,8 +1,8 @@ /* eslint-disable no-unused-vars */ -import type { ReactElement } from 'react'; -import type { Map, MapOptions } from 'maplibre-gl'; -import type { Feature, FeatureCollection, GeoJsonTypes } from 'geojson'; import type { DrawMode } from '@mapbox/mapbox-gl-draw'; +import type { Feature, FeatureCollection, GeoJsonTypes } from 'geojson'; +import type { Map, MapOptions } from 'maplibre-gl'; +import type { ReactElement } from 'react'; export type MapInstanceType = Map; @@ -83,6 +83,9 @@ export interface IAsyncPopup { hideButton?: boolean; getCoordOnProperties?: boolean; showPopup?: (clickedFeature: Record) => Boolean; + hasSecondaryButton?: boolean; + secondaryButtonText?: string; + handleSecondaryBtnClick?: (properties: Record) => void; } export type DrawModeTypes = DrawMode | null | undefined; diff --git a/src/frontend/src/views/IndividualProject/index.tsx b/src/frontend/src/views/IndividualProject/index.tsx index e27ceed6..7cb73892 100644 --- a/src/frontend/src/views/IndividualProject/index.tsx +++ b/src/frontend/src/views/IndividualProject/index.tsx @@ -1,19 +1,18 @@ /* eslint-disable jsx-a11y/interactive-supports-focus */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import { useParams, useNavigate } from 'react-router-dom'; -import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; -import { Flex } from '@Components/common/Layouts'; +import { useGetProjectsDetailQuery } from '@Api/projects'; import Tab from '@Components/common/Tabs'; import { + Contributions, + Instructions, MapSection, Tasks, - Instructions, - Contributions, } from '@Components/IndividualProject'; -import { useGetProjectsDetailQuery } from '@Api/projects'; -import { setProjectState } from '@Store/actions/project'; import { projectOptions } from '@Constants/index'; +import { setProjectState } from '@Store/actions/project'; +import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; +import { useNavigate, useParams } from 'react-router-dom'; // function to render the content based on active tab const getActiveTabContent = ( @@ -66,19 +65,19 @@ const IndividualProject = () => { }); return ( -
+
{/* <----------- temporary breadcrumb -----------> */} -
+
{ navigate('/projects'); }} > Project / - + { // @ts-ignore projectData?.name || '--' @@ -86,8 +85,8 @@ const IndividualProject = () => { {/* <----------- temporary breadcrumb -----------> */}
- -
+
+
{ activeTab={individualProjectActiveTab} clickable /> -
+
{getActiveTabContent( individualProjectActiveTab, projectData as Record, @@ -107,10 +106,10 @@ const IndividualProject = () => { )}
-
+
- +
); };