diff --git a/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx b/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx index f0834341..4b3b3b8c 100644 --- a/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/Contributions/TableSection/index.tsx @@ -56,7 +56,16 @@ const contributionsDataColumns = [ }, ]; -export default function TableSection({ isFetching }: { isFetching: boolean }) { +interface ITableSectionProps { + isFetching: boolean; + // eslint-disable-next-line no-unused-vars + handleTableRowClick: (rowData: any) => {}; +} + +export default function TableSection({ + isFetching, + handleTableRowClick, +}: ITableSectionProps) { const { id } = useParams(); const tasksData = useTypedSelector(state => state.project.tasksData); @@ -82,6 +91,7 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) { task_state: curr?.state, assets_url: selectedAssetsDetails?.assets_url, image_count: selectedAssetsDetails?.image_count, + task_id: curr?.id, }, ]; }, []); @@ -96,6 +106,7 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) { data={taskDataForTable as Record[]} withPagination={false} loading={isFetching || isUrlFetching} + handleTableRowClick={handleTableRowClick} /> ); } diff --git a/src/frontend/src/components/IndividualProject/Contributions/index.tsx b/src/frontend/src/components/IndividualProject/Contributions/index.tsx index 347542f9..f324d0ff 100644 --- a/src/frontend/src/components/IndividualProject/Contributions/index.tsx +++ b/src/frontend/src/components/IndividualProject/Contributions/index.tsx @@ -1,10 +1,22 @@ import TableSection from './TableSection'; -export default function Contributions({ isFetching }: { isFetching: boolean }) { +interface IContributionsProps { + isFetching: boolean; + // eslint-disable-next-line no-unused-vars + handleTableRowClick: (rowData: any) => {}; +} + +export default function Contributions({ + isFetching, + handleTableRowClick, +}: IContributionsProps) { return (
- +
); diff --git a/src/frontend/src/components/IndividualProject/MapSection/index.tsx b/src/frontend/src/components/IndividualProject/MapSection/index.tsx index 18b1c122..d421937e 100644 --- a/src/frontend/src/components/IndividualProject/MapSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/MapSection/index.tsx @@ -1,15 +1,10 @@ /* eslint-disable no-nested-ternary */ -/* eslint-disable no-unused-vars */ import { useCallback, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { LngLatBoundsLike, Map } from 'maplibre-gl'; import { FeatureCollection } from 'geojson'; import { toast } from 'react-toastify'; -import { - useGetProjectsDetailQuery, - useGetTaskStatesQuery, - useGetUserDetailsQuery, -} from '@Api/projects'; +import { useGetTaskStatesQuery, useGetUserDetailsQuery } from '@Api/projects'; import lock from '@Assets/images/lock.png'; import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher'; import { useMapLibreGLMap } from '@Components/common/MapLibreComponents'; @@ -53,6 +48,9 @@ const MapSection = ({ projectData }: { projectData: Record }) => { ); const tasksData = useTypedSelector(state => state.project.tasksData); const projectArea = useTypedSelector(state => state.project.projectArea); + const taskClickedOnTable = useTypedSelector( + state => state.project.taskClickedOnTable, + ); const { data: taskStates } = useGetTaskStatesQuery(id as string, { enabled: !!tasksData, @@ -305,6 +303,11 @@ const MapSection = ({ projectData }: { projectData: Record }) => { feature?.source?.includes('tasks-layer') } fetchPopupData={(properties: Record) => { + dispatch( + setProjectState({ + taskClickedOnTable: null, + }), + ); dispatch(setProjectState({ selectedTaskId: properties.id })); setLockedUser({ id: properties?.locked_user_id || userDetails?.id || '', @@ -338,6 +341,16 @@ const MapSection = ({ projectData }: { projectData: Record }) => { } secondaryButtonText="Unlock Task" handleSecondaryBtnClick={() => handleTaskUnLockClick()} + // trigger from popup outside + openPopupFor={taskClickedOnTable} + popupCoordinate={taskClickedOnTable?.centroidCoordinates} + onClose={() => + dispatch( + setProjectState({ + taskClickedOnTable: null, + }), + ) + } /> diff --git a/src/frontend/src/components/IndividualProject/Tasks/TableSection/index.tsx b/src/frontend/src/components/IndividualProject/Tasks/TableSection/index.tsx index d35f2266..340ab791 100644 --- a/src/frontend/src/components/IndividualProject/Tasks/TableSection/index.tsx +++ b/src/frontend/src/components/IndividualProject/Tasks/TableSection/index.tsx @@ -1,27 +1,28 @@ +import { useMemo } from 'react'; import DataTable from '@Components/common/DataTable'; import { useTypedSelector } from '@Store/hooks'; -import { useMemo } from 'react'; const tasksDataColumns = [ { header: 'ID', accessorKey: 'id', }, - // { - // header: 'Flight Time', - // accessorKey: 'flight_time', - // }, { header: 'Task Area in kmĀ²', accessorKey: 'task_area', }, - // { - // header: 'Status', - // accessorKey: 'status', - // }, ]; -export default function TableSection({ isFetching }: { isFetching: boolean }) { +interface ITableSectionProps { + isFetching: boolean; + // eslint-disable-next-line no-unused-vars + handleTableRowClick: (rowData: any) => {}; +} + +export default function TableSection({ + isFetching, + handleTableRowClick, +}: ITableSectionProps) { const tasksData = useTypedSelector(state => state.project.tasksData); const taskDataForTable = useMemo(() => { @@ -34,6 +35,7 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) { id: `Task# ${curr?.project_task_index}`, flight_time: curr?.flight_time || '-', task_area: Number(curr?.task_area)?.toFixed(3), + task_id: curr?.id, // status: curr?.state, }, ]; @@ -49,6 +51,7 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) { data={taskDataForTable as Record[]} withPagination={false} loading={isFetching} + handleTableRowClick={handleTableRowClick} /> ); } diff --git a/src/frontend/src/components/IndividualProject/Tasks/index.tsx b/src/frontend/src/components/IndividualProject/Tasks/index.tsx index a317751c..8e4fbe22 100644 --- a/src/frontend/src/components/IndividualProject/Tasks/index.tsx +++ b/src/frontend/src/components/IndividualProject/Tasks/index.tsx @@ -3,7 +3,16 @@ // import { FlexRow } from '@Components/common/Layouts'; import TableSection from './TableSection'; -export default function Tasks({ isFetching }: { isFetching: boolean }) { +interface ITasksProps { + isFetching: boolean; + // eslint-disable-next-line no-unused-vars + handleTableRowClick: (rowData: any) => {}; +} + +export default function Tasks({ + isFetching, + handleTableRowClick, +}: ITasksProps) { return (
{/* @@ -24,7 +33,10 @@ export default function Tasks({ isFetching }: { isFetching: boolean }) { */}
- +
); diff --git a/src/frontend/src/components/common/DataTable/index.tsx b/src/frontend/src/components/common/DataTable/index.tsx index df82fce5..c568a936 100644 --- a/src/frontend/src/components/common/DataTable/index.tsx +++ b/src/frontend/src/components/common/DataTable/index.tsx @@ -50,6 +50,7 @@ interface DataTableProps { useQueryOptions?: Record; data?: Record[]; loading?: boolean; + handleTableRowClick?: any; } const defaultPaginationState = { @@ -73,6 +74,7 @@ export default function DataTable({ useQueryOptions, data, loading, + handleTableRowClick, }: DataTableProps) { const [sorting, setSorting] = useState([]); const debouncedValue = useDebounceListener(searchInput || '', 800); @@ -233,6 +235,10 @@ export default function DataTable({ { + handleTableRowClick?.(row?.original); + }} + className={`${handleTableRowClick ? 'naxatw-cursor-pointer' : ''} `} > {row.getVisibleCells().map(cell => ( diff --git a/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx b/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx index f65bf3a4..f25c1734 100644 --- a/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx +++ b/src/frontend/src/components/common/MapLibreComponents/AsyncPopup/index.tsx @@ -30,11 +30,14 @@ export default function AsyncPopup({ secondaryButtonText = '', handleSecondaryBtnClick, showPopup = (_clickedFeature: Record) => true, + openPopupFor, + popupCoordinate, }: IAsyncPopup) { const [properties, setProperties] = useState | null>( null, ); const popupRef = useRef(null); + const [coordinates, setCoordinates] = useState(null); const [popupHTML, setPopupHTML] = useState(''); useEffect(() => { @@ -61,7 +64,8 @@ export default function AsyncPopup({ }, ); - popup.setLngLat(e.lngLat); + setCoordinates(e.lngLat); + // popup.setLngLat(e.lngLat); } map.on('click', displayPopup); }, [map, getCoordOnProperties, showPopup]); @@ -72,11 +76,13 @@ export default function AsyncPopup({ }, [map, properties]); // eslint-disable-line useEffect(() => { - if (!map || !properties || !popupUI || !popupRef.current) return; + if (!map || !properties || !popupUI || !popupRef.current || !coordinates) + return; const htmlString = renderToString(popupUI(properties)); popup.setDOMContent(popupRef.current).addTo(map); setPopupHTML(htmlString); - }, [map, popupUI, properties]); + popup.setLngLat(coordinates); + }, [map, popupUI, properties, coordinates]); const onPopupClose = () => { popup.remove(); @@ -84,6 +90,12 @@ export default function AsyncPopup({ setProperties(null); }; + useEffect(() => { + if (!map || !openPopupFor || !popupCoordinate) return; + setProperties(openPopupFor); + setCoordinates(popupCoordinate); + }, [map, openPopupFor, popupCoordinate]); + if (!properties) return
; return ( diff --git a/src/frontend/src/components/common/MapLibreComponents/types/index.ts b/src/frontend/src/components/common/MapLibreComponents/types/index.ts index 538e1b48..b23fa07b 100644 --- a/src/frontend/src/components/common/MapLibreComponents/types/index.ts +++ b/src/frontend/src/components/common/MapLibreComponents/types/index.ts @@ -86,6 +86,8 @@ export interface IAsyncPopup { hasSecondaryButton?: boolean; secondaryButtonText?: string; handleSecondaryBtnClick?: (properties: Record) => void; + openPopupFor?: Record | null; + popupCoordinate?: number[]; } export type DrawModeTypes = DrawMode | null | undefined; diff --git a/src/frontend/src/components/common/UserProfile/index.tsx b/src/frontend/src/components/common/UserProfile/index.tsx index 423cca9b..bd6e0f8d 100644 --- a/src/frontend/src/components/common/UserProfile/index.tsx +++ b/src/frontend/src/components/common/UserProfile/index.tsx @@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom'; import UserAvatar from '@Components/common/UserAvatar'; import { toast } from 'react-toastify'; import { getLocalStorageValue } from '@Utils/getLocalStorageValue'; +import { useGetUserDetailsQuery } from '@Api/projects'; export default function UserProfile() { const [toggle, setToggle] = useState(false); @@ -14,10 +15,19 @@ export default function UserProfile() { const userProfile = getLocalStorageValue('userprofile'); const role = localStorage.getItem('signedInAs'); + const { data: userDetails }: Record = useGetUserDetailsQuery({ + enabled: !!(userProfile?.role && role), + }); + useEffect(() => { - if (!userProfile || userProfile?.role?.includes(role)) return; + if ( + !userProfile || + userProfile?.role?.includes(role) || + userDetails?.role?.includes(role) + ) + return; navigate('/complete-profile'); - }, [userProfile, role, navigate]); + }, [userProfile, role, navigate, userDetails]); const settingOptions = [ { diff --git a/src/frontend/src/store/slices/project.ts b/src/frontend/src/store/slices/project.ts index d82d922c..85579032 100644 --- a/src/frontend/src/store/slices/project.ts +++ b/src/frontend/src/store/slices/project.ts @@ -7,6 +7,7 @@ export interface ProjectState { tasksData: Record[] | null; projectArea: Record | null; selectedTaskId: string; + taskClickedOnTable: Record | null; } const initialState: ProjectState = { @@ -14,6 +15,7 @@ const initialState: ProjectState = { tasksData: null, projectArea: null, selectedTaskId: '', + taskClickedOnTable: null, }; const setProjectState: CaseReducer< diff --git a/src/frontend/src/views/IndividualProject/index.tsx b/src/frontend/src/views/IndividualProject/index.tsx index cfe2d170..ccd2a046 100644 --- a/src/frontend/src/views/IndividualProject/index.tsx +++ b/src/frontend/src/views/IndividualProject/index.tsx @@ -12,6 +12,7 @@ import Skeleton from '@Components/RadixComponents/Skeleton'; import { projectOptions } from '@Constants/index'; import { setProjectState } from '@Store/actions/project'; import { useTypedDispatch, useTypedSelector } from '@Store/hooks'; +import centroid from '@turf/centroid'; import hasErrorBoundary from '@Utils/hasErrorBoundary'; import { useNavigate, useParams } from 'react-router-dom'; @@ -20,8 +21,16 @@ const getActiveTabContent = ( activeTab: string, data: Record, isProjectDataLoading: boolean, + // eslint-disable-next-line no-unused-vars + handleTableRowClick: (rowData: any) => {}, ) => { - if (activeTab === 'tasks') return ; + if (activeTab === 'tasks') + return ( + + ); if (activeTab === 'instructions') return ( ); if (activeTab === 'contributions') - return ; + return ( + + ); return <>; }; @@ -42,8 +56,9 @@ const IndividualProject = () => { const individualProjectActiveTab = useTypedSelector( state => state.project.individualProjectActiveTab, ); + const tasksList = useTypedSelector(state => state.project.tasksData); - const { data: projectData, isFetching: isProjectDataFetching } = + const { data: projectData, isFetching: isProjectDataFetching }: any = useGetProjectsDetailQuery(id as string, { onSuccess: (res: any) => { dispatch( @@ -66,6 +81,26 @@ const IndividualProject = () => { }, }); + const handleTableRowClick = (taskData: any) => { + const clickedTask = tasksList?.find( + (task: Record) => taskData?.task_id === task?.id, + ); + const taskDetailToSave = { + id: clickedTask?.id, + locked_user_id: clickedTask?.user_id, + locked_user_name: clickedTask?.name, + centroidCoordinates: centroid(clickedTask?.outline).geometry.coordinates, + }; + + dispatch( + setProjectState({ + taskClickedOnTable: taskDetailToSave, + }), + ); + + return {}; + }; + return (
{/* <----------- temporary breadcrumb -----------> */} @@ -105,9 +140,11 @@ const IndividualProject = () => { individualProjectActiveTab, projectData as Record, isProjectDataFetching, + handleTableRowClick, )}
+
{isProjectDataFetching ? ( diff --git a/src/frontend/src/views/UpdateUserProfile/index.tsx b/src/frontend/src/views/UpdateUserProfile/index.tsx index fbba9eba..a5cce424 100644 --- a/src/frontend/src/views/UpdateUserProfile/index.tsx +++ b/src/frontend/src/views/UpdateUserProfile/index.tsx @@ -9,7 +9,6 @@ import OtherDetails from '@Components/UpdateUserDetails/OtherDetails'; import Password from '@Components/UpdateUserDetails/Password'; import { tabOptions } from '@Constants/index'; import useWindowDimensions from '@Hooks/useWindowDimensions'; -import { useGetUserDetailsQuery } from '@Api/projects'; const getActiveFormContent = (activeTab: number, userType: string) => { switch (activeTab) { @@ -32,7 +31,6 @@ const UpdateUserProfile = () => { const dispatch = useDispatch(); const { width } = useWindowDimensions(); - useGetUserDetailsQuery(); const userProfileActiveTab = useTypedSelector( state => state.common.userProfileActiveTab, );