Skip to content

Commit

Permalink
feat(frontend): allow selection of additional features during project…
Browse files Browse the repository at this point in the history
… creation (#1806)

* fix(selectForm): debug param add

* feat(createProjectService): post additionalFeature if additionalFeature uploaded

* feat(dataExtract): fileUpload component add to upload additional features

* fix(createNewProject): add additionalFeature state

* fix(createProject): additionalFeatureGeojson state add

* fix(fileInputComponent): style fix

* feat(newDefineAreaMap): show additional features on map if uploaded

* feat(dataExtractValidation): additionalFeature add validation

* fix(dataExtract): additionalFeature add to formValue for validation
  • Loading branch information
NSUWAL123 authored Sep 24, 2024
1 parent 1dcf2e8 commit 1819e8c
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 9 deletions.
52 changes: 50 additions & 2 deletions src/frontend/src/api/CreateProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
OrganisationListModel,
} from '@/models/createproject/createProjectModel';
import { CommonActions } from '@/store/slices/CommonSlice';
import { ValidateCustomFormResponse } from '@/store/types/ICreateProject';
import { isStatusSuccess } from '@/utilfunctions/commonUtils';

const CreateProjectService = (
Expand All @@ -17,6 +16,7 @@ const CreateProjectService = (
formUpload: any,
dataExtractFile: any,
isOsmExtract: boolean,
additionalFeature: any,
) => {
return async (dispatch) => {
dispatch(CreateProjectActions.CreateProjectLoading(true));
Expand Down Expand Up @@ -74,11 +74,26 @@ const CreateProjectService = (
throw new Error(`Request failed with status ${extractResponse.status}`);
}

// post additional feature if available
const postAdditionalFeature = await dispatch(
PostAdditionalFeatureService(
`${import.meta.env.VITE_API_URL}/projects/${projectId}/additional-entity`,
additionalFeature,
),
);

hasAPISuccess = postAdditionalFeature;
if (!hasAPISuccess) {
throw new Error(`Request failed`);
}

// Generate project files
const generateProjectFile = await dispatch(
GenerateProjectFilesService(
`${import.meta.env.VITE_API_URL}/projects/${projectId}/generate-project-data`,
projectData,
additionalFeature
? { ...projectData, additional_entities: [additionalFeature?.name?.split('.')?.[0]] }
: projectData,
formUpload,
),
);
Expand Down Expand Up @@ -221,6 +236,39 @@ const GenerateProjectFilesService = (url: string, projectData: any, formUpload:
};
};

const PostAdditionalFeatureService = (url: string, file: File) => {
return async (dispatch) => {
const PostAdditionalFeature = async (url, file) => {
let isAPISuccess = true;

try {
const additionalFeatureFormData = new FormData();
additionalFeatureFormData.append('geojson', file);

const response = await axios.post(url, additionalFeatureFormData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});

isAPISuccess = isStatusSuccess(response.status);
} catch (error: any) {
isAPISuccess = false;
dispatch(
CommonActions.SetSnackBar({
open: true,
message: JSON.stringify(error?.response?.data?.detail),
variant: 'error',
duration: 2000,
}),
);
}
return isAPISuccess;
};
return await PostAdditionalFeature(url, file);
};
};

const OrganisationService = (url: string) => {
return async (dispatch) => {
dispatch(CreateProjectActions.GetOrganisationListLoading(true));
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/components/common/FileInputComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const FileInputComponent = ({
<p>{customFile?.name}</p>
</div>
)}
<p className="fmtm-text-gray-700 fmtm-mt-5">{fileDescription}</p>
<p className="fmtm-text-gray-700 fmtm-mt-2">{fileDescription}</p>
</div>
);
};
Expand Down
58 changes: 54 additions & 4 deletions src/frontend/src/components/createnewproject/DataExtract.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { geojson as fgbGeojson } from 'flatgeobuf';
import React, { useEffect, useState } from 'react';
import Button from '@/components/common/Button';
import { useDispatch } from 'react-redux';
import { CommonActions } from '@/store/slices/CommonSlice';
Expand All @@ -14,16 +14,22 @@ import FileInputComponent from '@/components/common/FileInputComponent';
import DataExtractValidation from '@/components/createnewproject/validation/DataExtractValidation';
import NewDefineAreaMap from '@/views/NewDefineAreaMap';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
import { checkGeomTypeInGeojson } from '@/utilfunctions/checkGeomTypeInGeojson';
import { task_split_type } from '@/types/enums';
import { dataExtractGeojsonType } from '@/store/types/ICreateProject';
import { CustomCheckbox } from '@/components/common/Checkbox';

const dataExtractOptions = [
{ name: 'data_extract', value: 'osm_data_extract', label: 'Use OSM map features' },
{ name: 'data_extract', value: 'custom_data_extract', label: 'Upload custom map features' },
];

const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload }) => {
const DataExtract = ({
flag,
customDataExtractUpload,
setCustomDataExtractUpload,
additionalFeature,
setAdditionalFeature,
}) => {
useDocumentTitle('Create Project: Map Features');
const dispatch = useDispatch();
const navigate = useNavigate();
Expand All @@ -32,6 +38,7 @@ const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload
const projectAoiGeojson = useAppSelector((state) => state.createproject.drawnGeojson);
const dataExtractGeojson = useAppSelector((state) => state.createproject.dataExtractGeojson);
const isFgbFetching = useAppSelector((state) => state.createproject.isFgbFetching);
const additionalFeatureGeojson = useAppSelector((state) => state.createproject.additionalFeatureGeojson);

const submission = () => {
dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues));
Expand Down Expand Up @@ -247,7 +254,7 @@ const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload
resetFile(setCustomDataExtractUpload);
generateDataExtract();
}}
className="fmtm-mt-6"
className="fmtm-mt-4 !fmtm-mb-8 fmtm-text-base"
isLoading={isFgbFetching}
loadingText="Generating Map Features..."
disabled={dataExtractGeojson && customDataExtractUpload ? true : false}
Expand All @@ -272,6 +279,48 @@ const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload
/>
</>
)}
{extractWays && (
<div className="fmtm-mt-4">
<CustomCheckbox
key="uploadAdditionalFeature"
label="Upload Additional Features"
checked={formValues?.hasAdditionalFeature}
onCheckedChange={(status) => {
handleCustomChange('hasAdditionalFeature', status);
handleCustomChange('additionalFeature', null);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(null));
setAdditionalFeature(null);
}}
className="fmtm-text-black"
labelClickable
/>
{formValues?.hasAdditionalFeature && (
<>
<FileInputComponent
onChange={async (e) => {
if (e?.target?.files) {
const uploadedFile = e?.target?.files[0];
setAdditionalFeature(uploadedFile);
handleCustomChange('additionalFeature', uploadedFile);
const additionalFeatureGeojson = await convertFileToFeatureCol(uploadedFile);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(additionalFeatureGeojson));
}
}}
onResetFile={() => {
resetFile(setAdditionalFeature);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(null));
handleCustomChange('additionalFeature', null);
}}
customFile={additionalFeature}
btnText="Upload Additional Features"
accept=".geojson"
fileDescription="*The supported file formats are .geojson"
errorMsg={errors.additionalFeature}
/>
</>
)}
</div>
)}
</div>
<div className="fmtm-flex fmtm-gap-5 fmtm-mx-auto fmtm-mt-10 fmtm-my-5">
<Button
Expand Down Expand Up @@ -299,6 +348,7 @@ const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload
<NewDefineAreaMap
uploadedOrDrawnGeojsonFile={projectAoiGeojson}
buildingExtractedGeojson={dataExtractGeojson}
additionalFeatureGeojson={additionalFeatureGeojson}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) =>
};
useEffect(() => {
if (customFormFile && !customFileValidity) {
dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate-form`, customFormFile));
dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate-form?debug=true`, customFormFile));
}
}, [customFormFile]);

