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

Frontend implementation of additional feature selection during project creation #1806

Merged
merged 10 commits into from
Sep 24, 2024
Merged
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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor change: this shouldn't use debug=true as it doesn't do any actual validation then (it's to return and inspect the form if there are errors).

I will merge anyway then update on the branch πŸ‘

}
}, [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
Loading