Skip to content

Commit e08274b

Browse files
authored
feat(app): add ResetAdvancedSettingsModal (#18549)
* feat(app): add ResetAdvancedSettingsModal
1 parent 924e431 commit e08274b

File tree

7 files changed

+294
-66
lines changed

7 files changed

+294
-66
lines changed

app/src/assets/localization/en/quick_transfer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
"aspirate_volume_µL": "Aspirate volume per well (µL)",
2323
"aspirate_volume": "Aspirate volume per well",
2424
"aspirate": "Aspirate",
25-
"touch_tip_description_aspirating": "Touch tip to each side of the well after aspirating",
26-
"touch_tip_description_dispensing": "Touch tip to each side of the well after dispensing",
2725
"attach_pipette": "Attach pipette",
2826
"blow_out_after_dispensing": "Blowout after dispensing",
2927
"blow_out_description": "Blow extra air through the tip",
@@ -122,6 +120,9 @@
122120
"quick_transfer_volume": "Quick Transfer {{volume}}µL",
123121
"quick_transfer": "Quick transfer",
124122
"reservoir": "Reservoirs",
123+
"reset_kind_settings": "Reset {{transferName}} settings?",
124+
"reset_settings_description": "Continuing will undo any changes and restore the {{transferName}} settings back to the default values.",
125+
"reset_settings_with_liquid_class_description": "Continuing will undo any changes and restore the {{transferName}} settings to the values associated with the {{liquidClassName}} liquid class.",
125126
"reset_settings": "Reset {{transferName}} settings",
126127
"retract_after_aspirating": "Retract after aspirating",
127128
"retract_after_dispensing": "Retract after dispensing",
@@ -171,6 +172,8 @@
171172
"too_many_pins_header": "You've hit your max!",
172173
"touch_tip_after_aspirating": "Touch tip after aspirating",
173174
"touch_tip_after_dispensing": "Touch tip after dispensing",
175+
"touch_tip_description_aspirating": "Touch tip to each side of the well after aspirating",
176+
"touch_tip_description_dispensing": "Touch tip to each side of the well after dispensing",
174177
"touch_tip_position_mm": "Touch tip position from top of well (mm)",
175178
"touch_tip_value": "{{speed}}mm/s, {{position}} mm from bottom",
176179
"touch_tip": "Touch tip",

app/src/organisms/ODD/QuickTransferFlow/Aspirate/index.tsx

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components'
55

66
import { MediumButton } from '/app/atoms/buttons'
77

8+
import { ResetAdvancedSettingsModal } from '../QuickTransferAdvancedSettings/ResetAdvancedSettingsModal'
89
import { AspirateSettingDetail } from './AspirateSettingDetail'
910
import { AspirateSettingItem } from './AspirateSettingItem'
1011
import { useAspirateSettingsConfig } from './hooks/useAspirateSettingsConfig'
@@ -29,47 +30,63 @@ export function Aspirate(props: AspirateProps): JSX.Element | null {
2930
selectedSetting,
3031
setSelectedSetting,
3132
] = useState<AspirateSettingOption | null>(null)
33+
const [
34+
showResetAdvancedSettingsModal,
35+
setShowResetAdvancedSettingsModal,
36+
] = useState<boolean>(false)
3237

3338
const aspirateSettingsItems = useAspirateSettingsConfig({
3439
state,
3540
dispatch,
3641
setSelectedSetting,
3742
})
3843

44+
const handleResetSettings = (): void => {
45+
setShowResetAdvancedSettingsModal(true)
46+
}
47+
const handleClose = (): void => {
48+
setShowResetAdvancedSettingsModal(false)
49+
}
50+
3951
return (
40-
<Flex
41-
gap={SPACING.spacing40}
42-
flexDirection={DIRECTION_COLUMN}
43-
paddingTop={PADDING_TOP_FOR_NAV}
44-
>
45-
<Flex gap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
46-
{selectedSetting == null ? (
47-
<Flex gap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
48-
{aspirateSettingsItems.map(displayItem => (
49-
<AspirateSettingItem
50-
key={displayItem.value}
51-
displayItem={displayItem}
52-
/>
53-
))}
54-
</Flex>
55-
) : null}
56-
<AspirateSettingDetail
57-
selectedSetting={selectedSetting}
58-
state={state}
59-
dispatch={dispatch}
60-
onBack={() => {
61-
setSelectedSetting(null)
62-
}}
52+
<>
53+
{showResetAdvancedSettingsModal ? (
54+
<ResetAdvancedSettingsModal
55+
kind="aspirate"
56+
liquidClass={state.liquidClass}
57+
onClose={handleClose}
58+
/>
59+
) : null}
60+
<Flex
61+
gap={SPACING.spacing40}
62+
flexDirection={DIRECTION_COLUMN}
63+
paddingTop={PADDING_TOP_FOR_NAV}
64+
>
65+
<Flex gap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
66+
{selectedSetting == null ? (
67+
<Flex gap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
68+
{aspirateSettingsItems.map(displayItem => (
69+
<AspirateSettingItem
70+
key={displayItem.option}
71+
displayItem={displayItem}
72+
/>
73+
))}
74+
</Flex>
75+
) : null}
76+
<AspirateSettingDetail
77+
selectedSetting={selectedSetting}
78+
state={state}
79+
dispatch={dispatch}
80+
onBack={() => {
81+
setSelectedSetting(null)
82+
}}
83+
/>
84+
</Flex>
85+
<MediumButton
86+
buttonText={t('reset_settings', { transferName: 'aspirate' })}
87+
onClick={handleResetSettings}
6388
/>
6489
</Flex>
65-
66-
{/* ToDo add reset button for aspirate settings */}
67-
<MediumButton
68-
buttonText={t('reset_settings', { transferName: 'aspirate' })}
69-
onClick={() => {
70-
console.log('reset aspirate settings')
71-
}}
72-
/>
73-
</Flex>
90+
</>
7491
)
7592
}