Expand Down
5 changes: 4 additions & 1 deletion src/frontend/src/components/createnewproject/SplitTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { task_split_type } from '@/types/enums';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
import { taskSplitOptionsType } from '@/store/types/ICreateProject';

const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => {
const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload, additionalFeature }) => {
useDocumentTitle('Create Project: Split Tasks');
const dispatch = useDispatch();
const navigate = useNavigate();
Expand All @@ -41,6 +41,7 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => {
const isTasksGenerated = useAppSelector((state) => state.createproject.isTasksGenerated);
const isFgbFetching = useAppSelector((state) => state.createproject.isFgbFetching);
const toggleSplittedGeojsonEdit = useAppSelector((state) => state.createproject.toggleSplittedGeojsonEdit);
const additionalFeatureGeojson = useAppSelector((state) => state.createproject.additionalFeatureGeojson);

const taskSplitOptions: taskSplitOptionsType[] = [
{
Expand Down Expand Up @@ -133,6 +134,7 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => {
projectDetails.customFormUpload,
customDataExtractUpload,
projectDetails.dataExtractWays === 'osm_data_extract',
additionalFeature,
),
);
dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...formValues }));
Expand Down Expand Up @@ -373,6 +375,7 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => {
}
// toggleSplittedGeojsonEdit
hasEditUndo
additionalFeatureGeojson={additionalFeatureGeojson}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ interface ProjectValues {
data_extractFile: object;
data_extract_options: string;
customDataExtractUpload: string;
hasAdditionalFeature: boolean;
additionalFeature: File;
}
interface ValidationErrors {
form_ways?: string;
dataExtractWays?: string;
data_extractFile?: string;
data_extract_options?: string;
customDataExtractUpload?: string;
additionalFeature?: string;
}

