mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat(webapp): import resource UI
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
|
||||
.root {
|
||||
|
||||
}
|
||||
|
||||
.title{
|
||||
color: #5F6B7C;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.iconWrap{
|
||||
color: #8F99A8;
|
||||
}
|
||||
.subtitle {
|
||||
color: #8F99A8;
|
||||
}
|
||||
.dropzoneContent{
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.buttons-wrap {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.dropzoneHint{
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
color: #8F99A8;
|
||||
justify-content: space-between;
|
||||
}
|
||||
49
packages/webapp/src/containers/Import/ImportDropzone.tsx
Normal file
49
packages/webapp/src/containers/Import/ImportDropzone.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
// @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 styles from './ImportDropzone.module.css';
|
||||
|
||||
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>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Group className={styles.dropzoneHint}>
|
||||
<Box>Supperted Formats: CSV, XLSX</Box>
|
||||
<Box>Maximum size: 25MB</Box>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.table {
|
||||
width: 100%;
|
||||
margin-top: 1.8rem;
|
||||
|
||||
thead{
|
||||
th{
|
||||
border-top: 1px solid #d9d9da;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #738091;
|
||||
}
|
||||
th.label{
|
||||
width: 32%;
|
||||
}
|
||||
}
|
||||
tbody{
|
||||
tr td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
packages/webapp/src/containers/Import/ImportFileMapping.tsx
Normal file
74
packages/webapp/src/containers/Import/ImportFileMapping.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useMemo } from 'react';
|
||||
import clsx from 'classnames';
|
||||
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';
|
||||
|
||||
export function ImportFileMapping() {
|
||||
return (
|
||||
<ImportFileMappingForm>
|
||||
<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>
|
||||
|
||||
<ImportFileMappingFloatingActions />
|
||||
</ImportFileMappingForm>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportFileMappingFields() {
|
||||
const { entityColumns, sheetColumns } = useImportFileContext();
|
||||
|
||||
const items = useMemo(
|
||||
() => sheetColumns.map((column) => ({ value: column, text: column })),
|
||||
[sheetColumns],
|
||||
);
|
||||
|
||||
const columns = entityColumns.map((column, index) => (
|
||||
<tr key={index}>
|
||||
<td className={'label'}>{column.name}</td>
|
||||
<td className={'field'}>
|
||||
<FSelect
|
||||
name={column.key}
|
||||
items={items}
|
||||
fill
|
||||
popoverProps={{ minimal: false }}
|
||||
minimal={false}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
return <>{columns}</>;
|
||||
}
|
||||
|
||||
function ImportFileMappingFloatingActions() {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group>
|
||||
<Button type="submit" intent={Intent.PRIMARY} loading={isSubmitting}>
|
||||
Next
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// @ts-nocheck
|
||||
import { useImportFileMapping } from '@/hooks/query/import';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import { useImportFileContext } from './ImportFileProvider';
|
||||
import { useMemo } from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
const validationSchema = null;
|
||||
|
||||
interface ImportFileMappingFormProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
type ImportFileMappingFormValues = Record<string, string | null>;
|
||||
|
||||
export function ImportFileMappingForm({
|
||||
children,
|
||||
}: ImportFileMappingFormProps) {
|
||||
const { mutateAsync: submitImportFileMapping } = useImportFileMapping();
|
||||
const { importId, setStep } = useImportFileContext();
|
||||
|
||||
const initialValues = useImportFileMappingInitialValues();
|
||||
|
||||
const handleSubmit = (
|
||||
values: ImportFileMappingFormValues,
|
||||
{ setSubmitting }: FormikHelpers<ImportFileMappingFormValues>,
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
const _values = transformValueToReq(values);
|
||||
|
||||
submitImportFileMapping([importId, _values])
|
||||
.then(() => {
|
||||
setSubmitting(false);
|
||||
setStep(2);
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
// validationSchema={validationSchema}
|
||||
>
|
||||
<Form>{children}</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
const transformValueToReq = (value: ImportFileMappingFormValues) => {
|
||||
const mapping = Object.keys(value)
|
||||
.filter((key) => !isEmpty(value[key]))
|
||||
.map((key) => ({ from: value[key], to: key }));
|
||||
return { mapping };
|
||||
};
|
||||
|
||||
const useImportFileMappingInitialValues = () => {
|
||||
const { entityColumns } = useImportFileContext();
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
entityColumns.reduce((acc, { key, name }) => {
|
||||
acc[key] = '';
|
||||
return acc;
|
||||
}, {}),
|
||||
[entityColumns],
|
||||
);
|
||||
};
|
||||
121
packages/webapp/src/containers/Import/ImportFilePreview.tsx
Normal file
121
packages/webapp/src/containers/Import/ImportFilePreview.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
// @ts-nocheck
|
||||
import { Button, Callout, Intent, Text } from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import {
|
||||
ImportFilePreviewBootProvider,
|
||||
useImportFilePreviewBootContext,
|
||||
} from './ImportFilePreviewBoot';
|
||||
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';
|
||||
|
||||
export function ImportFilePreview() {
|
||||
const { importId } = useImportFileContext();
|
||||
|
||||
return (
|
||||
<ImportFilePreviewBootProvider importId={importId}>
|
||||
<ImportFilePreviewContent />
|
||||
</ImportFilePreviewBootProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportFilePreviewContent() {
|
||||
const { importPreview } = useImportFilePreviewBootContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Callout>
|
||||
{importPreview.createdCount} of {importPreview.totalCount} Items in your
|
||||
file are ready to be imported.
|
||||
</Callout>
|
||||
|
||||
<Card>
|
||||
<Text>
|
||||
Items that are ready to be imported - {importPreview.createdCount}
|
||||
</Text>
|
||||
<ul>
|
||||
<li>Items to be created: ({importPreview.createdCount})</li>
|
||||
<li>Items to be skipped: ({importPreview.skippedCount})</li>
|
||||
<li>Items have errors: ({importPreview.errorsCount})</li>
|
||||
</ul>
|
||||
|
||||
<table className={clsx('bp4-html-table')}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={'number'}>#</th>
|
||||
<th className={'name'}>Name</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{importPreview?.errors.map((error, key) => (
|
||||
<tr key={key}>
|
||||
<td>{error.rowNumber}</td>
|
||||
<td>{error.rowNumber}</td>
|
||||
<td>
|
||||
{error.errorMessage.map((message) => (
|
||||
<div>{message}</div>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Text>
|
||||
Unmapped Sheet Columns - ({importPreview?.unmappedColumnsCount})
|
||||
</Text>
|
||||
|
||||
<ul>
|
||||
{importPreview.unmappedColumns?.map((column, key) => (
|
||||
<li key={key}>{column}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<ImportFilePreviewFloatingActions />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ImportFilePreviewFloatingActions() {
|
||||
const { importId } = useImportFileContext();
|
||||
const { importPreview } = useImportFilePreviewBootContext();
|
||||
const { mutateAsync: importFile, isLoading: isImportFileLoading } =
|
||||
useImportFileProcess();
|
||||
|
||||
const history = useHistory();
|
||||
const isValidToImport = importPreview?.createdCount > 0;
|
||||
|
||||
const handleSubmitBtn = () => {
|
||||
importFile(importId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.SUCCESS,
|
||||
message: `The ${
|
||||
importPreview.createdCount
|
||||
} of ${10} has imported successfully.`,
|
||||
});
|
||||
history.push('/accounts');
|
||||
})
|
||||
.catch((error) => {});
|
||||
};
|
||||
return (
|
||||
<div className={clsx(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
<Group>
|
||||
<Button
|
||||
type="submit"
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isImportFileLoading}
|
||||
onClick={handleSubmitBtn}
|
||||
disabled={!isValidToImport}
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useImportFilePreview } from '@/hooks/query/import';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
interface ImportFilePreviewBootContextValue {}
|
||||
|
||||
const ImportFilePreviewBootContext =
|
||||
createContext<ImportFilePreviewBootContextValue>(
|
||||
{} as ImportFilePreviewBootContextValue,
|
||||
);
|
||||
|
||||
export const useImportFilePreviewBootContext = () => {
|
||||
const context = useContext<ImportFilePreviewBootContextValue>(
|
||||
ImportFilePreviewBootContext,
|
||||
);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useImportFilePreviewBootContext must be used within an ImportFilePreviewBootProvider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface ImportFilePreviewBootProps {
|
||||
importId: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ImportFilePreviewBootProvider = ({
|
||||
importId,
|
||||
children,
|
||||
}: ImportFilePreviewBootProps) => {
|
||||
const {
|
||||
data: importPreview,
|
||||
isLoading: isImportPreviewLoading,
|
||||
isFetching: isImportPreviewFetching,
|
||||
} = useImportFilePreview(importId, {
|
||||
enabled: Boolean(importId),
|
||||
});
|
||||
|
||||
const value = {
|
||||
importPreview,
|
||||
isImportPreviewLoading,
|
||||
isImportPreviewFetching,
|
||||
};
|
||||
return (
|
||||
<ImportFilePreviewBootContext.Provider value={value}>
|
||||
{isImportPreviewLoading ? 'loading' : <>{children}</>}
|
||||
</ImportFilePreviewBootContext.Provider>
|
||||
);
|
||||
};
|
||||
87
packages/webapp/src/containers/Import/ImportFileProvider.tsx
Normal file
87
packages/webapp/src/containers/Import/ImportFileProvider.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
// @ts-nocheck
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
type EntityColumn = { key: string; name: string };
|
||||
type SheetColumn = string;
|
||||
type SheetMap = { from: string; to: string };
|
||||
|
||||
interface ImportFileContextValue {
|
||||
sheetColumns: SheetColumn[];
|
||||
setSheetColumns: Dispatch<SetStateAction<SheetColumn[]>>;
|
||||
|
||||
entityColumns: EntityColumn[];
|
||||
setEntityColumns: Dispatch<SetStateAction<EntityColumn[]>>;
|
||||
|
||||
sheetMapping: SheetMap[];
|
||||
setSheetMapping: Dispatch<SetStateAction<SheetMap[]>>;
|
||||
|
||||
step: number;
|
||||
setStep: Dispatch<SetStateAction<number>>;
|
||||
|
||||
importId: string;
|
||||
setImportId: Dispatch<SetStateAction<string>>;
|
||||
|
||||
resource: string;
|
||||
}
|
||||
interface ImportFileProviderProps {
|
||||
resource: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ImportFileContext = createContext<ImportFileContextValue>(
|
||||
{} as ImportFileContextValue,
|
||||
);
|
||||
|
||||
export const useImportFileContext = () => {
|
||||
const context = useContext<ImportFileContextValue>(ImportFileContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useImportFileContext must be used within an ImportFileProvider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const ImportFileProvider = ({
|
||||
resource,
|
||||
children,
|
||||
}: ImportFileProviderProps) => {
|
||||
const [sheetColumns, setSheetColumns] = useState<SheetColumn[]>([]);
|
||||
const [entityColumns, setEntityColumns] = useState<SheetColumn[]>([]);
|
||||
const [sheetMapping, setSheetMapping] = useState<SheetMap[]>([]);
|
||||
const [importId, setImportId] = useState<string>('');
|
||||
|
||||
const [step, setStep] = useState<number>(0);
|
||||
|
||||
const value = {
|
||||
sheetColumns,
|
||||
setSheetColumns,
|
||||
|
||||
entityColumns,
|
||||
setEntityColumns,
|
||||
|
||||
sheetMapping,
|
||||
setSheetMapping,
|
||||
|
||||
step,
|
||||
setStep,
|
||||
|
||||
importId,
|
||||
setImportId,
|
||||
|
||||
resource,
|
||||
};
|
||||
|
||||
return (
|
||||
<ImportFileContext.Provider value={value}>
|
||||
{children}
|
||||
</ImportFileContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
// @ts-nocheck
|
||||
import { AppToaster } from '@/components';
|
||||
import { useImportFileUpload } from '@/hooks/query/import';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { useImportFileContext } from './ImportFileProvider';
|
||||
|
||||
const initialValues = {
|
||||
file: null,
|
||||
} as ImportFileUploadValues;
|
||||
|
||||
interface ImportFileUploadFormProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
file: Yup.mixed().required('File is required'),
|
||||
});
|
||||
|
||||
interface ImportFileUploadValues {
|
||||
file: File | null;
|
||||
}
|
||||
|
||||
export function ImportFileUploadForm({ children }: ImportFileUploadFormProps) {
|
||||
const { mutateAsync: uploadImportFile } = useImportFileUpload();
|
||||
const { setStep, setSheetColumns, setEntityColumns, setImportId } = useImportFileContext();
|
||||
|
||||
const handleSubmit = (
|
||||
values: ImportFileUploadValues,
|
||||
{ setSubmitting }: FormikHelpers<ImportFileUploadValues>,
|
||||
) => {
|
||||
if (!values.file) return;
|
||||
|
||||
setSubmitting(true);
|
||||
const formData = new FormData();
|
||||
formData.append('file', values.file);
|
||||
formData.append('resource', 'Account');
|
||||
|
||||
uploadImportFile(formData)
|
||||
.then(({ data }) => {
|
||||
setImportId(data.import.import_id);
|
||||
setSheetColumns(data.sheet_columns);
|
||||
setEntityColumns(data.resource_columns);
|
||||
setStep(1);
|
||||
setSubmitting(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
<Form>{children}</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
margin-top: 2.2rem
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// @ts-nocheck
|
||||
import clsx from 'classnames';
|
||||
import { Group, 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';
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
11
packages/webapp/src/containers/Import/ImportPage.module.scss
Normal file
11
packages/webapp/src/containers/Import/ImportPage.module.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.root{
|
||||
padding: 32px 40px;
|
||||
min-width: 700px;
|
||||
max-width: 800px;
|
||||
width: 75%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.rootWrap {
|
||||
max-width: 1800px;
|
||||
}
|
||||
19
packages/webapp/src/containers/Import/ImportPage.tsx
Normal file
19
packages/webapp/src/containers/Import/ImportPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
// @ts-nocheck
|
||||
import { ImportStepper } from './ImportStepper';
|
||||
import { Box, DashboardInsider } from '@/components';
|
||||
import styles from './ImportPage.module.scss';
|
||||
import { ImportFileProvider } from './ImportFileProvider';
|
||||
|
||||
export default function ImportPage() {
|
||||
return (
|
||||
<DashboardInsider>
|
||||
<Box className={styles.rootWrap}>
|
||||
<Box className={styles.root}>
|
||||
<ImportFileProvider resource="account">
|
||||
<ImportStepper />
|
||||
</ImportFileProvider>
|
||||
</Box>
|
||||
</Box>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
|
||||
.root{
|
||||
background: #fff;
|
||||
border: 1px solid #D3D8DE;
|
||||
border-radius: 5px;
|
||||
padding: 16px;
|
||||
}
|
||||
.description{
|
||||
margin: 0;
|
||||
margin-top: 6px;
|
||||
color: #8F99A8;
|
||||
}
|
||||
.title{
|
||||
color: #5F6B7C;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.buttonWrap{
|
||||
flex: 25% 0;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// @ts-nocheck
|
||||
import { Box, Group } from '@/components';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import styles from './ImportSampleDownload.module.scss';
|
||||
|
||||
export function ImportSampleDownload() {
|
||||
return (
|
||||
<Group className={styles.root} noWrap>
|
||||
<Box>
|
||||
<h3 className={styles.title}>Table Example</h3>
|
||||
<p className={styles.description}>
|
||||
Download a sample file and compare it to your import file to ensure
|
||||
you have the file perfect for the import.
|
||||
</p>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.buttonWrap}>
|
||||
<Button minimal outlined>
|
||||
Download File
|
||||
</Button>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.content {
|
||||
margin-top: 2.4rem;
|
||||
}
|
||||
28
packages/webapp/src/containers/Import/ImportStepper.tsx
Normal file
28
packages/webapp/src/containers/Import/ImportStepper.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
// @ts-nocheck
|
||||
|
||||
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';
|
||||
|
||||
export function ImportStepper() {
|
||||
const { step } = useImportFileContext();
|
||||
|
||||
return (
|
||||
<Stepper active={step} classNames={{ content: styles.content }}>
|
||||
<Stepper.Step label={'File Upload'}>
|
||||
<ImportFileUploadStep />
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step label={'Mapping'}>
|
||||
<ImportFileMapping />
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step label={'Results'}>
|
||||
<ImportFilePreview />
|
||||
</Stepper.Step>
|
||||
</Stepper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user