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"
+ />
+
+
+