Skip to content

Commit

Permalink
feat(frontend): replace file upload button with drop zone (#2259)
Browse files Browse the repository at this point in the history
* fix(uploadArea): replace fileInput with uploadArea file component

* refactor(selectForm): replace fileInputComponent with selectForm

* refactor(dataExtract): replace custom map data fileInput with uploadArea component

* refactor(dataExtract): replace add map data fileInput with uploadArea component

* refactor(uploadArea): update styles

* fix(splitTasks): access file from corresponding file states

* refactor(convertFileToGeojson): convertFileToGeojson function add to utilFunctions

* fix(selectForm): disable form download if no category chosen and display message on tooltip on hover

* remove(fileInputComponent): remove fileInputComponent component
  • Loading branch information
NSUWAL123 authored Mar 7, 2025
1 parent 6e50457 commit e909a68
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 214 deletions.
65 changes: 0 additions & 65 deletions src/frontend/src/components/common/FileInputComponent.tsx

This file was deleted.

20 changes: 9 additions & 11 deletions src/frontend/src/components/common/UploadArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const UploadArea = ({ title, label, acceptedInput, data, onUploadFile }: uploadA
}
}
}}
className="fmtm-cursor-pointer fmtm-border-[1px] fmtm-border-[#D8D8D8] fmtm-border-dashed fmtm-rounded-md fmtm-flex fmtm-flex-col fmtm-gap-2 fmtm-py-4"
className="fmtm-cursor-pointer fmtm-border-[1px] fmtm-border-[#D8D8D8] fmtm-border-dashed fmtm-rounded-md fmtm-flex fmtm-flex-col fmtm-gap-1 fmtm-py-2"
>
<input
className="fmtm-hidden"
Expand All @@ -119,25 +119,23 @@ const UploadArea = ({ title, label, acceptedInput, data, onUploadFile }: uploadA
onChange={changeHandler}
/>
<div className="fmtm-flex fmtm-justify-center">
<AssetModules.CloudUploadOutlinedIcon style={{ fontSize: '32px' }} className="fmtm-text-primaryRed" />
</div>
<div className="fmtm-text-body-md fmtm-text-center fmtm-text-sm fmtm-text-[#757575] fmtm-font-archivo fmtm-font-semibold fmtm-tracking-wide">
{label}
<AssetModules.CloudUploadOutlinedIcon style={{ fontSize: '24px' }} className="fmtm-text-primaryRed" />
</div>
<p className="fmtm-body-md fmtm-text-center fmtm-text-grey-600">{label}</p>
</div>
{selectedFiles?.length > 0 && (
<div className="fmtm-mt-4 fmtm-w-full">
{selectedFiles?.map((item, i) => (
<div key={item.id} className="fmtm-flex fmtm-items-center fmtm-h-20 fmtm-w-full">
<div key={item.id} className="fmtm-flex fmtm-items-center fmtm-w-full">
{acceptedInput.includes('image') ? (
<img src={item.previewURL} className="fmtm-h-full fmtm-z-50" />
) : (
<div className="fmtm-p-3 fmtm-rounded-full fmtm-bg-red-50">
<AssetModules.DescriptionIcon className="!fmtm-text-[2rem] fmtm-text-[#4C4C4C]" />
<div className="fmtm-p-1 fmtm-rounded-full fmtm-bg-red-50">
<AssetModules.DescriptionIcon className="!fmtm-text-[1.5rem] fmtm-text-[#4C4C4C]" />
</div>
)}
<div className="fmtm-flex-1 fmtm-text-ellipsis fmtm-truncate fmtm-ml-4" title={item.file.name}>
{item.file.name}
<div className="fmtm-flex-1 fmtm-text-ellipsis fmtm-truncate fmtm-ml-4" title={item?.file?.name}>
{item?.file?.name}
</div>
<div className="fmtm-flex">
<div
Expand All @@ -147,7 +145,7 @@ const UploadArea = ({ title, label, acceptedInput, data, onUploadFile }: uploadA
onClick={() => handleDeleteFile(item.id)}
className="fmtm-px-4"
>
<AssetModules.DeleteIcon className="fmtm-text-grey-400 hover:fmtm-text-[#FF4538]" />
<AssetModules.DeleteIcon className="fmtm-text-grey-400 hover:fmtm-text-[#FF4538] !fmtm-text-[1.2rem]" />
</div>
</div>
</div>
Expand Down
120 changes: 49 additions & 71 deletions src/frontend/src/components/createnewproject/DataExtract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import { CreateProjectActions } from '@/store/slices/CreateProjectSlice';
import useForm from '@/hooks/useForm';
import { useAppDispatch, useAppSelector } from '@/types/reduxTypes';
import { FormCategoryService } from '@/api/CreateProjectService';
import FileInputComponent from '@/components/common/FileInputComponent';
import DataExtractValidation from '@/components/createnewproject/validation/DataExtractValidation';
import NewDefineAreaMap from '@/views/NewDefineAreaMap';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
import { task_split_type } from '@/types/enums';
import { dataExtractGeojsonType } from '@/store/types/ICreateProject';
import { CustomCheckbox } from '@/components/common/Checkbox';
import DescriptionSection from '@/components/createnewproject/Description';
import UploadArea from '@/components/common/UploadArea';
import { convertFileToGeojson } from '@/utilfunctions/convertFileToGeojson';

const VITE_API_URL = import.meta.env.VITE_API_URL;

const primaryGeomOptions = [
{ name: 'primary_geom_type', value: 'POLYGON', label: 'Polygons (e.g. buildings)' },
Expand Down Expand Up @@ -130,10 +133,7 @@ const DataExtract = ({
dispatch(CreateProjectActions.SetFgbFetchingStatus(true));

try {
const response = await axios.post(
`${import.meta.env.VITE_API_URL}/projects/generate-data-extract`,
dataExtractRequestFormData,
);
const response = await axios.post(`${VITE_API_URL}/projects/generate-data-extract`, dataExtractRequestFormData);

const fgbUrl = response.data.url;
// Append url to project data & remove custom files
Expand Down Expand Up @@ -193,45 +193,22 @@ const DataExtract = ({
navigate(url);
};

const convertFileToFeatureCol = async (file) => {
if (!file) return;
// Parse file as JSON
const fileReader = new FileReader();
const fileLoaded: any = await new Promise((resolve) => {
fileReader.onload = (e) => resolve(e.target?.result);
fileReader.readAsText(file, 'UTF-8');
});
const parsedJSON = JSON.parse(fileLoaded);

// Convert to FeatureCollection
let geojsonConversion;
if (parsedJSON.type === 'FeatureCollection') {
geojsonConversion = parsedJSON;
} else if (parsedJSON.type === 'Feature') {
geojsonConversion = {
type: 'FeatureCollection',
features: [parsedJSON],
};
} else {
geojsonConversion = {
type: 'FeatureCollection',
features: [{ type: 'Feature', properties: null, geometry: parsedJSON }],
};
const changeMapDataFileHandler = async (file, setDataExtractToState) => {
if (!file) {
resetFile(setCustomDataExtractUpload);
handleCustomChange('customDataExtractUpload', null);
dispatch(CreateProjectActions.setDataExtractGeojson(null));
return;
}
return geojsonConversion;
};

const changeFileHandler = async (event, setDataExtractToState) => {
const { files } = event.target;
const uploadedFile = files[0];
const uploadedFile = file?.file;
const fileType = uploadedFile.name.split('.').pop();

// Handle geojson and fgb types, return featurecollection geojson
let extractFeatCol;
if (['json', 'geojson'].includes(fileType)) {
// Set to state immediately for splitting
setDataExtractToState(uploadedFile);
extractFeatCol = await convertFileToFeatureCol(uploadedFile);
setDataExtractToState(file);
extractFeatCol = await convertFileToGeojson(uploadedFile);
} else if (['fgb'].includes(fileType)) {
const arrayBuffer = new Uint8Array(await uploadedFile.arrayBuffer());
extractFeatCol = fgbGeojson.deserialize(arrayBuffer);
Expand All @@ -240,7 +217,7 @@ const DataExtract = ({
setDataExtractToState(geojsonFile);
}
if (extractFeatCol && extractFeatCol?.features?.length > 0) {
handleCustomChange('customDataExtractUpload', event.target.files[0]);
handleCustomChange('customDataExtractUpload', uploadedFile);
handleCustomChange('task_split_type', task_split_type.CHOOSE_AREA_AS_TASK.toString());
// View on map
await dispatch(CreateProjectActions.setDataExtractGeojson(extractFeatCol));
Expand All @@ -252,8 +229,22 @@ const DataExtract = ({
return;
};

const changeAdditionalMapDataFileHandler = async (file) => {
if (!file) {
resetFile(setAdditionalFeature);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(null));
handleCustomChange('additionalFeature', null);
return;
}
const uploadedFile = file?.file;
setAdditionalFeature(file);
handleCustomChange('additionalFeature', uploadedFile);
const additionalFeatureGeojson = await convertFileToGeojson(uploadedFile);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(additionalFeatureGeojson));
};

useEffect(() => {
dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`));
dispatch(FormCategoryService(`${VITE_API_URL}/central/list-forms`));
}, []);

return (
Expand Down Expand Up @@ -336,20 +327,14 @@ const DataExtract = ({
)}
{extractType === 'custom_data_extract' && (
<>
<FileInputComponent
onChange={(e) => {
changeFileHandler(e, setCustomDataExtractUpload);
}}
onResetFile={() => {
resetFile(setCustomDataExtractUpload);
handleCustomChange('customDataExtractUpload', null);
dispatch(CreateProjectActions.setDataExtractGeojson(null));
<UploadArea
title="Upload Map Data"
label="The supported file formats are .geojson, .json, .fgb"
data={customDataExtractUpload ? [customDataExtractUpload] : []}
onUploadFile={(updatedFiles) => {
changeMapDataFileHandler(updatedFiles?.[0], setCustomDataExtractUpload);
}}
customFile={customDataExtractUpload}
btnText="Upload Map Data"
accept=".geojson,.json,.fgb"
fileDescription="*The supported file formats are .geojson, .json, .fgb"
errorMsg={errors.customDataExtractUpload}
acceptedInput=".geojson,.json,.fgb"
/>
</>
)}
Expand Down Expand Up @@ -381,27 +366,20 @@ const DataExtract = ({
</div>
{formValues?.hasAdditionalFeature && (
<>
<FileInputComponent
onChange={async (e) => {
if (e?.target?.files) {
const uploadedFile = e?.target?.files[0];
setAdditionalFeature(uploadedFile);
handleCustomChange('additionalFeature', uploadedFile);
const additionalFeatureGeojson = await convertFileToFeatureCol(uploadedFile);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(additionalFeatureGeojson));
}
}}
onResetFile={() => {
resetFile(setAdditionalFeature);
dispatch(CreateProjectActions.SetAdditionalFeatureGeojson(null));
handleCustomChange('additionalFeature', null);
<UploadArea
title="Upload Supporting Datasets"
label="The supported file formats are .geojson"
data={additionalFeature ? [additionalFeature] : []}
onUploadFile={(updatedFiles) => {
changeAdditionalMapDataFileHandler(updatedFiles?.[0]);
}}
customFile={additionalFeature}
btnText="Upload Supporting Datasets"
accept=".geojson"
fileDescription="*The supported file formats are .geojson"
errorMsg={errors.additionalFeature}
acceptedInput=".geojson"
/>
{errors.additionalFeature && (
<p className="fmtm-form-error fmtm-text-red-600 fmtm-text-sm fmtm-py-1">
{errors.additionalFeature}
</p>
)}
</>
)}
{additionalFeatureGeojson && (
Expand Down
Loading

0 comments on commit e909a68

Please sign in to comment.