diff --git a/src/frontend/src/api/CreateProjectService.ts b/src/frontend/src/api/CreateProjectService.ts index dd3ce11ee4..115348f54f 100755 --- a/src/frontend/src/api/CreateProjectService.ts +++ b/src/frontend/src/api/CreateProjectService.ts @@ -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 = ( @@ -17,6 +16,7 @@ const CreateProjectService = ( formUpload: any, dataExtractFile: any, isOsmExtract: boolean, + additionalFeature: any, ) => { return async (dispatch) => { dispatch(CreateProjectActions.CreateProjectLoading(true)); @@ -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, ), ); @@ -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)); diff --git a/src/frontend/src/components/common/FileInputComponent.tsx b/src/frontend/src/components/common/FileInputComponent.tsx index 7e6a965175..d06243188b 100644 --- a/src/frontend/src/components/common/FileInputComponent.tsx +++ b/src/frontend/src/components/common/FileInputComponent.tsx @@ -57,7 +57,7 @@ const FileInputComponent = ({

{customFile?.name}

)} -

{fileDescription}

+

{fileDescription}

); }; diff --git a/src/frontend/src/components/createnewproject/DataExtract.tsx b/src/frontend/src/components/createnewproject/DataExtract.tsx index 9e95fdf5a7..4deab4412a 100644 --- a/src/frontend/src/components/createnewproject/DataExtract.tsx +++ b/src/frontend/src/components/createnewproject/DataExtract.tsx @@ -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'; @@ -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(); @@ -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)); @@ -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} @@ -272,6 +279,48 @@ const DataExtract = ({ flag, customDataExtractUpload, setCustomDataExtractUpload /> )} + {extractWays && ( +
+ { + handleCustomChange('hasAdditionalFeature', status); + handleCustomChange('additionalFeature', null); + dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(null)); + setAdditionalFeature(null); + }} + className="fmtm-text-black" + labelClickable + /> + {formValues?.hasAdditionalFeature && ( + <> + { + 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} + /> + + )} +
+ )}
diff --git a/src/frontend/src/components/createnewproject/SelectForm.tsx b/src/frontend/src/components/createnewproject/SelectForm.tsx index 225b91d265..e3e74faa6a 100644 --- a/src/frontend/src/components/createnewproject/SelectForm.tsx +++ b/src/frontend/src/components/createnewproject/SelectForm.tsx @@ -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]); diff --git a/src/frontend/src/components/createnewproject/SplitTasks.tsx b/src/frontend/src/components/createnewproject/SplitTasks.tsx index df9753f9e1..b1a8f66102 100644 --- a/src/frontend/src/components/createnewproject/SplitTasks.tsx +++ b/src/frontend/src/components/createnewproject/SplitTasks.tsx @@ -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(); @@ -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[] = [ { @@ -133,6 +134,7 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => { projectDetails.customFormUpload, customDataExtractUpload, projectDetails.dataExtractWays === 'osm_data_extract', + additionalFeature, ), ); dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...formValues })); @@ -373,6 +375,7 @@ const SplitTasks = ({ flag, setGeojsonFile, customDataExtractUpload }) => { } // toggleSplittedGeojsonEdit hasEditUndo + additionalFeatureGeojson={additionalFeatureGeojson} /> diff --git a/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx b/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx index dbe413971e..d35afb6736 100644 --- a/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx +++ b/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx @@ -4,6 +4,8 @@ interface ProjectValues { data_extractFile: object; data_extract_options: string; customDataExtractUpload: string; + hasAdditionalFeature: boolean; + additionalFeature: File; } interface ValidationErrors { form_ways?: string; @@ -11,6 +13,7 @@ interface ValidationErrors { data_extractFile?: string; data_extract_options?: string; customDataExtractUpload?: string; + additionalFeature?: string; } function DataExtractValidation(values: ProjectValues) { @@ -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; } diff --git a/src/frontend/src/store/slices/CreateProjectSlice.ts b/src/frontend/src/store/slices/CreateProjectSlice.ts index d7ebe9fd63..975f304555 100755 --- a/src/frontend/src/store/slices/CreateProjectSlice.ts +++ b/src/frontend/src/store/slices/CreateProjectSlice.ts @@ -54,6 +54,7 @@ export const initialState: CreateProjectStateTypes = { toggleSplittedGeojsonEdit: false, customFileValidity: false, validatedCustomForm: null, + additionalFeatureGeojson: null, }; const CreateProject = createSlice({ @@ -225,6 +226,9 @@ const CreateProject = createSlice({ SetValidatedCustomFile(state, action) { state.validatedCustomForm = action.payload; }, + SetAdditionalFeatureGeojson(state, action) { + state.additionalFeatureGeojson = action.payload; + }, }, }); diff --git a/src/frontend/src/store/types/ICreateProject.ts b/src/frontend/src/store/types/ICreateProject.ts index dd7c3d6281..b938389fb1 100644 --- a/src/frontend/src/store/types/ICreateProject.ts +++ b/src/frontend/src/store/types/ICreateProject.ts @@ -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 }; @@ -114,6 +115,7 @@ export type ProjectDetailsTypes = { custom_tms_url: string; hasCustomTMS: boolean; customFormUpload: any; + hasAdditionalFeature: boolean; }; export type ProjectAreaTypes = { diff --git a/src/frontend/src/views/CreateNewProject.tsx b/src/frontend/src/views/CreateNewProject.tsx index 02e8359894..6594a8d874 100644 --- a/src/frontend/src/views/CreateNewProject.tsx +++ b/src/frontend/src/views/CreateNewProject.tsx @@ -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) { @@ -83,6 +84,8 @@ const CreateNewProject = () => { flag="create_project" customDataExtractUpload={customDataExtractUpload} setCustomDataExtractUpload={setCustomDataExtractUpload} + additionalFeature={additionalFeature} + setAdditionalFeature={setAdditionalFeature} /> ); case '/split-tasks': @@ -91,6 +94,7 @@ const CreateNewProject = () => { flag="create_project" setGeojsonFile={setGeojsonFile} customDataExtractUpload={customDataExtractUpload} + additionalFeature={additionalFeature} /> ); default: diff --git a/src/frontend/src/views/NewDefineAreaMap.tsx b/src/frontend/src/views/NewDefineAreaMap.tsx index 0d1c81e674..aca5f86059 100644 --- a/src/frontend/src/views/NewDefineAreaMap.tsx +++ b/src/frontend/src/views/NewDefineAreaMap.tsx @@ -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 = ({ @@ -29,6 +30,7 @@ const NewDefineAreaMap = ({ onModify, hasEditUndo, getAOIArea, + additionalFeatureGeojson, }: NewDefineAreaMapProps) => { const { mapRef, map }: { mapRef: any; map: any } = useOLMap({ center: [0, 0], @@ -81,6 +83,18 @@ const NewDefineAreaMap = ({ /> )} + {additionalFeatureGeojson && ( + + )} {buildingExtractedGeojson && (