diff --git a/e2e/picklist.spec.ts b/e2e/picklist.spec.ts
index 046f7fd..75a4748 100644
--- a/e2e/picklist.spec.ts
+++ b/e2e/picklist.spec.ts
@@ -34,6 +34,7 @@ const addProductToPickList = async (page: Page, productName: string) => {
};
test.describe('Active pick list', () => {
+ test.use({ viewport: { width: 1280, height: 900 } });
test('adds a product via search and renders it with quantity and packaging', async ({ page }) => {
await navigateToNewPickList(page);
@@ -45,7 +46,7 @@ test.describe('Active pick list', () => {
.click();
await expect(page.getByText(secondaryProduct, { exact: true })).toBeVisible();
- await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 });
});
test('creates a pick list and adds products from the search bar', async ({ page }) => {
@@ -67,7 +68,7 @@ test.describe('Active pick list', () => {
await addProductToPickList(page, additionalProduct);
- await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 });
});
test('allows adding and editing products from the manage products screen', async ({ page }) => {
@@ -190,7 +191,7 @@ test.describe('Active pick list', () => {
await expect(quantityLabel).toBeVisible();
await page.getByRole('button', { name: 'Increase quantity' }).click();
- await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 });
await page.reload();
await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible();
@@ -200,7 +201,7 @@ test.describe('Active pick list', () => {
await decrementButton.click();
await decrementButton.click();
- await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 });
await page.reload();
await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible();
@@ -251,21 +252,21 @@ test.describe('Active pick list', () => {
const increaseButton = page.getByLabel('Increase quantity');
await increaseButton.click();
- await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 });
await page.getByLabel('Switch to carton packaging').click();
- await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 });
await page.getByLabel('Decrease quantity').click();
- await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible({ timeout: 10000 });
await page.getByLabel('Switch to unit packaging').click();
- await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 });
await increaseButton.click();
await increaseButton.click();
await page.getByLabel('Switch to carton packaging').click();
- await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 });
await page.getByRole('button', { name: 'Save and Return' }).click();
await expect(page.getByRole('heading', { name: 'Pick Lists' })).toBeVisible();
@@ -273,10 +274,26 @@ test.describe('Active pick list', () => {
await page.reload();
await page.getByRole('link', { name: areaName }).first().click();
await expect(page.getByRole('heading', { name: `${areaName} List` })).toBeVisible();
- await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible();
+ await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 });
await expect(page.getByLabel('Switch to unit packaging')).toBeVisible();
});
+ test('shows inline controls on wide view without the popup trigger', async ({ page }) => {
+ await navigateToNewPickList(page);
+ await addProductToPickList(page, additionalProduct);
+
+ const productRow = page
+ .getByText(additionalProduct)
+ .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]')
+ .first();
+
+ await expect(productRow.getByLabel('Switch to carton packaging')).toBeVisible();
+ await expect(productRow.getByLabel('Decrease quantity')).toBeVisible();
+ await expect(productRow.getByLabel('Increase quantity')).toBeVisible();
+ await expect(productRow.getByLabel('Delete item')).toBeVisible();
+ await expect(productRow.getByLabel('Open item controls')).toHaveCount(0);
+ });
+
test('lets a user mark an item as picked then revert it back to pending', async ({ page }) => {
await navigateToNewPickList(page);
await addProductToPickList(page, additionalProduct);
@@ -316,3 +333,41 @@ test.describe('Active pick list', () => {
await expect(page.getByText(additionalProduct).first()).not.toBeVisible();
});
});
+
+test.describe('Active pick list responsive controls on mobile', () => {
+ test.use({ viewport: { width: 430, height: 900 } });
+
+ test('shows popup controls on narrow screens and hides inline buttons', async ({ page }) => {
+ await navigateToNewPickList(page);
+ await addProductToPickList(page, additionalProduct);
+
+ const productRow = page
+ .getByText(additionalProduct)
+ .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]')
+ .first();
+
+ await expect(productRow.getByLabel('Switch to carton packaging')).toHaveCount(0);
+ await expect(productRow.getByLabel('Decrease quantity')).toHaveCount(0);
+ await expect(productRow.getByLabel('Increase quantity')).toHaveCount(0);
+ await expect(productRow.getByLabel('Delete item')).toHaveCount(0);
+
+ const controlsButton = productRow.getByLabel('Open item controls');
+ await expect(controlsButton).toBeVisible();
+ await controlsButton.click();
+
+ const itemDialog = page.getByRole('dialog', { name: additionalProduct });
+ await expect(itemDialog).toBeVisible();
+ await expect(itemDialog.getByText(/Quantity:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 });
+ await expect(itemDialog.getByLabel('Increase quantity')).toBeVisible();
+
+ await itemDialog.getByLabel('Increase quantity').click();
+ await expect(itemDialog.getByText(/Quantity:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 });
+ await itemDialog.getByLabel('Switch to carton packaging').click();
+ await expect(itemDialog.getByText(/Quantity:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 });
+
+ await itemDialog.getByLabel('Delete item').click();
+ await expect(page.getByRole('dialog', { name: 'Delete item' })).toBeVisible();
+ await page.getByRole('button', { name: 'Cancel', exact: true }).click();
+ await expect(page.getByRole('dialog', { name: additionalProduct })).toBeVisible();
+ });
+});
diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx
index cf409ff..cc10b92 100644
--- a/src/components/PickItemRow.tsx
+++ b/src/components/PickItemRow.tsx
@@ -1,4 +1,4 @@
-import { Add, Close, Delete, Inventory2, Remove } from '@mui/icons-material';
+import { Add, Close, Delete, Inventory2, MoreHoriz, Remove } from '@mui/icons-material';
import {
Button,
Checkbox,
@@ -16,7 +16,7 @@ import { alpha, useTheme } from '@mui/material/styles';
import type React from 'react';
import { useState } from 'react';
import { PickItem } from '../models/PickItem';
-import { Product } from '../models/Product';
+import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product';
interface PickItemRowProps {
item: PickItem;
@@ -42,8 +42,8 @@ export const PickItemRow = ({
const [isControlsOpen, setIsControlsOpen] = useState(false);
const isNarrowScreen = useMediaQuery(theme.breakpoints.down('sm'));
const packagingLabel = item.is_carton
- ? product?.bulk_name ?? 'Carton'
- : product?.unit_type ?? 'Unit';
+ ? product?.bulk_name ?? DEFAULT_BULK_NAME
+ : product?.unit_type ?? DEFAULT_UNIT_TYPE;
const toggleStatus = (checked: boolean) => {
onStatusChange(checked ? 'picked' : 'pending');
@@ -80,7 +80,7 @@ export const PickItemRow = ({
justifyContent="space-between"
spacing={1.5}
sx={{
- p: 1,
+ p: 1.5,
borderRadius: 1,
bgcolor: 'background.paper',
boxShadow: 1,
@@ -97,29 +97,10 @@ export const PickItemRow = ({
checked={item.status === 'picked'}
onChange={(event) => toggleStatus(event.target.checked)}
inputProps={{ 'aria-label': 'Toggle picked status' }}
- onClick={(event) => event.stopPropagation()}
/>
-
-
-
- {product?.name ?? 'Unknown product'}
-
-
- {item.quantity} {packagingLabel}
-
+
+
+ {product?.name ?? 'Unknown product'}
{isNarrowScreen && (
@@ -128,6 +109,40 @@ export const PickItemRow = ({
)}
+ {isDesktop ? (
+
+ `0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}`
+ : 'none',
+ }}
+ >
+
+
+
+
+
+
+
+
+ setIsConfirmOpen(true)}
+ aria-label="Delete item"
+ sx={{ ml: { xs: 0, sm: 1 } }}
+ >
+
+
+
+ ) : (
+
+
+
+ )}
{!isNarrowScreen && (