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

Feat/data conflation UI #1743

Merged
merged 47 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
41938a2
fix(dialogTaskActions): on lock task for validation, redirect to subm…
NSUWAL123 Jul 29, 2024
1c8facd
fix(projectTaskStatus): syle, existingData, feature prop optional
NSUWAL123 Jul 29, 2024
b4388d5
fix(projectDetails): reorder updateTaskStatus arguments
NSUWAL123 Jul 29, 2024
3c63d64
fix(submissionTable): btn add to update task as mapped
NSUWAL123 Jul 29, 2024
f7a15af
fix(dialogTaskActions): if validated show merge with OSM btn & redire…
NSUWAL123 Jul 29, 2024
a221a51
fix(dataConflation): data conflation component and route setup
NSUWAL123 Jul 29, 2024
811f8b6
fix(merge): merge conflict solve
NSUWAL123 Jul 30, 2024
856c0c4
feat(taskInfo): conflation task section slicing
NSUWAL123 Jul 30, 2024
ae4eb13
feat(conflationMap): map & legend setup
NSUWAL123 Jul 30, 2024
5650926
fix(button): outline remove from button
NSUWAL123 Jul 30, 2024
f47b573
feat(dataConflation): merge OSM modal, taskInfo, conflationMap sectio…
NSUWAL123 Jul 30, 2024
e01d34d
fix(taskInfo): style fix
NSUWAL123 Jul 30, 2024
6e119f2
feat(dataConflation): submissionConflation component add, UI style fix
NSUWAL123 Jul 31, 2024
96bf18c
feat(submissionConflation): component UI slicing
NSUWAL123 Jul 31, 2024
42fd14b
fix(modal): show dialogTrigger only if dialogOpen prop present
NSUWAL123 Jul 31, 2024
3a788af
fix(dataConflation): UI fix
NSUWAL123 Jul 31, 2024
9acfd58
feat(mergeAttributes): UI slicing for merge attributes modal
NSUWAL123 Jul 31, 2024
2e4ae4e
feat(submissionConflation): add mergeAttributes component
NSUWAL123 Jul 31, 2024
272e8e8
fix(customTable): remove hover effect & add row bg distinguishable
NSUWAL123 Jul 31, 2024
543e762
feat(dataConflation): skeleton loader add
NSUWAL123 Jul 31, 2024
13988ee
fix(mergeAttributes): preventDefault and stopPropagation add to onCli…
NSUWAL123 Jul 31, 2024
0b6021e
Merge branch 'development' of github.com:hotosm/fmtm into feat/data-c…
NSUWAL123 Aug 2, 2024
dc70dcc
feat(dataConflation): store, slice add
NSUWAL123 Aug 2, 2024
faf2151
feat(dataConflation): submissionConflationGeojson service add
NSUWAL123 Aug 2, 2024
f79e22b
feat(dataConflation): submissionConflationGeojson service fetch
NSUWAL123 Aug 2, 2024
029778d
fix(dataConflation): UI style fix
NSUWAL123 Aug 5, 2024
a249e19
feat(conflationMap): display submissionConflation on map
NSUWAL123 Aug 5, 2024
ad0ed42
feat(conflationMap): on map feature click dispatch action to set sele…
NSUWAL123 Aug 6, 2024
5fadf63
feat(submissionConflation): replace actual data with dummy data
NSUWAL123 Aug 6, 2024
fa8998d
feat(mergeAttributes): replace dummy data with actual data
NSUWAL123 Aug 6, 2024
23cb6ef
feat(dataConflation): setSelectedFeatureOSMId action add
NSUWAL123 Aug 6, 2024
191327f
fix(dataConflation): UI style fix according to feature click
NSUWAL123 Aug 6, 2024
c50ddac
Merge branch 'development' of github.com:hotosm/fmtm into feat/data-c…
NSUWAL123 Aug 6, 2024
178772b
fix(conflationMap): update accessor key
NSUWAL123 Aug 6, 2024
ac51b91
fix(dataConflation): style fix
NSUWAL123 Aug 6, 2024
ccccf26
fix(submissionConflation): remove commented code
NSUWAL123 Aug 6, 2024
b9a628e
feat(taskInfo): task feature, conflict counts calculation
NSUWAL123 Aug 7, 2024
71aef57
fix(dataConflation): remove modal
NSUWAL123 Aug 7, 2024
b015eea
fix(dataConflation): loading state add
NSUWAL123 Aug 7, 2024
36248cd
fix(mergeAttributes): title add, hover effects add
NSUWAL123 Aug 7, 2024
d7e5f76
fix(mergeConflict): merge conflict solve
NSUWAL123 Aug 8, 2024
41e1896
fix(updateTaskStatus): update prop type
NSUWAL123 Aug 8, 2024
e94e150
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 8, 2024
66962e6
Merge branch 'development' of github.com:hotosm/fmtm into feat/data-c…
NSUWAL123 Aug 28, 2024
d22ff4c
fix(submissionsTable): replace empty string with null
NSUWAL123 Aug 28, 2024
4eb7924
fix(dialogTaskActions): hide merge data with OSM button
NSUWAL123 Aug 29, 2024
d6c44dc
Merge branch 'feat/data-conflation-ui' of github.com:NSUWAL123/fmtm i…
NSUWAL123 Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/frontend/src/api/DataConflation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from 'axios';
import { DataConflationActions } from '@/store/slices/DataConflationSlice';

