diff --git a/src/frontend/src/api/Project.js b/src/frontend/src/api/Project.js index db0345c321..cf6c776c81 100755 --- a/src/frontend/src/api/Project.js +++ b/src/frontend/src/api/Project.js @@ -221,6 +221,20 @@ export const GetProjectDashboard = (url) => { }; }; +export const GetEntityInfo = (url) => { + return async (dispatch) => { + const getEntityOsmMap = async (url) => { + try { + const response = await CoreModules.axios.get(url); + dispatch(ProjectActions.SetEntityOsmMap(response.data)); + } catch (error) { + } finally { + } + }; + await getEntityOsmMap(url); + }; +}; + export const GetProjectComments = (url) => { return async (dispatch) => { const getProjectComments = async (url) => { diff --git a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx new file mode 100644 index 0000000000..cfff1f101d --- /dev/null +++ b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx @@ -0,0 +1,108 @@ +// Popup used to display task feature info & link to ODK Collect + +import React from 'react'; +import CoreModules from '@/shared/CoreModules'; +import AssetModules from '@/shared/AssetModules'; +import Button from '@/components/common/Button'; +import { ProjectActions } from '@/store/slices/ProjectSlice'; + +type TaskFeatureSelectionProperties = { + osm_id: number; + tags: string; + timestamp: string; + version: number; + changeset: number; +}; + +type TaskFeatureSelectionPopupPropType = { + featureProperties: TaskFeatureSelectionProperties | null; +}; + +const TaskFeatureSelectionPopup = ({ featureProperties }: TaskFeatureSelectionPopupPropType) => { + const dispatch = CoreModules.useAppDispatch(); + const taskModalStatus = CoreModules.useAppSelector((state) => state.project.taskModalStatus); + const projectInfo = CoreModules.useAppSelector((state) => state.project.projectInfo); + const entityOsmMap = CoreModules.useAppSelector((state) => state.project.entityOsmMap); + return ( +
+
+
+ dispatch(ProjectActions.ToggleTaskModalStatus(false))} + /> +
+
+
+
+

Feature: {featureProperties?.osm_id}

+
+ +
+
+

+ Tags: + + {featureProperties?.tags} + +

+

+ Timestamp: + {featureProperties?.timestamp} +

+

+ Changeset: + {featureProperties?.changeset} +

+

+ Version: + {featureProperties?.version} +

+
+
+ +
+
+
+
+ ); +}; + +export default TaskFeatureSelectionPopup; diff --git a/src/frontend/src/components/ProjectDetailsV2/TaskSelectionPopup.tsx b/src/frontend/src/components/ProjectDetailsV2/TaskSelectionPopup.tsx index 59a0dd5952..bf7c17160a 100644 --- a/src/frontend/src/components/ProjectDetailsV2/TaskSelectionPopup.tsx +++ b/src/frontend/src/components/ProjectDetailsV2/TaskSelectionPopup.tsx @@ -78,7 +78,7 @@ const TaskSelectionPopup = ({ taskId, body, feature }: TaskSelectionPopupPropTyp style={{ width: '20px' }} className="fmtm-text-primaryRed group-hover:fmtm-text-red-700" /> -

MB TILES

+

Basemaps

({ + ...entity, + osm_id: entity.osm_id ? parseInt(entity.osm_id, 10) : null, + })); + }, SetProjectDashboardLoading(state, action) { state.projectDashboardLoading = action.payload; }, diff --git a/src/frontend/src/store/slices/TaskSlice.ts b/src/frontend/src/store/slices/TaskSlice.ts index 084526ffaa..60d83c6be0 100644 --- a/src/frontend/src/store/slices/TaskSlice.ts +++ b/src/frontend/src/store/slices/TaskSlice.ts @@ -6,6 +6,7 @@ const initialState: TaskStateTypes = { taskLoading: false, taskInfo: [], selectedTask: null, + selectedFeatureProps: null, projectBoundaryLoading: false, projectBoundary: [], convertToOsmLoading: false, @@ -45,10 +46,15 @@ const TaskSlice = createSlice({ const tasks = taskInfo.length; state.taskData = { ...featureSubmissionCount, task_count: tasks }; }, + SetSelectedTask(state, action) { state.selectedTask = action.payload; }, + SetSelectedFeatureProps(state, action) { + state.selectedFeatureProps = action.payload; + }, + GetDownloadProjectBoundary(state, action) { state.projectBoundary = action.payload; }, diff --git a/src/frontend/src/store/types/IProject.ts b/src/frontend/src/store/types/IProject.ts index 5d2f815a36..fcd0035b18 100644 --- a/src/frontend/src/store/types/IProject.ts +++ b/src/frontend/src/store/types/IProject.ts @@ -24,6 +24,7 @@ export type ProjectStateTypes = { mobileFooterSelection: string; projectDetailsLoading: boolean; projectDashboardDetail: projectDashboardDetailTypes; + entityOsmMap: EntityOsmMap[]; projectDashboardLoading: boolean; geolocationStatus: boolean; projectCommentsList: projectCommentsListTypes[]; @@ -35,6 +36,13 @@ export type ProjectStateTypes = { projectActivityLoading: boolean; }; +type EntityOsmMap = { + id: string; + osm_id: string; + status: number; + updated_at: string; +}; + type tilesListTypes = { id: number; project_id: number; diff --git a/src/frontend/src/store/types/ITask.ts b/src/frontend/src/store/types/ITask.ts index 2f84f1d0c7..da1704812c 100644 --- a/src/frontend/src/store/types/ITask.ts +++ b/src/frontend/src/store/types/ITask.ts @@ -5,6 +5,7 @@ export type TaskStateTypes = { taskLoading: boolean; taskInfo: taskInfoType[]; selectedTask: number | null; + selectedFeatureProps: number | null; projectBoundaryLoading: boolean; projectBoundary: []; convertToOsmLoading: boolean; diff --git a/src/frontend/src/views/ProjectDetailsV2.tsx b/src/frontend/src/views/ProjectDetailsV2.tsx index 07d60e8f91..b7cb9b644c 100644 --- a/src/frontend/src/views/ProjectDetailsV2.tsx +++ b/src/frontend/src/views/ProjectDetailsV2.tsx @@ -4,7 +4,7 @@ import '../styles/home.scss'; import WindowDimension from '@/hooks/WindowDimension'; // import MapDescriptionComponents from '@/components/MapDescriptionComponents'; import ActivitiesPanel from '@/components/ProjectDetailsV2/ActivitiesPanel'; -import { ProjectById, GetProjectDashboard } from '@/api/Project'; +import { ProjectById, GetProjectDashboard, GetEntityInfo } from '@/api/Project'; import { ProjectActions } from '@/store/slices/ProjectSlice'; import CustomizedSnackbar from '@/utilities/CustomizedSnackbar'; import OnScroll from '@/hooks/OnScroll'; @@ -14,6 +14,7 @@ import AssetModules from '@/shared/AssetModules'; import FmtmLogo from '@/assets/images/hotLog.png'; import GenerateBasemap from '@/components/GenerateBasemap'; import TaskSelectionPopup from '@/components/ProjectDetailsV2/TaskSelectionPopup'; +import FeatureSelectionPopup from '@/components/ProjectDetailsV2/FeatureSelectionPopup'; import DialogTaskActions from '@/components/DialogTaskActions'; import MobileFooter from '@/components/ProjectDetailsV2/MobileFooter'; import MobileActivitiesContents from '@/components/ProjectDetailsV2/MobileActivitiesContents'; @@ -30,7 +31,6 @@ import getTaskStatusStyle from '@/utilfunctions/getTaskStatusStyle'; import { defaultStyles } from '@/components/MapComponent/OpenLayersComponent/helpers/styleUtils'; import MapLegends from '@/components/MapLegends'; import Accordion from '@/components/common/Accordion'; -import AsyncPopup from '@/components/MapComponent/OpenLayersComponent/AsyncPopup/AsyncPopup'; import Button from '@/components/common/Button'; import ProjectInfo from '@/components/ProjectDetailsV2/ProjectInfo'; import useOutsideClick from '@/hooks/useOutsideClick'; @@ -54,7 +54,8 @@ const Home = () => { const [divRef, toggle, handleToggle] = useOutsideClick(); const [mainView, setView] = useState(); - const [featuresLayer, setFeaturesLayer] = useState(); + const [selectedTaskArea, setSelectedTaskArea] = useState(); + const [selectedTaskFeature, setSelectedTaskFeature] = useState(); const [dataExtractUrl, setDataExtractUrl] = useState(null); const [dataExtractExtent, setDataExtractExtent] = useState(null); const [taskBoundariesLayer, setTaskBoundariesLayer] = useState>(null); @@ -72,6 +73,7 @@ const Home = () => { const state = CoreModules.useAppSelector((state) => state.project); const projectInfo = useAppSelector((state) => state.home.selectedProject); const selectedTask = useAppSelector((state) => state.task.selectedTask); + const selectedFeatureProps = useAppSelector((state) => state.task.selectedFeatureProps); const stateSnackBar = useAppSelector((state) => state.home.snackbar); const mobileFooterSelection = useAppSelector((state) => state.project.mobileFooterSelection); const mapTheme = useAppSelector((state) => state.theme.hotTheme); @@ -154,29 +156,12 @@ const Home = () => { setTaskBoundariesLayer(taskBoundariesFeatcol); }, [state.projectTaskBoundries[0]?.taskBoundries?.length]); - const dataExtractDataPopup = (properties: dataExtractPropertyType) => { - return ( -
-

- OSM ID: #{properties?.osm_id} -

-
-

- Tags: {properties?.tags} -

-

- Timestamp: {properties?.timestamp} -

-

- Changeset: {properties?.changeset} -

-

- Version: {properties?.version} -

-
-
- ); - }; + /** + * Sets the data extract URL when the data extract URL in the state changes. + */ + useEffect(() => { + setDataExtractUrl(state.projectInfo.data_extract_url); + }, [state.projectInfo.data_extract_url]); /** * Handles the click event on a project task area. @@ -184,8 +169,10 @@ const Home = () => { * @param {Object} properties - Properties attached to task area boundary feature. * @param {Object} feature - The clicked task area feature. */ - const projectClickOnMapTask = (properties, feature) => { - setFeaturesLayer(feature); + const projectClickOnTaskArea = (properties, feature) => { + // Close task feature popup, open task area popup + setSelectedTaskFeature(undefined); + setSelectedTaskArea(feature); let extent = properties.geometry.getExtent(); setDataExtractExtent(properties.geometry); @@ -211,11 +198,39 @@ const Home = () => { }; /** - * Sets the data extract URL when the data extract URL in the state changes. + * Handles the click event on a task feature (geometry). + * + * @param {Object} properties - Properties attached to map feature. + * @param {Object} feature - The clicked feature. */ - useEffect(() => { - setDataExtractUrl(state.projectInfo.data_extract_url); - }, [state.projectInfo.data_extract_url]); + const projectClickOnTaskFeature = (properties, feature) => { + // Close task area popup, open task feature popup + setSelectedTaskArea(undefined); + setSelectedTaskFeature(feature); + + dispatch(CoreModules.TaskActions.SetSelectedFeatureProps(properties)); + + // let extent = properties.geometry.getExtent(); + // setDataExtractExtent(properties.geometry); + + // mapRef.current?.scrollIntoView({ + // block: 'center', + // behavior: 'smooth', + // }); + + dispatch(ProjectActions.ToggleTaskModalStatus(true)); + + // // Fit the map view to the clicked feature's extent based on the window size + // if (windowSize.width < 768 && map.getView().getZoom() < 17) { + // map.getView().fit(extent, { + // padding: [10, 20, 300, 20], + // }); + // } else if (windowSize.width > 768 && map.getView().getZoom() < 17) { + // map.getView().fit(extent, { + // padding: [20, 350, 50, 10], + // }); + // } + }; const buildingStyle = { ...defaultStyles, @@ -245,6 +260,7 @@ const Home = () => { useEffect(() => { dispatch(GetProjectDashboard(`${import.meta.env.VITE_API_URL}/projects/project_dashboard/${projectId}`)); + dispatch(GetEntityInfo(`${import.meta.env.VITE_API_URL}/projects/${projectId}/entities/statuses`)); }, []); useEffect(async () => { @@ -445,7 +461,7 @@ const Home = () => { duration: 2000, }} layerProperties={{ name: 'project-area' }} - mapOnClick={projectClickOnMapTask} + mapOnClick={projectClickOnTaskArea} zoomToLayer zIndex={5} getTaskStatusStyle={(feature) => getTaskStatusStyle(feature, mapTheme)} @@ -462,11 +478,11 @@ const Home = () => { constrainResolution: true, duration: 2000, }} + mapOnClick={projectClickOnTaskFeature} zoomToLayer zIndex={5} /> )} -
} @@ -547,17 +563,18 @@ const Home = () => {
)}
- {featuresLayer != undefined && ( + {selectedTaskArea != undefined && ( - + } /> )} + {selectedTaskFeature != undefined && } ); };