From 094de30bed64446f0e2bed3265cc62ce2b7438f4 Mon Sep 17 00:00:00 2001 From: NSUWAL123 Date: Mon, 23 Dec 2024 15:12:42 +0545 Subject: [PATCH 1/8] fix(submissions): update entity status on submission approve or reject --- .../ProjectSubmissions/SubmissionsTable.tsx | 2 ++ .../UpdateReviewStatusModal.tsx | 30 +++++++++++++------ .../src/store/slices/SubmissionSlice.ts | 2 ++ src/frontend/src/store/types/ISubmissions.ts | 2 ++ src/frontend/src/views/SubmissionDetails.tsx | 2 ++ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx index 52edfe47bb..126a571afe 100644 --- a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx +++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx @@ -482,6 +482,8 @@ const SubmissionsTable = ({ toggleView }) => { projectId: projectId, reviewState: row?.__system?.reviewState, taskUid: taskUid, + entity_id: row?.feature, + label: row?.meta?.entity?.label, }), ); }} diff --git a/src/frontend/src/components/ProjectSubmissions/UpdateReviewStatusModal.tsx b/src/frontend/src/components/ProjectSubmissions/UpdateReviewStatusModal.tsx index e756fcbcdc..09f4812995 100644 --- a/src/frontend/src/components/ProjectSubmissions/UpdateReviewStatusModal.tsx +++ b/src/frontend/src/components/ProjectSubmissions/UpdateReviewStatusModal.tsx @@ -7,7 +7,8 @@ import { UpdateReviewStateService } from '@/api/SubmissionService'; import TextArea from '../common/TextArea'; import Button from '../common/Button'; import { useAppSelector } from '@/types/reduxTypes'; -import { PostProjectComments } from '@/api/Project'; +import { PostProjectComments, UpdateEntityState } from '@/api/Project'; +import { entity_state } from '@/types/enums'; // Note these id values must be camelCase to match what ODK Central requires const reviewList: reviewListType[] = [ @@ -23,12 +24,6 @@ const reviewList: reviewListType[] = [ className: 'fmtm-bg-[#E9DFCF] fmtm-text-[#D99F00] fmtm-border-[#D99F00]', hoverClass: 'hover:fmtm-text-[#D99F00] hover:fmtm-border-[#D99F00]', }, - { - id: 'rejected', - title: 'Rejected', - className: 'fmtm-bg-[#E8D5D5] fmtm-text-[#D73F37] fmtm-border-[#D73F37]', - hoverClass: 'hover:fmtm-text-[#D73F37] hover:fmtm-border-[#D73F37]', - }, ]; const UpdateReviewStatusModal = () => { @@ -57,6 +52,17 @@ const UpdateReviewStatusModal = () => { }, ), ); + + dispatch( + UpdateEntityState( + `${import.meta.env.VITE_API_URL}/projects/${updateReviewStatusModal.projectId}/entity/status`, + { + entity_id: updateReviewStatusModal.entity_id, + status: reviewStatus === 'approved' ? entity_state['SURVEY_SUBMITTED'] : entity_state['MARKED_BAD'], + label: updateReviewStatusModal.label, + }, + ), + ); } if (noteComments.trim().length > 0) { dispatch( @@ -79,6 +85,8 @@ const UpdateReviewStatusModal = () => { taskId: null, reviewState: '', taskUid: null, + entity_id: null, + label: null, }), ); dispatch(SubmissionActions.UpdateReviewStateLoading(false)); @@ -91,11 +99,11 @@ const UpdateReviewStatusModal = () => {