export const SubmissionConflationGeojsonService: Function = (url: string) => {
return async (dispatch) => {
const getSubmissionGeojsonConflation = async (url) => {
try {
dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(true));
const getSubmissionConflationGeojsonResponse = await axios.get(url);
dispatch(DataConflationActions.SetSubmissionConflationGeojson(getSubmissionConflationGeojsonResponse.data));
dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(false));
} catch (error) {
dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(false));
}
};

await getSubmissionGeojsonConflation(url);
};
};
54 changes: 30 additions & 24 deletions src/frontend/src/api/ProjectTaskStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,49 @@ import CoreModules from '@/shared/CoreModules';
import { CommonActions } from '@/store/slices/CommonSlice';
import { projectTaskBoundriesType } from '@/models/project/projectModel';

const UpdateTaskStatus = (
export const UpdateTaskStatus = (
url: string,
style: any,
existingData: projectTaskBoundriesType[],
currentProjectId: string,
feature: Record<string, any>,
taskId: number,
taskId: string,
body: any,
params: { project_id: string },
style?: any,
existingData?: projectTaskBoundriesType[],
feature?: Record<string, any>,
) => {
return async (dispatch) => {
const updateTask = async (url: string, body: any, feature: Record<string, any>, params: { project_id: string }) => {
const updateTask = async (
url: string,
body: any,
params: { project_id: string },
feature?: Record<string, any>,
) => {
try {
dispatch(CommonActions.SetLoading(true));

const response = await CoreModules.axios.post(url, body, { params });
dispatch(ProjectActions.UpdateProjectTaskActivity(response.data));

await feature.setStyle(style);
if (feature && style) {
await feature.setStyle(style);

// assign userId to locked_by_user if status is locked_for_mapping or locked_for_validation
const prevProperties = feature.getProperties();
const isTaskLocked = ['LOCKED_FOR_MAPPING', 'LOCKED_FOR_VALIDATION'].includes(response.data.status);
const updatedProperties = { ...prevProperties, locked_by_user: isTaskLocked ? body.id : null };
feature.setProperties(updatedProperties);
// assign userId to locked_by_user if status is locked_for_mapping or locked_for_validation
const prevProperties = feature.getProperties();
const isTaskLocked = ['LOCKED_FOR_MAPPING', 'LOCKED_FOR_VALIDATION'].includes(response.data.status);
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,
task_status: response.data.status,
}),
);
}

dispatch(
ProjectActions.UpdateProjectTaskBoundries({
projectId: currentProjectId,
taskId,
locked_by_uid: body?.id,
locked_by_username: body?.username,
task_status: response.data.status,
}),
);
dispatch(CommonActions.SetLoading(false));
dispatch(
HomeActions.SetSnackBar({
Expand All @@ -60,8 +68,6 @@ const UpdateTaskStatus = (
);
}
};
await updateTask(url, body, feature, params);
await updateTask(url, body, params, feature);
};
};

export default UpdateTaskStatus;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import Accordion from '@/components/common/Accordion';
import useOutsideClick from '@/hooks/useOutsideClick';

const legendArray = [
{ name: 'No conflicts', color: '#40AC8C' },
{ name: 'Tag conflicts', color: '#DB9D35' },
{ name: 'Geometry conflicts', color: '#3C4A5E' },
{ name: 'Both conflicts', color: '#EB8B8B' },
];

const MapLegends = () => {
return (
<div className="fmtm-flex fmtm-flex-col fmtm-gap-1 fmtm-pb-2">
{legendArray?.map((legend) => (
<div className="fmtm-flex fmtm-items-center fmtm-gap-2">
<div
style={{ backgroundColor: legend.color }}
className={`fmtm-w-[0.875rem] fmtm-h-[0.875rem] fmtm-min-w-[0.875rem] fmtm-min-h-[0.875rem]`}
></div>
<p className="fmtm-text-xs fmtm-text-[#484848]">{legend.name}</p>
</div>
))}
</div>
);
};

const MapLegend = () => {
const [legendRef, legendToggle, handleLegendToggle] = useOutsideClick();

return (
<>
<Accordion
hasSeperator={false}
ref={legendRef}
body={<MapLegends />}
header={
<div className="fmtm-flex fmtm-items-center fmtm-gap-1 sm:fmtm-gap-2">
<p className="fmtm-text-base fmtm-font-bold">Legend</p>
</div>
}
onToggle={() => {
handleLegendToggle();
}}
className="fmtm-py-0 !fmtm-pb-0 fmtm-rounded-lg hover:fmtm-bg-gray-50"
collapsed={!legendToggle}
/>
</>
);
};

export default MapLegend;
68 changes: 68 additions & 0 deletions src/frontend/src/components/DataConflation/ConflationMap/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { MapContainer as MapComponent, useOLMap } from '@/components/MapComponent/OpenLayersComponent';
import LayerSwitcherControl from '@/components/MapComponent/OpenLayersComponent/LayerSwitcher/index';
import MapLegend from '@/components/DataConflation/ConflationMap/MapLegend';
import Button from '@/components/common/Button';
import { useAppSelector } from '@/types/reduxTypes';
import { VectorLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
import { useDispatch } from 'react-redux';
import { DataConflationActions } from '@/store/slices/DataConflationSlice';

const ConflationMap = () => {
const dispatch = useDispatch();

const submissionConflationGeojson = useAppSelector((state) => state.dataconflation.submissionConflationGeojson);
const submissionConflationGeojsonLoading = useAppSelector(
(state) => state.dataconflation.submissionConflationGeojsonLoading,
);

const { mapRef, map } = useOLMap({
center: [0, 0],
zoom: 4,
});

return (
<>
<MapComponent
ref={mapRef}
mapInstance={map}
className="map naxatw-relative naxatw-min-h-full !fmtm-h-full fmtm-w-[200px] fmtm-rounded-lg fmtm-overflow-hidden"
>
<VectorLayer
geojson={submissionConflationGeojson}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
constrainResolution: true,
duration: 2000,
}}
zoomToLayer
mapOnClick={(properties, feature) => {
dispatch(
DataConflationActions.SetSelectedFeatureOSMId(
feature?.getProperties()?.xid
? feature.getProperties().xid
: feature.getProperties()?.osm_id.toString(),
),
);
}}
/>
<LayerSwitcherControl visible="osm" />
<div className="fmtm-absolute fmtm-bottom-20 sm:fmtm-bottom-3 fmtm-left-3 fmtm-z-50 fmtm-rounded-lg">
<MapLegend />
</div>
<div className="fmtm-absolute fmtm-bottom-20 sm:fmtm-top-3 fmtm-right-3 fmtm-z-50 fmtm-rounded-lg fmtm-h-fit">
<Button
btnText="Upload to OSM"
type="button"
btnType="primary"
onClick={() => {}}
disabled={submissionConflationGeojsonLoading}
/>
</div>
</MapComponent>
</>
);
};

export default ConflationMap;
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useState } from 'react';
import { Modal } from '@/components/common/Modal';
import Button from '@/components/common/Button';
import Table, { TableHeader } from '@/components/common/CustomTable';

