diff --git a/packages/server/src/services/Import/ImportFileUpload.ts b/packages/server/src/services/Import/ImportFileUpload.ts index 8dad9a53c..048739e03 100644 --- a/packages/server/src/services/Import/ImportFileUpload.ts +++ b/packages/server/src/services/Import/ImportFileUpload.ts @@ -1,12 +1,11 @@ import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; -import { sanitizeResourceName } from './_utils'; +import { sanitizeResourceName, validateSheetEmpty } from './_utils'; import ResourceService from '../Resource/ResourceService'; import { IModelMetaField } from '@/interfaces'; import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileDataValidator } from './ImportFileDataValidator'; import { ImportFileUploadPOJO } from './interfaces'; -import { ServiceError } from '@/exceptions'; @Service() export class ImportFileUploadService { @@ -51,6 +50,10 @@ export class ImportFileUploadService { // Parse the buffer file to array data. const sheetData = this.importFileCommon.parseXlsxSheet(buffer); + + // Throws service error if the sheet data is empty. + validateSheetEmpty(sheetData); + const sheetColumns = this.importFileCommon.parseSheetColumns(sheetData); const coumnsStringified = JSON.stringify(sheetColumns); diff --git a/packages/server/src/services/Import/_utils.ts b/packages/server/src/services/Import/_utils.ts index 6c94bfade..6b0db6e96 100644 --- a/packages/server/src/services/Import/_utils.ts +++ b/packages/server/src/services/Import/_utils.ts @@ -1,9 +1,18 @@ import * as Yup from 'yup'; -import { defaultTo, upperFirst, camelCase, first, isUndefined, pickBy } from 'lodash'; +import { + defaultTo, + upperFirst, + camelCase, + first, + isUndefined, + pickBy, + isEmpty, +} from 'lodash'; import pluralize from 'pluralize'; import { ResourceMetaFieldsMap } from './interfaces'; import { IModelMetaField } from '@/interfaces'; import moment from 'moment'; +import { ServiceError } from '@/exceptions'; export const ERRORS = { RESOURCE_NOT_IMPORTABLE: 'RESOURCE_NOT_IMPORTABLE', @@ -13,6 +22,7 @@ export const ERRORS = { IMPORT_FILE_NOT_MAPPED: 'IMPORT_FILE_NOT_MAPPED', INVALID_MAP_DATE_FORMAT: 'INVALID_MAP_DATE_FORMAT', MAP_DATE_FORMAT_NOT_DEFINED: 'MAP_DATE_FORMAT_NOT_DEFINED', + IMPORTED_SHEET_EMPTY: 'IMPORTED_SHEET_EMPTY', }; export function trimObject(obj) { @@ -122,3 +132,13 @@ export const getUniqueImportableValue = ( return defaultTo(objectDTO[uniqueImportableKey], ''); }; + +/** + * Throws service error the given sheet is empty. + * @param {Array} sheetData + */ +export const validateSheetEmpty = (sheetData: Array) => { + if (isEmpty(sheetData)) { + throw new ServiceError(ERRORS.IMPORTED_SHEET_EMPTY); + } +}; diff --git a/packages/webapp/src/containers/Import/AlertsManager.tsx b/packages/webapp/src/containers/Import/AlertsManager.tsx new file mode 100644 index 000000000..ad170f2c3 --- /dev/null +++ b/packages/webapp/src/containers/Import/AlertsManager.tsx @@ -0,0 +1,53 @@ +import type { ReactNode } from 'react'; +import { useState, createContext, useContext } from 'react'; + +interface AlertsManagerContextValue { + alerts: (string | number)[]; + showAlert: (alert: string | number) => void; + hideAlert: (alert: string | number) => void; + + hideAlerts: () => void; + isAlertActive: (alert: string | number) => boolean; + findAlert: (alert: string | number) => string | number | undefined; +} + +const AlertsManagerContext = createContext( + {} as AlertsManagerContextValue, +); + +export function AlertsManager({ children }: { children: ReactNode }) { + const [alerts, setAlerts] = useState<(string | number)[]>([]); + + const showAlert = (type: string | number): void => { + setAlerts([...alerts, type]); + }; + const hideAlert = (type: string | number): void => { + alerts.filter((t) => t !== type); + }; + const hideAlerts = (): void => { + setAlerts([]); + }; + const isAlertActive = (type: string | number): boolean => { + return alerts.some((t) => t === type); + }; + const findAlert = (type: string | number): number | string | undefined => { + return alerts.find((t) => t === type); + }; + + return ( + + {children} + + ); +} + +export const useAlertsManager = () => useContext(AlertsManagerContext); diff --git a/packages/webapp/src/containers/Import/ImportDropzone.tsx b/packages/webapp/src/containers/Import/ImportDropzone.tsx index cae4f6d65..5dae46d06 100644 --- a/packages/webapp/src/containers/Import/ImportDropzone.tsx +++ b/packages/webapp/src/containers/Import/ImportDropzone.tsx @@ -3,8 +3,11 @@ import { Field } from 'formik'; import { Box, Group, Stack } from '@/components'; import styles from './ImportDropzone.module.css'; import { ImportDropzoneField } from './ImportDropzoneFile'; +import { useAlertsManager } from './AlertsManager'; export function ImportDropzone() { + const { hideAlerts } = useAlertsManager(); + return ( @@ -12,6 +15,7 @@ export function ImportDropzone() { { + hideAlerts(); form.setFieldValue('file', file); }} /> diff --git a/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx b/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx index bc0a7e1b5..73db99efc 100644 --- a/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx +++ b/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx @@ -5,7 +5,8 @@ import { Intent } from '@blueprintjs/core'; import { Formik, Form, FormikHelpers } from 'formik'; import * as Yup from 'yup'; import { useImportFileContext } from './ImportFileProvider'; -import { ImportStepperStep } from './_types'; +import { ImportAlert, ImportStepperStep } from './_types'; +import { useAlertsManager } from './AlertsManager'; const initialValues = { file: null, @@ -28,6 +29,7 @@ export function ImportFileUploadForm({ formikProps, formProps, }: ImportFileUploadFormProps) { + const { showAlert, hideAlerts } = useAlertsManager(); const { mutateAsync: uploadImportFile } = useImportFileUpload(); const { resource, @@ -42,6 +44,7 @@ export function ImportFileUploadForm({ values: ImportFileUploadValues, { setSubmitting }: FormikHelpers, ) => { + hideAlerts(); if (!values.file) return; setSubmitting(true); @@ -69,6 +72,9 @@ export function ImportFileUploadForm({ message: 'The extenstion of uploaded file is not supported.', }); } + if (data.errors.find((er) => er.type === 'IMPORTED_SHEET_EMPTY')) { + showAlert(ImportAlert.IMPORTED_SHEET_EMPTY); + } setSubmitting(false); }); }; diff --git a/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx b/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx index b1c6f9e01..c991de56b 100644 --- a/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx +++ b/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import { Classes } from '@blueprintjs/core'; +import { Callout, Classes, Intent } from '@blueprintjs/core'; import { Stack } from '@/components'; import { ImportDropzone } from './ImportDropzone'; import { ImportSampleDownload } from './ImportSampleDownload'; @@ -7,29 +7,50 @@ import { ImportFileUploadForm } from './ImportFileUploadForm'; import { ImportFileUploadFooterActions } from './ImportFileFooterActions'; import { ImportFileContainer } from './ImportFileContainer'; import { useImportFileContext } from './ImportFileProvider'; +import { AlertsManager, useAlertsManager } from './AlertsManager'; +import { ImportAlert } from './_types'; + +function ImportFileUploadCallouts() { + const { isAlertActive } = useAlertsManager(); + return ( + <> + {isAlertActive(ImportAlert.IMPORTED_SHEET_EMPTY) && ( + + The imported sheet is empty. + + )} + + ); +} export function ImportFileUploadStep() { const { exampleDownload } = useImportFileContext(); return ( - - -

- Download a sample file and compare it with your import file to ensure - it is properly formatted. It's not necessary for the columns to be in - the same order, you can map them later. -

+ + + +

+ Download a sample file and compare it with your import file to + ensure it is properly formatted. It's not necessary for the columns + to be in the same order, you can map them later. +

- - - {exampleDownload && } - -
+ + - -
+ + + {exampleDownload && } + +
+ + + + + ); } diff --git a/packages/webapp/src/containers/Import/_types.ts b/packages/webapp/src/containers/Import/_types.ts index 0d7856819..08df767d9 100644 --- a/packages/webapp/src/containers/Import/_types.ts +++ b/packages/webapp/src/containers/Import/_types.ts @@ -3,3 +3,7 @@ export enum ImportStepperStep { Mapping = 1, Preview = 2, } + +export enum ImportAlert { + IMPORTED_SHEET_EMPTY = 'IMPORTED_SHEET_EMPTY' +} \ No newline at end of file