Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock map feature if 'Map Feature In ODK' clicked #1516

Merged
merged 10 commits into from
May 24, 2024
Merged
16 changes: 16 additions & 0 deletions src/frontend/src/api/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
8 changes: 8 additions & 0 deletions src/frontend/src/api/ProjectTaskStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
130 changes: 99 additions & 31 deletions src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>;
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 (
<div
className={`fmtm-duration-1000 fmtm-z-[10002] fmtm-h-fit ${
Expand Down Expand Up @@ -76,30 +113,61 @@ const TaskFeatureSelectionPopup = ({ featureProperties }: TaskFeatureSelectionPo
</p>
</div>
</div>
{(task_status === 'READY' || task_status === 'LOCKED_FOR_MAPPING') && (
<div className="fmtm-p-2 sm:fmtm-p-5 fmtm-border-t">
<Button
btnText="MAP FEATURE IN ODK"
btnType="primary"
type="submit"
className="fmtm-font-bold !fmtm-rounded fmtm-text-sm !fmtm-py-2 !fmtm-w-full fmtm-flex fmtm-justify-center"
disabled={
(task_status === 'LOCKED_FOR_MAPPING' &&
authDetails &&
currentTaskInfo?.locked_by_uid !== authDetails?.id) ||
entity?.status !== 0
}
isLoading={updateEntityStatusLoading}
onClick={() => {
// XForm name is constructed from lower case project title with underscores
const projectName = projectInfo.title.toLowerCase().split(' ').join('_');
const projectCategory = projectInfo.xform_category;
const formName = `${projectName}_${projectCategory}`;

<div className="fmtm-p-2 sm:fmtm-p-5 fmtm-border-t">
<Button
btnText="MAP FEATURE IN ODK"
btnType="primary"
type="submit"
className="fmtm-font-bold !fmtm-rounded fmtm-text-sm !fmtm-py-2 !fmtm-w-full fmtm-flex fmtm-justify-center"
onClick={() => {
// XForm name is constructed from lower case project title with underscores
const projectName = projectInfo.title.toLowerCase().split(' ').join('_');
const projectCategory = projectInfo.xform_category;
const formName = `${projectName}_${projectCategory}`;

const entity = entityOsmMap.find((x) => x.osm_id === featureProperties?.osm_id);
const entityUuid = entity ? entity.id : null;
const entity = entityOsmMap.find((x) => x.osm_id === featureProperties?.osm_id);
const entityUuid = entity ? entity.id : null;

if (!formName || !entityUuid) {
return;
}
if (!formName || !entityUuid) {
return;
}
dispatch(
UpdateEntityStatus(`${import.meta.env.VITE_API_URL}/projects/${currentProjectId}/entity/status`, {
entity_id: entityUuid,
status: 1,
label: '',
}),
);
if (task_status === 'READY') {
dispatch(
ProjectTaskStatus(
`${import.meta.env.VITE_API_URL}/tasks/${currentTaskInfo?.id}/new-status/1`,
geoStyle,
taskBoundaryData,
currentProjectId,
taskFeature,
map,
view,
taskId,
authDetails,
{ project_id: currentProjectId },
),
);
}

document.location.href = `odkcollect://form/${formName}?existing=${entityUuid}`;
}}
/>
</div>
document.location.href = `odkcollect://form/${formName}?existing=${entityUuid}`;
}}
/>
</div>
)}
</div>
</div>
);
Expand Down
32 changes: 32 additions & 0 deletions src/frontend/src/store/slices/ProjectSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const initialState: ProjectStateTypes = {
},
entityOsmMap: [],
entityOsmMapLoading: false,
updateEntityStatusLoading: false,
projectDashboardLoading: false,
geolocationStatus: false,
projectCommentsList: [],
Expand Down Expand Up @@ -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;
},
Expand Down
8 changes: 7 additions & 1 deletion src/frontend/src/store/slices/TaskSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/store/types/IProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type ProjectStateTypes = {
projectDashboardDetail: projectDashboardDetailTypes;
entityOsmMap: EntityOsmMap[];
entityOsmMapLoading: boolean;
updateEntityStatusLoading: boolean;
projectDashboardLoading: boolean;
geolocationStatus: boolean;
projectCommentsList: projectCommentsListTypes[];
Expand Down
10 changes: 9 additions & 1 deletion src/frontend/src/store/types/ITask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type TaskStateTypes = {
taskLoading: boolean;
taskInfo: taskSubmissionInfoType[];
selectedTask: number | null;
selectedFeatureProps: number | null;
selectedFeatureProps: TaskFeatureSelectionProperties;
projectBoundaryLoading: boolean;
projectBoundary: [];
convertToOsmLoading: boolean;
Expand All @@ -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;
};
14 changes: 11 additions & 3 deletions src/frontend/src/views/ProjectDetailsV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -562,7 +562,7 @@ const Home = () => {
</div>
)}
</div>
{selectedTaskArea != undefined && (
{selectedTaskArea != undefined && selectedTaskFeature === undefined && (
<TaskSelectionPopup
taskId={selectedTask}
feature={selectedTaskArea}
Expand All @@ -573,7 +573,15 @@ const Home = () => {
}
/>
)}
{selectedTaskFeature != undefined && <FeatureSelectionPopup featureProperties={selectedFeatureProps} />}
{selectedTaskFeature != undefined && selectedTask && selectedTaskArea && (
<FeatureSelectionPopup
map={map}
view={mainView}
featureProperties={selectedFeatureProps}
taskId={selectedTask}
taskFeature={selectedTaskArea}
/>
)}
</div>
);
};
Expand Down
Loading