diff --git a/app/src/assets/localization/en/module_wizard_flows.json b/app/src/assets/localization/en/module_wizard_flows.json index a502b0ef797..940367641c2 100644 --- a/app/src/assets/localization/en/module_wizard_flows.json +++ b/app/src/assets/localization/en/module_wizard_flows.json @@ -26,6 +26,7 @@ "location_occupied": "A {{fixture}} is currently specified here on the deck configuration", "module_calibrating": "Stand back, {{moduleName}} is calibrating", "module_calibration": "Module calibration", + "module_heating_or_cooling": "Module calibration cannot proceed while heating or cooling", "module_secured": "The module must be fully secured in its caddy and secured in the deck slot.", "module_too_hot": "Module is too hot to proceed to module calibration", "move_gantry_to_front": "Move gantry to front", diff --git a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx index d7ece8d4c0a..28744a218cf 100644 --- a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx @@ -3,7 +3,12 @@ import { useTranslation } from 'react-i18next' import { Flex, POSITION_RELATIVE, useHoverTooltip } from '@opentrons/components' -import { MODULE_MODELS_OT2_ONLY } from '@opentrons/shared-data' +import { + HEATERSHAKER_MODULE_TYPE, + MODULE_MODELS_OT2_ONLY, + TEMPERATURE_MODULE_TYPE, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' import { MenuList } from '../../atoms/MenuList' import { Tooltip } from '../../atoms/Tooltip' import { MenuItem } from '../../atoms/MenuList/MenuItem' @@ -69,6 +74,23 @@ export const ModuleOverflowMenu = ( isDisabled = true } + let isHeatingOrCooling + switch (module.moduleType) { + case TEMPERATURE_MODULE_TYPE: + isHeatingOrCooling = module.data.status !== 'idle' + break + case HEATERSHAKER_MODULE_TYPE: + isHeatingOrCooling = module.data.temperatureStatus !== 'idle' + break + case THERMOCYCLER_MODULE_TYPE: + isHeatingOrCooling = + module.data.lidTemperatureStatus !== 'idle' || + module.data.status !== 'idle' + break + default: + isHeatingOrCooling = false + } + const { menuOverflowItemsByModuleType } = useModuleOverflowMenu( module, handleAboutClick, @@ -79,6 +101,18 @@ export const ModuleOverflowMenu = ( isIncompatibleWithOT3 ) + const isCalibrateDisabled = !isPipetteReady || isTooHot || isHeatingOrCooling + let calibrateDisabledReason + if (!isPipetteReady) { + calibrateDisabledReason = t('calibrate_pipette') + } else if (isTooHot) { + calibrateDisabledReason = t('module_too_hot') + } else if (isHeatingOrCooling) { + calibrateDisabledReason = t('module_heating_or_cooling') + } else { + calibrateDisabledReason = null + } + return ( @@ -89,7 +123,7 @@ export const ModuleOverflowMenu = ( <> {i18n.format( @@ -99,9 +133,9 @@ export const ModuleOverflowMenu = ( 'capitalize' )} - {!isPipetteReady || isTooHot ? ( + {isCalibrateDisabled ? ( - {t(!isPipetteReady ? 'calibrate_pipette' : 'module_too_hot')} + {calibrateDisabledReason} ) : null} diff --git a/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx b/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx index f749237e6e0..57c51f4d2f9 100644 --- a/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx @@ -19,6 +19,8 @@ import { import { useCurrentRunId } from '../../ProtocolUpload/hooks' import { ModuleOverflowMenu } from '../ModuleOverflowMenu' +import type { TemperatureStatus } from '@opentrons/api-client' + vi.mock('../../Devices/hooks') vi.mock('../../RunTimeControl/hooks') vi.mock('../../ProtocolUpload/hooks') @@ -535,6 +537,44 @@ describe('ModuleOverflowMenu', () => { expect(calibrate).toBeDisabled() }) + it('renders a disabled calibrate button if module is heating or cooling', () => { + vi.mocked(useIsFlex).mockReturnValue(true) + const mockHeatingModule = { + ...mockHeaterShaker, + data: { + ...mockHeaterShaker.data, + temperatureStatus: 'heating' as TemperatureStatus, + }, + } + props = { + ...props, + module: mockHeatingModule, + } + render(props) + + const calibrate = screen.getByRole('button', { name: 'Calibrate' }) + expect(calibrate).toBeDisabled() + }) + + it('renders a disabled calibrate button if module temperature status errors', () => { + vi.mocked(useIsFlex).mockReturnValue(true) + const mockHeatingModule = { + ...mockHeaterShaker, + data: { + ...mockHeaterShaker.data, + temperatureStatus: 'error' as TemperatureStatus, + }, + } + props = { + ...props, + module: mockHeatingModule, + } + render(props) + + const calibrate = screen.getByRole('button', { name: 'Calibrate' }) + expect(calibrate).toBeDisabled() + }) + it('a mock function should be called when clicking Calibrate if pipette is ready', () => { vi.mocked(useIsFlex).mockReturnValue(true) props = {