diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b0da011e8..303dc7255d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,13 @@ repos: rev: v3.0.3 hooks: - id: prettier - args: [--ignore-unknown, --no-error-on-unmatched-pattern, "!chart/**"] + args: + [ + --ignore-unknown, + --no-error-on-unmatched-pattern, + "!chart/**", + "!src/frontend/pnpm-lock.yaml", + ] # Lint: Markdown - repo: https://github.com/igorshubovych/markdownlint-cli diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index f57fa2fab0..c7b78333f7 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -17,6 +17,7 @@ "@mui/material": "^5.14.12", "@mui/system": "^5.14.12", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-switch": "^1.0.3", "@reduxjs/toolkit": "^1.9.1", @@ -31,6 +32,7 @@ "eslint": "^8.44.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^5.0.0", + "geojson-validation": "^1.0.2", "install": "^0.13.0", "lucide-react": "^0.276.0", "mini-css-extract-plugin": "^2.7.5", @@ -54,7 +56,6 @@ "@types/react-dom": "^17.0.2", "@vitejs/plugin-react": "^4.1.0", "autoprefixer": "^10.4.15", - "babel-plugin-import": "^1.13.6", "css-loader": "^6.3.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-react": "^7.33.0", @@ -4087,6 +4088,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.0.3.tgz", + "integrity": "sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz", @@ -6232,15 +6257,6 @@ "node": ">=8" } }, - "node_modules/babel-plugin-import": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.6.tgz", - "integrity": "sha512-N7FYnGh0DFsvDRkAPsvFq/metVfVD7P2h1rokOPpEH4cZbdRHCW+2jbXt0nnuqowkm/xhh2ww1anIdEpfYa7ZA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -8624,6 +8640,14 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-validation": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/geojson-validation/-/geojson-validation-1.0.2.tgz", + "integrity": "sha512-K5jrJ4wFvORn2pRKeg181LL0QPYuEKn2KHPvfH1m2QtFlAXFLKdseqt0XwBM3ELOY7kNM1fglRQ6ZwUQZ5S00A==", + "bin": { + "gjv": "bin/gjv" + } + }, "node_modules/geotiff": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 81ce5f8291..737dd5d3b4 100755 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -50,6 +50,7 @@ "@mui/material": "^5.14.12", "@mui/system": "^5.14.12", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-switch": "^1.0.3", "@reduxjs/toolkit": "^1.9.1", @@ -64,6 +65,7 @@ "eslint": "^8.44.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^5.0.0", + "geojson-validation": "^1.0.2", "install": "^0.13.0", "lucide-react": "^0.276.0", "mini-css-extract-plugin": "^2.7.5", diff --git a/src/frontend/pnpm-lock.yaml b/src/frontend/pnpm-lock.yaml index 41ea612d2b..9efb8d8f20 100644 --- a/src/frontend/pnpm-lock.yaml +++ b/src/frontend/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: '@radix-ui/react-dialog': specifier: ^1.0.5 version: 1.0.5(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2) + '@radix-ui/react-progress': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2) '@radix-ui/react-select': specifier: ^1.2.2 version: 1.2.2(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2) @@ -71,6 +74,9 @@ dependencies: eslint-plugin-prettier: specifier: ^5.0.0 version: 5.0.0(eslint-config-prettier@8.10.0)(eslint@8.51.0)(prettier@3.0.3) + geojson-validation: + specifier: ^1.0.2 + version: 1.0.2 install: specifier: ^0.13.0 version: 0.13.0 @@ -2719,6 +2725,29 @@ packages: react-dom: 17.0.2(react@17.0.2) dev: false + /@radix-ui/react-progress@1.0.3(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2): + resolution: + { integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag== } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.1 + '@radix-ui/react-context': 1.0.1(@types/react@17.0.67)(react@17.0.2) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2) + '@types/react': 17.0.67 + '@types/react-dom': 17.0.21 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + /@radix-ui/react-select@1.2.2(@types/react-dom@17.0.21)(@types/react@17.0.67)(react-dom@17.0.2)(react@17.0.2): resolution: { integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw== } @@ -5410,6 +5439,12 @@ packages: engines: { node: '>=6.9.0' } dev: true + /geojson-validation@1.0.2: + resolution: + { integrity: sha512-K5jrJ4wFvORn2pRKeg181LL0QPYuEKn2KHPvfH1m2QtFlAXFLKdseqt0XwBM3ELOY7kNM1fglRQ6ZwUQZ5S00A== } + hasBin: true + dev: false + /geotiff@2.0.7: resolution: { integrity: sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q== } diff --git a/src/frontend/src/api/CreateProjectService.ts b/src/frontend/src/api/CreateProjectService.ts index 0630f94d64..7dbbf1cbde 100755 --- a/src/frontend/src/api/CreateProjectService.ts +++ b/src/frontend/src/api/CreateProjectService.ts @@ -5,7 +5,6 @@ import { FormCategoryListModel, OrganisationListModel, } from '../models/createproject/createProjectModel'; -import enviroment from '../environment'; import { CommonActions } from '../store/slices/CommonSlice'; import { ValidateCustomFormResponse } from 'store/types/ICreateProject'; @@ -27,7 +26,7 @@ const CreateProjectService: Function = ( const resp: ProjectDetailsModel = postNewProjectDetails.data; await dispatch(CreateProjectActions.PostProjectDetails(resp)); - if (payload.splitting_algorithm === 'Choose Area as Tasks') { + if (payload.splitting_algorithm === 'choose_area_as_task') { await dispatch( UploadAreaService(`${import.meta.env.VITE_API_URL}/projects/${resp.id}/upload_multi_polygon`, fileUpload), ); @@ -163,7 +162,7 @@ const GenerateProjectQRService: Function = (url: string, payload: any, formUploa try { const isPolygon = payload.data_extractWays === 'Polygon'; const generateApiFormData = new FormData(); - if (payload.form_ways === 'Upload a Custom Form') { + if (payload.form_ways === 'custom_form') { generateApiFormData.append('extract_polygon', isPolygon.toString()); generateApiFormData.append('upload', formUpload); if (dataExtractFile) { @@ -171,7 +170,6 @@ const GenerateProjectQRService: Function = (url: string, payload: any, formUploa } } else { generateApiFormData.append('extract_polygon', isPolygon.toString()); - generateApiFormData.append('upload', ''); if (dataExtractFile) { generateApiFormData.append('data_extracts', dataExtractFile); } @@ -184,7 +182,6 @@ const GenerateProjectQRService: Function = (url: string, payload: any, formUploa const resp: string = postNewProjectDetails.data; await dispatch(CreateProjectActions.GenerateProjectQRLoading(false)); dispatch(CommonActions.SetLoading(false)); - await dispatch(CreateProjectActions.ClearCreateProjectFormData()); await dispatch(CreateProjectActions.GenerateProjectQRSuccess(resp)); } catch (error: any) { dispatch(CommonActions.SetLoading(false)); diff --git a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js index 4d5d97cf16..aa6c761fd4 100644 --- a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js +++ b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js @@ -13,6 +13,7 @@ import { defaultStyles, getStyles } from '../helpers/styleUtils'; import { isExtentValid } from '../helpers/layerUtils'; import { Draw, Modify, Select, defaults as defaultInteractions } from 'ol/interaction.js'; import { getArea } from 'ol/sphere'; +import { valid } from 'geojson-validation'; const selectElement = 'singleselect'; @@ -135,6 +136,7 @@ const VectorLayer = ({ useEffect(() => { if (!map) return; if (!geojson) return; + if (!valid(geojson)) return; const vectorLyr = new OLVectorLayer({ source: new VectorSource({ diff --git a/src/frontend/src/components/TasksLayer.jsx b/src/frontend/src/components/TasksLayer.jsx index 8da7610851..88e66ee35b 100755 --- a/src/frontend/src/components/TasksLayer.jsx +++ b/src/frontend/src/components/TasksLayer.jsx @@ -70,10 +70,14 @@ const TasksLayer = (map, view, feature) => { // The extent of the vector layer var extent = [minX, minY, maxX, maxY]; - map.getView().fit(extent, { - duration: 2000, // Animation duration in milliseconds - padding: [50, 50, 50, 200], // Optional padding around the extent - }); + // Checking if Taskboundaries exist if doesn't exist dont throw error window + const checkForInfinity = extent.some((ext) => ext === Infinity); + if (!checkForInfinity) { + map.getView().fit(extent, { + duration: 2000, // Animation duration in milliseconds + padding: [50, 50, 50, 200], // Optional padding around the extent + }); + } map.addLayer(vectorLayer); map.on('loadend', function () { map.getTargetElement().classList.remove('spinner'); diff --git a/src/frontend/src/components/common/Button.tsx b/src/frontend/src/components/common/Button.tsx index 693ba03af4..90e9512c36 100644 --- a/src/frontend/src/components/common/Button.tsx +++ b/src/frontend/src/components/common/Button.tsx @@ -1,14 +1,17 @@ +import { Loader2 } from 'lucide-react'; import React from 'react'; interface IButton { btnText: string; - btnType: 'primary' | 'secondary' | 'other'; - type: 'submit' | 'button'; + btnType: 'primary' | 'secondary' | 'other' | 'disabled'; + type?: 'submit' | 'button'; onClick?: (event: React.MouseEvent) => void; className?: string; count?: number; dataTip?: string; icon?: React.ReactNode; + isLoading?: boolean; + disabled?: boolean; } const btnStyle = (btnType, className) => { @@ -20,26 +23,41 @@ const btnStyle = (btnType, className) => { case 'other': return `fmtm-py-1 fmtm-px-5 fmtm-bg-red-500 fmtm-text-white fmtm-rounded-lg hover:fmtm-bg-red-600`; + case 'disabled': + return `fmtm-py-1 fmtm-px-5 fmtm-text-white fmtm-rounded-lg fmtm-bg-gray-400 fmtm-cursor-not-allowed`; default: return 'fmtm-primary'; } }; -const Button = ({ btnText, btnType, type, onClick, className, count, dataTip, icon }: IButton) => ( +const Button = ({ btnText, btnType, type, onClick, disabled, className, count, dataTip, icon, isLoading }: IButton) => (
); diff --git a/src/frontend/src/components/common/FileInputComponent.tsx b/src/frontend/src/components/common/FileInputComponent.tsx index 7e004de7c3..4cc6714479 100644 --- a/src/frontend/src/components/common/FileInputComponent.tsx +++ b/src/frontend/src/components/common/FileInputComponent.tsx @@ -1,44 +1,51 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import AssetModules from '../../shared/AssetModules.js'; -const FileInputComponent = ({ customFileRef, customFormFile, onChange, onResetFile }) => { - const changeFileHandler = (event) => { - // handle file change logic here - }; - - const resetFile = () => { - // reset file logic here - }; - +const FileInputComponent = ({ + accept = '.geojson, .json', + customFile, + onChange, + onResetFile, + btnText = 'Select File', + fileDescription = '*The supported file formats are zipped shapefile, geojson or kml files.', + errorMsg, +}) => { + const customFileRef = useRef(null); return ( -
-
+
+
- + { + onResetFile(); + customFileRef.current.value = ''; + }} + />
- {customFormFile && ( -
-

{customFormFile?.name}

+ {errorMsg &&

{errorMsg}

} + + {customFile && ( +
+

{customFile?.name}

)} -

- *The supported file formats are zipped shapefile, geojson or kml files. -

+

{fileDescription}

); }; diff --git a/src/frontend/src/components/common/Modal.tsx b/src/frontend/src/components/common/Modal.tsx index 62bed3675e..9b7206d3c5 100644 --- a/src/frontend/src/components/common/Modal.tsx +++ b/src/frontend/src/components/common/Modal.tsx @@ -33,7 +33,7 @@ interface IModalProps { title: React.FC; description: React.FC; open: boolean; - onOpenChange: () => {}; + onOpenChange: (boolean) => void; } const DialogContent = React.forwardRef< diff --git a/src/frontend/src/components/common/ProgressBar.tsx b/src/frontend/src/components/common/ProgressBar.tsx new file mode 100644 index 0000000000..8c68a646de --- /dev/null +++ b/src/frontend/src/components/common/ProgressBar.tsx @@ -0,0 +1,39 @@ +'use client'; + +import React from 'react'; +import * as Progress from '@radix-ui/react-progress'; + +interface ProgressBarProps { + currentStep?: number; + totalSteps?: number; + currentProgress?: number; +} +const ProgressBar: React.FC = ({ currentStep, totalSteps }) => { + const stepProgressInPercent = currentStep && totalSteps ? (currentStep / totalSteps) * 100 : 0; + + return ( + <> +

+ {currentStep} out of {totalSteps} tasks +

+ + +

{stepProgressInPercent.toFixed(2)}%

+
+
+ + ); +}; + +export default ProgressBar; diff --git a/src/frontend/src/components/common/RadioButton.tsx b/src/frontend/src/components/common/RadioButton.tsx index 9ab65e86a4..dca0aec7ae 100644 --- a/src/frontend/src/components/common/RadioButton.tsx +++ b/src/frontend/src/components/common/RadioButton.tsx @@ -13,9 +13,10 @@ interface RadioButtonProps { direction: 'row' | 'column'; onChangeData: (value: string) => void; value: string; + errorMsg?: string; } -const RadioButton: React.FC = ({ topic, options, direction, onChangeData, value }) => ( +const RadioButton: React.FC = ({ topic, options, direction, onChangeData, value, errorMsg }) => (

{topic}

@@ -45,6 +46,7 @@ const RadioButton: React.FC = ({ topic, options, direction, on
); })} + {errorMsg &&

{errorMsg}

}
); diff --git a/src/frontend/src/components/common/Select.tsx b/src/frontend/src/components/common/Select.tsx index 9fce23e4d3..89671cd503 100644 --- a/src/frontend/src/components/common/Select.tsx +++ b/src/frontend/src/components/common/Select.tsx @@ -116,6 +116,7 @@ interface ICustomSelect { valueKey: string; label: string; onValueChange: (value: string | null | number) => void; + errorMsg: string; } export const CustomSelect = ({ @@ -127,6 +128,7 @@ export const CustomSelect = ({ valueKey, label, onValueChange, + errorMsg, }: ICustomSelect) => { return (
@@ -149,6 +151,7 @@ export const CustomSelect = ({
+ {errorMsg &&

{errorMsg}

}
); }; diff --git a/src/frontend/src/components/common/StepSwitcher.tsx b/src/frontend/src/components/common/StepSwitcher.tsx index 0d1fd1626a..3d9c9e9939 100644 --- a/src/frontend/src/components/common/StepSwitcher.tsx +++ b/src/frontend/src/components/common/StepSwitcher.tsx @@ -34,7 +34,9 @@ const StepSwitcher = ({ data, flag }) => {
= index ? 'fmtm-bg-primaryRed' : 'fmtm-bg-transparent' }`} onClick={() => toggleStep(step)} diff --git a/src/frontend/src/components/createnewproject/DataExtract.tsx b/src/frontend/src/components/createnewproject/DataExtract.tsx index 286d77ae03..59f05b81be 100644 --- a/src/frontend/src/components/createnewproject/DataExtract.tsx +++ b/src/frontend/src/components/createnewproject/DataExtract.tsx @@ -1,18 +1,16 @@ -import React, { useEffect, useRef, useState } from 'react'; -import enviroment from '../../environment'; -import { FormCategoryService } from '../../api/CreateProjectService'; -import CoreModules from '../../shared/CoreModules.js'; +import React, { useEffect } from 'react'; import Button from '../../components/common/Button'; import { useDispatch } from 'react-redux'; import { CommonActions } from '../../store/slices/CommonSlice'; import RadioButton from '../../components/common/RadioButton'; import { useNavigate } from 'react-router-dom'; -import { CustomSelect } from '../../components/common/Select'; import { CreateProjectActions } from '../../store/slices/CreateProjectSlice'; import useForm from '../../hooks/useForm'; import { useAppSelector } from '../../types/reduxTypes'; -import DataExtractValidation from './validation/DataExtractValidation'; +import { FormCategoryService } from '../../api/CreateProjectService'; import FileInputComponent from '../../components/common/FileInputComponent'; +import DataExtractValidation from './validation/DataExtractValidation'; +import NewDefineAreaMap from '../../views/NewDefineAreaMap'; const dataExtractOptions = [ { name: 'data_extract', value: 'osm_data_extract', label: 'Use OSM data extract' }, @@ -25,18 +23,19 @@ const osmFeatureTypeOptions = [ { name: 'osm_feature_type', value: 'polygon', label: 'Polygon' }, ]; -const DataExtract = ({ flag, customFormFile, setCustomFormFile }) => { +const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygonUpload, setCustomPolygonUpload }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const customFileRef: any = useRef(); const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails); - const formCategoryList = CoreModules.useAppSelector((state) => state.createproject.formCategoryList); + const drawnGeojson = useAppSelector((state) => state.createproject.drawnGeojson); + const buildingGeojson = useAppSelector((state) => state.createproject.buildingGeojson); + const lineGeojson = useAppSelector((state) => state.createproject.lineGeojson); const submission = () => { dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues)); - dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 3 })); - navigate('/new-upload-area'); + dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 5 })); + navigate('/split-tasks'); }; const { handleSubmit, @@ -49,18 +48,37 @@ const DataExtract = ({ flag, customFormFile, setCustomFormFile }) => { dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: step })); navigate(url); }; - const changeFileHandler = (event) => { + const changeFileHandler = (event, setFileUploadToState, fileType) => { const { files } = event.target; - setCustomFormFile(files[0]); + setFileUploadToState(files[0]); + convertFileToGeojson(files[0], fileType); }; - useEffect(() => { - dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); - }, []); - - const resetFile = () => { - customFileRef.current.value = ''; - setCustomFormFile(null); + const resetFile = (setFileUploadToState) => { + setFileUploadToState(null); + }; + const convertFileToGeojson = async (file, fileType) => { + if (!file) return; + const fileReader = new FileReader(); + const fileLoaded = await new Promise((resolve) => { + fileReader.onload = (e) => resolve(e.target.result); + fileReader.readAsText(file, 'UTF-8'); + }); + const parsedJSON = JSON.parse(fileLoaded); + let geojsonConversion; + if (parsedJSON.type === 'FeatureCollection') { + geojsonConversion = parsedJSON; + } else { + geojsonConversion = { + type: 'FeatureCollection', + features: [{ type: 'Feature', properties: null, geometry: parsedJSON }], + }; + } + if (fileType === 'building') { + dispatch(CreateProjectActions.SetBuildingGeojson(geojsonConversion)); + } else if (fileType === 'line') { + dispatch(CreateProjectActions.SetLineGeojson(geojsonConversion)); + } }; useEffect(() => { @@ -106,6 +124,7 @@ const DataExtract = ({ flag, customFormFile, setCustomFormFile }) => { onChangeData={(value) => { handleCustomChange('dataExtractWays', value); }} + errorMsg={errors.dataExtractWays} /> {formValues.dataExtractWays === 'osm_data_extract' && (
@@ -117,22 +136,35 @@ const DataExtract = ({ flag, customFormFile, setCustomFormFile }) => { onChangeData={(value) => { handleCustomChange('dataExtractFeatureType', value); }} + errorMsg={errors.dataExtractFeatureType} />
)} {formValues.dataExtractWays === 'custom_data_extract' && ( <> { + changeFileHandler(e, setCustomPolygonUpload, 'building'); + handleCustomChange('customPolygonUpload', e.target.files[0]); + }} + onResetFile={() => resetFile(setCustomPolygonUpload)} + customFile={customPolygonUpload} + btnText="Upload a Polygon" + accept=".geojson,.json" + fileDescription="*The supported file formats are .geojson, .json" + errorMsg={errors.customPolygonUpload} /> { + changeFileHandler(e, setCustomLineUpload, 'line'); + handleCustomChange('customLineUpload', e.target.files[0]); + }} + onResetFile={() => resetFile(setCustomLineUpload)} + customFile={customLineUpload} + btnText="Upload a Line" + accept=".geojson,.json" + fileDescription="*The supported file formats are .geojson, .json" + errorMsg={errors.setCustomLineUpload} /> )} @@ -142,13 +174,19 @@ const DataExtract = ({ flag, customFormFile, setCustomFormFile }) => { btnText="PREVIOUS" btnType="secondary" type="button" - onClick={() => toggleStep(2, '/new-upload-area')} + onClick={() => toggleStep(2, '/upload-area')} className="fmtm-font-bold" />
-
+
+ +
diff --git a/src/frontend/src/components/createnewproject/ProjectDetailsForm.tsx b/src/frontend/src/components/createnewproject/ProjectDetailsForm.tsx index 8d314b401d..4bf5e29cfd 100644 --- a/src/frontend/src/components/createnewproject/ProjectDetailsForm.tsx +++ b/src/frontend/src/components/createnewproject/ProjectDetailsForm.tsx @@ -13,7 +13,6 @@ import AssetModules from '../../shared/AssetModules.js'; import { createPopup } from '../../utilfunctions/createPopup'; import { CustomSelect } from '../../components/common/Select'; import { OrganisationService } from '../../api/CreateProjectService'; -import environment from '../../environment'; const ProjectDetailsForm = ({ flag }) => { const dispatch = useDispatch(); @@ -27,10 +26,10 @@ const ProjectDetailsForm = ({ flag }) => { const submission = () => { dispatch(CreateProjectActions.SetIndividualProjectDetailsData(values)); dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 2 })); - navigate('/new-upload-area'); + navigate('/upload-area'); }; - const { handleSubmit, handleCustomChange, values, errors }: any = useForm( + const { handleSubmit, handleCustomChange, values, errors, checkValidationOnly }: any = useForm( projectDetails, submission, CreateProjectValidation, @@ -39,12 +38,16 @@ const ProjectDetailsForm = ({ flag }) => { const onFocus = () => { dispatch(OrganisationService(`${import.meta.env.VITE_API_URL}/organization/`)); }; + useEffect(() => { window.addEventListener('focus', onFocus); onFocus(); // Calls onFocus when the window first loads return () => { window.removeEventListener('focus', onFocus); + // dispatch( + // CreateProjectActions.SetCreateProjectValidations({ key: 'projectDetails', value: checkValidationOnly() }), + // ); }; }, []); diff --git a/src/frontend/src/components/createnewproject/SelectForm.tsx b/src/frontend/src/components/createnewproject/SelectForm.tsx index 830fc4101e..4c8576c7e0 100644 --- a/src/frontend/src/components/createnewproject/SelectForm.tsx +++ b/src/frontend/src/components/createnewproject/SelectForm.tsx @@ -1,21 +1,74 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { CommonActions } from '../../store/slices/CommonSlice'; import Button from '../../components/common/Button'; import { CustomSelect } from '../../components/common/Select'; import { useNavigate } from 'react-router-dom'; +import RadioButton from '../common/RadioButton'; +import { CreateProjectActions } from '../../store/slices/CreateProjectSlice'; +import useForm from '../../hooks/useForm'; +import { useAppSelector } from '../../types/reduxTypes'; +import FileInputComponent from '../common/FileInputComponent'; +import SelectFormValidation from './validation/SelectFormValidation'; +import { FormCategoryService, ValidateCustomForm } from '../../api/CreateProjectService'; +import NewDefineAreaMap from '../../views/NewDefineAreaMap'; -const selectFormWaysList = ['Use Existing Form', 'Upload a Custom Form']; +const osmFeatureTypeOptions = [ + { name: 'form_ways', value: 'existing_form', label: 'Use Existing Form' }, + { name: 'form_ways', value: 'custom_form', label: 'Upload a Custom Form' }, +]; +// @ts-ignore +const DefineAreaMap = React.lazy(() => import('../../views/DefineAreaMap')); -const SelectForm = ({ flag }) => { +const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const selectFormWays = selectFormWaysList.map((item) => ({ label: item, value: item })); + + const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails); + const drawnGeojson = useAppSelector((state) => state.createproject.drawnGeojson); + + const submission = () => { + dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues)); + dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 4 })); + navigate('/data-extract'); + }; + const { + handleSubmit, + handleCustomChange, + values: formValues, + errors, + }: any = useForm(projectDetails, submission, SelectFormValidation); + const formCategoryList = useAppSelector((state) => state.createproject.formCategoryList); + + /** + * Function to handle the change event of a file input. + * + * @param {Event} event - The change event object. + */ + const changeFileHandler = (event): void => { + // Get the selected files from the event target + const { files } = event.target; + // Set the selected file as the customFormFile state + setCustomFormFile(files[0]); + handleCustomChange('customFormUpload', files[0]); + }; + const resetFile = (): void => { + setCustomFormFile(null); + }; + + useEffect(() => { + dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); + }, []); const toggleStep = (step, url) => { dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: step })); navigate(url); }; + useEffect(() => { + if (customFormFile) { + dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate_form`, customFormFile)); + } + }, [customFormFile]); return (
@@ -28,34 +81,66 @@ const SelectForm = ({ flag }) => {
-
- console.log(value)} +
+
+ { + handleCustomChange('formCategorySelection', value); + }} + errorMsg={errors.formCategorySelection} + /> +
+ { + handleCustomChange('formWays', value); + }} + errorMsg={errors.formWays} /> + {formValues.formWays === 'custom_form' ? ( + + ) : null}
+ +
+
-
diff --git a/src/frontend/src/components/createnewproject/SplitTasks.tsx b/src/frontend/src/components/createnewproject/SplitTasks.tsx index 89eae459f8..f1bfad98fd 100644 --- a/src/frontend/src/components/createnewproject/SplitTasks.tsx +++ b/src/frontend/src/components/createnewproject/SplitTasks.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import Button from '../../components/common/Button'; import RadioButton from '../../components/common/RadioButton'; import AssetModules from '../../shared/AssetModules.js'; @@ -9,20 +9,52 @@ import { CreateProjectActions } from '../../store/slices/CreateProjectSlice'; import CoreModules from '../../shared/CoreModules'; import useForm from '../../hooks/useForm'; import DefineTaskValidation from '../../components/createproject/validation/DefineTaskValidation'; +import NewDefineAreaMap from '../../views/NewDefineAreaMap'; +import { useAppSelector } from '../../types/reduxTypes'; +import { + CreateProjectService, + GenerateProjectLog, + GetDividedTaskFromGeojson, + TaskSplittingPreviewService, +} from '../../api/CreateProjectService'; +import environment from '../../environment'; +import LoadingBar from '../../components/createproject/LoadingBar'; +import { Modal } from '../../components/common/Modal'; +import ProgressBar from '../../components/common/ProgressBar'; const alogrithmList = [ { name: 'define_tasks', value: 'divide_on_square', label: 'Divide on square' }, { name: 'define_tasks', value: 'choose_area_as_task', label: 'Choose area as task' }, { name: 'define_tasks', value: 'task_splitting_algorithm', label: 'Task Splitting Algorithm' }, ]; +let generateProjectLogIntervalCb: any = null; -const SplitTasks = ({ flag, geojsonFile, setGeojsonFile }) => { +const SplitTasks = ({ flag, geojsonFile, setGeojsonFile, customLineUpload, customPolygonUpload, customFormFile }) => { const dispatch = useDispatch(); const navigate = useNavigate(); + const [toggleStatus, setToggleStatus] = useState(false); + + const divRef = useRef(null); const splitTasksSelection = CoreModules.useAppSelector((state) => state.createproject.splitTasksSelection); const drawnGeojson = CoreModules.useAppSelector((state) => state.createproject.drawnGeojson); const projectDetails = CoreModules.useAppSelector((state) => state.createproject.projectDetails); + const buildingGeojson = useAppSelector((state) => state.createproject.buildingGeojson); + const lineGeojson = useAppSelector((state) => state.createproject.lineGeojson); + const userDetails: any = CoreModules.useAppSelector((state) => state.login.loginToken); + + const generateQrSuccess: any = CoreModules.useAppSelector((state) => state.createproject.generateQrSuccess); + const projectDetailsResponse = CoreModules.useAppSelector((state) => state.createproject.projectDetailsResponse); + const generateProjectLog: any = CoreModules.useAppSelector((state) => state.createproject.generateProjectLog); + const dividedTaskGeojson = CoreModules.useAppSelector((state) => state.createproject.dividedTaskGeojson); + const projectDetailsLoading = CoreModules.useAppSelector((state) => state.createproject.projectDetailsLoading); + const generateProjectLogLoading = CoreModules.useAppSelector( + (state) => state.createproject.generateProjectLogLoading, + ); + const dividedTaskLoading = CoreModules.useAppSelector((state) => state.createproject.dividedTaskLoading); + const taskSplittingGeojsonLoading = CoreModules.useAppSelector( + (state) => state.createproject.taskSplittingGeojsonLoading, + ); const toggleStep = (step, url) => { dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: step })); @@ -30,14 +62,52 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile }) => { }; const submission = () => { - // // const previousValues = location.state.values; - // if (formValues.splitting_algorithm === 'Divide on Square') { - // generateTasksOnMap(); - // } - // dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...formValues })); - // navigate('/select-form'); - - toggleStep(5, '/new-select-form'); + const blob = new Blob([JSON.stringify(dividedTaskGeojson || drawnGeojson)], { type: 'application/json' }); + + // Create a file object from the Blob + const drawnGeojsonFile = new File([blob], 'data.json', { type: 'application/json' }); + + dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues)); + const hashtags = projectDetails.hashtags; + const arrayHashtag = hashtags + .split('#') + .map((item) => item.trim()) + .filter(Boolean); + dispatch( + CreateProjectService( + `${import.meta.env.VITE_API_URL}/projects/create_project`, + { + project_info: { + name: projectDetails.name, + short_description: projectDetails.short_description, + description: projectDetails.description, + }, + author: { + username: userDetails.username, + id: userDetails.id, + }, + odk_central: { + odk_central_url: projectDetails.odk_central_url, + odk_central_user: projectDetails.odk_central_user, + odk_central_password: projectDetails.odk_central_password, + }, + // dont send xform_title if upload custom form is selected + xform_title: projectDetails.formWays === 'custom_form' ? null : projectDetails.formCategorySelection, + dimension: projectDetails.dimension, + splitting_algorithm: splitTasksSelection, + form_ways: projectDetails.formWays, + // "uploaded_form": projectDetails.uploaded_form, + data_extractWays: projectDetails.data_extractWays, + hashtags: arrayHashtag, + organisation_id: projectDetails.organisation_id, + }, + drawnGeojsonFile, + customFormFile, + customPolygonUpload, + customLineUpload, + ), + ); + dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...formValues })); }; const { @@ -47,100 +117,287 @@ const SplitTasks = ({ flag, geojsonFile, setGeojsonFile }) => { errors, }: any = useForm(projectDetails, submission, DefineTaskValidation); + const generateTaskBasedOnSelection = (e) => { + dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...formValues })); + + e.preventDefault(); + e.stopPropagation(); + const blob = new Blob([JSON.stringify(drawnGeojson)], { type: 'application/json' }); + + // Create a file object from the Blob + const drawnGeojsonFile = new File([blob], 'data.json', { type: 'application/json' }); + if (splitTasksSelection === 'divide_on_square') { + dispatch( + GetDividedTaskFromGeojson(`${import.meta.env.VITE_API_URL}/projects/preview_tasks/`, { + geojson: drawnGeojsonFile, + dimension: formValues?.dimension, + }), + ); + } else if (splitTasksSelection === 'task_splitting_algorithm') { + const a = document.createElement('a'); + a.href = URL.createObjectURL(drawnGeojsonFile); + a.download = 'test.json'; + a.click(); + dispatch( + TaskSplittingPreviewService( + `${import.meta.env.VITE_API_URL}/projects/task_split`, + drawnGeojsonFile, + formValues?.average_buildings_per_task, + false, + // dataExtractFile ? true : false, + ), + ); + } + }; + //Log Functions + useEffect(() => { + if (generateQrSuccess) { + if (generateProjectLogIntervalCb === null) { + dispatch( + GenerateProjectLog(`${import.meta.env.VITE_API_URL}/projects/generate-log/`, { + project_id: projectDetailsResponse?.id, + uuid: generateQrSuccess.task_id, + }), + ); + setToggleStatus(true); + } + } + }, [generateQrSuccess]); + useEffect(() => { + if (generateQrSuccess && generateProjectLog?.status === 'FAILED') { + clearInterval(generateProjectLogIntervalCb); + dispatch( + CommonActions.SetSnackBar({ + open: true, + message: `QR Generation Failed. ${generateProjectLog?.message}`, + variant: 'error', + duration: 10000, + }), + ); + } else if (generateQrSuccess && generateProjectLog?.status === 'SUCCESS') { + clearInterval(generateProjectLogIntervalCb); + const encodedProjectId = environment.encode(projectDetailsResponse?.id); + dispatch( + CommonActions.SetSnackBar({ + open: true, + message: 'QR Generation Completed.', + variant: 'success', + duration: 2000, + }), + ); + dispatch(CreateProjectActions.SetGenerateProjectLog(null)); + dispatch(CreateProjectActions.SetGenerateProjectQRSuccess(null)); + navigate(`/project_details/${encodedProjectId}`); + dispatch(CreateProjectActions.ClearCreateProjectFormData()); + } + if (generateQrSuccess && generateProjectLog?.status === 'PENDING') { + if (generateProjectLogIntervalCb === null) { + generateProjectLogIntervalCb = setInterval(() => { + dispatch( + GenerateProjectLog(`${import.meta.env.VITE_API_URL}/projects/generate-log/`, { + project_id: projectDetailsResponse?.id, + uuid: generateQrSuccess.task_id, + }), + ); + }, 2000); + } + } + }, [generateQrSuccess, generateProjectLog]); + + useEffect(() => { + return () => { + clearInterval(generateProjectLogIntervalCb); + generateProjectLogIntervalCb = null; + dispatch(CreateProjectActions.SetGenerateProjectLog(null)); + }; + }, []); + + // END + const renderTraceback = (errorText: string) => { + if (!errorText) { + return null; + } + + return errorText.split('\n').map((line, index) => ( +
+ {index + 1}. + {line} +
+ )); + }; + + const parsedTaskGeojsonCount = dividedTaskGeojson?.features?.length || drawnGeojson?.features?.length; + const totalSteps = dividedTaskGeojson?.features ? dividedTaskGeojson?.features?.length : parsedTaskGeojsonCount; return ( -
-
-
-
Split Tasks
-

- Fill in your project basic information such as name, description, hashtag, etc. - To complete the first step, you will need the account credentials of ODK central server.{' '} - Here are the instructions for setting up a Central ODK Server on Digital Ocean. -

-
-
-
-
-
- dispatch(CreateProjectActions.SetSplitTasksSelection(value))} - /> - {splitTasksSelection === 'divide_on_square' && ( -
-

Dimension of square in metres:

- console.log(e.target.value)} - className="fmtm-outline-none fmtm-border-[1px] fmtm-border-gray-600 fmtm-h-7 fmtm-w-16 fmtm-px-2 " - /> -
- )} - {splitTasksSelection === 'task_splitting_algorithm' && ( -
-

Average number of buildings per task:

- console.log(e.target.value)} - className="fmtm-outline-none fmtm-border-[1px] fmtm-border-gray-600 fmtm-h-7 fmtm-w-16 fmtm-px-2 " - /> -
- )} - {(splitTasksSelection === 'divide_on_square' || splitTasksSelection === 'task_splitting_algorithm') && ( -
-
- */} + +
+
+ } + open={toggleStatus} + onOpenChange={(value) => { + setToggleStatus(value); + }} + /> + +
+
+
Split Tasks
+

