Skip to content

Commit

Permalink
Merge pull request #220 from UgnisSoftware/UGN-422
Browse files Browse the repository at this point in the history
Back button navigation
  • Loading branch information
masiulis committed Nov 27, 2023
2 parents 6a3168a + c62e20f commit 9d032fd
Show file tree
Hide file tree
Showing 16 changed files with 163 additions and 62 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Common date-time formats can be viewed [here](https://docs.sheetjs.com/docs/csf/
autoMapSelectValues?: boolean
// Headers matching accuracy: 1 for strict and up for more flexible matching. Default: 2
autoMapDistance?: number
// Enable navigation in stepper component and show back button. Default: false
isNavigationEnabled?: boolean
```

## Customisation
Expand Down
1 change: 1 addition & 0 deletions src/ReactSpreadsheetImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const defaultRSIProps: Partial<RsiProps<any>> = {
autoMapSelectValues: false,
allowInvalidSubmit: true,
autoMapDistance: 2,
isNavigationEnabled: false,
translations: translations,
uploadStepHook: async (value) => value,
selectHeaderStepHook: async (headerValues, data) => ({ headerValues, data }),
Expand Down
34 changes: 26 additions & 8 deletions src/components/ContinueButton.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { Button, ModalFooter } from "@chakra-ui/react"
import { Button, ModalFooter, useStyleConfig } from "@chakra-ui/react"
import { themeOverrides } from "../theme"

type ContinueButtonProps = {
onContinue: (val: any) => void
onBack?: () => void
title: string
backTitle?: string
isLoading?: boolean
}

export const ContinueButton = ({ onContinue, title, isLoading }: ContinueButtonProps) => (
<ModalFooter>
<Button size="lg" w="21rem" onClick={onContinue} isLoading={isLoading}>
{title}
</Button>
</ModalFooter>
)
export const ContinueButton = ({ onContinue, onBack, title, backTitle, isLoading }: ContinueButtonProps) => {
const styles = useStyleConfig("Modal") as (typeof themeOverrides)["components"]["Modal"]["baseStyle"]
const nextButtonMobileWidth = onBack ? "8rem" : "100%"
return (
<ModalFooter>
{onBack && (
<Button size="md" sx={styles.backButton} onClick={onBack} isLoading={isLoading} variant="link">
{backTitle}
</Button>
)}
<Button
size="lg"
w={{ base: nextButtonMobileWidth, md: "21rem" }}
sx={styles.continueButton}
onClick={onContinue}
isLoading={isLoading}
>
{title}
</Button>
</ModalFooter>
)
}
25 changes: 0 additions & 25 deletions src/hooks/useRsiInitialStep.ts

This file was deleted.

9 changes: 8 additions & 1 deletion src/steps/MatchColumnsStep/MatchColumnsStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type MatchColumnsProps<T extends string> = {
data: RawData[]
headerValues: RawData
onContinue: (data: any[], rawData: RawData[], columns: Columns<T>) => void
onBack?: () => void
}

export enum ColumnType {
Expand Down Expand Up @@ -62,7 +63,12 @@ export type Column<T extends string> =

export type Columns<T extends string> = Column<T>[]

export const MatchColumnsStep = <T extends string>({ data, headerValues, onContinue }: MatchColumnsProps<T>) => {
export const MatchColumnsStep = <T extends string>({
data,
headerValues,
onContinue,
onBack,
}: MatchColumnsProps<T>) => {
const toast = useToast()
const dataExample = data.slice(0, 2)
const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations } = useRsi<T>()
Expand Down Expand Up @@ -173,6 +179,7 @@ export const MatchColumnsStep = <T extends string>({ data, headerValues, onConti
<ColumnGrid
columns={columns}
onContinue={handleOnContinue}
onBack={onBack}
isLoading={isLoading}
userColumn={(column) => (
<UserTableColumn
Expand Down
4 changes: 4 additions & 0 deletions src/steps/MatchColumnsStep/components/ColumnGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ColumnGridProps<T extends string> = {
userColumn: (column: Column<T>) => React.ReactNode
templateColumn: (column: Column<T>) => React.ReactNode
onContinue: (val: Record<string, string>[]) => void
onBack?: () => void
isLoading: boolean
}

Expand All @@ -21,6 +22,7 @@ export const ColumnGrid = <T extends string>({
userColumn,
templateColumn,
onContinue,
onBack,
isLoading,
}: ColumnGridProps<T>) => {
const { translations } = useRsi()
Expand Down Expand Up @@ -66,7 +68,9 @@ export const ColumnGrid = <T extends string>({
<ContinueButton
isLoading={isLoading}
onContinue={onContinue}
onBack={onBack}
title={translations.matchColumnsStep.nextButtonTitle}
backTitle={translations.matchColumnsStep.backButtonTitle}
/>
</>
)
Expand Down
5 changes: 4 additions & 1 deletion src/steps/SelectHeaderStep/SelectHeaderStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import type { RawData } from "../../types"
type SelectHeaderProps = {
data: RawData[]
onContinue: (headerValues: RawData, data: RawData[]) => Promise<void>
onBack?: () => void
}

export const SelectHeaderStep = ({ data, onContinue }: SelectHeaderProps) => {
export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps) => {
const styles = useStyleConfig(
"SelectHeaderStep",
) as (typeof themeOverrides)["components"]["SelectHeaderStep"]["baseStyle"]
Expand All @@ -36,7 +37,9 @@ export const SelectHeaderStep = ({ data, onContinue }: SelectHeaderProps) => {
</ModalBody>
<ContinueButton
onContinue={handleContinue}
onBack={onBack}
title={translations.selectHeaderStep.nextButtonTitle}
backTitle={translations.selectHeaderStep.backButtonTitle}
isLoading={isLoading}
/>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/steps/SelectHeaderStep/tests/SelectHeaderStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ describe("Select header step tests", () => {
const selectRowIndex = 2

const onContinue = jest.fn()
const onBack = jest.fn()
render(
<Providers theme={defaultTheme} rsiValues={mockRsiValues}>
<ModalWrapper isOpen={true} onClose={() => {}}>
<SelectHeaderStep data={data} onContinue={onContinue} />
<SelectHeaderStep data={data} onContinue={onContinue} onBack={onBack} />
</ModalWrapper>
</Providers>,
)
Expand Down
5 changes: 4 additions & 1 deletion src/steps/SelectSheetStep/SelectSheetStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import type { themeOverrides } from "../../theme"
type SelectSheetProps = {
sheetNames: string[]
onContinue: (sheetName: string) => Promise<void>
onBack?: () => void
}

export const SelectSheetStep = ({ sheetNames, onContinue }: SelectSheetProps) => {
export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetProps) => {
const [isLoading, setIsLoading] = useState(false)
const { translations } = useRsi()
const [value, setValue] = useState(sheetNames[0])
Expand Down Expand Up @@ -42,7 +43,9 @@ export const SelectSheetStep = ({ sheetNames, onContinue }: SelectSheetProps) =>
<ContinueButton
isLoading={isLoading}
onContinue={() => handleOnContinue(value)}
onBack={onBack}
title={translations.uploadStep.selectSheet.nextButtonTitle}
backTitle={translations.uploadStep.selectSheet.backButtonTitle}
/>
</>
)
Expand Down
45 changes: 38 additions & 7 deletions src/steps/Steps.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
import { UploadFlow } from "./UploadFlow"
import { StepState, StepType, UploadFlow } from "./UploadFlow"
import { ModalHeader } from "@chakra-ui/react"
import { useSteps, Step, Steps as Stepper } from "chakra-ui-steps"
import { CgCheck } from "react-icons/cg"

import { useRsi } from "../hooks/useRsi"
import { useRsiInitialStep } from "../hooks/useRsiInitialStep"
import { useRef, useState } from "react"
import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps"

const CheckIcon = ({ color }: { color: string }) => <CgCheck size="2.25rem" color={color} />

export const Steps = () => {
const { initialStepState, translations } = useRsi()
const { initialStepState, translations, isNavigationEnabled } = useRsi()

const { steps, initialStep } = useRsiInitialStep(initialStepState?.type)
const initialStep = stepTypeToStepIndex(initialStepState?.type)

const { nextStep, activeStep } = useSteps({
const { nextStep, activeStep, setStep } = useSteps({
initialStep,
})

const [state, setState] = useState<StepState>(initialStepState || { type: StepType.upload })

const history = useRef<StepState[]>([])

const onClickStep = (stepIndex: number) => {
const type = stepIndexToStepType(stepIndex)
const historyIdx = history.current.findIndex((v) => v.type === type)
if (historyIdx === -1) return
const nextHistory = history.current.slice(0, historyIdx + 1)
history.current = nextHistory
setState(nextHistory[nextHistory.length - 1])
setStep(stepIndex)
}

const onBack = () => {
onClickStep(Math.max(activeStep - 1, 0))
}

const onNext = (v: StepState) => {
history.current.push(state)
setState(v)
v.type !== StepType.selectSheet && nextStep()
}

return (
<>
<ModalHeader display={["none", "none", "block"]}>
<Stepper activeStep={activeStep} checkIcon={CheckIcon}>
<Stepper
activeStep={activeStep}
checkIcon={CheckIcon}
onClickStep={isNavigationEnabled ? onClickStep : undefined}
responsive={false}
>
{steps.map((key) => (
<Step label={translations[key].title} key={key} />
))}
</Stepper>
</ModalHeader>
<UploadFlow nextStep={nextStep} />
<UploadFlow state={state} onNext={onNext} onBack={isNavigationEnabled ? onBack : undefined} />
</>
)
}
27 changes: 13 additions & 14 deletions src/steps/UploadFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ export type StepState =
}

interface Props {
nextStep: () => void
state: StepState
onNext: (v: StepState) => void
onBack?: () => void
}

export const UploadFlow = ({ nextStep }: Props) => {
export const UploadFlow = ({ state, onNext, onBack }: Props) => {
const {
initialStepState,
maxRecords,
translations,
uploadStepHook,
Expand All @@ -57,7 +58,6 @@ export const UploadFlow = ({ nextStep }: Props) => {
rowHook,
tableHook,
} = useRsi()
const [state, setState] = useState<StepState>(initialStepState || { type: StepType.upload })
const [uploadedFile, setUploadedFile] = useState<File | null>(null)
const toast = useToast()
const errorToast = useCallback(
Expand Down Expand Up @@ -88,16 +88,15 @@ export const UploadFlow = ({ nextStep }: Props) => {
}
try {
const mappedWorkbook = await uploadStepHook(mapWorkbook(workbook))
setState({
onNext({
type: StepType.selectHeader,
data: mappedWorkbook,
})
nextStep()
} catch (e) {
errorToast((e as Error).message)
}
} else {
setState({ type: StepType.selectSheet, workbook })
onNext({ type: StepType.selectSheet, workbook })
}
}}
/>
Expand All @@ -113,15 +112,15 @@ export const UploadFlow = ({ nextStep }: Props) => {
}
try {
const mappedWorkbook = await uploadStepHook(mapWorkbook(state.workbook, sheetName))
setState({
onNext({
type: StepType.selectHeader,
data: mappedWorkbook,
})
nextStep()
} catch (e) {
errorToast((e as Error).message)
}
}}
onBack={onBack}
/>
)
case StepType.selectHeader:
Expand All @@ -131,16 +130,16 @@ export const UploadFlow = ({ nextStep }: Props) => {
onContinue={async (...args) => {
try {
const { data, headerValues } = await selectHeaderStepHook(...args)
setState({
onNext({
type: StepType.matchColumns,
data,
headerValues,
})
nextStep()
} catch (e) {
errorToast((e as Error).message)
}
}}
onBack={onBack}
/>
)
case StepType.matchColumns:
Expand All @@ -152,19 +151,19 @@ export const UploadFlow = ({ nextStep }: Props) => {
try {
const data = await matchColumnsStepHook(values, rawData, columns)
const dataWithMeta = await addErrorsAndRunHooks(data, fields, rowHook, tableHook)
setState({
onNext({
type: StepType.validateData,
data: dataWithMeta,
})
nextStep()
} catch (e) {
errorToast((e as Error).message)
}
}}
onBack={onBack}
/>
)
case StepType.validateData:
return <ValidationStep initialData={state.data} file={uploadedFile!} />
return <ValidationStep initialData={state.data} file={uploadedFile!} onBack={onBack} />
default:
return <Progress isIndeterminate />
}
Expand Down
10 changes: 8 additions & 2 deletions src/steps/ValidationStep/ValidationStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import type { RowsChangeData } from "react-data-grid"
type Props<T extends string> = {
initialData: (Data<T> & Meta)[]
file: File
onBack?: () => void
}

export const ValidationStep = <T extends string>({ initialData, file }: Props<T>) => {
export const ValidationStep = <T extends string>({ initialData, file, onBack }: Props<T>) => {
const { translations, fields, onClose, onSubmit, rowHook, tableHook } = useRsi<T>()
const styles = useStyleConfig(
"ValidationStep",
Expand Down Expand Up @@ -151,7 +152,12 @@ export const ValidationStep = <T extends string>({ initialData, file }: Props<T>
}}
/>
</ModalBody>
<ContinueButton onContinue={onContinue} title={translations.validationStep.nextButtonTitle} />
<ContinueButton
onContinue={onContinue}
onBack={onBack}
title={translations.validationStep.nextButtonTitle}
backTitle={translations.validationStep.backButtonTitle}
/>
</>
)
}
Loading

0 comments on commit 9d032fd

Please sign in to comment.