diff --git a/src/screens/ActivePickListScreen.test.tsx b/src/screens/ActivePickListScreen.test.tsx
index 972d046..6d29c36 100644
--- a/src/screens/ActivePickListScreen.test.tsx
+++ b/src/screens/ActivePickListScreen.test.tsx
@@ -128,12 +128,93 @@ describe('ActivePickListScreen product search', () => {
);
const combobox = screen.getByRole('combobox');
+ await user.click(combobox);
await user.type(combobox, 'cola');
expect(await screen.findByRole('option', { name: /cola \(drinks\)/i })).toBeVisible();
expect(screen.queryByRole('option', { name: /chips \(snacks\)/i })).not.toBeInTheDocument();
});
+ it('omits products already on the pick list from search options', 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,
+ },
+ ]);
+
+ const user = userEvent.setup();
+
+ render(
+
+
+ } />
+
+ ,
+ );
+
+ const combobox = screen.getByRole('combobox');
+ await user.click(combobox);
+
+ const listbox = await screen.findByRole('listbox');
+
+ expect(
+ within(listbox).queryByRole('option', { name: /cola \(drinks\)/i }),
+ ).not.toBeInTheDocument();
+ expect(within(listbox).getByRole('option', { name: /chips \(snacks\)/i })).toBeVisible();
+ });
+
+ it('shows a placeholder when no products are available after filtering', () => {
+ 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: false,
+ status: 'pending',
+ created_at: 0,
+ updated_at: 0,
+ },
+ {
+ id: 'item-3',
+ pick_list_id: 'list-1',
+ product_id: 'prod-3',
+ quantity: 1,
+ is_carton: false,
+ status: 'pending',
+ created_at: 0,
+ updated_at: 0,
+ },
+ ]);
+
+ render(
+
+
+ } />
+
+ ,
+ );
+
+ expect(screen.getByText(/no available products/i)).toBeVisible();
+ });
+
it('adds a pick item when a product is selected', async () => {
const user = userEvent.setup();
@@ -275,7 +356,7 @@ describe('ActivePickListScreen product search', () => {
expect(screen.queryByRole('option', { name: /chips \(snacks\)/i })).not.toBeInTheDocument();
});
- it('updates an existing pick item when the same packaging is selected', async () => {
+ it('prevents selecting products that are already on the pick list', async () => {
pickItemsMock.mockReturnValue([
{
id: 'item-1',
@@ -301,11 +382,11 @@ describe('ActivePickListScreen product search', () => {
const combobox = screen.getByRole('combobox');
await user.click(combobox);
+ await user.type(combobox, 'cola');
- const listbox = await screen.findByRole('listbox');
- await user.click(within(listbox).getByRole('option', { name: /cola \(drinks\)/i }));
-
- expect(updateMock).toHaveBeenCalledWith('item-1', expect.objectContaining({ quantity: 3 }));
+ expect(screen.queryByRole('option', { name: /cola \(drinks\)/i })).not.toBeInTheDocument();
+ expect(screen.getAllByText(/no available products/i)).not.toHaveLength(0);
+ expect(updateMock).not.toHaveBeenCalled();
expect(addMock).not.toHaveBeenCalled();
});
diff --git a/src/screens/ActivePickListScreen.tsx b/src/screens/ActivePickListScreen.tsx
index 6f4b69d..e88d833 100644
--- a/src/screens/ActivePickListScreen.tsx
+++ b/src/screens/ActivePickListScreen.tsx
@@ -115,15 +115,24 @@ export const ActivePickListScreen = () => {
const packagingTypeCount = Number(hasCartonItems) + Number(hasUnitItems);
const singlePackagingType = packagingTypeCount === 1;
+ const productIdsInList = useMemo(
+ () => new Set(items.map((item) => item.product_id)),
+ [items],
+ );
+
const filteredProducts = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
- if (!normalizedQuery) return categoryFilteredProducts;
+ const availableProducts = categoryFilteredProducts.filter(
+ (product) => !productIdsInList.has(product.id),
+ );
- return categoryFilteredProducts.filter((product) => {
+ if (!normalizedQuery) return availableProducts;
+
+ return availableProducts.filter((product) => {
const searchableText = `${product.name} ${product.category} ${product.barcode ?? ''}`.toLowerCase();
return searchableText.includes(normalizedQuery);
});
- }, [categoryFilteredProducts, query]);
+ }, [categoryFilteredProducts, productIdsInList, query]);
useEffect(() => {
if (!selectedProduct) return;
@@ -284,7 +293,7 @@ export const ActivePickListScreen = () => {
}
}}
filterOptions={(options) => options}
- noOptionsText={query.trim() ? 'No matching products' : 'No products available'}
+ noOptionsText="No available products"
fullWidth
renderInput={(params) => (
{
/>
)}
/>
+ {filteredProducts.length === 0 ? (
+
+ No available products
+
+ ) : null}
Selecting a product immediately adds it to the pick list.