Update Review Status

} - className="!fmtm-w-fit !fmtm-outline-none fmtm-rounded-xl" + className="!fmtm-w-[23rem] !fmtm-outline-none fmtm-rounded-xl" description={
-
+
{reviewList.map((reviewBtn) => (
From 27ee849fc26dba2c5fe44b5252f9ff5a150ff541 Mon Sep 17 00:00:00 2001 From: NSUWAL123 Date: Wed, 25 Dec 2024 10:34:17 +0545 Subject: [PATCH 6/8] fix(projectModel): update outline type --- src/frontend/src/models/project/projectModel.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/frontend/src/models/project/projectModel.ts b/src/frontend/src/models/project/projectModel.ts index acdfb21793..a0f1291d77 100644 --- a/src/frontend/src/models/project/projectModel.ts +++ b/src/frontend/src/models/project/projectModel.ts @@ -25,16 +25,7 @@ export type projectInfoType = { name: string; outline: { type: string; - geometry: { - type: string; - coordinates: []; - }; - properties: { - id: number; - bbox: [number, number, number, number]; - }; - id: number; - bbox: null | number[]; + coordinates: []; }; priority: number; location_str: string; From 96c1d264656192ab2002be3500a8da6e243e2e21 Mon Sep 17 00:00:00 2001 From: NSUWAL123 Date: Wed, 25 Dec 2024 10:38:22 +0545 Subject: [PATCH 7/8] fix(vectorLayer): processGeojson prop function add --- .../OpenLayersComponent/Layers/VectorLayer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js index a7ac7532a0..05f7874d30 100644 --- a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js +++ b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js @@ -57,6 +57,7 @@ const VectorLayer = ({ layerProperties, rotation, getAOIArea, + processGeojson, }) => { const [vectorLayer, setVectorLayer] = useState(null); useEffect(() => { @@ -185,8 +186,7 @@ const VectorLayer = ({ async function loadFgbRemote(filterExtent = true, extractGeomCol = true) { this.clear(); - const filteredFeatures = []; - + let filteredFeatures = []; for await (let feature of FGBGeoJson.deserialize(fgbUrl, fgbBoundingBox(fgbExtent.getExtent()))) { if (extractGeomCol && feature.geometry.type === 'GeometryCollection') { // Extract first geom from geomcollection @@ -209,6 +209,11 @@ const VectorLayer = ({ filteredFeatures.push(extractGeom); } } + // Process Geojson if needed i.e. filter, modify, etc + // ex: in our use case we are filtering only rejected entities + if (processGeojson) { + filteredFeatures = processGeojson(filteredFeatures); + } this.addFeatures(filteredFeatures); } From f0b6f87e0c4f38e7875c310cf8f3c298a5de46c8 Mon Sep 17 00:00:00 2001 From: NSUWAL123 Date: Wed, 25 Dec 2024 10:51:18 +0545 Subject: [PATCH 8/8] feat(projectDetailsV2): pulse rejected entities --- src/frontend/src/views/ProjectDetailsV2.tsx | 75 ++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/views/ProjectDetailsV2.tsx b/src/frontend/src/views/ProjectDetailsV2.tsx index b1cb292918..d3b622e94f 100644 --- a/src/frontend/src/views/ProjectDetailsV2.tsx +++ b/src/frontend/src/views/ProjectDetailsV2.tsx @@ -34,9 +34,13 @@ import { useAppSelector } from '@/types/reduxTypes'; import Comments from '@/components/ProjectDetailsV2/Comments'; import { Geolocation } from '@/utilfunctions/Geolocation'; import Instructions from '@/components/ProjectDetailsV2/Instructions'; -import { CustomCheckbox } from '@/components/common/Checkbox'; import useDocumentTitle from '@/utilfunctions/useDocumentTitle'; import QrcodeComponent from '@/components/QrcodeComponent'; +import { Feature } from 'ol'; +import { Polygon } from 'ol/geom'; +import { Style } from 'ol/style'; +import { Stroke } from 'ol/style'; +import { entity_state } from '@/types/enums'; const ProjectDetailsV2 = () => { useDocumentTitle('Project Details'); @@ -67,6 +71,7 @@ const ProjectDetailsV2 = () => { const taskModalStatus = CoreModules.useAppSelector((state) => state.project.taskModalStatus); const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails); const entityOsmMap = useAppSelector((state) => state?.project?.entityOsmMap); + const projectDetails = useAppSelector((state) => state.project.projectInfo); useEffect(() => { if (state.projectInfo.name) { @@ -251,6 +256,48 @@ const ProjectDetailsV2 = () => { dispatch(GetEntityInfo(`${import.meta.env.VITE_API_URL}/projects/${projectId}/entities/statuses`)); }, []); + // filter rejected entity + const filterRejectedEntity = (features) => { + if (features?.length === 0) return []; + const badEntities = entityOsmMap?.filter((entity) => entity?.status === entity_state['MARKED_BAD']); + + return badEntities?.map((entity) => { + const feature = features?.find((feature) => feature?.getProperties()?.osm_id === entity?.osm_id); + return feature; + }); + }; + + // pulse rejected entity feature stroke effect + useEffect(() => { + if (!map) return; + let layer; + let width = 1; + let expanding = true; + + const interval = setInterval(() => { + const allLayers = map?.getAllLayers(); + // layer representing bad entities + layer = allLayers?.find((layer) => layer.getProperties().name === 'bad-entities'); + + if (expanding) { + width += 0.3; + if (width >= 6) expanding = false; + } else { + width -= 0.3; + if (width <= 1) expanding = true; + } + + // apply style to the layer + layer.setStyle( + new Style({ + stroke: new Stroke({ color: 'rgb(215,63,62,0.6)', width: width }), + }), + ); + }, 50); + + return () => clearInterval(interval); + }, [map, entityOsmMap]); + return (
{/* Customized Modal For Generate Tiles */} @@ -428,7 +475,7 @@ const ProjectDetailsV2 = () => { }} /> )} - {dataExtractUrl && isValidUrl(dataExtractUrl) && dataExtractExtent && ( + {dataExtractUrl && isValidUrl(dataExtractUrl) && dataExtractExtent && selectedTask && ( { zIndex={5} /> )} + {/* layer to display rejected entities */} + {dataExtractUrl && + isValidUrl(dataExtractUrl) && + projectDetails?.outline?.coordinates && + entityOsmMap?.length > 0 && ( + filterRejectedEntity(features)} + layerProperties={{ name: 'bad-entities' }} + /> + )}