From dfb5e7bc31752466c6b1a48ff4417b24188e2103 Mon Sep 17 00:00:00 2001 From: Nishit Suwal <81785002+NSUWAL123@users.noreply.github.com> Date: Fri, 24 May 2024 19:49:54 +0545 Subject: [PATCH] fix: lock map feature if 'Map Feature In ODK' clicked (#1516) * feat(project): updateEntityStatus add to update entity status * feat(project): reducer function to update entityStatus and track entityUpdateLoading state * fix(taskSlice): initial state for selectedFeatureProps * feat(ITask): taskFeatureSelectionProperties type add * feat(projectDetailsV2): taskId, taskFeature props pass to featureSlectionPopup component * feat(featureSlectionPopup): post update entity status on Map In ODK button click * feat (project): update projectTaskBoundries locked by uid and username * fix(projectDetailsV2): pass map & view to featureSelectionPopup as props * feat(featureSelectionPopup): lock parent task on child entity lock for map, disable map feature in odk button under certain conditions --- src/frontend/src/api/Project.js | 16 +++ src/frontend/src/api/ProjectTaskStatus.js | 8 ++ .../FeatureSelectionPopup.tsx | 130 +++++++++++++----- src/frontend/src/store/slices/ProjectSlice.ts | 32 +++++ src/frontend/src/store/slices/TaskSlice.ts | 8 +- src/frontend/src/store/types/IProject.ts | 1 + src/frontend/src/store/types/ITask.ts | 10 +- src/frontend/src/views/ProjectDetailsV2.tsx | 14 +- 8 files changed, 183 insertions(+), 36 deletions(-) diff --git a/src/frontend/src/api/Project.js b/src/frontend/src/api/Project.js index 33552dc867..3f6122c966 100755 --- a/src/frontend/src/api/Project.js +++ b/src/frontend/src/api/Project.js @@ -299,6 +299,22 @@ export const GetProjectTaskActivity = (url) => { }; }; +export const UpdateEntityStatus = (url, payload) => { + return async (dispatch) => { + const updateEntityStatus = async (url, payload) => { + try { + dispatch(ProjectActions.UpdateEntityStatusLoading(true)); + const response = await CoreModules.axios.post(url, payload); + dispatch(ProjectActions.UpdateEntityStatus(response.data)); + dispatch(ProjectActions.UpdateEntityStatusLoading(false)); + } catch (error) { + dispatch(ProjectActions.UpdateEntityStatusLoading(false)); + } + }; + await updateEntityStatus(url, payload); + }; +}; + export const DownloadSubmissionGeojson = (url, projectName) => { return async (dispatch) => { dispatch(ProjectActions.SetDownloadSubmissionGeojsonLoading(true)); diff --git a/src/frontend/src/api/ProjectTaskStatus.js b/src/frontend/src/api/ProjectTaskStatus.js index bda330079f..918aec0c4e 100755 --- a/src/frontend/src/api/ProjectTaskStatus.js +++ b/src/frontend/src/api/ProjectTaskStatus.js @@ -21,6 +21,14 @@ const UpdateTaskStatus = (url, style, existingData, currentProjectId, feature, m const updatedProperties = { ...prevProperties, locked_by_user: isTaskLocked ? body.id : null }; feature.setProperties(updatedProperties); + dispatch( + ProjectActions.UpdateProjectTaskBoundries({ + projectId: currentProjectId, + taskId, + locked_by_uid: body?.id, + locked_by_username: body?.username, + }), + ); dispatch(CommonActions.SetLoading(false)); dispatch( HomeActions.SetSnackBar({ diff --git a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx index cf8310f727..9244279c3e 100644 --- a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx +++ b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx @@ -1,28 +1,65 @@ // Popup used to display task feature info & link to ODK Collect -import React from 'react'; +import React, { useEffect, useState } 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; -}; +import environment from '@/environment'; +import { useParams } from 'react-router-dom'; +import { UpdateEntityStatus } from '@/api/Project'; +import { TaskFeatureSelectionProperties } from '@/store/types/ITask'; +import ProjectTaskStatus from '@/api/ProjectTaskStatus'; +import MapStyles from '@/hooks/MapStyles'; type TaskFeatureSelectionPopupPropType = { featureProperties: TaskFeatureSelectionProperties | null; + taskId: number; + taskFeature: Record; + map: any; + view: any; }; -const TaskFeatureSelectionPopup = ({ featureProperties }: TaskFeatureSelectionPopupPropType) => { +const TaskFeatureSelectionPopup = ({ + featureProperties, + taskId, + taskFeature, + map, + view, +}: TaskFeatureSelectionPopupPropType) => { const dispatch = CoreModules.useAppDispatch(); + const params = useParams(); + const geojsonStyles = MapStyles(); const taskModalStatus = CoreModules.useAppSelector((state) => state.project.taskModalStatus); const projectInfo = CoreModules.useAppSelector((state) => state.project.projectInfo); const entityOsmMap = CoreModules.useAppSelector((state) => state.project.entityOsmMap); + + const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails); + const currentProjectId = params.id; + const [task_status, set_task_status] = useState('READY'); + const projectData = CoreModules.useAppSelector((state) => state.project.projectTaskBoundries); + const projectIndex = projectData.findIndex((project) => project.id == currentProjectId); + const projectTaskActivityList = CoreModules.useAppSelector((state) => state?.project?.projectTaskActivity); + const taskBoundaryData = CoreModules.useAppSelector((state) => state.project.projectTaskBoundries); + const updateEntityStatusLoading = CoreModules.useAppSelector((state) => state.project.updateEntityStatusLoading); + const currentTaskInfo = { + ...taskBoundaryData?.[projectIndex]?.taskBoundries?.filter((task) => { + return task?.index == taskId; + })?.[0], + }; + const geoStyle = geojsonStyles['LOCKED_FOR_MAPPING']; + const entity = entityOsmMap.find((x) => x.osm_id === featureProperties?.osm_id); + + useEffect(() => { + if (projectIndex != -1) { + const currentStatus = projectTaskActivityList.length > 0 ? projectTaskActivityList[0].status : 'READY'; + const findCorrectTaskStatusIndex = environment.tasksStatus.findIndex((data) => data?.label == currentStatus); + const tasksStatus = + taskFeature?.id_ != undefined ? environment?.tasksStatus[findCorrectTaskStatusIndex]?.['label'] : ''; + set_task_status(tasksStatus); + } + }, [projectTaskActivityList, taskId, taskFeature, entityOsmMap]); + return (
+ {(task_status === 'READY' || task_status === 'LOCKED_FOR_MAPPING') && ( +
+
+ document.location.href = `odkcollect://form/${formName}?existing=${entityUuid}`; + }} + /> + + )} ); diff --git a/src/frontend/src/store/slices/ProjectSlice.ts b/src/frontend/src/store/slices/ProjectSlice.ts index 76c7bb53da..973c4d2b6e 100755 --- a/src/frontend/src/store/slices/ProjectSlice.ts +++ b/src/frontend/src/store/slices/ProjectSlice.ts @@ -32,6 +32,7 @@ const initialState: ProjectStateTypes = { }, entityOsmMap: [], entityOsmMapLoading: false, + updateEntityStatusLoading: false, projectDashboardLoading: false, geolocationStatus: false, projectCommentsList: [], @@ -142,6 +143,37 @@ const ProjectSlice = createSlice({ UpdateProjectTaskActivity(state, action) { state.projectTaskActivity = [action.payload, ...state.projectTaskActivity]; }, + UpdateEntityStatusLoading(state, action) { + state.updateEntityStatusLoading = action.payload; + }, + UpdateEntityStatus(state, action) { + const updatedEntityOsmMap = state.entityOsmMap?.map((entity) => { + if (entity.id === action.payload.id) { + return action.payload; + } + return entity; + }); + state.entityOsmMap = updatedEntityOsmMap; + }, + UpdateProjectTaskBoundries(state, action) { + const updatedProjectTaskBoundries = state.projectTaskBoundries?.map((boundary) => { + if (boundary.id == action.payload.projectId) { + const updatedBoundary = boundary?.taskBoundries?.map((taskBoundary) => { + if (taskBoundary?.index === action.payload.taskId) { + return { + ...taskBoundary, + locked_by_uid: action.payload.locked_by_uid, + locked_by_username: action.payload.locked_by_username, + }; + } + return taskBoundary; + }); + return { id: boundary.id, taskBoundries: updatedBoundary }; + } + return boundary; + }); + state.projectTaskBoundries = updatedProjectTaskBoundries; + }, SetDownloadSubmissionGeojsonLoading(state, action) { state.downloadSubmissionLoading = action.payload; }, diff --git a/src/frontend/src/store/slices/TaskSlice.ts b/src/frontend/src/store/slices/TaskSlice.ts index d8299d7cea..dda28ab5f2 100644 --- a/src/frontend/src/store/slices/TaskSlice.ts +++ b/src/frontend/src/store/slices/TaskSlice.ts @@ -8,7 +8,13 @@ const initialState: TaskStateTypes = { taskLoading: false, taskInfo: [], selectedTask: null, - selectedFeatureProps: null, + selectedFeatureProps: { + osm_id: 0, + tags: '', + timestamp: '', + version: 0, + changeset: 0, + }, projectBoundaryLoading: false, projectBoundary: [], convertToOsmLoading: false, diff --git a/src/frontend/src/store/types/IProject.ts b/src/frontend/src/store/types/IProject.ts index 42d5c2d659..43c05c0aa9 100644 --- a/src/frontend/src/store/types/IProject.ts +++ b/src/frontend/src/store/types/IProject.ts @@ -26,6 +26,7 @@ export type ProjectStateTypes = { projectDashboardDetail: projectDashboardDetailTypes; entityOsmMap: EntityOsmMap[]; entityOsmMapLoading: boolean; + updateEntityStatusLoading: boolean; projectDashboardLoading: boolean; geolocationStatus: boolean; projectCommentsList: projectCommentsListTypes[]; diff --git a/src/frontend/src/store/types/ITask.ts b/src/frontend/src/store/types/ITask.ts index df7e7ae0ce..0e1870384d 100644 --- a/src/frontend/src/store/types/ITask.ts +++ b/src/frontend/src/store/types/ITask.ts @@ -4,7 +4,7 @@ export type TaskStateTypes = { taskLoading: boolean; taskInfo: taskSubmissionInfoType[]; selectedTask: number | null; - selectedFeatureProps: number | null; + selectedFeatureProps: TaskFeatureSelectionProperties; projectBoundaryLoading: boolean; projectBoundary: []; convertToOsmLoading: boolean; @@ -18,3 +18,11 @@ type downloadSubmissionLoadingTypes = { type: '' | 'json' | 'csv'; loading: boolean; }; + +export type TaskFeatureSelectionProperties = { + osm_id: number; + tags: string; + timestamp: string; + version: number; + changeset: number; +}; diff --git a/src/frontend/src/views/ProjectDetailsV2.tsx b/src/frontend/src/views/ProjectDetailsV2.tsx index 37a449f8b6..53765f7613 100644 --- a/src/frontend/src/views/ProjectDetailsV2.tsx +++ b/src/frontend/src/views/ProjectDetailsV2.tsx @@ -206,7 +206,7 @@ const Home = () => { */ const projectClickOnTaskFeature = (properties, feature) => { // Close task area popup, open task feature popup - setSelectedTaskArea(undefined); + // setSelectedTaskArea(undefined); setSelectedTaskFeature(feature); dispatch(CoreModules.TaskActions.SetSelectedFeatureProps(properties)); @@ -562,7 +562,7 @@ const Home = () => { )} - {selectedTaskArea != undefined && ( + {selectedTaskArea != undefined && selectedTaskFeature === undefined && ( { } /> )} - {selectedTaskFeature != undefined && } + {selectedTaskFeature != undefined && selectedTask && selectedTaskArea && ( + + )} ); };