Skip to content

Commit 0644c37

Browse files
authored
fix(protocol-designer): always enable save in EditInstrumentsModal (#18928)
This PR updates the EditInstrumentsModal component to ensure the "Save" button is always enabled. The behavior of save should be determined by the conditions previously used to set the "disabled" prop on the button. Now, clicking the button when saving if not valid will result in the error message "Add at least one pipette to save" displaying. Closes AUTH-1321
1 parent 8c8323d commit 0644c37

File tree

4 files changed

+52
-28
lines changed

4 files changed

+52
-28
lines changed

protocol-designer/src/assets/localization/en/onboarding.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"add_custom_tips": "Add custom pipette tips",
33
"add_fixtures": "Add your fixtures",
4-
"add_gripper_for_absorbance_reader": "Add a gripper to enable adding Absorbance Plate Reader",
54
"add_gripper": "Add a gripper",
5+
"add_gripper_for_absorbance_reader": "Add a gripper to enable adding Absorbance Plate Reader",
66
"add_modules": "Add your modules",
7-
"add_pipette_to_continue": "Add a pipette to continue",
87
"add_pipette": "Add a pipette",
8+
"add_pipette_to_continue": "Add a pipette to continue",
99
"add_your_pipettes": "Add your pipettes",
1010
"are_you_sure_clear_slot": "Are you sure you want to clear slot?",
1111
"are_you_sure_reconfigure_slot": "Are you sure you want to reconfigure the slot?",
@@ -27,11 +27,12 @@
2727
"modules_added": "Modules added",
2828
"name": "Name",
2929
"need_gripper": "Do you want to move labware automatically with the gripper?",
30+
"pipette": "{{mount}} Mount",
3031
"pipette_gen": "Pipette generation",
32+
"pipette_required": "Add at least one pipette to save",
3133
"pipette_tips": "Pipette tips",
3234
"pipette_type": "Pipette type",
3335
"pipette_vol": "Pipette volume",
34-
"pipette": "{{mount}} Mount",
3536
"place_hardware": "Place the modules and fixtures that you are using for this protocol onto the deck.",
3637
"quantity": "Quantity",
3738
"remove": "Remove",
@@ -57,7 +58,7 @@
5758
"wasteChute": "Waste Chute",
5859
"which_fixtures": "Which fixtures will you be using?",
5960
"which_modules": "Select modules to use in your protocol.",
60-
"which_pipette_second": "Now add your second pipette",
6161
"which_pipette": "Pick your first pipette. If you need a second pipette, you can add it next.",
62+
"which_pipette_second": "Now add your second pipette",
6263
"your_pipettes": "Your pipettes"
6364
}

protocol-designer/src/components/organisms/EditInstrumentsModal/PipetteOverview.tsx

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { LINK_BUTTON_STYLE } from '../../atoms'
3030
import { PipetteInfoItem } from '../PipetteInfoItem'
3131
import { getSectionsFromPipetteName } from './utils'
3232

