diff --git a/app/src/assets/localization/en/shared.json b/app/src/assets/localization/en/shared.json index 5613508b242..899adfc5a31 100644 --- a/app/src/assets/localization/en/shared.json +++ b/app/src/assets/localization/en/shared.json @@ -63,6 +63,7 @@ "robot_is_busy": "Robot is busy", "robot_is_reachable_but_not_responding": "This robot's API server is not responding correctly to requests at IP address {{hostname}}", "robot_was_seen_but_is_unreachable": "This robot has been seen recently, but is currently not reachable at IP address {{hostname}}", + "save": "save", "something_went_wrong": "something went wrong", "sort_by": "Sort by", "stand_back_robot_is_in_motion": "Stand back, robot is in motion", diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx index 034a18c1e77..de7c062f9d1 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx @@ -26,6 +26,6 @@ const Template: Story> = args => ( export const Default = Template.bind({}) Default.args = { fixtureLocation: 'cutoutD3', - setShowAddFixtureModal: () => {}, + closeModal: () => {}, isOnDevice: true, } diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx index 47ec3077d61..c7452bd4ebc 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx @@ -54,15 +54,13 @@ import type { CutoutConfig, CutoutId, CutoutFixtureId, - DeckConfiguration, } from '@opentrons/shared-data' import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' import type { LegacyModalProps } from '../../molecules/LegacyModal' interface AddFixtureModalProps { cutoutId: CutoutId - setShowAddFixtureModal: (showAddFixtureModal: boolean) => void - setCurrentDeckConfig?: React.Dispatch> + closeModal: () => void providedFixtureOptions?: CutoutFixtureId[] isOnDevice?: boolean } @@ -75,8 +73,7 @@ type OptionStage = export function AddFixtureModal({ cutoutId, - setShowAddFixtureModal, - setCurrentDeckConfig, + closeModal, providedFixtureOptions, isOnDevice = false, }: AddFixtureModalProps): JSX.Element { @@ -109,18 +106,14 @@ export function AddFixtureModal({ slotName: getCutoutDisplayName(cutoutId), }), hasExitIcon: providedFixtureOptions == null, - onClick: () => { - setShowAddFixtureModal(false) - }, + onClick: closeModal, } const modalProps: LegacyModalProps = { title: t('add_to_slot', { slotName: getCutoutDisplayName(cutoutId), }), - onClose: () => { - setShowAddFixtureModal(false) - }, + onClose: closeModal, closeOnOutsideClick: true, childrenPadding: SPACING.spacing24, width: '26.75rem', @@ -289,22 +282,7 @@ export function AddFixtureModal({ ) } - const handleAddODD = (addedCutoutConfigs: CutoutConfig[]): void => { - if (setCurrentDeckConfig != null) - setCurrentDeckConfig( - (prevDeckConfig: DeckConfiguration): DeckConfiguration => - prevDeckConfig.map((fixture: CutoutConfig) => { - const replacementCutoutConfig = addedCutoutConfigs.find( - c => c.cutoutId === fixture.cutoutId - ) - return replacementCutoutConfig ?? fixture - }) - ) - - setShowAddFixtureModal(false) - } - - const handleAddDesktop = (addedCutoutConfigs: CutoutConfig[]): void => { + const handleAddFixture = (addedCutoutConfigs: CutoutConfig[]): void => { const newDeckConfig = deckConfig.map(fixture => { const replacementCutoutConfig = addedCutoutConfigs.find( c => c.cutoutId === fixture.cutoutId @@ -313,7 +291,7 @@ export function AddFixtureModal({ }) updateDeckConfiguration(newDeckConfig) - setShowAddFixtureModal(false) + closeModal() } const fixtureOptions = availableOptions.map(cutoutConfigs => ( @@ -327,9 +305,7 @@ export function AddFixtureModal({ )} buttonText={t('add')} onClickHandler={() => { - isOnDevice - ? handleAddODD(cutoutConfigs) - : handleAddDesktop(cutoutConfigs) + handleAddFixture(cutoutConfigs) }} isOnDevice={isOnDevice} /> @@ -341,9 +317,7 @@ export function AddFixtureModal({ - providedFixtureOptions != null - ? null - : setShowAddFixtureModal(false) + providedFixtureOptions != null ? null : closeModal() } > diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx index 38cc283f8e9..17ae8511513 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx @@ -23,9 +23,8 @@ import type { Modules } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('../../../resources/deck_configuration') -const mockSetShowAddFixtureModal = vi.fn() +const mockCloseModal = vi.fn() const mockUpdateDeckConfiguration = vi.fn() -const mockSetCurrentDeckConfig = vi.fn() const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -39,8 +38,7 @@ describe('Touchscreen AddFixtureModal', () => { beforeEach(() => { props = { cutoutId: 'cutoutD3', - setShowAddFixtureModal: mockSetShowAddFixtureModal, - setCurrentDeckConfig: mockSetCurrentDeckConfig, + closeModal: mockCloseModal, isOnDevice: true, } vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ @@ -69,7 +67,6 @@ describe('Touchscreen AddFixtureModal', () => { render(props) fireEvent.click(screen.getAllByText('Select options')[1]) fireEvent.click(screen.getAllByText('Add')[0]) - expect(mockSetCurrentDeckConfig).toHaveBeenCalled() }) it('when fixture options are provided, should only render those options', () => { @@ -96,7 +93,7 @@ describe('Desktop AddFixtureModal', () => { beforeEach(() => { props = { cutoutId: 'cutoutD3', - setShowAddFixtureModal: mockSetShowAddFixtureModal, + closeModal: mockCloseModal, } vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ updateDeckConfiguration: mockUpdateDeckConfiguration, diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx index e6d048bcf52..27b6bbad2ba 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx @@ -3,6 +3,10 @@ import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach } from 'vitest' +import { + DeckConfiguration, + TRASH_BIN_ADAPTER_FIXTURE, +} from '@opentrons/shared-data' import { DeckConfigurator } from '@opentrons/components' import { useModulesQuery, @@ -16,8 +20,12 @@ import { DeckFixtureSetupInstructionsModal } from '../DeckFixtureSetupInstructio import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' import { DeviceDetailsDeckConfiguration } from '../' import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { + useDeckConfigurationEditingTools, + useNotifyDeckConfigurationQuery, +} from '../../../resources/deck_configuration' +import type { UseQueryResult } from 'react-query' import type { MaintenanceRun } from '@opentrons/api-client' import type * as OpentronsComponents from '@opentrons/components' @@ -35,6 +43,12 @@ vi.mock('../../../resources/maintenance_runs') vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') vi.mock('../../../resources/deck_configuration') +const mockDeckConfig = [ + { + cutoutId: 'cutoutC3', + cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE, + }, +] const ROBOT_NAME = 'otie' const mockUpdateDeckConfiguration = vi.fn() const RUN_STATUSES = { @@ -63,9 +77,6 @@ describe('DeviceDetailsDeckConfiguration', () => { robotName: ROBOT_NAME, } vi.mocked(useModulesQuery).mockReturnValue({ data: { data: [] } } as any) - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ - data: [], - } as any) vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ updateDeckConfiguration: mockUpdateDeckConfiguration, } as any) @@ -83,6 +94,14 @@ describe('DeviceDetailsDeckConfiguration', () => { .calledWith(ROBOT_NAME) .thenReturn(false) when(vi.mocked(useIsRobotViewable)).calledWith(ROBOT_NAME).thenReturn(true) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: mockDeckConfig, + } as UseQueryResult) + vi.mocked(useDeckConfigurationEditingTools).mockReturnValue({ + addFixtureToCutout: vi.fn(), + removeFixtureFromCutout: vi.fn(), + addFixtureModal: null, + }) }) afterEach(() => { @@ -132,7 +151,9 @@ describe('DeviceDetailsDeckConfiguration', () => { }) it('should render no deck fixtures, if deck configs are not set', () => { - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue([] as any) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [], + } as any) render(props) screen.getByText('No deck fixtures') }) diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx index b6f62c8c08a..d28df33bf69 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx @@ -19,19 +19,11 @@ import { StyledText, TYPOGRAPHY, } from '@opentrons/components' -import { - useModulesQuery, - useUpdateDeckConfigurationMutation, -} from '@opentrons/react-api-client' +import { useModulesQuery } from '@opentrons/react-api-client' import { getCutoutDisplayName, getFixtureDisplayName, - SINGLE_RIGHT_CUTOUTS, SINGLE_SLOT_FIXTURES, - SINGLE_LEFT_SLOT_FIXTURE, - SINGLE_RIGHT_SLOT_FIXTURE, - SINGLE_CENTER_SLOT_FIXTURE, - SINGLE_LEFT_CUTOUTS, getDeckDefFromRobotType, FLEX_ROBOT_TYPE, } from '@opentrons/shared-data' @@ -39,12 +31,14 @@ import { import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' import { Banner } from '../../atoms/Banner' import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal' -import { AddFixtureModal } from './AddFixtureModal' import { useIsRobotViewable, useRunStatuses } from '../Devices/hooks' import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { + useDeckConfigurationEditingTools, + useNotifyDeckConfigurationQuery, +} from '../../resources/deck_configuration' -import type { CutoutFixtureId, CutoutId } from '@opentrons/shared-data' +import type { CutoutId } from '@opentrons/shared-data' const DECK_CONFIG_REFETCH_INTERVAL = 5000 const RUN_REFETCH_INTERVAL = 5000 @@ -65,12 +59,6 @@ export function DeviceDetailsDeckConfiguration({ showSetupInstructionsModal, setShowSetupInstructionsModal, ] = React.useState(false) - const [showAddFixtureModal, setShowAddFixtureModal] = React.useState( - false - ) - const [targetCutoutId, setTargetCutoutId] = React.useState( - null - ) const { data: modulesData } = useModulesQuery() const deckConfig = @@ -78,7 +66,6 @@ export function DeviceDetailsDeckConfiguration({ refetchInterval: DECK_CONFIG_REFETCH_INTERVAL, }).data ?? [] const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) - const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() const { isRunRunning } = useRunStatuses() const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ refetchInterval: RUN_REFETCH_INTERVAL, @@ -87,59 +74,11 @@ export function DeviceDetailsDeckConfiguration({ const isMaintenanceRunExisting = maintenanceRunData?.data?.id != null const isRobotViewable = useIsRobotViewable(robotName) - const handleClickAdd = (cutoutId: CutoutId): void => { - setTargetCutoutId(cutoutId) - setShowAddFixtureModal(true) - } - - const handleClickRemove = ( - cutoutId: CutoutId, - cutoutFixtureId: CutoutFixtureId - ): void => { - let replacementFixtureId: CutoutFixtureId = SINGLE_CENTER_SLOT_FIXTURE - if (SINGLE_RIGHT_CUTOUTS.includes(cutoutId)) { - replacementFixtureId = SINGLE_RIGHT_SLOT_FIXTURE - } else if (SINGLE_LEFT_CUTOUTS.includes(cutoutId)) { - replacementFixtureId = SINGLE_LEFT_SLOT_FIXTURE - } - - const fixtureGroup = - deckDef.cutoutFixtures.find(cf => cf.id === cutoutFixtureId) - ?.fixtureGroup ?? {} - - let newDeckConfig = deckConfig - if (cutoutId in fixtureGroup) { - const groupMap = - fixtureGroup[cutoutId]?.find(group => - Object.entries(group).every(([cId, cfId]) => - deckConfig.find( - config => - config.cutoutId === cId && config.cutoutFixtureId === cfId - ) - ) - ) ?? {} - newDeckConfig = deckConfig.map(cutoutConfig => - cutoutConfig.cutoutId in groupMap - ? { - ...cutoutConfig, - cutoutFixtureId: replacementFixtureId, - opentronsModuleSerialNumber: undefined, - } - : cutoutConfig - ) - } else { - newDeckConfig = deckConfig.map(cutoutConfig => - cutoutConfig.cutoutId === cutoutId - ? { - ...cutoutConfig, - cutoutFixtureId: replacementFixtureId, - opentronsModuleSerialNumber: undefined, - } - : cutoutConfig - ) - } - updateDeckConfiguration(newDeckConfig) - } + const { + addFixtureToCutout, + removeFixtureFromCutout, + addFixtureModal, + } = useDeckConfigurationEditingTools(false) // do not show standard slot in fixture display list const { displayList: fixtureDisplayList } = deckConfig.reduce<{ @@ -199,12 +138,7 @@ export function DeviceDetailsDeckConfiguration({ return ( <> - {showAddFixtureModal && targetCutoutId != null ? ( - - ) : null} + {addFixtureModal} {showSetupInstructionsModal ? ( cutoutId) } deckConfig={deckConfig} - handleClickAdd={handleClickAdd} - handleClickRemove={handleClickRemove} + handleClickAdd={addFixtureToCutout} + handleClickRemove={removeFixtureFromCutout} /> { when(vi.mocked(useMostRecentCompletedAnalysis)) .calledWith('mockRunId') .thenReturn(PROTOCOL_DETAILS.protocolData) - vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ - updateDeckConfiguration: mockUpdateDeckConfiguration, - } as any) vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({ data: [], } as unknown) as UseQueryResult) + vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ + updateDeckConfiguration: vi.fn(), + } as any) vi.mocked(useModulesQuery).mockReturnValue(({ data: { data: [] }, } as unknown) as UseQueryResult) @@ -88,18 +87,11 @@ describe('ProtocolSetupDeckConfiguration', () => { render(props) screen.getByText('Deck configuration') screen.getByText('mock BaseDeck') - screen.getByText('Confirm') - }) - - it('should call a mock function when tapping the back button', () => { - render(props) - fireEvent.click(screen.getByTestId('ChildNavigation_Back_Button')) - expect(mockSetSetupScreen).toHaveBeenCalledWith('modules') + screen.getByText('Save') }) it('should call a mock function when tapping confirm button', () => { render(props) - fireEvent.click(screen.getByText('Confirm')) - expect(mockUpdateDeckConfiguration).toHaveBeenCalled() + fireEvent.click(screen.getByText('Save')) }) }) diff --git a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx b/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx index c3c65e6318f..7f6f78bc343 100644 --- a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx +++ b/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx @@ -15,9 +15,9 @@ import { MAGNETIC_BLOCK_V1_FIXTURE, MODULE_FIXTURES_BY_MODEL, STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, getSimplestDeckConfigForProtocol, } from '@opentrons/shared-data' -import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' import { ChildNavigation } from '../ChildNavigation' import { AddFixtureModal } from '../DeviceDetailsDeckConfiguration/AddFixtureModal' @@ -29,7 +29,6 @@ import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configurat import type { CutoutFixtureId, CutoutId, - DeckConfiguration, ModuleModel, } from '@opentrons/shared-data' import type { ModuleOnDeck } from '@opentrons/components' @@ -48,7 +47,11 @@ export function ProtocolSetupDeckConfiguration({ setSetupScreen, providedFixtureOptions, }: ProtocolSetupDeckConfigurationProps): JSX.Element { - const { t } = useTranslation(['protocol_setup', 'devices_landing', 'shared']) + const { i18n, t } = useTranslation([ + 'protocol_setup', + 'devices_landing', + 'shared', + ]) const [ showConfigurationModal, @@ -66,28 +69,28 @@ export function ProtocolSetupDeckConfiguration({ mostRecentAnalysis ).map(({ cutoutId, cutoutFixtureId }) => ({ cutoutId, cutoutFixtureId })) - const targetDeckConfig = simplestDeckConfig.find( + const targetCutoutConfig = simplestDeckConfig.find( deck => deck.cutoutId === cutoutId ) const mergedDeckConfig = deckConfig.map(config => - targetDeckConfig != null && config.cutoutId === targetDeckConfig.cutoutId - ? targetDeckConfig + targetCutoutConfig != null && + config.cutoutId === targetCutoutConfig.cutoutId + ? targetCutoutConfig : config ) - const [ - currentDeckConfig, - setCurrentDeckConfig, - ] = React.useState(mergedDeckConfig) - const modulesOnDeck = currentDeckConfig.reduce( + const modulesOnDeck = mergedDeckConfig.reduce( (acc, cutoutConfig) => { const matchingFixtureIdsAndModel = Object.entries( MODULE_FIXTURES_BY_MODEL ).find(([_moduleModel, moduleFixtureIds]) => moduleFixtureIds.includes(cutoutConfig.cutoutFixtureId) ) - if (matchingFixtureIdsAndModel != null) { + if ( + matchingFixtureIdsAndModel != null && + cutoutConfig.cutoutFixtureId !== THERMOCYCLER_V2_REAR_FIXTURE + ) { const [matchingModel] = matchingFixtureIdsAndModel return [ ...acc, @@ -117,9 +120,7 @@ export function ProtocolSetupDeckConfiguration({ [] ) - const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() const handleClickConfirm = (): void => { - updateDeckConfiguration(currentDeckConfig) setSetupScreen('modules') } @@ -135,9 +136,8 @@ export function ProtocolSetupDeckConfiguration({ {showConfigurationModal && cutoutId != null ? ( setShowConfigurationModal(false)} providedFixtureOptions={providedFixtureOptions} - setCurrentDeckConfig={setCurrentDeckConfig} isOnDevice /> ) : null} @@ -147,8 +147,7 @@ export function ProtocolSetupDeckConfiguration({ setSetupScreen('modules')} - buttonText={t('shared:confirm')} + buttonText={i18n.format(t('shared:save'), 'capitalize')} onClickButton={handleClickConfirm} /> diff --git a/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx b/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx index 9efe3bc0aa2..d0fab1277b0 100644 --- a/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx +++ b/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx @@ -12,7 +12,10 @@ import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data' import { i18n } from '../../../i18n' import { DeckFixtureSetupInstructionsModal } from '../../../organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' import { DeckConfigurationEditor } from '..' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { + useNotifyDeckConfigurationQuery, + useDeckConfigurationEditingTools, +} from '../../../resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { DeckConfiguration } from '@opentrons/shared-data' @@ -71,13 +74,18 @@ describe('DeckConfigurationEditor', () => { vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({ updateDeckConfiguration: mockUpdateDeckConfiguration, } as any) + vi.mocked(useDeckConfigurationEditingTools).mockReturnValue({ + addFixtureToCutout: vi.fn(), + removeFixtureFromCutout: vi.fn(), + addFixtureModal: null, + }) }) it('should render text, button and DeckConfigurator', () => { render() screen.getByText('Deck configuration') screen.getByText('Setup Instructions') - screen.getByText('Confirm') + screen.getByText('Save') expect(vi.mocked(DeckConfigurator)).toHaveBeenCalled() }) @@ -86,26 +94,4 @@ describe('DeckConfigurationEditor', () => { fireEvent.click(screen.getByText('Setup Instructions')) expect(vi.mocked(DeckFixtureSetupInstructionsModal)).toHaveBeenCalled() }) - - it('should call a mock function when tapping confirm', () => { - // (kk:10/26/2023) - // Once get approval, I will be able to update this case - // render() - // screen.getByText('Confirm').click() - // expect(mockUpdateDeckConfiguration).toHaveBeenCalled() - }) - - it('should call a mock function when tapping back button if there is no change', () => { - render() - fireEvent.click(screen.getByTestId('ChildNavigation_Back_Button')) - expect(mockGoBack).toHaveBeenCalled() - }) - - it('should render modal when tapping back button if there is a change', () => { - // (kk:10/26/2023) - // Once get approval, I will be able to update this case - // render() - // screen.getByTestId('ChildNavigation_Back_Button').click() - // expect(mockGoBack).toHaveBeenCalled() - }) }) diff --git a/app/src/pages/DeckConfiguration/index.tsx b/app/src/pages/DeckConfiguration/index.tsx index 2c90d5249b8..2ed8530bcbd 100644 --- a/app/src/pages/DeckConfiguration/index.tsx +++ b/app/src/pages/DeckConfiguration/index.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useHistory } from 'react-router-dom' -import isEqual from 'lodash/isEqual' import { DeckConfigurator, @@ -11,30 +10,16 @@ import { JUSTIFY_CENTER, JUSTIFY_SPACE_AROUND, } from '@opentrons/components' -import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' -import { - SINGLE_RIGHT_CUTOUTS, - SINGLE_LEFT_SLOT_FIXTURE, - SINGLE_RIGHT_SLOT_FIXTURE, - SINGLE_LEFT_CUTOUTS, - SINGLE_CENTER_SLOT_FIXTURE, - getDeckDefFromRobotType, - FLEX_ROBOT_TYPE, -} from '@opentrons/shared-data' import { SmallButton } from '../../atoms/buttons' import { ChildNavigation } from '../../organisms/ChildNavigation' -import { AddFixtureModal } from '../../organisms/DeviceDetailsDeckConfiguration/AddFixtureModal' import { DeckFixtureSetupInstructionsModal } from '../../organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' import { DeckConfigurationDiscardChangesModal } from '../../organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' import { getTopPortalEl } from '../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' - -import type { - CutoutFixtureId, - CutoutId, - DeckConfiguration, -} from '@opentrons/shared-data' +import { + useDeckConfigurationEditingTools, + useNotifyDeckConfigurationQuery, +} from '../../resources/deck_configuration' export function DeckConfigurationEditor(): JSX.Element { const { t, i18n } = useTranslation([ @@ -47,96 +32,25 @@ export function DeckConfigurationEditor(): JSX.Element { showSetupInstructionsModal, setShowSetupInstructionsModal, ] = React.useState(false) - const [ - showConfigurationModal, - setShowConfigurationModal, - ] = React.useState(false) - const [targetCutoutId, setTargetCutoutId] = React.useState( - null - ) + + const isOnDevice = true + const { + addFixtureToCutout, + removeFixtureFromCutout, + addFixtureModal, + } = useDeckConfigurationEditingTools(isOnDevice) + const [ showDiscardChangeModal, setShowDiscardChangeModal, ] = React.useState(false) - const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] - const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() - - const [ - currentDeckConfig, - setCurrentDeckConfig, - ] = React.useState(deckConfig) - - const handleClickAdd = (cutoutId: CutoutId): void => { - setTargetCutoutId(cutoutId) - setShowConfigurationModal(true) - } - - const handleClickRemove = ( - cutoutId: CutoutId, - cutoutFixtureId: CutoutFixtureId - ): void => { - let replacementFixtureId: CutoutFixtureId = SINGLE_CENTER_SLOT_FIXTURE - if (SINGLE_RIGHT_CUTOUTS.includes(cutoutId)) { - replacementFixtureId = SINGLE_RIGHT_SLOT_FIXTURE - } else if (SINGLE_LEFT_CUTOUTS.includes(cutoutId)) { - replacementFixtureId = SINGLE_LEFT_SLOT_FIXTURE - } - - const fixtureGroup = - deckDef.cutoutFixtures.find(cf => cf.id === cutoutFixtureId) - ?.fixtureGroup ?? {} - - let newDeckConfig = currentDeckConfig - if (cutoutId in fixtureGroup) { - const groupMap = - fixtureGroup[cutoutId]?.find(group => - Object.entries(group).every(([cId, cfId]) => - currentDeckConfig.find( - config => - config.cutoutId === cId && config.cutoutFixtureId === cfId - ) - ) - ) ?? {} - newDeckConfig = currentDeckConfig.map(cutoutConfig => - cutoutConfig.cutoutId in groupMap - ? { - ...cutoutConfig, - cutoutFixtureId: replacementFixtureId, - opentronsModuleSerialNumber: undefined, - } - : cutoutConfig - ) - } else { - newDeckConfig = currentDeckConfig.map(cutoutConfig => - cutoutConfig.cutoutId === cutoutId - ? { - ...cutoutConfig, - cutoutFixtureId: replacementFixtureId, - opentronsModuleSerialNumber: undefined, - } - : cutoutConfig - ) - } - setCurrentDeckConfig(newDeckConfig) - } const handleClickConfirm = (): void => { - if (!isEqual(deckConfig, currentDeckConfig)) { - updateDeckConfiguration(currentDeckConfig) - } history.goBack() } - const handleClickBack = (): void => { - if (!isEqual(deckConfig, currentDeckConfig)) { - setShowDiscardChangeModal(true) - } else { - history.goBack() - } - } - const secondaryButtonProps: React.ComponentProps = { onClick: () => setShowSetupInstructionsModal(true), buttonText: i18n.format(t('setup_instructions'), 'titleCase'), @@ -145,10 +59,6 @@ export function DeckConfigurationEditor(): JSX.Element { iconPlacement: 'startIcon', } - React.useEffect(() => { - setCurrentDeckConfig(deckConfig) - }, [deckConfig]) - return ( <> {createPortal( @@ -161,17 +71,10 @@ export function DeckConfigurationEditor(): JSX.Element { {showSetupInstructionsModal ? ( - ) : null} - {showConfigurationModal && targetCutoutId != null ? ( - ) : null} + {addFixtureModal} , getTopPortalEl() )} @@ -181,16 +84,15 @@ export function DeckConfigurationEditor(): JSX.Element { > diff --git a/app/src/resources/deck_configuration/hooks.ts b/app/src/resources/deck_configuration/hooks.tsx similarity index 53% rename from app/src/resources/deck_configuration/hooks.ts rename to app/src/resources/deck_configuration/hooks.tsx index ed48b705c5c..a3bf96173c3 100644 --- a/app/src/resources/deck_configuration/hooks.ts +++ b/app/src/resources/deck_configuration/hooks.tsx @@ -1,3 +1,4 @@ +import * as React from 'react' import { getInitialAndMovedLabwareInSlots } from '@opentrons/components' import { FLEX_ROBOT_TYPE, @@ -6,6 +7,11 @@ import { getCutoutIdForAddressableArea, getDeckDefFromRobotType, getLabwareDisplayName, + SINGLE_CENTER_SLOT_FIXTURE, + SINGLE_LEFT_CUTOUTS, + SINGLE_LEFT_SLOT_FIXTURE, + SINGLE_RIGHT_CUTOUTS, + SINGLE_RIGHT_SLOT_FIXTURE, SINGLE_SLOT_FIXTURES, } from '@opentrons/shared-data' @@ -13,11 +19,14 @@ import type { CompletedProtocolAnalysis, CutoutConfigProtocolSpec, CutoutFixtureId, + CutoutId, ProtocolAnalysisOutput, RobotType, } from '@opentrons/shared-data' import { useNotifyDeckConfigurationQuery } from './useNotifyDeckConfigurationQuery' +import { AddFixtureModal } from '../../organisms/DeviceDetailsDeckConfiguration/AddFixtureModal' +import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' const DECK_CONFIG_REFETCH_INTERVAL = 5000 @@ -100,3 +109,91 @@ export function useDeckConfigurationCompatibility( [] ) } + +interface DeckConfigurationEditingTools { + addFixtureToCutout: (cutoutId: CutoutId) => void + removeFixtureFromCutout: ( + cutoutId: CutoutId, + cutoutFixtureId: CutoutFixtureId + ) => void + addFixtureModal: React.ReactNode +} +export function useDeckConfigurationEditingTools( + isOnDevice: boolean +): DeckConfigurationEditingTools { + const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + const deckConfig = + useNotifyDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_REFETCH_INTERVAL, + }).data ?? [] + const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() + const [targetCutoutId, setTargetCutoutId] = React.useState( + null + ) + + const addFixtureToCutout = (cutoutId: CutoutId): void => { + setTargetCutoutId(cutoutId) + } + + const removeFixtureFromCutout = ( + cutoutId: CutoutId, + cutoutFixtureId: CutoutFixtureId + ): void => { + let replacementFixtureId: CutoutFixtureId = SINGLE_CENTER_SLOT_FIXTURE + if (SINGLE_RIGHT_CUTOUTS.includes(cutoutId)) { + replacementFixtureId = SINGLE_RIGHT_SLOT_FIXTURE + } else if (SINGLE_LEFT_CUTOUTS.includes(cutoutId)) { + replacementFixtureId = SINGLE_LEFT_SLOT_FIXTURE + } + + const fixtureGroup = + deckDef.cutoutFixtures.find(cf => cf.id === cutoutFixtureId) + ?.fixtureGroup ?? {} + + let newDeckConfig = deckConfig + if (cutoutId in fixtureGroup) { + const groupMap = + fixtureGroup[cutoutId]?.find(group => + Object.entries(group).every(([cId, cfId]) => + deckConfig.find( + config => + config.cutoutId === cId && config.cutoutFixtureId === cfId + ) + ) + ) ?? {} + newDeckConfig = deckConfig.map(cutoutConfig => + cutoutConfig.cutoutId in groupMap + ? { + ...cutoutConfig, + cutoutFixtureId: replacementFixtureId, + opentronsModuleSerialNumber: undefined, + } + : cutoutConfig + ) + } else { + newDeckConfig = deckConfig.map(cutoutConfig => + cutoutConfig.cutoutId === cutoutId + ? { + ...cutoutConfig, + cutoutFixtureId: replacementFixtureId, + opentronsModuleSerialNumber: undefined, + } + : cutoutConfig + ) + } + updateDeckConfiguration(newDeckConfig) + } + + return { + addFixtureToCutout, + removeFixtureFromCutout, + addFixtureModal: + targetCutoutId != null ? ( + setTargetCutoutId(null)} + isOnDevice={isOnDevice} + /> + ) : null, + } +}