diff --git a/packages/server/src/api/controllers/Import/ImportController.ts b/packages/server/src/api/controllers/Import/ImportController.ts index c2d90cbe2..d8c44586c 100644 --- a/packages/server/src/api/controllers/Import/ImportController.ts +++ b/packages/server/src/api/controllers/Import/ImportController.ts @@ -42,7 +42,7 @@ export class ImportController extends BaseController { this.asyncMiddleware(this.mapping.bind(this)), this.catchServiceErrors ); - router.post( + router.get( '/:import_id/preview', this.asyncMiddleware(this.preview.bind(this)), this.catchServiceErrors diff --git a/packages/webapp/src/components/Dropzone/Dropzone.tsx b/packages/webapp/src/components/Dropzone/Dropzone.tsx index cf335d7ca..8682a499b 100644 --- a/packages/webapp/src/components/Dropzone/Dropzone.tsx +++ b/packages/webapp/src/components/Dropzone/Dropzone.tsx @@ -1,5 +1,6 @@ // @ts-nocheck import React from 'react'; +import { Ref, useCallback } from 'react'; import clsx from 'classnames'; import { Accept, @@ -226,6 +227,7 @@ export const Dropzone = (_props: DropzoneProps) => { }); const isIdle = !isDragAccept && !isDragReject; + assignRef(openRef, open); return ( = Ref | undefined; + +export function assignRef(ref: PossibleRef, value: T) { + if (typeof ref === 'function') { + ref(value); + } else if (typeof ref === 'object' && ref !== null && 'current' in ref) { + (ref as React.MutableRefObject).current = value; + } +} + +export function mergeRefs(...refs: PossibleRef[]) { + return (node: T | null) => { + refs.forEach((ref) => assignRef(ref, node)); + }; +} + +export function useMergedRef(...refs: PossibleRef[]) { + return useCallback(mergeRefs(...refs), refs); +} \ No newline at end of file diff --git a/packages/webapp/src/components/Layout/Stack/Stack.tsx b/packages/webapp/src/components/Layout/Stack/Stack.tsx index da1a9af6c..d5960ac1f 100644 --- a/packages/webapp/src/components/Layout/Stack/Stack.tsx +++ b/packages/webapp/src/components/Layout/Stack/Stack.tsx @@ -30,7 +30,7 @@ export function Stack(props: StackProps) { const StackStyled = styled(Box)` display: flex; flex-direction: column; - align-items: align; + align-items: ${(props: StackProps) => props.align}; justify-content: justify; gap: ${(props: StackProps) => props.spacing}px; `; diff --git a/packages/webapp/src/components/Stepper/Stepper.tsx b/packages/webapp/src/components/Stepper/Stepper.tsx index 9d58874e2..448398628 100644 --- a/packages/webapp/src/components/Stepper/Stepper.tsx +++ b/packages/webapp/src/components/Stepper/Stepper.tsx @@ -81,8 +81,8 @@ export function Stepper({ active > _children.length - 1 ? completedContent : stepContent; return ( - - {items} + + {items} {content} ); diff --git a/packages/webapp/src/containers/Import/ImportDropzone.tsx b/packages/webapp/src/containers/Import/ImportDropzone.tsx index 95e1532b5..cae4f6d65 100644 --- a/packages/webapp/src/containers/Import/ImportDropzone.tsx +++ b/packages/webapp/src/containers/Import/ImportDropzone.tsx @@ -1,42 +1,20 @@ // @ts-nocheck -import { Button } from '@blueprintjs/core'; import { Field } from 'formik'; -import { Box, Group, Icon, Stack } from '@/components'; -import { Dropzone } from '@/components/Dropzone'; -import { MIME_TYPES } from '@/components/Dropzone/mine-types'; +import { Box, Group, Stack } from '@/components'; import styles from './ImportDropzone.module.css'; +import { ImportDropzoneField } from './ImportDropzoneFile'; export function ImportDropzone() { return ( - {({ form: { setFieldValue } }) => ( - setFieldValue('file', files[0])} - onReject={(files) => console.log('rejected files', files)} - maxSize={5 * 1024 ** 2} - accept={[MIME_TYPES.csv, MIME_TYPES.xls, MIME_TYPES.xlsx]} - classNames={{ content: styles.dropzoneContent }} - > - - - - - -

- Drag images here or click to select files -

- - Drag and Drop file here or Choose file - -
- - - -
-
+ {({ form }) => ( + { + form.setFieldValue('file', file); + }} + /> )}
diff --git a/packages/webapp/src/containers/Import/ImportDropzoneFile.tsx b/packages/webapp/src/containers/Import/ImportDropzoneFile.tsx new file mode 100644 index 000000000..2cec9242f --- /dev/null +++ b/packages/webapp/src/containers/Import/ImportDropzoneFile.tsx @@ -0,0 +1,80 @@ +// @ts-nocheck +import { useRef } from 'react'; +import { Button, Intent } from '@blueprintjs/core'; +import { Box, Icon, Stack } from '@/components'; +import { Dropzone, DropzoneProps } from '@/components/Dropzone'; +import { MIME_TYPES } from '@/components/Dropzone/mine-types'; +import styles from './ImportDropzone.module.css'; +import { useUncontrolled } from '@/hooks/useUncontrolled'; + +interface ImportDropzoneFieldProps { + initialValue?: File; + value?: File; + onChange?: (file: File) => void; + dropzoneProps?: DropzoneProps; +} + +export function ImportDropzoneField({ + initialValue, + value, + onChange, + dropzoneProps, +}: ImportDropzoneFieldProps) { + const [localValue, handleChange] = useUncontrolled({ + value, + initialValue, + finalValue: null, + onChange, + }); + const openRef = useRef<() => void>(null); + + const handleRemove = () => { + handleChange(null); + }; + + return ( + handleChange(files[0])} + onReject={(files) => console.log('rejected files', files)} + maxSize={5 * 1024 ** 2} + accept={[MIME_TYPES.csv, MIME_TYPES.xls, MIME_TYPES.xlsx]} + classNames={{ content: styles.dropzoneContent }} + activateOnClick={false} + openRef={openRef} + {...dropzoneProps} + > + + + + + {localValue ? ( + +

{localValue.name}

+ +
+ ) : ( + +

+ Drag images here or click to select files +

+ + Drag and Drop file here or Choose file + +
+ )} + + +
+
+ ); +} diff --git a/packages/webapp/src/containers/Import/ImportFileActions.module.scss b/packages/webapp/src/containers/Import/ImportFileActions.module.scss new file mode 100644 index 000000000..fa4c9d19a --- /dev/null +++ b/packages/webapp/src/containers/Import/ImportFileActions.module.scss @@ -0,0 +1,5 @@ + + +.root{ + +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Import/ImportFileContainer.tsx b/packages/webapp/src/containers/Import/ImportFileContainer.tsx new file mode 100644 index 000000000..a2b32f84b --- /dev/null +++ b/packages/webapp/src/containers/Import/ImportFileContainer.tsx @@ -0,0 +1,9 @@ +import styles from './ImportFileUploadStep.module.scss'; + +interface ImportFileContainerProps { + children: React.ReactNode; +} + +export function ImportFileContainer({ children }: ImportFileContainerProps) { + return
{children}
; +} diff --git a/packages/webapp/src/containers/Import/ImportFileFooterActions.tsx b/packages/webapp/src/containers/Import/ImportFileFooterActions.tsx new file mode 100644 index 000000000..b8ae21f2e --- /dev/null +++ b/packages/webapp/src/containers/Import/ImportFileFooterActions.tsx @@ -0,0 +1,26 @@ +// @ts-nocheck +import clsx from 'classnames'; +import { Group } from '@/components'; +import { CLASSES } from '@/constants'; +import { Button, Intent } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; +import styles from './ImportFileActions.module.scss'; +import { useImportFileContext } from './ImportFileProvider'; + +export function ImportFileUploadFooterActions() { + const { isSubmitting } = useFormikContext(); + const { setStep } = useImportFileContext(); + + const handleCancelBtnClick = () => {}; + + return ( +
+ + + + +
+ ); +} diff --git a/packages/webapp/src/containers/Import/ImportFileMapping.module.scss b/packages/webapp/src/containers/Import/ImportFileMapping.module.scss index ef05600f9..873dcf043 100644 --- a/packages/webapp/src/containers/Import/ImportFileMapping.module.scss +++ b/packages/webapp/src/containers/Import/ImportFileMapping.module.scss @@ -1,6 +1,11 @@ .table { width: 100%; - margin-top: 1.8rem; + margin-top: 1.4rem; + + th.label, + td.label{ + width: 32% !important; + } thead{ th{ @@ -8,14 +13,19 @@ padding-top: 8px; padding-bottom: 8px; color: #738091; - } - th.label{ - width: 32%; + font-weight: 500; } } + tbody{ tr td { vertical-align: middle; } + + tr td{ + :global(.bp4-popover-target .bp4-button){ + max-width: 250px; + } + } } } \ No newline at end of file diff --git a/packages/webapp/src/containers/Import/ImportFileMapping.tsx b/packages/webapp/src/containers/Import/ImportFileMapping.tsx index 0e0e23e29..b0ccb24ca 100644 --- a/packages/webapp/src/containers/Import/ImportFileMapping.tsx +++ b/packages/webapp/src/containers/Import/ImportFileMapping.tsx @@ -1,32 +1,36 @@ import { useMemo } from 'react'; import clsx from 'classnames'; +import { Button, Intent } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; import { FSelect, Group } from '@/components'; import { ImportFileMappingForm } from './ImportFileMappingForm'; import { useImportFileContext } from './ImportFileProvider'; -import styles from './ImportFileMapping.module.scss'; import { CLASSES } from '@/constants'; -import { Button, Intent } from '@blueprintjs/core'; -import { useFormikContext } from 'formik'; +import { ImportFileContainer } from './ImportFileContainer'; +import styles from './ImportFileMapping.module.scss'; +import { ImportStepperStep } from './_types'; export function ImportFileMapping() { return ( -

- Review and map the column headers in your csv/xlsx file with the - Bigcapital fields. -

+ +

+ Review and map the column headers in your csv/xlsx file with the + Bigcapital fields. +

- - - - - - - - - - -
Bigcapital FieldsSheet Column Headers
+ + + + + + + + + + +
Bigcapital FieldsSheet Column Headers
+
@@ -43,14 +47,14 @@ function ImportFileMappingFields() { const columns = entityColumns.map((column, index) => ( - {column.name} - + {column.name} + @@ -60,14 +64,19 @@ function ImportFileMappingFields() { function ImportFileMappingFloatingActions() { const { isSubmitting } = useFormikContext(); + const { setStep } = useImportFileContext(); + + const handleCancelBtnClick = () => { + setStep(ImportStepperStep.Upload); + }; return (
- + + -
); diff --git a/packages/webapp/src/containers/Import/ImportFilePreview.tsx b/packages/webapp/src/containers/Import/ImportFilePreview.tsx index 69ea80756..17aa8476d 100644 --- a/packages/webapp/src/containers/Import/ImportFilePreview.tsx +++ b/packages/webapp/src/containers/Import/ImportFilePreview.tsx @@ -1,6 +1,7 @@ // @ts-nocheck import { Button, Callout, Intent, Text } from '@blueprintjs/core'; import clsx from 'classnames'; +import { useHistory } from 'react-router-dom'; import { ImportFilePreviewBootProvider, useImportFilePreviewBootContext, @@ -9,7 +10,7 @@ import { useImportFileContext } from './ImportFileProvider'; import { AppToaster, Card, Group } from '@/components'; import { useImportFileProcess } from '@/hooks/query/import'; import { CLASSES } from '@/constants'; -import { useHistory } from 'react-router-dom'; +import { ImportStepperStep } from './_types'; export function ImportFilePreview() { const { importId } = useImportFileContext(); @@ -81,7 +82,7 @@ function ImportFilePreviewContent() { } function ImportFilePreviewFloatingActions() { - const { importId } = useImportFileContext(); + const { importId, setStep } = useImportFileContext(); const { importPreview } = useImportFilePreviewBootContext(); const { mutateAsync: importFile, isLoading: isImportFileLoading } = useImportFileProcess(); @@ -102,9 +103,14 @@ function ImportFilePreviewFloatingActions() { }) .catch((error) => {}); }; + const handleCancelBtnClick = () => { + setStep(ImportStepperStep.Mapping); + }; + return (
- + + -
); diff --git a/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx b/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx index 5dbbff2b3..5eeafaa26 100644 --- a/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx +++ b/packages/webapp/src/containers/Import/ImportFileUploadForm.tsx @@ -5,6 +5,7 @@ import { Intent } from '@blueprintjs/core'; import { Formik, Form, FormikHelpers } from 'formik'; import * as Yup from 'yup'; import { useImportFileContext } from './ImportFileProvider'; +import { ImportStepperStep } from './_types'; const initialValues = { file: null, @@ -22,9 +23,14 @@ interface ImportFileUploadValues { file: File | null; } -export function ImportFileUploadForm({ children }: ImportFileUploadFormProps) { +export function ImportFileUploadForm({ + children, + formikProps, + formProps, +}: ImportFileUploadFormProps) { const { mutateAsync: uploadImportFile } = useImportFileUpload(); - const { setStep, setSheetColumns, setEntityColumns, setImportId } = useImportFileContext(); + const { setStep, setSheetColumns, setEntityColumns, setImportId } = + useImportFileContext(); const handleSubmit = ( values: ImportFileUploadValues, @@ -42,7 +48,7 @@ export function ImportFileUploadForm({ children }: ImportFileUploadFormProps) { setImportId(data.import.import_id); setSheetColumns(data.sheet_columns); setEntityColumns(data.resource_columns); - setStep(1); + setStep(ImportStepperStep.Mapping); setSubmitting(false); }) .catch((error) => { @@ -55,8 +61,9 @@ export function ImportFileUploadForm({ children }: ImportFileUploadFormProps) { initialValues={initialValues} onSubmit={handleSubmit} validationSchema={validationSchema} + {...formikProps} > -
{children}
+
{children}
); } diff --git a/packages/webapp/src/containers/Import/ImportFileUploadStep.module.scss b/packages/webapp/src/containers/Import/ImportFileUploadStep.module.scss new file mode 100644 index 000000000..61fc5c3fd --- /dev/null +++ b/packages/webapp/src/containers/Import/ImportFileUploadStep.module.scss @@ -0,0 +1,16 @@ +.root { + margin-top: 2.2rem +} + +.content { + flex: 1; + padding: 32px 20px; + min-width: 660px; + max-width: 760px; + width: 75%; + margin-left: auto; + margin-right: auto; +} + +.form { +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Import/ImportFileUploadStep.style.scss b/packages/webapp/src/containers/Import/ImportFileUploadStep.style.scss deleted file mode 100644 index 059702469..000000000 --- a/packages/webapp/src/containers/Import/ImportFileUploadStep.style.scss +++ /dev/null @@ -1,3 +0,0 @@ -.root { - margin-top: 2.2rem -} \ No newline at end of file diff --git a/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx b/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx index 9c6523823..04d126a78 100644 --- a/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx +++ b/packages/webapp/src/containers/Import/ImportFileUploadStep.tsx @@ -1,40 +1,26 @@ // @ts-nocheck -import clsx from 'classnames'; -import { Group, Stack } from '@/components'; +import { Stack } from '@/components'; import { ImportDropzone } from './ImportDropzone'; import { ImportSampleDownload } from './ImportSampleDownload'; -import { CLASSES } from '@/constants'; -import { Button, Intent } from '@blueprintjs/core'; import { ImportFileUploadForm } from './ImportFileUploadForm'; -import { useFormikContext } from 'formik'; +import { ImportFileUploadFooterActions } from './ImportFileFooterActions'; +import { ImportFileContainer } from './ImportFileContainer'; export function ImportFileUploadStep() { return ( -

- Download a sample file and compare it to your import file to ensure you - have the file perfect for the import. -

+ +

+ Download a sample file and compare it to your import file to ensure + you have the file perfect for the import. +

+ + + + +
- - - -
); } - -function ImportFileUploadFooterActions() { - const { isSubmitting } = useFormikContext(); - return ( -
- - - - -
- ); -} diff --git a/packages/webapp/src/containers/Import/ImportPage.module.scss b/packages/webapp/src/containers/Import/ImportPage.module.scss index 201c1b9f2..867462f16 100644 --- a/packages/webapp/src/containers/Import/ImportPage.module.scss +++ b/packages/webapp/src/containers/Import/ImportPage.module.scss @@ -1,10 +1,5 @@ .root{ - padding: 32px 40px; - min-width: 700px; - max-width: 800px; - width: 75%; - margin-left: auto; - margin-right: auto; + } .rootWrap { max-width: 1800px; diff --git a/packages/webapp/src/containers/Import/ImportPage.tsx b/packages/webapp/src/containers/Import/ImportPage.tsx index 7b01e0fb9..e12732b61 100644 --- a/packages/webapp/src/containers/Import/ImportPage.tsx +++ b/packages/webapp/src/containers/Import/ImportPage.tsx @@ -1,18 +1,17 @@ // @ts-nocheck import { ImportStepper } from './ImportStepper'; import { Box, DashboardInsider } from '@/components'; -import styles from './ImportPage.module.scss'; import { ImportFileProvider } from './ImportFileProvider'; +import styles from './ImportPage.module.scss'; + export default function ImportPage() { return ( - - - - - - + + + + ); diff --git a/packages/webapp/src/containers/Import/ImportStepper.module.scss b/packages/webapp/src/containers/Import/ImportStepper.module.scss index 6eacdc66a..2971a3632 100644 --- a/packages/webapp/src/containers/Import/ImportStepper.module.scss +++ b/packages/webapp/src/containers/Import/ImportStepper.module.scss @@ -1,3 +1,18 @@ .content { - margin-top: 2.4rem; + margin-top: 0; + margin-bottom: 0; + border-top: 1px solid #DCE0E5; +} + +.root { + +} + +.items { + padding: 32px 20px; + min-width: 660px; + max-width: 760px; + width: 75%; + margin-left: auto; + margin-right: auto; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Import/ImportStepper.tsx b/packages/webapp/src/containers/Import/ImportStepper.tsx index 5ab7b9548..404f715d3 100644 --- a/packages/webapp/src/containers/Import/ImportStepper.tsx +++ b/packages/webapp/src/containers/Import/ImportStepper.tsx @@ -2,16 +2,22 @@ import { Stepper } from '@/components/Stepper'; import { ImportFileUploadStep } from './ImportFileUploadStep'; -import styles from './ImportStepper.module.scss'; import { useImportFileContext } from './ImportFileProvider'; import { ImportFileMapping } from './ImportFileMapping'; import { ImportFilePreview } from './ImportFilePreview'; +import styles from './ImportStepper.module.scss'; export function ImportStepper() { const { step } = useImportFileContext(); return ( - + diff --git a/packages/webapp/src/containers/Import/_types.ts b/packages/webapp/src/containers/Import/_types.ts new file mode 100644 index 000000000..0d7856819 --- /dev/null +++ b/packages/webapp/src/containers/Import/_types.ts @@ -0,0 +1,5 @@ +export enum ImportStepperStep { + Upload = 0, + Mapping = 1, + Preview = 2, +}