app/src/organisms/ODD/QuickTransferFlow/Dispense/index.tsx

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components'
55

66
import { MediumButton } from '/app/atoms/buttons'
77

8+
import { ResetAdvancedSettingsModal } from '../QuickTransferAdvancedSettings/ResetAdvancedSettingsModal'
89
import { DispenseSettingDetail } from './DispenseSettingDetail'
910
import { DispenseSettingItem } from './DispenseSettingItem'
1011
import { useDispenseSettingsConfig } from './hooks/useDispenseSettingsConfig'
@@ -30,42 +31,59 @@ export function Dispense(props: DispenseProps): JSX.Element | null {
3031
selectedSetting,
3132
setSelectedSetting,
3233
] = useState<DispenseSettingOption | null>(null)
34+
const [
35+
showResetAdvancedSettingsModal,
36+
setShowResetAdvancedSettingsModal,
37+
] = useState<boolean>(false)
3338
const dispenseSettingsItems = useDispenseSettingsConfig({
3439
state,
3540
setSelectedSetting,
3641
})
3742

43+
const handleResetSettings = (): void => {
44+
setShowResetAdvancedSettingsModal(true)
45+
}
46+
const handleClose = (): void => {
47+
setShowResetAdvancedSettingsModal(false)
48+
}
49+
3850
return (
39-
<Flex
40-
gridGap={SPACING.spacing40}
41-
flexDirection={DIRECTION_COLUMN}
42-
paddingTop={PADDING_TOP_FOR_NAV}
43-
>
44-
<Flex gridGap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
45-
{selectedSetting == null
46-
? dispenseSettingsItems.map(displayItem => (
47-
<DispenseSettingItem
48-
key={displayItem.value}
49-
displayItem={displayItem}
50-
/>
51-
))
52-
: null}
53-
<DispenseSettingDetail
54-
selectedSetting={selectedSetting}
55-
state={state}
56-
dispatch={dispatch}
57-
onBack={() => {
58-
setSelectedSetting(null)
59-
}}
51+
<>
52+
{showResetAdvancedSettingsModal ? (
53+
<ResetAdvancedSettingsModal
54+
kind="dispense"
55+
liquidClass={state.liquidClass}
56+
onClose={handleClose}
57+
/>
58+
) : null}
59+
<Flex
60+
gridGap={SPACING.spacing40}
61+
flexDirection={DIRECTION_COLUMN}
62+
paddingTop={PADDING_TOP_FOR_NAV}
63+
>
64+
<Flex gridGap={SPACING.spacing8} flexDirection={DIRECTION_COLUMN}>
65+
{selectedSetting == null
66+
? dispenseSettingsItems.map(displayItem => (
67+
<DispenseSettingItem
68+
key={displayItem.option}
69+
displayItem={displayItem}
70+
/>
71+
))
72+
: null}
73+
<DispenseSettingDetail
74+
selectedSetting={selectedSetting}
75+
state={state}
76+
dispatch={dispatch}
77+
onBack={() => {
78+
setSelectedSetting(null)
79+
}}
80+
/>
81+
</Flex>
82+
<MediumButton
83+
buttonText={t('reset_settings', { transferName: 'dispense' })}
84+
onClick={handleResetSettings}
6085
/>
6186
</Flex>
62-
{/* ToDo add reset button for aspirate settings */}
63-
<MediumButton
64-
buttonText={t('reset_settings', { transferName: 'dispense' })}
65-
onClick={() => {
66-
console.log('reset dispense settings')
67-
}}
68-
/>
69-
</Flex>
87+
</>
7088
)
7189
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { createPortal } from 'react-dom'
2+
import { useTranslation } from 'react-i18next'
3+
4+
import {
5+
COLORS,
6+
DIRECTION_COLUMN,
7+
Flex,
8+
SPACING,
9+
StyledText,
10+
} from '@opentrons/components'
11+
12+
import { getTopPortalEl } from '/app/App/portal'
13+
import { SmallButton } from '/app/atoms/buttons'
14+
import { OddModal } from '/app/molecules/OddModal'
15+
16+
import type { LiquidClass } from '@opentrons/shared-data'
17+
import type { FlowRateKind } from '../types'
18+
19+
interface ResetAdvancedSettingsModalProps {
20+
kind: Omit<FlowRateKind, 'blowout'>
21+
liquidClass: LiquidClass
22+
onClose: () => void
23+
}
24+
25+
export function ResetAdvancedSettingsModal({
26+
kind,
27+
liquidClass,
28+
onClose,
29+
}: ResetAdvancedSettingsModalProps): JSX.Element {
30+
const { i18n, t } = useTranslation(['quick_transfer', 'shared'])
31+
const { displayName, liquidClassName } = liquidClass
32+
const modalHeader = {
33+
title: t('reset_kind_settings', { transferName: kind }),
34+
iconName: 'ot-alert',
35+
iconColor: COLORS.yellow50,
36+
}
37+
const modalProps = {
38+
header: { ...modalHeader },
39+
}
40+
41+
const handleClickContinue = (): void => {
42+
console.log('todo add hooks to reset settings for', kind)
43+
}
44+
45+
return createPortal(
46+
<OddModal {...modalProps}>
47+
<Flex flexDirection={DIRECTION_COLUMN} gap={SPACING.spacing32}>
48+
<StyledText oddStyle="bodyTextRegular">
49+
{liquidClassName !== 'none'
50+
? t('reset_settings_with_liquid_class_description', {
51+
transferName: kind,
52+
liquidClassName: displayName,
53+
})
54+
: t('reset_settings_description', {
55+
transferName: kind,
56+
})}
57+
</StyledText>
58+
<Flex gap={SPACING.spacing8}>
59+
<SmallButton
60+
flex="1"
61+
buttonType="secondary"
62+
buttonText={i18n.format(t('shared:cancel'), 'capitalize')}
63+
onClick={onClose}
64+
/>
65+
<SmallButton
66+
flex="1"
67+
buttonText={i18n.format(t('shared:continue'), 'capitalize')}
68+
onClick={handleClickContinue}
69+
/>
70+
</Flex>
71+
</Flex>
72+
</OddModal>,
73+
getTopPortalEl()
74+
)
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { fireEvent, screen } from '@testing-library/react'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
4+
import { renderWithProviders } from '/app/__testing-utils__'
5+
import { i18n } from '/app/i18n'
6+
7+
import { ResetAdvancedSettingsModal } from '../ResetAdvancedSettingsModal'
8+
9+
import type { ComponentProps } from 'react'
10+
import type { LiquidClass } from '@opentrons/shared-data'
11+
12+
const render = (props: ComponentProps<typeof ResetAdvancedSettingsModal>) => {
13+
return renderWithProviders(<ResetAdvancedSettingsModal {...props} />, {
14+
i18nInstance: i18n,
15+
})
16+
}
17+
18+
describe('ResetAdvancedSettingsModal', () => {
19+
let props: ComponentProps<typeof ResetAdvancedSettingsModal>
20+
21+
beforeEach(() => {
22+
props = {
23+
kind: 'aspirate',
24+
liquidClass: {
25+
displayName: 'Water',
26+
liquidClassName: 'water',
27+
} as LiquidClass,
28+
onClose: vi.fn(),
29+
}
30+
})
31+
32+
it('renders the modal with correct title and description with liquid class - aspirate', () => {
33+
render(props)
34+
screen.getByText('Reset aspirate settings?')
35+
screen.getByText(
36+
'Continuing will undo any changes and restore the aspirate settings to the values associated with the Water liquid class.'
37+
)
38+
screen.getByText('Cancel')
39+
screen.getByText('Continue')
40+
})
41+
42+
it('renders the modal with correct title and description - aspirate', () => {
43+
props.liquidClass = {
44+
displayName: '',
45+
liquidClassName: 'none',
46+
} as LiquidClass
47+
render(props)
48+
screen.getByText('Reset aspirate settings?')
49+
screen.getByText(
50+
'Continuing will undo any changes and restore the aspirate settings back to the default values.'
51+
)
52+
screen.getByText('Cancel')
53+
screen.getByText('Continue')
54+
})
55+
56+
it('renders the modal with correct title and description with liquid class - dispense', () => {
57+
props.kind = 'dispense'
58+
render(props)
59+
screen.getByText('Reset dispense settings?')
60+
screen.getByText(
61+
'Continuing will undo any changes and restore the dispense settings to the values associated with the Water liquid class.'
62+
)
63+
screen.getByText('Cancel')
64+
screen.getByText('Continue')
65+
})
66+
67+
it('renders the modal with correct title and description - dispense', () => {
68+
props.kind = 'dispense'
69+
props.liquidClass = {
70+
displayName: '',
71+
liquidClassName: 'none',
72+
} as LiquidClass
73+
render(props)
74+
screen.getByText('Reset dispense settings?')
75+
screen.getByText(
76+
'Continuing will undo any changes and restore the dispense settings back to the default values.'
77+
)
78+
screen.getByText('Cancel')
79+
screen.getByText('Continue')
80+
})
81+
82+
it('calls onClose when the close button is clicked', () => {
83+
render(props)
84+
85+
fireEvent.click(screen.getByText('Cancel'))
86+
expect(props.onClose).toHaveBeenCalled()
87+
})
88+
// Todo add continue button test
89+
})

0 commit comments

Comments
 (0)