mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: wip import resource UI
This commit is contained in:
@@ -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 (
|
||||
<Stack spacing={0}>
|
||||
<Field id={'file'} name={'file'} type="file">
|
||||
{({ form: { setFieldValue } }) => (
|
||||
<Dropzone
|
||||
onDrop={(files) => 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 }}
|
||||
>
|
||||
<Stack spacing={10} className={styles.content}>
|
||||
<Box className={styles.iconWrap}>
|
||||
<Icon icon="download" iconSize={26} />
|
||||
</Box>
|
||||
<Stack spacing={4}>
|
||||
<h4 className={styles.title}>
|
||||
Drag images here or click to select files
|
||||
</h4>
|
||||
<span className={styles.subtitle}>
|
||||
Drag and Drop file here or Choose file
|
||||
</span>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Button intent="none" minimal outlined>
|
||||
Upload File
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Dropzone>
|
||||
{({ form }) => (
|
||||
<ImportDropzoneField
|
||||
value={form.file}
|
||||
onChange={(file) => {
|
||||
form.setFieldValue('file', file);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
|
||||
80
packages/webapp/src/containers/Import/ImportDropzoneFile.tsx
Normal file
80
packages/webapp/src/containers/Import/ImportDropzoneFile.tsx
Normal file
@@ -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 (
|
||||
<Dropzone
|
||||
onDrop={(files) => 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}
|
||||
>
|
||||
<Stack spacing={12} align="center" className={styles.content}>
|
||||
<Box className={styles.iconWrap}>
|
||||
<Icon icon="download" iconSize={26} />
|
||||
</Box>
|
||||
{localValue ? (
|
||||
<Stack spacing={6} justify="center" align="center">
|
||||
<h4 className={styles.title}>{localValue.name}</h4>
|
||||
<Button small minimal intent={Intent.DANGER} onClick={handleRemove}>
|
||||
Remove
|
||||
</Button>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack spacing={4} align="center">
|
||||
<h4 className={styles.title}>
|
||||
Drag images here or click to select files
|
||||
</h4>
|
||||
<span className={styles.subtitle}>
|
||||
Drag and Drop file here or Choose file
|
||||
</span>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Button
|
||||
intent="none"
|
||||
onClick={() => openRef.current?.()}
|
||||
style={{ pointerEvents: 'all' }}
|
||||
minimal
|
||||
outlined
|
||||
>
|
||||
{localValue ? 'Replace File' : 'Upload File'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
.root{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import styles from './ImportFileUploadStep.module.scss';
|
||||
|
||||
interface ImportFileContainerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ImportFileContainer({ children }: ImportFileContainerProps) {
|
||||
return <div className={styles.content}>{children}</div>;
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS, styles.root)}>
|
||||
<Group spacing={10}>
|
||||
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||
<Button type="submit" intent={Intent.PRIMARY} loading={isSubmitting}>
|
||||
Next
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<ImportFileMappingForm>
|
||||
<p>
|
||||
Review and map the column headers in your csv/xlsx file with the
|
||||
Bigcapital fields.
|
||||
</p>
|
||||
<ImportFileContainer>
|
||||
<p>
|
||||
Review and map the column headers in your csv/xlsx file with the
|
||||
Bigcapital fields.
|
||||
</p>
|
||||
|
||||
<table className={clsx('bp4-html-table', styles.table)}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={'label'}>Bigcapital Fields</th>
|
||||
<th className={'field'}>Sheet Column Headers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ImportFileMappingFields />
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={clsx('bp4-html-table', styles.table)}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.label}>Bigcapital Fields</th>
|
||||
<th className={styles.field}>Sheet Column Headers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ImportFileMappingFields />
|
||||
</tbody>
|
||||
</table>
|
||||
</ImportFileContainer>
|
||||
|
||||
<ImportFileMappingFloatingActions />
|
||||
</ImportFileMappingForm>
|
||||
@@ -43,14 +47,14 @@ function ImportFileMappingFields() {
|
||||
|
||||
const columns = entityColumns.map((column, index) => (
|
||||
<tr key={index}>
|
||||
<td className={'label'}>{column.name}</td>
|
||||
<td className={'field'}>
|
||||
<td className={styles.label}>{column.name}</td>
|
||||
<td className={styles.field}>
|
||||
<FSelect
|
||||
name={column.key}
|
||||
items={items}
|
||||
fill
|
||||
popoverProps={{ minimal: false }}
|
||||
minimal={false}
|
||||
popoverProps={{ minimal: true }}
|
||||
minimal={true}
|
||||
fill={true}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -60,14 +64,19 @@ function ImportFileMappingFields() {
|
||||
|
||||
function ImportFileMappingFloatingActions() {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
const { setStep } = useImportFileContext();
|
||||
|
||||
const handleCancelBtnClick = () => {
|
||||
setStep(ImportStepperStep.Upload);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group>
|
||||
<Group spacing={10}>
|
||||
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||
<Button type="submit" intent={Intent.PRIMARY} loading={isSubmitting}>
|
||||
Next
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group>
|
||||
<Group spacing={10}>
|
||||
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
intent={Intent.PRIMARY}
|
||||
@@ -114,7 +120,6 @@ function ImportFilePreviewFloatingActions() {
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<Form>{children}</Form>
|
||||
<Form {...formProps}>{children}</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.root {
|
||||
margin-top: 2.2rem
|
||||
}
|
||||
@@ -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 (
|
||||
<ImportFileUploadForm>
|
||||
<p style={{ marginBottom: 18 }}>
|
||||
Download a sample file and compare it to your import file to ensure you
|
||||
have the file perfect for the import.
|
||||
</p>
|
||||
<ImportFileContainer>
|
||||
<p style={{ marginBottom: 18 }}>
|
||||
Download a sample file and compare it to your import file to ensure
|
||||
you have the file perfect for the import.
|
||||
</p>
|
||||
<Stack spacing={40}>
|
||||
<ImportDropzone />
|
||||
<ImportSampleDownload />
|
||||
</Stack>
|
||||
</ImportFileContainer>
|
||||
|
||||
<Stack spacing={40}>
|
||||
<ImportDropzone />
|
||||
<ImportSampleDownload />
|
||||
</Stack>
|
||||
<ImportFileUploadFooterActions />
|
||||
</ImportFileUploadForm>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportFileUploadFooterActions() {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
return (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group spacing={10}>
|
||||
<Button type="submit" intent={Intent.PRIMARY} loading={isSubmitting}>
|
||||
Next
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<DashboardInsider>
|
||||
<Box className={styles.rootWrap}>
|
||||
<Box className={styles.root}>
|
||||
<ImportFileProvider resource="account">
|
||||
<ImportStepper />
|
||||
</ImportFileProvider>
|
||||
</Box>
|
||||
<Box className={styles.root}>
|
||||
<ImportFileProvider resource="account">
|
||||
<ImportStepper />
|
||||
</ImportFileProvider>
|
||||
</Box>
|
||||
</DashboardInsider>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
<Stepper active={step} classNames={{ content: styles.content }}>
|
||||
<Stepper
|
||||
active={step}
|
||||
classNames={{
|
||||
content: styles.content,
|
||||
items: styles.items,
|
||||
}}
|
||||
>
|
||||
<Stepper.Step label={'File Upload'}>
|
||||
<ImportFileUploadStep />
|
||||
</Stepper.Step>
|
||||
|
||||
5
packages/webapp/src/containers/Import/_types.ts
Normal file
5
packages/webapp/src/containers/Import/_types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum ImportStepperStep {
|
||||
Upload = 0,
|
||||
Mapping = 1,
|
||||
Preview = 2,
|
||||
}
|
||||
Reference in New Issue
Block a user