Skip to content

Commit 5297ac0

Browse files
committed
feat: hide steps depending on approval branch, show disabled mrf step
1 parent cc22c20 commit 5297ac0

File tree

7 files changed

+167
-26
lines changed

7 files changed

+167
-26
lines changed

packages/frontend/src/components/Editor/components/AddStepButton.tsx

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { IStep } from '@plumber/types'
22

3+
import { useContext, useMemo } from 'react'
34
import { BiPlus } from 'react-icons/bi'
45
import { Box, Divider, useDisclosure } from '@chakra-ui/react'
56
import { IconButton, TouchableTooltip } from '@opengovsg/design-system-react'
67

78
import EmptyFlowStepHeader from '@/components/EmptyFlowStepHeader'
89
import FlowStepConfigurationModal from '@/components/FlowStepConfigurationModal'
10+
import { MrfContext } from '@/contexts/MrfContext'
911
import { useUnsavedChanges } from '@/hooks/useUnsavedChanges'
1012

13+
import { DisabledFlowStep } from './DisabledFlowStep'
1114
import UnsavedChangesAlert from './UnsavedChangesAlert'
1215

1316
interface AddStepButtonProps {
@@ -21,6 +24,28 @@ interface AddStepButtonProps {
2124
export function AddStepButton(props: AddStepButtonProps): JSX.Element {
2225
const { isHidden, isLastStep, step, isDisabled, showEmptyAction } = props
2326
const { isOpen, onOpen, onClose } = useDisclosure()
27+
const { approvalBranches, disabledMrfStepToDisplay } = useContext(MrfContext)
28+
const approvalBranch =
29+
step.config.approval?.branch || approvalBranches[step.id]
30+
31+
const { dividerColor, iconBgColor } = useMemo(() => {
32+
if (approvalBranch === undefined) {
33+
return {
34+
dividerColor: 'base.divider.strong',
35+
iconBgColor: undefined,
36+
}
37+
}
38+
if (approvalBranch === 'approve') {
39+
return {
40+
dividerColor: 'green.500',
41+
iconBgColor: 'green.50',
42+
}
43+
}
44+
return {
45+
dividerColor: 'red.500',
46+
iconBgColor: 'red.50',
47+
}
48+
}, [approvalBranch])
2449

2550
const {
2651
cancelRef,
@@ -32,14 +57,29 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
3257
onProceed: onOpen,
3358
})
3459

60+
const shouldShowDisabledMrfStep = isLastStep && disabledMrfStepToDisplay
61+
const containerHeight = useMemo(() => {
62+
if (showEmptyAction) {
63+
return undefined
64+
}
65+
if (isHidden) {
66+
return 12
67+
}
68+
if (shouldShowDisabledMrfStep) {
69+
return 36
70+
}
71+
return 16
72+
}, [showEmptyAction, isHidden, shouldShowDisabledMrfStep])
73+
3574
return (
3675
<Box
3776
pos="relative"
3877
display="flex"
3978
flexDir="column"
4079
alignItems="center"
4180
alignSelf="stretch"
42-
h={showEmptyAction ? undefined : isHidden ? 12 : 16}
81+
h={containerHeight}
82+
w="100%"
4383
>
4484
{isHidden || (isDisabled && !isLastStep) ? (
4585
!isLastStep && (
@@ -65,7 +105,7 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
65105
)}
66106
{/* Top vertical line */}
67107
<Box h={6}>
68-
<Divider orientation="vertical" borderColor="base.divider.strong" />
108+
<Divider orientation="vertical" borderColor={dividerColor} />
69109
</Box>
70110
<TouchableTooltip
71111
label={isDisabled ? '' : 'Add step'}
@@ -81,6 +121,7 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
81121
variant={isLastStep ? 'outline' : 'clear'}
82122
size="xs"
83123
color="interaction.sub.default"
124+
bg={iconBgColor}
84125
borderRadius="full"
85126
pointerEvents={isDisabled ? 'none' : 'auto'}
86127
_hover={{
@@ -89,20 +130,26 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element {
89130
_active={{
90131
bg: 'interaction.muted.neutral.active',
91132
}}
92-
borderColor={isLastStep ? 'interaction.sub.default' : undefined}
133+
borderColor={isLastStep ? dividerColor : undefined}
93134
h={8}
94135
/>
95136
</TouchableTooltip>
96137
{/* Bottom vertical line */}
97138
{!isLastStep && (
98139
<Box h={6}>
99-
<Divider
100-
orientation="vertical"
101-
borderColor="base.divider.strong"
102-
/>
140+
<Divider orientation="vertical" borderColor={dividerColor} />
103141
</Box>
104142
)}
105143

144+
{shouldShowDisabledMrfStep && (
145+
<DisabledFlowStep
146+
step={disabledMrfStepToDisplay}
147+
tooltipText={
148+
'This step will only happen if the previous MRF step is approved'
149+
}
150+
/>
151+
)}
152+
106153
{isOpen && (
107154
<FlowStepConfigurationModal
108155
onClose={onClose}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { IStep } from '@plumber/types'
2+
3+
import { useContext } from 'react'
4+
import { Flex } from '@chakra-ui/react'
5+
import { TouchableTooltip } from '@opengovsg/design-system-react'
6+
7+
import StepAppIcon from '@/components/FlowStep/components/StepAppIcon'
8+
import StepCaptionAndDemo from '@/components/FlowStep/components/StepCaptionAndDemo'
9+
import { flowStepStyles } from '@/components/FlowStep/styles'
10+
import { EditorContext } from '@/contexts/Editor'
11+
import { getFlowStepHeaderWidth } from '@/helpers/editor'
12+
import { useStepMetadata } from '@/hooks/useStepMetadata'
13+
14+
interface DisabledFlowStepProps {
15+
step: IStep
16+
tooltipText: string
17+
}
18+
19+
export function DisabledFlowStep(props: DisabledFlowStepProps) {
20+
const { step, tooltipText } = props
21+
22+
const { allApps, isMobile, isDrawerOpen } = useContext(EditorContext)
23+
24+
const { app, caption } = useStepMetadata(allApps, step, true)
25+
const headerWidth = getFlowStepHeaderWidth(isDrawerOpen, isMobile, false)
26+
27+
return (
28+
<Flex
29+
flexDir="row"
30+
alignItems="center"
31+
width="100%"
32+
display={isMobile ? 'block' : 'flex'}
33+
my={4}
34+
>
35+
<TouchableTooltip label={tooltipText} wrapperStyles={{ width: '100%' }}>
36+
<Flex
37+
data-test="flow-step"
38+
{...flowStepStyles.container}
39+
h="64px"
40+
w={headerWidth}
41+
>
42+
<Flex
43+
{...flowStepStyles.topHeader}
44+
opacity={0.4}
45+
pointerEvents="none"
46+
>
47+
<StepAppIcon app={app} step={step} />
48+
<StepCaptionAndDemo app={app} caption={caption} />
49+
</Flex>
50+
</Flex>
51+
</TouchableTooltip>
52+
</Flex>
53+
)
54+
}

packages/frontend/src/components/FlowStep/components/ApproveReject.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useContext } from 'react'
22
import { Flex, Tab, TabList, TabProps, Tabs } from '@chakra-ui/react'
33

4+
import { EditorContext } from '@/contexts/Editor'
45
import { MrfContext } from '@/contexts/MrfContext'
56

67
const tabStyle = (primaryColor: string): TabProps => {
@@ -24,15 +25,18 @@ const tabStyle = (primaryColor: string): TabProps => {
2425
}
2526

2627
export function ApproveReject({ stepId }: { stepId: string }) {
28+
const { setCurrentStepId, onDrawerClose } = useContext(EditorContext)
2729
const { approvalBranches, setApprovalBranch } = useContext(MrfContext)
2830

2931
const isApproveBranch = approvalBranches[stepId] === 'approve'
3032

3133
const onChange = useCallback(
3234
(index: number) => {
3335
setApprovalBranch(stepId, index === 0 ? 'approve' : 'reject')
36+
setCurrentStepId(null)
37+
onDrawerClose()
3438
},
35-
[stepId, setApprovalBranch],
39+
[stepId, setApprovalBranch, setCurrentStepId, onDrawerClose],
3640
)
3741

3842
return (

packages/frontend/src/components/FlowStep/index.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,6 @@ export default function FlowStep(
161161
// are removed once user executes a successful test
162162
const hasInfoBox = shouldShowTemplateMsg || shouldTestStepAgain
163163

164-
const approvalBranch = step.config.approval?.branch
165-
166164
if (!allApps) {
167165
return <CircularProgress isIndeterminate my={2} />
168166
}
@@ -234,9 +232,6 @@ export default function FlowStep(
234232
borderTopRadius={hasInfoBox ? 'none' : 'lg'}
235233
h={isNested ? '48px' : '64px'}
236234
w={headerWidth}
237-
// approval branch can be approve, reject or undefined
238-
// boxShadow specified in theme/foundations/shadows.ts
239-
boxShadow={isNested ? 'none' : approvalBranch}
240235
>
241236
<Flex {...flowStepStyles.topHeader} onClick={handleClick}>
242237
<StepAppIcon

packages/frontend/src/components/FlowStepGroup/index.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ export default function FlowStepGroup(props: FlowStepGroupProps) {
7070
(step) => step.key === TOOLBOX_ACTIONS.ForEach,
7171
)
7272

73-
const approvalBranch = groupedSteps[0]?.[0]?.config?.approval?.branch
74-
7573
return (
7674
<Flex
7775
w={
@@ -87,10 +85,6 @@ export default function FlowStepGroup(props: FlowStepGroupProps) {
8785
display={isMobile ? 'block' : 'flex'}
8886
w={getFlowStepHeaderWidth(isDrawerOpen, isMobile)}
8987
minW={MIN_FLOW_STEP_WIDTH}
90-
// approval branch can be approve, reject or undefined
91-
// boxShadow specified in theme/foundations/shadows.ts
92-
// we display for entire group instead of individual nested steps
93-
boxShadow={approvalBranch}
9488
>
9589
<Box {...flowStepGroupStyles.header} w="100%">
9690
<Flex

packages/frontend/src/contexts/MrfContext.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ interface MrfContextReturnValue {
1414
[stepId: string]: IStepApprovalBranch
1515
}
1616
setApprovalBranch: (stepId: string, branch: IStepApprovalBranch) => void
17+
disabledMrfStepToDisplay: IStep | null
1718
}
1819

1920
export const MrfContext = createContext<MrfContextReturnValue>({
2021
mrfSteps: [],
2122
mrfApprovalSteps: [],
2223
approvalBranches: {},
2324
setApprovalBranch: () => null,
25+
disabledMrfStepToDisplay: null,
2426
})
2527

2628
interface MrfContextProviderProps {
@@ -30,6 +32,9 @@ interface MrfContextProviderProps {
3032
export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
3133
const { flow } = useContext(EditorContext)
3234

35+
const [disabledMrfStepToDisplay, setDisabledMrfStepToDisplay] =
36+
useState<IStep | null>(null)
37+
3338
const mrfSteps = flow.steps.filter(
3439
(step) => step.appKey === FORMSG_APP_KEY && step.key === MRF_ACTION_KEY,
3540
)
@@ -52,6 +57,13 @@ export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
5257
...prev,
5358
[stepId]: branch,
5459
}))
60+
if (branch === 'reject') {
61+
const nextMrfStepId =
62+
mrfSteps[mrfSteps.findIndex((mrfStep) => mrfStep.id === stepId) + 1]
63+
setDisabledMrfStepToDisplay(nextMrfStepId ?? null)
64+
} else {
65+
setDisabledMrfStepToDisplay(null)
66+
}
5567
}
5668

5769
return (
@@ -61,6 +73,7 @@ export const MrfContextProvider = ({ children }: MrfContextProviderProps) => {
6173
mrfApprovalSteps,
6274
approvalBranches,
6375
setApprovalBranch,
76+
disabledMrfStepToDisplay,
6477
}}
6578
>
6679
{children}

packages/frontend/src/contexts/StepsToDisplay.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createContext, useContext, useMemo } from 'react'
66
import { extractBranchesWithSteps } from '@/helpers/toolbox'
77

88
import { EditorContext } from './Editor'
9+
import { MrfContext } from './MrfContext'
910

1011
export type StepToDisplayContextValue = {
1112
triggerStep: IStep | null
@@ -31,8 +32,38 @@ export function StepsToDisplayProvider({
3132
children,
3233
}: StepExecutionsProviderProps): JSX.Element {
3334
const { allApps, flow } = useContext(EditorContext)
35+
const { approvalBranches } = useContext(MrfContext)
3436

35-
const steps = flow.steps
37+
const allSteps = flow.steps
38+
39+
const stepsToDisplay = useMemo(() => {
40+
let firstRejectBranchStepId: string | null = null
41+
return allSteps.filter((step) => {
42+
if (
43+
firstRejectBranchStepId != null &&
44+
step.config?.approval?.stepId !== firstRejectBranchStepId
45+
) {
46+
return false
47+
}
48+
49+
if (!firstRejectBranchStepId && approvalBranches[step.id] === 'reject') {
50+
firstRejectBranchStepId = step.id
51+
return true
52+
}
53+
54+
if (!step.config?.approval) {
55+
return true
56+
}
57+
58+
const approvalConfigStepId = step.config?.approval?.stepId
59+
const approvalConfigBranch = step.config?.approval?.branch
60+
61+
if (approvalBranches[approvalConfigStepId] === approvalConfigBranch) {
62+
return true
63+
}
64+
return false
65+
})
66+
}, [allSteps, approvalBranches])
3667

3768
const appsWithActions: IApp[] = allApps.filter(
3869
(app: IApp) => !!app.actions?.length,
@@ -57,7 +88,7 @@ export function StepsToDisplayProvider({
5788
return [null, [], []]
5889
}
5990

60-
const groupStepIdx = steps.findIndex((step, index) => {
91+
const groupStepIdx = stepsToDisplay.findIndex((step, index) => {
6192
if (
6293
// We ignore the 1st step because it's either a trigger, or a
6394
// step-grouping action that is using a nested Editor to edit steps in
@@ -73,15 +104,18 @@ export function StepsToDisplayProvider({
73104

74105
let branchesWithSteps: IStep[][] = []
75106
if (groupStepIdx !== -1) {
76-
branchesWithSteps = extractBranchesWithSteps(steps.slice(groupStepIdx), 0)
107+
branchesWithSteps = extractBranchesWithSteps(
108+
stepsToDisplay.slice(groupStepIdx),
109+
0,
110+
)
77111
}
78112

79-
const triggerStep = steps[0]
113+
const triggerStep = stepsToDisplay[0]
80114

81115
return groupStepIdx === -1
82-
? [triggerStep, steps.slice(1), []]
83-
: [triggerStep, steps.slice(1, groupStepIdx), branchesWithSteps]
84-
}, [groupingActions, steps])
116+
? [triggerStep, stepsToDisplay.slice(1), []]
117+
: [triggerStep, stepsToDisplay.slice(1, groupStepIdx), branchesWithSteps]
118+
}, [groupingActions, stepsToDisplay])
85119

86120
return (
87121
<StepsToDisplayContext.Provider

0 commit comments

Comments
 (0)