33+
import type { Dispatch, SetStateAction } from 'react'
3334
import type { RobotType } from '@opentrons/shared-data'
3435
import type { AdditionalEquipmentName } from '@opentrons/step-generation'
3536
import type {
@@ -51,6 +52,8 @@ interface PipetteOverviewProps {
5152
labware: AllTemporalPropertiesForTimelineFrame['labware']
5253
robotType: RobotType
5354
pipetteConfig: PipetteConfig
55+
showNoPipetteError: boolean
56+
setSaveAttemptFailed: Dispatch<SetStateAction<boolean>>
5457
leftPipette?: PipetteOnDeck
5558
rightPipette?: PipetteOnDeck
5659
gripper?: Gripper
@@ -62,6 +65,8 @@ export function PipetteOverview({
6265
labware,
6366
robotType,
6467
pipetteConfig,
68+
showNoPipetteError,
69+
setSaveAttemptFailed,
6570
leftPipette,
6671
rightPipette,
6772
gripper,
@@ -107,6 +112,12 @@ export function PipetteOverview({
107112
setSelectedTips,
108113
} = pipetteConfig
109114

115+
const handleAddPipette = (): void => {
116+
setPage('add')
117+
setMount(targetPipetteMount)
118+
setSaveAttemptFailed(false)
119+
}
120+
110121
return (
111122
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing24}>
112123
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing8}>
@@ -187,16 +198,23 @@ export function PipetteOverview({
187198
) : null}
188199
{has96Channel ||
189200
(leftPipette != null && rightPipette != null) ? null : (
190-
<Flex width={FLEX_MAX_CONTENT}>
191-
<EmptySelectorButton
192-
onClick={() => {
193-
setPage('add')
194-
setMount(targetPipetteMount)
195-
}}
196-
text={t('add_pipette')}
197-
textAlignment="left"
198-
iconName="plus"
199-
/>
201+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
202+
<Flex width={FLEX_MAX_CONTENT}>
203+
<EmptySelectorButton
204+
onClick={handleAddPipette}
205+
text={t('add_pipette')}
206+
textAlignment="left"
207+
iconName="plus"
208+
/>
209+
</Flex>
210+
{showNoPipetteError ? (
211+
<StyledText
212+
desktopStyle="bodyDefaultRegular"
213+
color={COLORS.red50}
214+
>
215+
{t('pipette_required')}
216+
</StyledText>
217+
) : null}
200218
</Flex>
201219
)}
202220
</Flex>

protocol-designer/src/components/organisms/EditInstrumentsModal/__tests__/EditInstrumentsModal.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ describe('EditInstrumentsModal', () => {
122122
resetFields: vi.fn(),
123123
})
124124
render(props)
125-
expect(screen.getByText('Save')).toBeDisabled()
125+
fireEvent.click(screen.getByText('Save'))
126+
expect(mockOnClose).not.toHaveBeenCalled()
126127
})
127128

128129
it('should render text and buttons - pipette overview', () => {

protocol-designer/src/components/organisms/EditInstrumentsModal/index.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react'
12
import { createPortal } from 'react-dom'
23
import { useTranslation } from 'react-i18next'
34
import { useDispatch, useSelector } from 'react-redux'
@@ -45,6 +46,7 @@ export function EditInstrumentsModal(
4546
const initialDeckSetup = useSelector(getInitialDeckSetup)
4647
const additionalEquipment = useSelector(getAdditionalEquipment)
4748
const pipetteEntities = useSelector(getPipetteEntities)
49+
const [saveAttemptFailed, setSaveAttemptFailed] = useState<boolean>(false)
4850
const { pipettes, labware } = initialDeckSetup
4951
const pipettesOnDeck = Object.values(pipettes)
5052
const has96Channel = getHas96Channel(pipetteEntities)
@@ -69,7 +71,19 @@ export function EditInstrumentsModal(
6971
? `${pipetteVolume}_${pipetteType}`
7072
: `${pipetteVolume}_${pipetteType}_${pipetteGen.toLowerCase()}`
7173

74+
const canSave =
75+
(page === 'add' &&
76+
pipetteVolume != null &&
77+
pipetteType != null &&
78+
pipetteGen != null &&
79+
selectedTips.length > 0) ||
80+
(page === 'overview' && pipettesOnDeck.length > 0)
7281
const handleOnSave = (): void => {
82+
if (!canSave) {
83+
setSaveAttemptFailed(true)
84+
return
85+
}
86+
7387
if (page === 'overview') {
7488
onClose()
7589
} else {
@@ -118,19 +132,7 @@ export function EditInstrumentsModal(
118132
>
119133
{page === 'overview' ? t('cancel') : t('back')}
120134
</SecondaryButton>
121-
<PrimaryButton
122-
disabled={
123-
(page === 'add' &&
124-
(pipetteVolume == null ||
125-
pipetteType == null ||
126-
pipetteGen == null ||
127-
selectedTips.length === 0)) ||
128-
(page === 'overview' && pipettesOnDeck.length === 0)
129-
}
130-
onClick={handleOnSave}
131-
>
132-
{t('save')}
133-
</PrimaryButton>
135+
<PrimaryButton onClick={handleOnSave}>{t('save')}</PrimaryButton>
134136
</Flex>
135137
}
136138
>
@@ -144,6 +146,8 @@ export function EditInstrumentsModal(
144146
rightPipette={rightPipette}
145147
gripper={gripper}
146148
pipetteConfig={pipetteConfig}
149+
showNoPipetteError={saveAttemptFailed && !canSave}
150+
setSaveAttemptFailed={setSaveAttemptFailed}
147151
/>
148152
) : (
149153
<PipetteConfiguration

0 commit comments

Comments
 (0)