+ Fill in your project basic information such as name, description, hashtag, etc. + To complete the first step, you will need the account credentials of ODK central server.{' '} + Here are the instructions for setting up a Central ODK Server on Digital Ocean. +

+
+
+
+
+
+ { + handleCustomChange('splitTaskOption', value); + dispatch(CreateProjectActions.SetSplitTasksSelection(value)); + }} + errorMsg={errors.splitTaskOption} + /> + {splitTasksSelection === 'divide_on_square' && ( + <> +
+

Dimension of square in metres:

+ handleCustomChange('dimension', e.target.value)} + className="fmtm-outline-none fmtm-border-[1px] fmtm-border-gray-600 fmtm-h-7 fmtm-w-16 fmtm-px-2 " + /> +
+ {errors.dimension && ( +

{errors.dimension}

+ )} + + )} + {formValues.splitTaskOption === 'task_splitting_algorithm' && ( + <> +
+

Average number of buildings per task:

+ handleCustomChange('average_buildings_per_task', e.target.value)} + className="fmtm-outline-none fmtm-border-[1px] fmtm-border-gray-600 fmtm-h-7 fmtm-w-16 fmtm-px-2 " + /> +
+ {errors.average_buildings_per_task && ( +

+ {errors.average_buildings_per_task} +

+ )} + + )} + {(splitTasksSelection === 'divide_on_square' || + splitTasksSelection === 'task_splitting_algorithm') && ( +
+
+
-
- )} - {splitTasksSelection && ( -

- Total number of task: 10 -

- )} + )} + {splitTasksSelection && ( +

+ Total number of task: {totalSteps} +

+ )} +
+
+
-
-
+ ) : null}
-
-
- + + ); }; diff --git a/src/frontend/src/components/createnewproject/UploadArea.tsx b/src/frontend/src/components/createnewproject/UploadArea.tsx index 0632b27e4a..e8cb8d91f8 100644 --- a/src/frontend/src/components/createnewproject/UploadArea.tsx +++ b/src/frontend/src/components/createnewproject/UploadArea.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { CommonActions } from '../../store/slices/CommonSlice'; import Button from '../../components/common/Button'; import { useDispatch } from 'react-redux'; @@ -6,8 +6,12 @@ import RadioButton from '../../components/common/RadioButton'; import AssetModules from '../../shared/AssetModules.js'; import DrawSvg from './DrawSvg'; import { useNavigate } from 'react-router-dom'; -import CoreModules from '../../shared/CoreModules'; import { CreateProjectActions } from '../../store/slices/CreateProjectSlice'; +import useForm from '../../hooks/useForm'; +import { useAppSelector } from '../../types/reduxTypes'; +import UploadAreaValidation from './validation/UploadAreaValidation'; +import FileInputComponent from '../common/FileInputComponent'; +import NewDefineAreaMap from '../../views/NewDefineAreaMap'; // @ts-ignore const DefineAreaMap = React.lazy(() => import('../../views/DefineAreaMap')); @@ -29,41 +33,73 @@ const uploadAreaOptions = [ const UploadArea = ({ flag, geojsonFile, setGeojsonFile }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const geojsonFileRef: any = useRef(null); + // const [uploadAreaFile, setUploadAreaFile] = useState(null); - const drawnGeojson = CoreModules.useAppSelector((state) => state.createproject.drawnGeojson); - const uploadAreaSelection = CoreModules.useAppSelector((state) => state.createproject.uploadAreaSelection); - const drawToggle = CoreModules.useAppSelector((state) => state.createproject.drawToggle); - const totalAreaSelection = CoreModules.useAppSelector((state) => state.createproject.totalAreaSelection); + const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails); + const drawnGeojson = useAppSelector((state) => state.createproject.drawnGeojson); + const uploadAreaSelection = useAppSelector((state) => state.createproject.uploadAreaSelection); + const drawToggle = useAppSelector((state) => state.createproject.drawToggle); + const totalAreaSelection = useAppSelector((state) => state.createproject.totalAreaSelection); + const submission = () => { + dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues)); + dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 3 })); + navigate('/select-form'); + }; + const { + handleSubmit, + handleCustomChange, + values: formValues, + errors, + }: any = useForm(projectDetails, submission, UploadAreaValidation); const toggleStep = (step, url) => { dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: step })); navigate(url); }; - const changeFileHandler = (event) => { - const { files } = event.target; - setGeojsonFile(files[0]); - }; - - const onCreateProjectSubmission = () => { - if (!drawnGeojson && !geojsonFile) { - return; - } else { - toggleStep(3, '/new-select-form'); - } - }; + // const onCreateProjectSubmission = () => { + // if (!drawnGeojson && !geojsonFile) { + // return; + // } else { + // toggleStep(3, '/new-select-form'); + // } + // }; useEffect(() => { setGeojsonFile(null); dispatch(CreateProjectActions.SetDrawnGeojson(null)); dispatch(CreateProjectActions.SetTotalAreaSelection(null)); }, [uploadAreaSelection]); + const convertFileToGeojson = async (file) => { + if (!file) return; + const fileReader = new FileReader(); + const fileLoaded = await new Promise((resolve) => { + fileReader.onload = (e) => resolve(e.target.result); + fileReader.readAsText(file, 'UTF-8'); + }); + const parsedJSON = JSON.parse(fileLoaded); + let geojsonConversion; + if (parsedJSON.type === 'FeatureCollection') { + geojsonConversion = parsedJSON; + } else { + geojsonConversion = { + type: 'FeatureCollection', + features: [{ type: 'Feature', properties: null, geometry: parsedJSON }], + }; + } + addGeojsonToState(geojsonConversion); + }; + const addGeojsonToState = (geojson) => { + dispatch(CreateProjectActions.SetDrawnGeojson(geojson)); + }; + const changeFileHandler = (event) => { + const { files } = event.target; + handleCustomChange('uploadedAreaFile', files[0].name); + setGeojsonFile(files[0]); + convertFileToGeojson(files[0]); + }; const resetFile = () => { - if (geojsonFileRef.current) { - geojsonFileRef.current.value = ''; - } setGeojsonFile(null); dispatch(CreateProjectActions.SetDrawnGeojson(null)); dispatch(CreateProjectActions.SetTotalAreaSelection(null)); @@ -81,13 +117,17 @@ const UploadArea = ({ flag, geojsonFile, setGeojsonFile }) => {
-
+
{ + handleCustomChange('uploadAreaSelection', value); dispatch(CreateProjectActions.SetUploadAreaSelection(value)); if (value === 'draw') { dispatch(CreateProjectActions.SetDrawToggle(!drawToggle)); @@ -96,6 +136,7 @@ const UploadArea = ({ flag, geojsonFile, setGeojsonFile }) => { } }} value={uploadAreaSelection} + errorMsg={errors.uploadAreaSelection} /> {uploadAreaSelection === 'draw' && (
@@ -110,41 +151,53 @@ const UploadArea = ({ flag, geojsonFile, setGeojsonFile }) => {

Total Area: {totalAreaSelection}

+ {errors.drawnGeojson && ( +

{errors.drawnGeojson}

+ )}
)} {uploadAreaSelection === 'upload_file' && ( -
-
- -
- resetFile()} /> -
-
- {geojsonFile && ( -
-

{geojsonFile?.name}

-
- )} -

- *The supported file formats are zipped shapefile, geojson or kml files. -

-

- Total Area: 234 sq.km -

-
+ + //
+ //
+ // + //
+ // resetFile()} /> + //
+ //
+ // {geojsonFile && ( + //
+ //

{geojsonFile?.name}

+ //
+ // )} + //

+ // *The supported file formats are zipped shapefile, geojson or kml files. + //

+ //

+ // Total Area: 234 sq.km + //

+ //
)}
@@ -152,25 +205,19 @@ const UploadArea = ({ flag, geojsonFile, setGeojsonFile }) => { btnText="PREVIOUS" btnType="secondary" type="button" - onClick={() => toggleStep(1, '/new-create-project')} - className="fmtm-font-bold" - /> -
-
+
- { - // dispatch(CreateProjectActions.SetDividedTaskGeojson(JSON.parse(geojson))); + handleCustomChange('drawnGeojson', geojson); dispatch(CreateProjectActions.SetDrawnGeojson(JSON.parse(geojson))); - // setGeojsonFile(JSON.parse(geojson)); dispatch(CreateProjectActions.SetTotalAreaSelection(area)); }} /> diff --git a/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx b/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx index 38656db1d0..c89df08753 100644 --- a/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx +++ b/src/frontend/src/components/createnewproject/validation/DataExtractValidation.tsx @@ -1,34 +1,37 @@ interface ProjectValues { - formCategorySelection: string; form_ways: string; dataExtractWays: string; data_extractFile: object; data_extract_options: string; dataExtractFeatureType: string; + customPolygonUpload: string; + customLineUpload: string; } interface ValidationErrors { - formCategorySelection?: string; form_ways?: string; dataExtractWays?: string; data_extractFile?: string; data_extract_options?: string; dataExtractFeatureType?: string; + customPolygonUpload?: string; + customLineUpload?: string; } function DataExtractValidation(values: ProjectValues) { const errors: ValidationErrors = {}; - if (!values?.formCategorySelection) { - errors.formCategorySelection = 'Form Category is Required.'; + if (!values?.dataExtractWays) { + errors.dataExtractWays = 'Data Extract Ways is Required.'; } - if (!values.dataExtractWays) { - errors.dataExtractWays = 'Select Data Extract Options.'; + + if (values.dataExtractWays && values.dataExtractWays === 'osm_data_extract' && !values.dataExtractFeatureType) { + errors.dataExtractFeatureType = 'OSM Feature Type is Required.'; } - if (values.dataExtractWays && values.dataExtractWays === 'Upload Custom Data Extract' && !values.data_extractFile) { - errors.data_extractFile = 'Data Extract File is Required.'; + if (values.dataExtractWays && values.dataExtractWays === 'custom_data_extract' && !values.customPolygonUpload) { + errors.customPolygonUpload = 'Custom Polygon is Required.'; } - if (values.dataExtractWays && values.dataExtractWays === 'osm_data_extract' && !values.dataExtractFeatureType) { - errors.dataExtractFeatureType = 'Data Extract Ways is Required.'; + if (values.dataExtractWays && values.dataExtractWays === 'custom_data_extract' && !values.customLineUpload) { + errors.customLineUpload = 'Custom Line is required'; } console.log(errors); diff --git a/src/frontend/src/components/createnewproject/validation/SelectFormValidation.tsx b/src/frontend/src/components/createnewproject/validation/SelectFormValidation.tsx index af4b0b2b97..92cf2c9dd6 100644 --- a/src/frontend/src/components/createnewproject/validation/SelectFormValidation.tsx +++ b/src/frontend/src/components/createnewproject/validation/SelectFormValidation.tsx @@ -1,20 +1,25 @@ interface ProjectValues { - xform_title: string; - form_ways: string; + formCategorySelection: string; + formWays: string; + customFormUpload: File | null; } interface ValidationErrors { - xform_title?: string; - form_ways?: string; + formCategorySelection?: string; + formWays?: string; + customFormUpload?: string; } function SelectFormValidation(values: ProjectValues) { const errors: ValidationErrors = {}; - if (!values?.xform_title) { - errors.xform_title = 'Form Category is Required.'; + if (!values?.formCategorySelection) { + errors.formCategorySelection = 'Form Category is Required.'; } - if (!values?.form_ways) { - errors.form_ways = 'Form Selection is Required.'; + if (!values?.formWays) { + errors.formWays = 'Form Selection is Required.'; + } + if (values?.formWays === 'custom_form' && !values?.customFormUpload) { + errors.customFormUpload = 'Form needs to be Uploaded.'; } console.log(errors); diff --git a/src/frontend/src/components/createnewproject/validation/UploadAreaValidation.tsx b/src/frontend/src/components/createnewproject/validation/UploadAreaValidation.tsx new file mode 100644 index 0000000000..628ec5b0fa --- /dev/null +++ b/src/frontend/src/components/createnewproject/validation/UploadAreaValidation.tsx @@ -0,0 +1,37 @@ +interface ProjectValues { + uploadAreaSelection: string; + dataExtractWays: string; + data_extractFile: object; + data_extract_options: string; + dataExtractFeatureType: string; + drawnGeojson: string; + uploadedAreaFile: string; +} +interface ValidationErrors { + uploadAreaSelection?: string; + dataExtractWays?: string; + data_extractFile?: string; + data_extract_options?: string; + dataExtractFeatureType?: string; + drawnGeojson?: string; + uploadedAreaFile?: string; +} + +function UploadAreaValidation(values: ProjectValues) { + const errors: ValidationErrors = {}; + + if (!values.uploadAreaSelection) { + errors.uploadAreaSelection = 'Select Upload Area Options.'; + } + if (values.uploadAreaSelection === 'draw' && !values.drawnGeojson) { + errors.drawnGeojson = 'Drawing Area is Required.'; + } + if (values.uploadAreaSelection === 'upload_file' && !values.uploadedAreaFile) { + errors.uploadedAreaFile = 'Uploaded Area File is Required.'; + } + + console.log(errors); + return errors; +} + +export default UploadAreaValidation; diff --git a/src/frontend/src/components/createproject/DataExtract.tsx b/src/frontend/src/components/createproject/DataExtract.tsx index 5e9558e3b3..4fc7355532 100755 --- a/src/frontend/src/components/createproject/DataExtract.tsx +++ b/src/frontend/src/components/createproject/DataExtract.tsx @@ -37,11 +37,6 @@ const DataExtract: React.FC = ({ const projectDetails = CoreModules.useAppSelector((state) => state.createproject.projectDetails); // //we use use-selector from redux to get all state of projectDetails from createProject slice - // Fetching form category list - useEffect(() => { - dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); - }, []); - // END const selectExtractWaysList = ['Centroid', 'Polygon']; const selectExtractWays = selectExtractWaysList.map((item) => ({ label: item, value: item })); const dataExtractOptionsList = ['Data Extract Ways', 'Upload Custom Data Extract']; @@ -49,12 +44,6 @@ const DataExtract: React.FC = ({ const formCategoryData = formCategoryList.map((item) => ({ label: item.title, value: item.title })); // //we use use-selector from redux to get state of dividedTaskGeojson from createProject slice - // Fetching form category list - useEffect(() => { - dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); - }, []); - // END - const submission = () => { // const previousValues = location.state.values; dispatch(CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, ...values })); diff --git a/src/frontend/src/components/createproject/DefineTasks.tsx b/src/frontend/src/components/createproject/DefineTasks.tsx index f733b31ff2..1d635322b6 100755 --- a/src/frontend/src/components/createproject/DefineTasks.tsx +++ b/src/frontend/src/components/createproject/DefineTasks.tsx @@ -88,7 +88,7 @@ const DefineTasks: React.FC = ({ geojsonFile, setGeojsonFile, dataExtractFi `${import.meta.env.VITE_API_URL}/projects/task_split`, drawnGeojsonFile, formValues?.no_of_buildings, - dataExtractFile ? true : false, + dataExtractFile ? false : false, ), ); } else { @@ -97,7 +97,7 @@ const DefineTasks: React.FC = ({ geojsonFile, setGeojsonFile, dataExtractFi `${import.meta.env.VITE_API_URL}/projects/task_split`, geojsonFile, formValues?.no_of_buildings, - dataExtractFile ? true : false, + dataExtractFile ? false : false, ), ); } diff --git a/src/frontend/src/components/createproject/FormSelection.tsx b/src/frontend/src/components/createproject/FormSelection.tsx index 3bf46a8b55..c80acf131c 100755 --- a/src/frontend/src/components/createproject/FormSelection.tsx +++ b/src/frontend/src/components/createproject/FormSelection.tsx @@ -39,11 +39,6 @@ const FormSelection: React.FC = ({ const projectDetails = useAppSelector((state) => state.createproject.projectDetails); // //we use use-selector from redux to get all state of projectDetails from createProject slice - // Fetching form category list - useEffect(() => { - dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); - }, []); - // END const selectFormWaysList = ['Use Existing Form', 'Upload a Custom Form']; const selectFormWays = selectFormWaysList.map((item) => ({ label: item, value: item })); const userDetails: any = CoreModules.useAppSelector((state) => state.login.loginToken); @@ -61,10 +56,6 @@ const FormSelection: React.FC = ({ const projectDetailsLoading = CoreModules.useAppSelector((state) => state.createproject.projectDetailsLoading); // //we use use-selector from redux to get state of dividedTaskGeojson from createProject slice - // Fetching form category list - useEffect(() => { - dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`)); - }, []); // END const submission = () => { diff --git a/src/frontend/src/components/createproject/ProjectDetailsForm.tsx b/src/frontend/src/components/createproject/ProjectDetailsForm.tsx index f5a4446e16..36337f1f62 100755 --- a/src/frontend/src/components/createproject/ProjectDetailsForm.tsx +++ b/src/frontend/src/components/createproject/ProjectDetailsForm.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from 'react'; -import windowDimention from '../../hooks/WindowDimension'; +import React, { useEffect } from 'react'; import CoreModules from '../../shared/CoreModules'; import AssetModules from '../../shared/AssetModules'; import { useNavigate } from 'react-router-dom'; @@ -14,22 +13,17 @@ import { useAppSelector } from '../../types/reduxTypes'; const ProjectDetailsForm: React.FC = () => { const defaultTheme: any = CoreModules.useAppSelector((state) => state.theme.hotTheme); - // // const state:any = CoreModules.useAppSelector(state=>state.project.projectData) - // // console.log('state main :',state) - - // const { type } = windowDimention(); - // //get window dimension const navigate = useNavigate(); const dispatch = CoreModules.useAppDispatch(); - // //dispatch function to perform redux state mutation + //dispatch function to perform redux state mutation const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails); - // //we use use selector from redux to get all state of projectDetails from createProject slice + //we use use selector from redux to get all state of projectDetails from createProject slice const organizationListData: any = useAppSelector((state) => state.createproject.organizationList); - // //we use use selector from redux to get all state of projectDetails from createProject slice + //we use use selector from redux to get all state of projectDetails from createProject slice useEffect(() => { // dispatch(OrganisationService(`${import.meta.env.VITE_API_URL}/organization/`)); diff --git a/src/frontend/src/components/createproject/validation/DefineTaskValidation.tsx b/src/frontend/src/components/createproject/validation/DefineTaskValidation.tsx index 83027d4c25..924f86e9c9 100644 --- a/src/frontend/src/components/createproject/validation/DefineTaskValidation.tsx +++ b/src/frontend/src/components/createproject/validation/DefineTaskValidation.tsx @@ -1,24 +1,32 @@ interface ProjectValues { - splitting_algorithm: string; + splitTaskOption: string; dimension: number; + average_buildings_per_task: number; } interface ValidationErrors { - splitting_algorithm?: string; + splitTaskOption?: string; dimension?: string; + average_buildings_per_task?: string; } function DefineTaskValidation(values: ProjectValues) { const errors: ValidationErrors = {}; - if (!values?.splitting_algorithm) { - errors.splitting_algorithm = 'Splitting Algorithm is Required.'; + if (!values?.splitTaskOption) { + errors.splitTaskOption = 'Splitting Task Option is required.'; } - if (values?.splitting_algorithm === 'Divide on Square' && !values?.dimension) { + if (values?.splitTaskOption === 'divide_on_square' && !values?.dimension) { errors.dimension = 'Dimension is Required.'; } - if (values?.splitting_algorithm === 'Divide on Square' && values?.dimension && values.dimension < 9) { + if (values?.splitTaskOption === 'divide_on_square' && values?.dimension && values.dimension < 9) { errors.dimension = 'Dimension should be greater than 10 or equal to 10.'; } + if (values?.splitTaskOption === 'task_splitting_algorithm' && !values?.average_buildings_per_task) { + errors.average_buildings_per_task = 'Average number of buildings per task is required.'; + } + if (values?.splitTaskOption === 'task_splitting_algorithm' && !values?.average_buildings_per_task) { + errors.average_buildings_per_task = 'Average number of buildings per task is required.'; + } console.log(errors); return errors; diff --git a/src/frontend/src/constants/StepFormConstants.ts b/src/frontend/src/constants/StepFormConstants.ts index 25e6650e6e..29baa62ba7 100644 --- a/src/frontend/src/constants/StepFormConstants.ts +++ b/src/frontend/src/constants/StepFormConstants.ts @@ -7,31 +7,31 @@ interface ICreateProjectSteps { export const createProjectSteps: ICreateProjectSteps[] = [ { - url: '/new-create-project', + url: '/create-project', step: 1, label: '01', name: 'Project Details', }, { - url: '/new-upload-area', + url: '/upload-area', step: 2, label: '02', name: 'Upload Area', }, { - url: '/new-select-form', + url: '/select-form', step: 3, label: '03', name: 'Select Form', }, { - url: '/new-data-extract', + url: '/data-extract', step: 4, label: '04', name: 'Data Extract', }, { - url: '/new-define-tasks', + url: '/split-tasks', step: 5, label: '05', name: 'Split Tasks', diff --git a/src/frontend/src/hooks/useForm.tsx b/src/frontend/src/hooks/useForm.tsx index 21d7f7db2e..4000b0b4b0 100755 --- a/src/frontend/src/hooks/useForm.tsx +++ b/src/frontend/src/hooks/useForm.tsx @@ -18,7 +18,16 @@ const useForm = (initialState, callback, validate) => { const handleAllValues = (value) => { setValues(value); }; - + const checkValidationOnly = (formValues) => { + const x = validate(formValues); + console.log(x, 'x'); + console.log(formValues, 'values'); + if (Object.keys(x)?.length === 0) { + return false; + } else { + return true; + } + }; const handleSubmit = (event) => { event.preventDefault(); // setErrors(validate(values)); @@ -63,6 +72,7 @@ const useForm = (initialState, callback, validate) => { handleAllValues, setValues, setErrors, + checkValidationOnly, }; }; diff --git a/src/frontend/src/index.css b/src/frontend/src/index.css index b802820e62..f35428d486 100755 --- a/src/frontend/src/index.css +++ b/src/frontend/src/index.css @@ -178,3 +178,56 @@ button { } } } + +.currentstep-pointer { + animation: pulse 2s infinite; +} +.fmtm-progress-indicator { + background-image: linear-gradient( + -45deg, + rgba(255, 255, 255, 0.2) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.2) 50%, + rgba(255, 255, 255, 0.2) 75%, + transparent 75%, + transparent + ); + background-size: 50px 50px; + + animation: move 2s linear infinite; +} + +@-webkit-keyframes pulse { + 0% { + -webkit-box-shadow: 0 0 0 0 rgb(244, 22, 22, 0.8); + } + 70% { + -webkit-box-shadow: 0 0 0 20px rgba(244, 157, 22, 0.8); + } + 100% { + -webkit-box-shadow: 0 0 0 0 rgba(244, 157, 22, 0.8); + } +} +@keyframes pulse { + 0% { + -moz-box-shadow: 0 0 0 0 rgb(244, 22, 22); + box-shadow: 0 0 0 0 rgba(244, 22, 22); + } + 70% { + -moz-box-shadow: 0 0 0 10px rgba(244, 157, 22, 0); + box-shadow: 0 0 0 10px rgba(244, 157, 22, 0); + } + 100% { + -moz-box-shadow: 0 0 0 0 rgba(244, 157, 22, 0); + box-shadow: 0 0 0 0 rgba(244, 157, 22, 0); + } +} +@keyframes move { + 0% { + background-position: 0 0; + } + 100% { + background-position: 50px 50px; + } +} diff --git a/src/frontend/src/routes.jsx b/src/frontend/src/routes.jsx index 6cd81390ff..7654563b93 100755 --- a/src/frontend/src/routes.jsx +++ b/src/frontend/src/routes.jsx @@ -132,66 +132,66 @@ const routes = createBrowserRouter([ ), }, - { - path: '/create-project', - element: ( - - Loading...
}> - - - - - - ), - }, - { - path: '/upload-area', - element: ( - - Loading...
}> - - - - - - ), - }, - { - path: '/data-extract', - element: ( - - Loading...
}> - - - - - - ), - }, - { - path: '/define-tasks', - element: ( - - Loading...
}> - - - - - - ), - }, - { - path: '/select-form', - element: ( - - Loading...
}> - - - - - - ), - }, + // { + // path: '/create-project', + // element: ( + // + // Loading...
}> + // + // + // + // + // + // ), + // }, + // { + // path: '/upload-area', + // element: ( + // + // Loading...}> + // + // + // + // + // + // ), + // }, + // { + // path: '/data-extract', + // element: ( + // + // Loading...}> + // + // + // + // + // + // ), + // }, + // { + // path: '/define-tasks', + // element: ( + // + // Loading...}> + // + // + // + // + // + // ), + // }, + // { + // path: '/select-form', + // element: ( + // + // Loading...}> + // + // + // + // + // + // ), + // }, { path: '/basemap-selection', element: ( @@ -205,7 +205,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-create-project', + path: '/create-project', element: ( Loading...}> @@ -217,7 +217,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-upload-area', + path: '/upload-area', element: ( Loading...}> @@ -229,7 +229,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-data-extract', + path: '/data-extract', element: ( Loading...}> @@ -241,7 +241,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-define-tasks', + path: '/split-tasks', element: ( Loading...}> @@ -253,7 +253,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-select-form', + path: '/select-form', element: ( Loading...}> @@ -265,7 +265,7 @@ const routes = createBrowserRouter([ ), }, { - path: '/new-basemap-selection', + path: '/basemap-selection', element: ( Loading...}> @@ -313,7 +313,7 @@ const routes = createBrowserRouter([ ), }, { - path: 'edit-project/define-tasks/:projectId', + path: 'edit-project/split-tasks/:projectId', element: ( Loading...}> diff --git a/src/frontend/src/store/Store.ts b/src/frontend/src/store/Store.ts index cc0a9cebd5..cc20f452e9 100755 --- a/src/frontend/src/store/Store.ts +++ b/src/frontend/src/store/Store.ts @@ -30,6 +30,7 @@ const rootReducer = combineReducers({ home: HomeSlice.reducer, theme: ThemeSlice.reducer, createproject: CreateProjectReducer, + // createproject: persist('createproject', ['projectDetails', 'projectInfo'], CreateProjectReducer), organization: OrganizationSlice.reducer, // added common slice in order to handle all the common things like snackbar etc common: CommonSlice.reducer, diff --git a/src/frontend/src/store/slices/CreateProjectSlice.ts b/src/frontend/src/store/slices/CreateProjectSlice.ts index 330159d90b..636be30b51 100755 --- a/src/frontend/src/store/slices/CreateProjectSlice.ts +++ b/src/frontend/src/store/slices/CreateProjectSlice.ts @@ -1,4 +1,4 @@ -import { CreateProjectStateTypes } from 'store/types/ICreateProject'; +import { CreateProjectStateTypes } from '../types/ICreateProject'; import { createSlice } from '@reduxjs/toolkit'; export const initialState: CreateProjectStateTypes = { @@ -43,6 +43,9 @@ export const initialState: CreateProjectStateTypes = { uploadAreaSelection: null, totalAreaSelection: null, splitTasksSelection: null, + buildingGeojson: null, + lineGeojson: null, + createProjectValidations: {}, }; const CreateProject = createSlice({ @@ -60,8 +63,27 @@ const CreateProject = createSlice({ }, ClearCreateProjectFormData(state) { // state.projectDetailsResponse = null - state.projectDetails = { dimension: 10, no_of_buildings: 5, hashtags: 'hotosm-fmtm ' }; + state.projectDetails = { + dimension: 10, + no_of_buildings: 5, + hashtags: '#FMTM ', + name: '', + short_description: '', + odk_central_url: '', + odk_central_user: '', + odk_central_password: '', + description: '', + organisation_id: null, + }; state.projectArea = null; + state.totalAreaSelection = null; + state.splitTasksSelection = null; + state.buildingGeojson = null; + state.lineGeojson = null; + state.taskSplittingGeojson = null; + state.drawnGeojson = null; + state.generateProjectLog = null; + state.generateProjectLogLoading = false; }, UploadAreaLoading(state, action) { state.projectAreaLoading = action.payload; @@ -143,6 +165,7 @@ const CreateProject = createSlice({ }, GetTaskSplittingPreview(state, action) { state.dividedTaskGeojson = action.payload; + // state.drawnGeojson = action.payload; state.taskSplittingGeojson = action.payload; }, SetEditProjectBoundaryServiceLoading(state, action) { @@ -166,6 +189,18 @@ const CreateProject = createSlice({ SetSplitTasksSelection(state, action) { state.splitTasksSelection = action.payload; }, + SetBuildingGeojson(state, action) { + state.buildingGeojson = action.payload; + }, + SetLineGeojson(state, action) { + state.lineGeojson = action.payload; + }, + SetCreateProjectValidations(state, action) { + state.createProjectValidations = { + ...state.createProjectValidations, + [action.payload.key]: action.payload.value, + }; + }, }, }); diff --git a/src/frontend/src/store/types/ICreateProject.ts b/src/frontend/src/store/types/ICreateProject.ts index bcba4d082d..bfff35c129 100644 --- a/src/frontend/src/store/types/ICreateProject.ts +++ b/src/frontend/src/store/types/ICreateProject.ts @@ -29,6 +29,9 @@ export type CreateProjectStateTypes = { uploadAreaSelection: string | null; totalAreaSelection: string | null; splitTasksSelection: string | null; + buildingGeojson: null; + lineGeojson: null; + createProjectValidations: {}; }; export type ValidateCustomFormResponse = { detail: { message: string; possible_reason: string }; diff --git a/src/frontend/src/views/CreateNewProject.tsx b/src/frontend/src/views/CreateNewProject.tsx index c58afd3b3a..67e75c3fe1 100644 --- a/src/frontend/src/views/CreateNewProject.tsx +++ b/src/frontend/src/views/CreateNewProject.tsx @@ -16,23 +16,25 @@ const CreateNewProject = () => { const dispatch = useDispatch(); const [geojsonFile, setGeojsonFile] = useState(null); + const [customLineUpload, setCustomLineUpload] = useState(null); + const [customPolygonUpload, setCustomPolygonUpload] = useState(null); const [customFormFile, setCustomFormFile] = useState(null); useEffect(() => { switch (location.pathname) { - case '/new-create-project': + case '/create-project': dispatch(CommonActions.SetCurrentStepFormStep({ flag: 'create_project', step: 1 })); break; - case '/new-upload-area': + case '/upload-area': dispatch(CommonActions.SetCurrentStepFormStep({ flag: 'create_project', step: 2 })); break; - case '/new-select-form': + case '/select-form': dispatch(CommonActions.SetCurrentStepFormStep({ flag: 'create_project', step: 3 })); break; - case '/new-data-extract': + case '/data-extract': dispatch(CommonActions.SetCurrentStepFormStep({ flag: 'create_project', step: 4 })); break; - case '/new-define-tasks': + case '/split-tasks': dispatch(CommonActions.SetCurrentStepFormStep({ flag: 'create_project', step: 5 })); break; default: @@ -41,35 +43,42 @@ const CreateNewProject = () => { } }, [location.pathname]); - // const currentStep = CoreModules.useAppSelector((state) => state.common.currentStepFormStep.create_project); const getCreateProjectContent = (): JSX.Element => { - // switch (currentStep.step) { - // case 1: - // return ; - // case 2: - // return ; - // case 3: - // return ; - // case 4: - // return ; - // case 5: - // return ; - // default: - // return ; - // } switch (location.pathname) { - case '/new-create-project': + case '/create-project': return ; - case '/new-upload-area': + case '/upload-area': return ; - case '/new-select-form': - return ; - case '/new-data-extract': + case '/select-form': return ( - + + ); + case '/data-extract': + return ( + + ); + case '/split-tasks': + return ( + ); - case '/new-define-tasks': - return ; default: return ; } diff --git a/src/frontend/src/views/NewDefineAreaMap.tsx b/src/frontend/src/views/NewDefineAreaMap.tsx new file mode 100644 index 0000000000..adf00607c6 --- /dev/null +++ b/src/frontend/src/views/NewDefineAreaMap.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import useOLMap from '../hooks/useOlMap'; +import { MapContainer as MapComponent } from '../components/MapComponent/OpenLayersComponent'; +import LayerSwitcherControl from '../components/MapComponent/OpenLayersComponent/LayerSwitcher/index.js'; +import { VectorLayer } from '../components/MapComponent/OpenLayersComponent/Layers'; +import { GeoJSONFeatureTypes } from '../store/types/ICreateProject'; + +type NewDefineAreaMapProps = { + drawToggle?: boolean; + splittedGeojson: GeoJSONFeatureTypes; + uploadedOrDrawnGeojsonFile: GeoJSONFeatureTypes; + buildingExtractedGeojson?: GeoJSONFeatureTypes; + lineExtractedGeojson?: GeoJSONFeatureTypes; + onDraw?: () => void; +}; +const NewDefineAreaMap = ({ + drawToggle, + uploadedOrDrawnGeojsonFile, + splittedGeojson, + buildingExtractedGeojson, + lineExtractedGeojson, + onDraw, +}: NewDefineAreaMapProps) => { + const { mapRef, map } = useOLMap({ + // center: fromLonLat([85.3, 27.7]), + center: [0, 0], + zoom: 1, + maxZoom: 25, + }); + const isDrawOrGeojsonFile = drawToggle || uploadedOrDrawnGeojsonFile; + + return ( +
+ + + {splittedGeojson && ( + + )} + {isDrawOrGeojsonFile && !splittedGeojson && ( + + )} + + {buildingExtractedGeojson && ( + + )} + {lineExtractedGeojson && ( + + )} + +
+ ); +}; + +export default NewDefineAreaMap; diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json index 944de178aa..42eacebc78 100644 --- a/src/frontend/tsconfig.json +++ b/src/frontend/tsconfig.json @@ -18,7 +18,7 @@ "noEmit": true, // "jsx": "react-jsx", "jsx": "react", - "baseUrl": "../frontend/src", + "baseUrl": "./src", "noImplicitAny": false, //FIXME: Change This "true" to "false" To Integrate Types Instead Of "Any" Types. "strictNullChecks": true, "strictFunctionTypes": true,