From e7e9425aa12fa744ba2f1acdb26f54440485b294 Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Tue, 25 Nov 2025 08:26:59 +1000 Subject: [PATCH] Add packaging filter to active pick list --- src/screens/ActivePickListScreen.test.tsx | 134 ++++++++++++++++++++++ src/screens/ActivePickListScreen.tsx | 67 ++++++++++- 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/src/screens/ActivePickListScreen.test.tsx b/src/screens/ActivePickListScreen.test.tsx index 857d655..02d012b 100644 --- a/src/screens/ActivePickListScreen.test.tsx +++ b/src/screens/ActivePickListScreen.test.tsx @@ -67,6 +67,8 @@ vi.mock('../context/DBProvider', () => ({ })); describe('ActivePickListScreen product search', () => { + const getRadio = (testId: string) => within(screen.getByTestId(testId)).getByRole('radio'); + beforeEach(() => { addMock.mockReset(); updateMock.mockReset(); @@ -573,4 +575,136 @@ describe('ActivePickListScreen product search', () => { expect(togglePicked).toBeDisabled(); expect(togglePicked).toBeChecked(); }); + + it('enables packaging filters when both packaging types are visible and filters items', async () => { + pickItemsMock.mockReturnValue([ + { + id: 'item-1', + pick_list_id: 'list-1', + product_id: 'prod-1', + quantity: 1, + is_carton: false, + status: 'pending', + created_at: 0, + updated_at: 0, + }, + { + id: 'item-2', + pick_list_id: 'list-1', + product_id: 'prod-2', + quantity: 1, + is_carton: true, + status: 'pending', + created_at: 0, + updated_at: 0, + }, + ]); + + const user = userEvent.setup(); + + render( + + + } /> + + , + ); + + const allRadio = getRadio('packaging-all'); + const unitsRadio = getRadio('packaging-units'); + const cartonsRadio = getRadio('packaging-cartons'); + + expect(allRadio).toBeChecked(); + expect(unitsRadio).not.toBeDisabled(); + expect(cartonsRadio).not.toBeDisabled(); + + await user.click(unitsRadio); + expect(unitsRadio).toBeChecked(); + expect(screen.getByText('Cola')).toBeVisible(); + expect(screen.queryByText('Chips')).not.toBeInTheDocument(); + + await user.click(cartonsRadio); + expect(cartonsRadio).toBeChecked(); + expect(screen.getByText('Chips')).toBeVisible(); + expect(screen.queryByText('Cola')).not.toBeInTheDocument(); + }); + + it('disables units and cartons packaging options when only units are visible', () => { + pickItemsMock.mockReturnValue([ + { + id: 'item-1', + pick_list_id: 'list-1', + product_id: 'prod-1', + quantity: 1, + is_carton: false, + status: 'pending', + created_at: 0, + updated_at: 0, + }, + ]); + + render( + + + } /> + + , + ); + + expect(getRadio('packaging-all')).toBeChecked(); + expect(getRadio('packaging-units')).toBeDisabled(); + expect(getRadio('packaging-cartons')).toBeDisabled(); + }); + + it('resets packaging filter to all and disables options when visible items become single packaging type', async () => { + pickItemsMock.mockReturnValue([ + { + id: 'item-1', + pick_list_id: 'list-1', + product_id: 'prod-1', + quantity: 1, + is_carton: false, + status: 'pending', + created_at: 0, + updated_at: 0, + }, + { + id: 'item-2', + pick_list_id: 'list-1', + product_id: 'prod-2', + quantity: 1, + is_carton: true, + status: 'picked', + created_at: 0, + updated_at: 0, + }, + ]); + + const user = userEvent.setup(); + + render( + + + } /> + + , + ); + + const cartonsRadio = getRadio('packaging-cartons'); + + expect(cartonsRadio).not.toBeDisabled(); + + await user.click(cartonsRadio); + expect(cartonsRadio).toBeChecked(); + expect(screen.getByText('Chips')).toBeVisible(); + expect(screen.queryByText('Cola')).not.toBeInTheDocument(); + + await user.click(screen.getByLabelText(/show picked/i)); + + expect(getRadio('packaging-all')).toBeChecked(); + expect(getRadio('packaging-units')).toBeDisabled(); + expect(getRadio('packaging-cartons')).toBeDisabled(); + expect(screen.getByText('Cola')).toBeVisible(); + expect(screen.queryByText('Chips')).not.toBeInTheDocument(); + }); }); diff --git a/src/screens/ActivePickListScreen.tsx b/src/screens/ActivePickListScreen.tsx index 9f8c258..c3fc4bf 100644 --- a/src/screens/ActivePickListScreen.tsx +++ b/src/screens/ActivePickListScreen.tsx @@ -10,6 +10,10 @@ import { TextField, Tooltip, Typography, + FormControl, + FormLabel, + RadioGroup, + Radio, } from '@mui/material'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import SearchIcon from '@mui/icons-material/Search'; @@ -37,6 +41,7 @@ export const ActivePickListScreen = () => { const [showPicked, setShowPicked] = useState(true); const [itemState, setItemState] = useState(items); const [isBatchUpdating, setIsBatchUpdating] = useState(false); + const [packagingFilter, setPackagingFilter] = useState<'all' | 'units' | 'cartons'>('all'); useEffect(() => { setItemState((current) => { @@ -63,6 +68,21 @@ export const ActivePickListScreen = () => { [itemState, showPicked], ); + const packagingInfo = useMemo(() => { + const visible = itemsAfterShowPicked ?? []; + const uniqueValues = new Set(visible.map((it) => !!it.is_carton)); + return { + visibleCount: visible.length, + uniquePackagingCount: uniqueValues.size, + }; + }, [itemsAfterShowPicked]); + + useEffect(() => { + if (packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1) { + setPackagingFilter('all'); + } + }, [packagingInfo.visibleCount, packagingInfo.uniquePackagingCount]); + const allItemsPicked = useMemo( () => itemState.length > 0 && itemState.every((item) => item.status === 'picked'), [itemState], @@ -165,9 +185,16 @@ export const ActivePickListScreen = () => { } }, [allItemsPicked, showPicked]); - // Sort the items that are actually visible (after showPicked) + // Sort the items that are actually visible (after showPicked and packaging filter) const visibleItems = useMemo(() => { - const arr = [...itemsAfterShowPicked]; + let arr = [...itemsAfterShowPicked]; + + if (packagingFilter === 'units') { + arr = arr.filter((item) => !item.is_carton); + } else if (packagingFilter === 'cartons') { + arr = arr.filter((item) => item.is_carton); + } + arr.sort((a, b) => { const nameA = normalizeName(productMap.get(a.product_id)?.name ?? ''); const nameB = normalizeName(productMap.get(b.product_id)?.name ?? ''); @@ -178,7 +205,7 @@ export const ActivePickListScreen = () => { return timeA - timeB; }); return arr; - }, [itemsAfterShowPicked, productMap]); + }, [itemsAfterShowPicked, productMap, packagingFilter]); const updateItemState = (itemId: string, updater: (item: PickItem) => PickItem) => { setItemState((current) => current.map((item) => (item.id === itemId ? updater(item) : item))); @@ -393,6 +420,40 @@ export const ActivePickListScreen = () => { flexWrap="wrap" rowGap={1} > + + + Packaging + + setPackagingFilter(value as 'all' | 'units' | 'cartons')} + > + } + label="All" + data-testid="packaging-all" + /> + } + label="Units" + disabled={packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1} + data-testid="packaging-units" + /> + } + label="Cartons" + disabled={packagingInfo.visibleCount === 0 || packagingInfo.uniquePackagingCount === 1} + data-testid="packaging-cartons" + /> + + +