type mergeAttributesPropType = {
selectedConflateMethod: 'submission_feature' | 'osm_feature' | 'merge_attributes' | '';
setSelectedConflateMethod: (value: '') => void;
submissionTags: Record<string, any>;
osmTags: Record<string, any>;
};

const MergeAttributes = ({
selectedConflateMethod,
setSelectedConflateMethod,
submissionTags,
osmTags,
}: mergeAttributesPropType) => {
const [chosenAttribute, setChosenAttribute] = useState({});

const tableData: any = [];
for (const [key, value] of Object.entries(osmTags)) {
if (submissionTags?.[key] && submissionTags?.[key] !== value) {
tableData.push({ name: key, osm: value, submission: submissionTags?.[key] });
}
}

return (
<>
<Modal
title={<p className="fmtm-text-left">Merge Data With OSM</p>}
description={
<div className="fmtm-mt-1">
<div>
<Table
flag="primarytable"
style={{ maxHeight: '60vh', width: '100%' }}
data={tableData}
onRowClick={() => {}}
isLoading={false}
>
<TableHeader
dataField="Feature Name"
headerClassName="featureHeader"
rowClassName="featureRow"
dataFormat={(row) => <div title={row?.name}>{row?.name}</div>}
/>
<TableHeader
dataField="OSM Tags"
headerClassName="osmHeader"
rowClassName="osmRow !fmtm-p-0"
dataFormat={(row) => (
<div
className={`fmtm-flex fmtm-items-center fmtm-justify-between !fmtm-h-full fmtm-absolute fmtm-top-0 fmtm-left-0 fmtm-w-full fmtm-p-3 ${
chosenAttribute[row?.name] === row?.osm
? 'fmtm-bg-[#9FD5C5]'
: 'hover:fmtm-bg-gray-100 fmtm-duration-200'
}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setChosenAttribute((prev) => ({ ...prev, [row?.name]: row?.osm }));
}}
title={row?.osm}
>
<label
htmlFor={row?.name}
className={`fmtm-text-base ${
chosenAttribute[row?.name] === row?.osm ? 'fmtm-text-white' : 'fmtm-text-gray-500'
} fmtm-mb-[2px] fmtm-cursor-pointer fmtm-flex fmtm-items-center fmtm-gap-2`}
>
<p>{row?.osm}</p>
</label>
<input
type="radio"
id={row?.name}
name={row?.name}
value={row?.osm}
className={`fmtm-accent-[#5BAD8C] fmtm-cursor-pointer fmtm-bg-white`}
checked={chosenAttribute[row?.name] === row?.osm}
/>
</div>
)}
/>
<TableHeader
dataField="Submission #457"
headerClassName="submissionHeader"
rowClassName="submissionRow !fmtm-p-0"
dataFormat={(row) => (
<div
className={`fmtm-flex fmtm-items-center fmtm-justify-between !fmtm-h-full fmtm-absolute fmtm-top-0 fmtm-left-0 fmtm-w-full fmtm-p-3 ${
chosenAttribute[row?.name] === row?.submission
? 'fmtm-bg-[#9FD5C5]'
: 'hover:fmtm-bg-gray-100 fmtm-duration-200'
}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setChosenAttribute((prev) => ({ ...prev, [row?.name]: row?.submission }));
}}
title={row?.submission}
>
<label
htmlFor={row?.name}
className={`fmtm-text-base ${
chosenAttribute[row?.name] === row?.submission ? 'fmtm-text-white' : 'fmtm-text-gray-500'
} fmtm-mb-[2px] fmtm-cursor-pointer fmtm-flex fmtm-items-center fmtm-gap-2`}
>
<p>{row?.submission}</p>
</label>
<input
type="radio"
id={row?.name}
name={row?.name}
value={row?.submission}
className={`fmtm-accent-[#5BAD8C] fmtm-cursor-pointer`}
checked={chosenAttribute[row?.name] === row?.submission}
/>
</div>
)}
/>
</Table>
</div>
<div className="fmtm-flex fmtm-gap-[0.625rem] fmtm-justify-center fmtm-mt-5">
<Button
btnText="Save"
btnType="primary"
onClick={() => {
setSelectedConflateMethod('');
}}
type="button"
className="!fmtm-rounded fmtm-text-sm !fmtm-py-2 fmtm-font-bold"
/>
</div>
</div>
}
open={selectedConflateMethod === 'merge_attributes'}
onOpenChange={() => setSelectedConflateMethod('')}
className="fmtm-max-w-[70rem] fmtm-rounded-xl"
/>
</>
);
};

export default MergeAttributes;
Loading
Loading