function DataExtractValidation(values: ProjectValues) {
Expand All @@ -24,6 +27,10 @@ function DataExtractValidation(values: ProjectValues) {
errors.customDataExtractUpload = 'A GeoJSON file is required.';
}

if (values.hasAdditionalFeature && !values.additionalFeature) {
errors.additionalFeature = 'Additional Feature is Required.';
}

return errors;
}

Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/store/slices/CreateProjectSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const initialState: CreateProjectStateTypes = {
toggleSplittedGeojsonEdit: false,
customFileValidity: false,
validatedCustomForm: null,
additionalFeatureGeojson: null,
};

const CreateProject = createSlice({
Expand Down Expand Up @@ -225,6 +226,9 @@ const CreateProject = createSlice({
SetValidatedCustomFile(state, action) {
state.validatedCustomForm = action.payload;
},
SetAdditionalFeatureGeojson(state, action) {
state.additionalFeatureGeojson = action.payload;
},
},
});

Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/store/types/ICreateProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type CreateProjectStateTypes = {
toggleSplittedGeojsonEdit: boolean;
customFileValidity: boolean;
validatedCustomForm: any;
additionalFeatureGeojson: GeoJSONFeatureTypes | null;
};
export type ValidateCustomFormResponse = {
detail: { message: string; possible_reason: string };
Expand Down Expand Up @@ -114,6 +115,7 @@ export type ProjectDetailsTypes = {
custom_tms_url: string;
hasCustomTMS: boolean;
customFormUpload: any;
hasAdditionalFeature: boolean;
};

export type ProjectAreaTypes = {
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/views/CreateNewProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const CreateNewProject = () => {
const [geojsonFile, setGeojsonFile] = useState(null);
const [customDataExtractUpload, setCustomDataExtractUpload] = useState(null);
const [customFormFile, setCustomFormFile] = useState(null);
const [additionalFeature, setAdditionalFeature] = useState(null);

useEffect(() => {
if (location.pathname !== '/create-project' && !projectDetails.name && !projectDetails.odk_central_url) {
Expand Down Expand Up @@ -83,6 +84,8 @@ const CreateNewProject = () => {
flag="create_project"
customDataExtractUpload={customDataExtractUpload}
setCustomDataExtractUpload={setCustomDataExtractUpload}
additionalFeature={additionalFeature}
setAdditionalFeature={setAdditionalFeature}
/>
);
case '/split-tasks':
Expand All @@ -91,6 +94,7 @@ const CreateNewProject = () => {
flag="create_project"
setGeojsonFile={setGeojsonFile}
customDataExtractUpload={customDataExtractUpload}
additionalFeature={additionalFeature}
/>
);
default:
Expand Down
14 changes: 14 additions & 0 deletions src/frontend/src/views/NewDefineAreaMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type NewDefineAreaMapProps = {
onModify?: ((geojson: any, area?: number) => void) | null;
hasEditUndo?: boolean;
getAOIArea?: ((area?: number) => void) | null;
additionalFeatureGeojson?: GeoJSONFeatureTypes | null;
};

const NewDefineAreaMap = ({
Expand All @@ -29,6 +30,7 @@ const NewDefineAreaMap = ({
onModify,
hasEditUndo,
getAOIArea,
additionalFeatureGeojson,
}: NewDefineAreaMapProps) => {
const { mapRef, map }: { mapRef: any; map: any } = useOLMap({
center: [0, 0],
Expand Down Expand Up @@ -81,6 +83,18 @@ const NewDefineAreaMap = ({
/>
)}

{additionalFeatureGeojson && (
<VectorLayer
geojson={additionalFeatureGeojson}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
constrainResolution: true,
duration: 500,
}}
zoomToLayer
/>
)}
{buildingExtractedGeojson && (
<VectorLayer
geojson={buildingExtractedGeojson}
Expand Down

0 comments on commit 1819e8c

Please sign in to comment.