Fixed: In CSV import now users are able to come back to the previous step. (#5625)

Users now can make a back transition from the current step state.

- Added a `BackButton` component to `spreadsheet-import` in order to use
it within the step state components.
- Used the prebuilt `prevStep` from `useStepBar` and passed it as a prop
to the `Uploadflow` to get the previous state as activestep.
- Added a `previousState` to set the previous state with the required
key data.
- Added a `handleOnBack` function in `Uploadflow` to set the correct
state and call the `prevStep` function to make the transition.
- Added a callback function `onBack` and passed it as props to each step
state component.

fixes: #5564 



https://github.com/twentyhq/twenty/assets/140178357/be7e1a0a-0fb8-41f2-a207-dfc3208ca6f0

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
This commit is contained in:
Shashank Vishwakarma
2024-05-30 22:13:56 +05:30
committed by GitHub
parent a12c1aad5e
commit c7f2150ac7
11 changed files with 60 additions and 21 deletions

View File

@ -16,22 +16,22 @@ const StyledButton = styled(MainButton)`
width: 200px; width: 200px;
`; `;
type ContinueButtonProps = { type StepNavigationButtonProps = {
onContinue: (val: any) => void; onClick: () => void;
title: string; title: string;
isLoading?: boolean; isLoading?: boolean;
}; };
export const ContinueButton = ({ export const StepNavigationButton = ({
onContinue, onClick,
title, title,
isLoading, isLoading,
}: ContinueButtonProps) => ( }: StepNavigationButtonProps) => (
<StyledFooter> <StyledFooter>
<StyledButton <StyledButton
Icon={isLoading ? CircularProgressBar : undefined} Icon={isLoading ? CircularProgressBar : undefined}
title={title} title={title}
onClick={!isLoading ? onContinue : undefined} onClick={!isLoading ? onClick : undefined}
/> />
</StyledFooter> </StyledFooter>
); );

View File

@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ContinueButton } from '@/spreadsheet-import/components/ContinueButton';
import { Heading } from '@/spreadsheet-import/components/Heading'; import { Heading } from '@/spreadsheet-import/components/Heading';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { Field, RawData } from '@/spreadsheet-import/types'; import { Field, RawData } from '@/spreadsheet-import/types';
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields'; import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
@ -49,6 +49,7 @@ export type MatchColumnsStepProps<T extends string> = {
data: RawData[]; data: RawData[];
headerValues: RawData; headerValues: RawData;
onContinue: (data: any[], rawData: RawData[], columns: Columns<T>) => void; onContinue: (data: any[], rawData: RawData[], columns: Columns<T>) => void;
onBack: () => void;
}; };
export enum ColumnType { export enum ColumnType {
@ -112,6 +113,7 @@ export const MatchColumnsStep = <T extends string>({
data, data,
headerValues, headerValues,
onContinue, onContinue,
onBack,
}: MatchColumnsStepProps<T>) => { }: MatchColumnsStepProps<T>) => {
const { enqueueDialog } = useDialogManager(); const { enqueueDialog } = useDialogManager();
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
@ -284,11 +286,12 @@ export const MatchColumnsStep = <T extends string>({
)} )}
/> />
</StyledContent> </StyledContent>
<ContinueButton <StepNavigationButton
onClick={handleOnContinue}
isLoading={isLoading} isLoading={isLoading}
onContinue={handleOnContinue}
title="Next" title="Next"
/> />
<StepNavigationButton onClick={onBack} title="Back" />
</> </>
); );
}; };

View File

@ -1,8 +1,8 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ContinueButton } from '@/spreadsheet-import/components/ContinueButton';
import { Heading } from '@/spreadsheet-import/components/Heading'; import { Heading } from '@/spreadsheet-import/components/Heading';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { RawData } from '@/spreadsheet-import/types'; import { RawData } from '@/spreadsheet-import/types';
import { Modal } from '@/ui/layout/modal/components/Modal'; import { Modal } from '@/ui/layout/modal/components/Modal';
@ -21,11 +21,13 @@ const StyledTableContainer = styled.div`
type SelectHeaderStepProps = { type SelectHeaderStepProps = {
data: RawData[]; data: RawData[];
onContinue: (headerValues: RawData, data: RawData[]) => Promise<void>; onContinue: (headerValues: RawData, data: RawData[]) => Promise<void>;
onBack: () => void;
}; };
export const SelectHeaderStep = ({ export const SelectHeaderStep = ({
data, data,
onContinue, onContinue,
onBack,
}: SelectHeaderStepProps) => { }: SelectHeaderStepProps) => {
const [selectedRows, setSelectedRows] = useState<ReadonlySet<number>>( const [selectedRows, setSelectedRows] = useState<ReadonlySet<number>>(
new Set([0]), new Set([0]),
@ -53,11 +55,12 @@ export const SelectHeaderStep = ({
/> />
</StyledTableContainer> </StyledTableContainer>
</Modal.Content> </Modal.Content>
<ContinueButton <StepNavigationButton
onContinue={handleContinue} onClick={handleContinue}
title="Next" title="Next"
isLoading={isLoading} isLoading={isLoading}
/> />
<StepNavigationButton onClick={onBack} title="Back" />
</> </>
); );
}; };

View File

@ -1,8 +1,8 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ContinueButton } from '@/spreadsheet-import/components/ContinueButton';
import { Heading } from '@/spreadsheet-import/components/Heading'; import { Heading } from '@/spreadsheet-import/components/Heading';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { Radio } from '@/ui/input/components/Radio'; import { Radio } from '@/ui/input/components/Radio';
import { RadioGroup } from '@/ui/input/components/RadioGroup'; import { RadioGroup } from '@/ui/input/components/RadioGroup';
import { Modal } from '@/ui/layout/modal/components/Modal'; import { Modal } from '@/ui/layout/modal/components/Modal';
@ -27,11 +27,13 @@ const StyledRadioContainer = styled.div`
type SelectSheetStepProps = { type SelectSheetStepProps = {
sheetNames: string[]; sheetNames: string[];
onContinue: (sheetName: string) => Promise<void>; onContinue: (sheetName: string) => Promise<void>;
onBack: () => void;
}; };
export const SelectSheetStep = ({ export const SelectSheetStep = ({
sheetNames, sheetNames,
onContinue, onContinue,
onBack,
}: SelectSheetStepProps) => { }: SelectSheetStepProps) => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -58,11 +60,12 @@ export const SelectSheetStep = ({
</RadioGroup> </RadioGroup>
</StyledRadioContainer> </StyledRadioContainer>
</StyledContent> </StyledContent>
<ContinueButton <StepNavigationButton
onClick={() => handleOnContinue(value)}
isLoading={isLoading} isLoading={isLoading}
onContinue={() => handleOnContinue(value)}
title="Next" title="Next"
/> />
<StepNavigationButton onClick={onBack} title="Back" />
</> </>
); );
}; };

View File

@ -35,7 +35,7 @@ export const Steps = () => {
initialStepState?.type, initialStepState?.type,
); );
const { nextStep, activeStep } = useStepBar({ const { nextStep, prevStep, activeStep } = useStepBar({
initialStep, initialStep,
}); });
@ -48,7 +48,7 @@ export const Steps = () => {
))} ))}
</StepBar> </StepBar>
</StyledHeader> </StyledHeader>
<UploadFlow nextStep={nextStep} /> <UploadFlow nextStep={nextStep} prevStep={prevStep} />
</> </>
); );
}; };

View File

@ -59,14 +59,18 @@ export type StepState =
interface UploadFlowProps { interface UploadFlowProps {
nextStep: () => void; nextStep: () => void;
prevStep: () => void;
} }
export const UploadFlow = ({ nextStep }: UploadFlowProps) => { export const UploadFlow = ({ nextStep, prevStep }: UploadFlowProps) => {
const theme = useTheme(); const theme = useTheme();
const { initialStepState } = useSpreadsheetImportInternal(); const { initialStepState } = useSpreadsheetImportInternal();
const [state, setState] = useState<StepState>( const [state, setState] = useState<StepState>(
initialStepState || { type: StepType.upload }, initialStepState || { type: StepType.upload },
); );
const [previousState, setPreviousState] = useState<StepState>(
initialStepState || { type: StepType.upload },
);
const [uploadedFile, setUploadedFile] = useState<File | null>(null); const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const { const {
maxRecords, maxRecords,
@ -87,6 +91,11 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
[enqueueSnackBar], [enqueueSnackBar],
); );
const onBack = useCallback(() => {
setState(previousState);
prevStep();
}, [prevStep, previousState]);
switch (state.type) { switch (state.type) {
case StepType.upload: case StepType.upload:
return ( return (
@ -138,6 +147,7 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
} else { } else {
setState({ type: StepType.selectSheet, workbook }); setState({ type: StepType.selectSheet, workbook });
} }
setPreviousState(state);
nextStep(); nextStep();
}} }}
/> />
@ -164,10 +174,12 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
type: StepType.selectHeader, type: StepType.selectHeader,
data: mappedWorkbook, data: mappedWorkbook,
}); });
setPreviousState(state);
} catch (e) { } catch (e) {
errorToast((e as Error).message); errorToast((e as Error).message);
} }
}} }}
onBack={onBack}
/> />
); );
case StepType.selectHeader: case StepType.selectHeader:
@ -184,11 +196,13 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
data, data,
headerValues, headerValues,
}); });
setPreviousState(state);
nextStep(); nextStep();
} catch (e) { } catch (e) {
errorToast((e as Error).message); errorToast((e as Error).message);
} }
}} }}
onBack={onBack}
/> />
); );
case StepType.matchColumns: case StepType.matchColumns:
@ -203,11 +217,13 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
type: StepType.validateData, type: StepType.validateData,
data, data,
}); });
setPreviousState(state);
nextStep(); nextStep();
} catch (e) { } catch (e) {
errorToast((e as Error).message); errorToast((e as Error).message);
} }
}} }}
onBack={onBack}
/> />
); );
case StepType.validateData: case StepType.validateData:
@ -223,6 +239,10 @@ export const UploadFlow = ({ nextStep }: UploadFlowProps) => {
type: StepType.loading, type: StepType.loading,
}) })
} }
onBack={() => {
onBack();
setPreviousState(initialStepState || { type: StepType.upload });
}}
/> />
); );
case StepType.loading: case StepType.loading:

View File

@ -4,8 +4,8 @@ import { RowsChangeData } from 'react-data-grid';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconTrash } from 'twenty-ui'; import { IconTrash } from 'twenty-ui';
import { ContinueButton } from '@/spreadsheet-import/components/ContinueButton';
import { Heading } from '@/spreadsheet-import/components/Heading'; import { Heading } from '@/spreadsheet-import/components/Heading';
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
import { Table } from '@/spreadsheet-import/components/Table'; import { Table } from '@/spreadsheet-import/components/Table';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
import { Data } from '@/spreadsheet-import/types'; import { Data } from '@/spreadsheet-import/types';
@ -64,12 +64,14 @@ type ValidationStepProps<T extends string> = {
initialData: Data<T>[]; initialData: Data<T>[];
file: File; file: File;
onSubmitStart?: () => void; onSubmitStart?: () => void;
onBack: () => void;
}; };
export const ValidationStep = <T extends string>({ export const ValidationStep = <T extends string>({
initialData, initialData,
file, file,
onSubmitStart, onSubmitStart,
onBack,
}: ValidationStepProps<T>) => { }: ValidationStepProps<T>) => {
const { enqueueDialog } = useDialogManager(); const { enqueueDialog } = useDialogManager();
const { fields, onClose, onSubmit, rowHook, tableHook } = const { fields, onClose, onSubmit, rowHook, tableHook } =
@ -238,7 +240,8 @@ export const ValidationStep = <T extends string>({
/> />
</StyledScrollContainer> </StyledScrollContainer>
</StyledContent> </StyledContent>
<ContinueButton onContinue={onContinue} title="Confirm" /> <StepNavigationButton onClick={onContinue} title="Confirm" />
<StepNavigationButton onClick={onBack} title="Back" />
</> </>
); );
}; };

View File

@ -67,6 +67,7 @@ export const Default = () => (
headerValues={mockData[0] as string[]} headerValues={mockData[0] as string[]}
data={mockData.slice(1)} data={mockData.slice(1)}
onContinue={() => null} onContinue={() => null}
onBack={() => null}
/> />
</ModalWrapper> </ModalWrapper>
</Providers> </Providers>

View File

@ -26,6 +26,7 @@ export const Default = () => (
<SelectHeaderStep <SelectHeaderStep
data={headerSelectionTableFields} data={headerSelectionTableFields}
onContinue={() => Promise.resolve()} onContinue={() => Promise.resolve()}
onBack={() => Promise.resolve()}
/> />
</ModalWrapper> </ModalWrapper>
</Providers> </Providers>

View File

@ -25,6 +25,7 @@ export const Default = () => (
<SelectSheetStep <SelectSheetStep
sheetNames={sheetNames} sheetNames={sheetNames}
onContinue={() => Promise.resolve()} onContinue={() => Promise.resolve()}
onBack={() => Promise.resolve()}
/> />
</ModalWrapper> </ModalWrapper>
</Providers> </Providers>

View File

@ -25,7 +25,11 @@ export const Default = () => (
<DialogManagerScope dialogManagerScopeId="dialog-manager"> <DialogManagerScope dialogManagerScopeId="dialog-manager">
<Providers values={mockRsiValues}> <Providers values={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => null}> <ModalWrapper isOpen={true} onClose={() => null}>
<ValidationStep initialData={editableTableInitialData} file={file} /> <ValidationStep
initialData={editableTableInitialData}
file={file}
onBack={() => Promise.resolve()}
/>
</ModalWrapper> </ModalWrapper>
</Providers> </Providers>
</DialogManagerScope> </DialogManagerScope>