Files
twenty_crm/front/src/modules/spreadsheet-import/components/steps/UploadFlow.tsx
Jérémy M 56cada6335 feat: wip import csv [part 1] (#1033)
* feat: wip import csv

* feat: start implementing twenty UI

* feat: new radio button component

* feat: use new radio button component and fix scroll issue

* fix: max height modal

* feat: wip try to customize react-data-grid to match design

* feat: wip match columns

* feat: wip match column selection

* feat: match column

* feat: clean heading component & try to fix scroll in last step

* feat: validation step

* fix: small cleaning and remove unused component

* feat: clean folder architecture

* feat: remove translations

* feat: remove chackra theme

* feat: remove unused libraries

* feat: use option button to open spreadsheet & fix stories

* Fix lint and fix imports

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2023-08-15 15:12:47 -07:00

207 lines
5.8 KiB
TypeScript

import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import type XLSX from 'xlsx-ugnis';
import { useRsi } from '@/spreadsheet-import/hooks/useRsi';
import type { RawData } from '@/spreadsheet-import/types';
import { exceedsMaxRecords } from '@/spreadsheet-import/utils/exceedsMaxRecords';
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
import { Modal } from '@/ui/modal/components/Modal';
import { CircularProgressBar } from '@/ui/progress-bar/components/CircularProgressBar';
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
import { MatchColumnsStep } from './MatchColumnsStep/MatchColumnsStep';
import { SelectHeaderStep } from './SelectHeaderStep/SelectHeaderStep';
import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
import { UploadStep } from './UploadStep/UploadStep';
import { ValidationStep } from './ValidationStep/ValidationStep';
const ProgressBarContainer = styled(Modal.Content)`
align-items: center;
display: flex;
justify-content: center;
`;
export enum StepType {
upload = 'upload',
selectSheet = 'selectSheet',
selectHeader = 'selectHeader',
matchColumns = 'matchColumns',
validateData = 'validateData',
}
export type StepState =
| {
type: StepType.upload;
}
| {
type: StepType.selectSheet;
workbook: XLSX.WorkBook;
}
| {
type: StepType.selectHeader;
data: RawData[];
}
| {
type: StepType.matchColumns;
data: RawData[];
headerValues: RawData;
}
| {
type: StepType.validateData;
data: any[];
};
interface Props {
nextStep: () => void;
}
export const UploadFlow = ({ nextStep }: Props) => {
const theme = useTheme();
const { initialStepState } = useRsi();
const [state, setState] = useState<StepState>(
initialStepState || { type: StepType.upload },
);
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const {
maxRecords,
uploadStepHook,
selectHeaderStepHook,
matchColumnsStepHook,
} = useRsi();
const { enqueueSnackBar } = useSnackBar();
const errorToast = useCallback(
(description: string) => {
enqueueSnackBar(description, {
title: 'Error',
variant: 'error',
});
},
[enqueueSnackBar],
);
switch (state.type) {
case StepType.upload:
return (
<UploadStep
onContinue={async (workbook, file) => {
setUploadedFile(file);
const isSingleSheet = workbook.SheetNames.length === 1;
if (isSingleSheet) {
if (
maxRecords &&
exceedsMaxRecords(
workbook.Sheets[workbook.SheetNames[0]],
maxRecords,
)
) {
errorToast(
`Too many records. Up to ${maxRecords.toString()} allowed`,
);
return;
}
try {
const mappedWorkbook = await uploadStepHook(
mapWorkbook(workbook),
);
setState({
type: StepType.selectHeader,
data: mappedWorkbook,
});
} catch (e) {
errorToast((e as Error).message);
}
} else {
setState({ type: StepType.selectSheet, workbook });
}
nextStep();
}}
/>
);
case StepType.selectSheet:
return (
<SelectSheetStep
sheetNames={state.workbook.SheetNames}
onContinue={async (sheetName) => {
if (
maxRecords &&
exceedsMaxRecords(state.workbook.Sheets[sheetName], maxRecords)
) {
errorToast(
`Too many records. Up to ${maxRecords.toString()} allowed`,
);
return;
}
try {
const mappedWorkbook = await uploadStepHook(
mapWorkbook(state.workbook, sheetName),
);
setState({
type: StepType.selectHeader,
data: mappedWorkbook,
});
} catch (e) {
errorToast((e as Error).message);
}
}}
/>
);
case StepType.selectHeader:
return (
<SelectHeaderStep
data={state.data}
onContinue={async (...args) => {
try {
const { data, headerValues } = await selectHeaderStepHook(
...args,
);
setState({
type: StepType.matchColumns,
data,
headerValues,
});
nextStep();
} catch (e) {
errorToast((e as Error).message);
}
}}
/>
);
case StepType.matchColumns:
return (
<MatchColumnsStep
data={state.data}
headerValues={state.headerValues}
onContinue={async (values, rawData, columns) => {
try {
const data = await matchColumnsStepHook(values, rawData, columns);
setState({
type: StepType.validateData,
data,
});
nextStep();
} catch (e) {
errorToast((e as Error).message);
}
}}
/>
);
case StepType.validateData:
if (!uploadedFile) {
throw new Error('File not found');
}
return <ValidationStep initialData={state.data} file={uploadedFile} />;
default:
return (
<ProgressBarContainer>
<CircularProgressBar
size={80}
barWidth={8}
barColor={theme.font.color.primary}
/>
</ProgressBarContainer>
);
}
};