diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 58f3ade51..ac395022e 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -40,6 +40,7 @@ from loguru import logger as log from osm_fieldwork.data_models import data_models_path from osm_fieldwork.make_data_extract import getChoices +from osm_fieldwork.update_form import update_xls_form from osm_fieldwork.xlsforms import xlsforms_path from sqlalchemy.orm import Session from sqlalchemy.sql import text @@ -676,7 +677,19 @@ async def validate_form(form: UploadFile): ) contents = await form.read() - return await central_crud.read_and_test_xform(BytesIO(contents), file_ext) + updated_file_bytes = update_xls_form(BytesIO(contents)) + + # open bytes again to avoid I/O error on closed bytes + form_data = BytesIO(updated_file_bytes.getvalue()) + + await central_crud.read_and_test_xform(updated_file_bytes, file_ext) + + # Return the updated form as a StreamingResponse + return StreamingResponse( + form_data, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": f"attachment; filename={form.filename}"}, + ) @router.post("/{project_id}/generate-project-data") diff --git a/src/backend/pdm.lock b/src/backend/pdm.lock index e3960f149..9ebb3d708 100644 --- a/src/backend/pdm.lock +++ b/src/backend/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "debug", "dev", "docs", "test", "monitoring"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:42d7234076416057ff7b9305f5b39cff8c58ed6488bea22e55515f51915caa57" +content_hash = "sha256:93891510bf2629cff6e0cd56c022b832de117be958b7349236095548ccdc90f3" [[package]] name = "aiohttp" @@ -1579,32 +1579,34 @@ files = [ [[package]] name = "osm-fieldwork" -version = "0.15.0" +version = "0.16.0" requires_python = ">=3.10" summary = "Processing field data from ODK to OpenStreetMap format." dependencies = [ "PyYAML>=6.0.0", - "aiohttp>=3.9.3", + "aiohttp>=3.8.4", "codetiming>=1.3.0", "flatdict>=4.0.1", "geojson>=2.5.0", "haversine>=2.8.0", "levenshtein>=0.20.0", "mercantile>=1.2.1", + "openpyxl>=3.0.10", "osm-rawdata>=0.1.7", "pandas>=1.5.0", "pmtiles>=3.2.0", "progress>=1.6", "py-cpuinfo>=9.0.0", "pySmartDL>=1.3.4", + "python-calamine>=0.2.3", "requests>=2.26.0", "segno>=1.5.2", "shapely>=1.8.5", "xmltodict>=0.13.0", ] files = [ - {file = "osm-fieldwork-0.15.0.tar.gz", hash = "sha256:9491ffa6aabdd665e87b34dc6bf663541d8af8f9f6637dbc16b28c8f2a9bed72"}, - {file = "osm_fieldwork-0.15.0-py3-none-any.whl", hash = "sha256:93b3beda2a2e1112a6f34a4cbdb6eba308543af68eab34f9829e6df08f7d2cd8"}, + {file = "osm-fieldwork-0.16.0.tar.gz", hash = "sha256:da125269caf64caced40bead2f060dd035561aacb0650793edca01adcf3c172c"}, + {file = "osm_fieldwork-0.16.0-py3-none-any.whl", hash = "sha256:dba09f395cf9bcdfdfbe63c504ef9b3e2775a63e4e869bd19a5080942f8cf638"}, ] [[package]] @@ -2116,6 +2118,78 @@ files = [ {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] +[[package]] +name = "python-calamine" +version = "0.2.3" +requires_python = ">=3.8" +summary = "Python binding for Rust's library for reading excel and odf file - calamine" +files = [ + {file = "python_calamine-0.2.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e5a36cca8b447295e9edddbe055857bdfdec56cb78554455a03bacd78e3c45a0"}, + {file = "python_calamine-0.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b5b0803c70269d93b67c42f03e5711a7ba02166fd473a6cb89ef71632167154"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73766349215f69854afb092ef891cb1ff253f4b6611342566c469b46516c6ada"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3bf4cf41518541016b9442082360a83f3579955a872cfca5cec50acc3101cce5"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f1f6dab7b44deed8cf7b45a6d6d2743b622ba5e21a8b73f52ef1064cc5e3638"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1991261d40be3d577ce48c0884c6403aefd1cbef5dcc451e039746aa1d185931"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f675e7f45d9e3f1430f3114701133432c279aba06442e743220f6b648023b5ee"}, + {file = "python_calamine-0.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb7444454cff2c1ad44e7f1a1be776845cbad8f1210d868c7058d2183b3da74"}, + {file = "python_calamine-0.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7a604306cd5ceca720f0426deb49192f2ede5eedd1597b7ff4fa9659a36dc462"}, + {file = "python_calamine-0.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b95afd1a1cd3871d472aa117537b8731c1609756347874b251300cff152176a5"}, + {file = "python_calamine-0.2.3-cp311-none-win32.whl", hash = "sha256:a0ae5a740c9d97b2842d948a91f926a0fab278d247d816fe786219b94507c5a2"}, + {file = "python_calamine-0.2.3-cp311-none-win_amd64.whl", hash = "sha256:a32c64e74673fb0203ad877c6ba4832de7976fd31c79c637552b567d295ff6b5"}, + {file = "python_calamine-0.2.3-cp311-none-win_arm64.whl", hash = "sha256:f8c4c9e7ade09b4122c59e3e0da7e5fba872a0e47d3076702185a4ffdf99dec4"}, + {file = "python_calamine-0.2.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:40e5f75c4a7bb2105e3bd65e7b4656e085c6d86e46af1c56468a2f87c2ed639a"}, + {file = "python_calamine-0.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3557bdd36060db4929f42bf4c2c728a76af60ccc95d5c98f2110331d993a7299"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa75b28686f9dc727d26a97b41c6a2a6ca1d2c679139b6199edbae2782e7c77"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2c8577b00e13f5f43b1c03a2eca01848c3b24467ebaf597729d1e483613c110"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4639255202380251833a9ab75c077e687ebbef2120f54030b2dc46eb6ce43105"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:583656c6a6e8efac8951cd72459e2d84eea5f2617214ebc7e1c96217b44a0fa1"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68fc61b34a1d82d3eee2109d323268dd455107dfb639b027aa5c388e2781273c"}, + {file = "python_calamine-0.2.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64bb1f212275ed0288f578ee817e5cad4a063cfe5c38bf4c4dc6968957cb95b0"}, + {file = "python_calamine-0.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a7da299c1676dc34cd5f0adf93e92139afbfb832722d5d50a696ac180885aabb"}, + {file = "python_calamine-0.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:599752629ab0c5231159c5bea4f94795dd9b11a36c02dd5bd0613cf257ecd710"}, + {file = "python_calamine-0.2.3-cp312-none-win32.whl", hash = "sha256:fc73da2863c3251862583d64c0d07fe907f489a86a205e2b6ac94a39a1df1b42"}, + {file = "python_calamine-0.2.3-cp312-none-win_amd64.whl", hash = "sha256:a8d1662b4767f863c17ea4c1afc3c3fe3174d7b007ae77349d481e6792d142fe"}, + {file = "python_calamine-0.2.3-cp312-none-win_arm64.whl", hash = "sha256:87af11076364ade6f3da9e33993b6f55ec8dfd5f017129de688fd6d94d7bc24a"}, + {file = "python_calamine-0.2.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1ae98e1db1d3e74df08291f66d872bf7a4c47d96d39f8f589bff5dab873fbd13"}, + {file = "python_calamine-0.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc270e8827191e7125600c97b61b3c78ec17d394820c2607c801f93c3475a0aa"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c25b18eca7976aac0748fc122fa5109be66801d94b77a7676125fb825a8b67b9"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:484330c0a917879afc615dc15e5ad925953a726f1a839ce3c35504a5befdae0c"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c15ccb20f49eb6f824664ca8ec741edf09679977c2d41d13a02f0532f71a318b"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19421a1b8a808333c39b03e007b74c85220700ceed1229449a21d51803d0671b"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cd8e3069c57a26eea5e6d3addb3dab812cc39b70f0cd11246d6f6592b7f293"}, + {file = "python_calamine-0.2.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d13822a6669a00da497394719a1fa63033ab79858fd653d330a6a7a681a5f6ce"}, + {file = "python_calamine-0.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:767db722eeb9c4d3847a87e4c3c4c9cc3e48938efaed4c507a5dd538a6bc5910"}, + {file = "python_calamine-0.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:4cac4095c25c64ef091fd994f62c5169f3ab0eec39c5bdbd0f319cac633b8183"}, + {file = "python_calamine-0.2.3-cp313-none-win32.whl", hash = "sha256:79aab3dc2c54525896b24002756e12fe09ec573efc2787285c244520bc17c39f"}, + {file = "python_calamine-0.2.3-cp313-none-win_amd64.whl", hash = "sha256:bd6606c893493eb555db5e63aef85b87fd806e6a0aa59bad0dbb591b88db2a0d"}, + {file = "python_calamine-0.2.3-cp313-none-win_arm64.whl", hash = "sha256:9f7b93851c941efba8387bb3c004437541230e8253230868204a079f1dacc21a"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:10f28b56fb84bd622e23f32881fd17b07ab039e7f2cacdfb6101dce702e77970"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d00cef2e12e4b6660b5fab13f936194263e7e11f707f7951b1867995278051df"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7aebcbd105e49516dd1831f05a0ffca7c9b85f855bf3a9c68f9bc509a212e381"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5a9182590f5ad12e08a0ba9b72dfe0e6b1780ff95153926e2f4564a6018a14"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2af3805806088acc7b4d766b58b03d08947a7100e1ef26e55509161adbb36201"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5283e049cc36a0e2442f72d0c2c156dc1e7dc7ca48cba02d52c5cb223525b5c3"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:9b7d0ef322f073099ea69e4a3db8c31ff4c4f7cdf4cd333f0577ab0c9320eaf5"}, + {file = "python_calamine-0.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0bcd07be6953efb08340ccb19b9ae0732b104a9e672edf1ffd2d6b3cc226d815"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a8b12de6e2329643dd6b0a56570b853b94149ca7b1b323db3f69a06f61ec1e2"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cad27b0e491060dc72653ccd9288301120b23261e3e374f2401cc133547615d4"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:303e2f2a1bdfaf428db7aca50d954667078c0cdf1b585ff090dfca2fac9107d7"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a21187b6ebcdabdfe2113df11c2a522b9adc02bcf54bd3ba424ca8c6762cd9b"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2773094cc62602f6bcc2acd8e905b3e2292daf6a6c24ddbc85f41065604fd9d4"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6de5646a9ec3d24b5089ed174f4dcee13620e65e20dc463097c00e803c81f86f"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e976c948ab18e9fee589994b68878381e1e393d870362babf9634258deb4f13b"}, + {file = "python_calamine-0.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:00fdfd24d13d8b04619dd933be4888bc6a70427e217fb179f3a1f71f2e377219"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab7d60482520508ebf00476cde1b97011084a2e73ac49b2ca32003547e7444c9"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00c915fc67b0b4e1ddd000d374bd808d947f2ecb0f6051a4669a77abada4b7b8"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c869fe1b568a2a970b13dd59a58a13a81a667aff2f365a95a577555585ff14bc"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:602ebad70b176a41f22547d6bb99a6d32a531a11dbf74720f3984e6bf98c94ab"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f6a7c4eb79803ee7cdfd00a0b8267c60c33f25da8bb9275f6168a4dd1a54db76"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:68275fed9dcbe90a9185c9919980933e4feea925db178461f0cdb336a2587021"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5efc667fd002db9482a7b9f2c70b41fa69c86e18206132be1a0adcad3c998c17"}, + {file = "python_calamine-0.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d2d845cbcd767c7b85c616849f0c6cd619662adb98d86af2a3fd8630d6acc48d"}, + {file = "python_calamine-0.2.3.tar.gz", hash = "sha256:d6b3858c3756629d9b4a166de0facfa6c8033fa0b73dcddd3d82144f3170c0dc"}, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 850883c94..8fb47feb1 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "pyjwt>=2.8.0", "async-lru>=2.0.4", "osm-login-python==2.0.0", - "osm-fieldwork==0.15.0", + "osm-fieldwork==0.16.0", "osm-rawdata==0.3.2", "fmtm-splitter==1.3.0", ] diff --git a/src/frontend/src/api/CreateProjectService.ts b/src/frontend/src/api/CreateProjectService.ts index 4ac164106..f96bf19a8 100755 --- a/src/frontend/src/api/CreateProjectService.ts +++ b/src/frontend/src/api/CreateProjectService.ts @@ -462,14 +462,17 @@ const ValidateCustomForm = (url: string, formUpload: any) => { const formUploadFormData = new FormData(); formUploadFormData.append('form', formUpload); - const getTaskSplittingResponse = await axios.post(url, formUploadFormData); - const resp: ValidateCustomFormResponse = getTaskSplittingResponse.data; - dispatch(CreateProjectActions.ValidateCustomForm(resp)); + // response is in file format so we need to convert it to blob + const getTaskSplittingResponse = await axios.post(url, formUploadFormData, { + responseType: 'blob', + }); + const resp = getTaskSplittingResponse.data; + dispatch(CreateProjectActions.SetValidatedCustomFile(new File([resp], 'form.xlsx', { type: resp.type }))); dispatch(CreateProjectActions.ValidateCustomFormLoading(false)); dispatch( CommonActions.SetSnackBar({ open: true, - message: JSON.stringify(resp.message), + message: 'Your Form is Valid', variant: 'success', duration: 2000, }), @@ -479,7 +482,7 @@ const ValidateCustomForm = (url: string, formUpload: any) => { dispatch( CommonActions.SetSnackBar({ open: true, - message: error?.response?.data?.detail || 'Something Went Wrong', + message: JSON.parse(await error?.response?.data.text())?.detail || 'Something Went Wrong', variant: 'error', duration: 5000, }), diff --git a/src/frontend/src/components/createnewproject/SelectForm.tsx b/src/frontend/src/components/createnewproject/SelectForm.tsx index 51dc48e19..225b91d26 100644 --- a/src/frontend/src/components/createnewproject/SelectForm.tsx +++ b/src/frontend/src/components/createnewproject/SelectForm.tsx @@ -13,6 +13,7 @@ import { FormCategoryService, ValidateCustomForm } from '@/api/CreateProjectServ import NewDefineAreaMap from '@/views/NewDefineAreaMap'; import { CustomCheckbox } from '../common/Checkbox'; import useDocumentTitle from '@/utilfunctions/useDocumentTitle'; +import { Loader2 } from 'lucide-react'; const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => { useDocumentTitle('Create Project: Select Category'); @@ -21,8 +22,9 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => const projectDetails = useAppSelector((state) => state.createproject.projectDetails); const drawnGeojson = useAppSelector((state) => state.createproject.drawnGeojson); - const dataExtractGeojson = useAppSelector((state) => state.createproject.dataExtractGeojson); const customFileValidity = useAppSelector((state) => state.createproject.customFileValidity); + const validatedCustomForm = useAppSelector((state) => state.createproject.validatedCustomForm); + const validateCustomFormLoading = useAppSelector((state) => state.createproject.validateCustomFormLoading); const submission = () => { dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues)); @@ -60,12 +62,12 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => const { files } = event.target; // Set the selected file as the customFormFile state setCustomFormFile(files[0]); - handleCustomChange('customFormUpload', files[0]); }; const resetFile = (): void => { handleCustomChange('customFormUpload', null); dispatch(CreateProjectActions.SetCustomFileValidity(false)); setCustomFormFile(null); + dispatch(CreateProjectActions.SetValidatedCustomFile(null)); }; useEffect(() => { @@ -81,10 +83,17 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => dispatch(ValidateCustomForm(`${import.meta.env.VITE_API_URL}/projects/validate-form`, customFormFile)); } }, [customFormFile]); + + //add validated form to state + useEffect(() => { + if (!validatedCustomForm) return; + handleCustomChange('customFormUpload', validatedCustomForm); + }, [validatedCustomForm]); + return (
-
Select Category
+
Select Category

You may choose a pre-configured form, or upload a custom XLS form. Click{' '} @@ -102,6 +111,10 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => You may either download the sample XLS file and modify all fields that are not hidden, or edit the sample form interactively in the browser. + + Note: Additional questions will be incorporated into your custom form to assess the digitization + status. +

@@ -187,6 +200,12 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => fileDescription="*The supported file formats are .xlsx, .xls, .xml" errorMsg={errors.customFormUpload} /> + {validateCustomFormLoading && ( +
+ +

Validating form...

+
+ )}
) : null}
@@ -201,7 +220,13 @@ const SelectForm = ({ flag, geojsonFile, customFormFile, setCustomFormFile }) => }} className="fmtm-font-bold" /> -