From f14a1ffd013d0dc01f7d5daa1babb127c1d82e7f Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Mon, 24 Nov 2025 08:56:26 +1000 Subject: [PATCH] Improve pick list item interactions and manage products search --- e2e/picklist.spec.ts | 4 +- src/components/PickItemRow.tsx | 1 - src/screens/ActivePickListScreen.tsx | 70 ++++++++++++++++++++++------ src/screens/ManageProductsScreen.tsx | 16 ++++++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/e2e/picklist.spec.ts b/e2e/picklist.spec.ts index 6fa0565..046f7fd 100644 --- a/e2e/picklist.spec.ts +++ b/e2e/picklist.spec.ts @@ -123,7 +123,9 @@ test.describe('Active pick list', () => { await expect(page.getByText('Product updated.')).toBeVisible(); await expect(page.getByRole('button', { name: `Edit ${updatedListProductName}` })).toBeVisible(); - await expect(page.getByRole('button', { name: `Edit ${listProductName}` })).toHaveCount(0); + await expect( + page.getByRole('button', { name: `Edit ${listProductName}`, exact: true }), + ).toHaveCount(0); await page.getByRole('button', { name: `Delete ${updatedListProductName}` }).click(); diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index d621a35..e719614 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -130,7 +130,6 @@ export const PickItemRow = ({ color="error" variant="contained" onClick={handleConfirmDelete} - aria-label="Confirm delete" > Delete diff --git a/src/screens/ActivePickListScreen.tsx b/src/screens/ActivePickListScreen.tsx index a572980..5f1d9d3 100644 --- a/src/screens/ActivePickListScreen.tsx +++ b/src/screens/ActivePickListScreen.tsx @@ -37,10 +37,15 @@ export const ActivePickListScreen = () => { const [query, setQuery] = useState(''); const [itemFilter, setItemFilter] = useState<'all' | 'cartons' | 'units'>('all'); const [showPicked, setShowPicked] = useState(true); + const [itemState, setItemState] = useState(items); + + useEffect(() => { + setItemState(items); + }, [items]); const allItemsPicked = useMemo( - () => items.length > 0 && items.every((item) => item.status === 'picked'), - [items], + () => itemState.length > 0 && itemState.every((item) => item.status === 'picked'), + [itemState], ); const productMap = useMemo(() => { @@ -58,8 +63,8 @@ export const ActivePickListScreen = () => { ); const visibleItemsByStatus = useMemo( - () => (showPicked ? items : items.filter((item) => item.status !== 'picked')), - [items, showPicked], + () => (showPicked ? itemState : itemState.filter((item) => item.status !== 'picked')), + [itemState, showPicked], ); const sortedItems = useMemo(() => { @@ -138,8 +143,8 @@ export const ActivePickListScreen = () => { const singlePackagingType = packagingTypeCount === 1; const productIdsInList = useMemo( - () => new Set(items.map((item) => item.product_id)), - [items], + () => new Set(itemState.map((item) => item.product_id)), + [itemState], ); const filteredProducts = useMemo(() => { @@ -201,11 +206,18 @@ export const ActivePickListScreen = () => { return filteredItems; }, [itemFilter, showPicked, sortedItems]); + const updateItemState = (itemId: string, updater: (item: PickItem) => PickItem) => { + setItemState((current) => current.map((item) => (item.id === itemId ? updater(item) : item))); + }; + const handleIncrementQuantity = async (itemId: string) => { const existing = await db.pickItems.get(itemId); if (!existing) return; + + const nextQuantity = existing.quantity + 1; + updateItemState(itemId, (item) => ({ ...item, quantity: nextQuantity, updated_at: Date.now() })); await db.pickItems.update(itemId, { - quantity: existing.quantity + 1, + quantity: nextQuantity, updated_at: Date.now(), }); }; @@ -216,6 +228,7 @@ export const ActivePickListScreen = () => { const nextQuantity = Math.max(1, (existing.quantity || 1) - 1); + updateItemState(itemId, (item) => ({ ...item, quantity: nextQuantity, updated_at: Date.now() })); await db.pickItems.update(itemId, { quantity: nextQuantity, updated_at: Date.now(), @@ -226,27 +239,41 @@ export const ActivePickListScreen = () => { const existing = await db.pickItems.get(itemId); if (!existing) return; + const nextCartonFlag = !existing.is_carton; + const nextQuantity = existing.quantity || 1; + + updateItemState(itemId, (item) => ({ + ...item, + is_carton: nextCartonFlag, + quantity: nextQuantity, + updated_at: Date.now(), + })); await db.pickItems.update(itemId, { - is_carton: !existing.is_carton, - quantity: existing.quantity || 1, + is_carton: nextCartonFlag, + quantity: nextQuantity, updated_at: Date.now(), }); }; const handleStatusChange = async (itemId: string, status: PickItem['status']) => { const nextStatus = status === 'picked' ? 'picked' : 'pending'; + updateItemState(itemId, (item) => ({ ...item, status: nextStatus, updated_at: Date.now() })); await db.pickItems.update(itemId, { status: nextStatus, updated_at: Date.now() }); }; const handleDeleteItem = async (itemId: string) => { + setItemState((current) => current.filter((item) => item.id !== itemId)); await db.pickItems.delete(itemId); }; const handleMarkAllPicked = async () => { setShowPicked(true); const timestamp = Date.now(); + setItemState((current) => + current.map((item) => ({ ...item, status: 'picked', updated_at: timestamp })), + ); await Promise.all( - items.map((item) => + itemState.map((item) => db.pickItems.update(item.id, { status: 'picked', updated_at: timestamp }), ), ); @@ -255,25 +282,38 @@ export const ActivePickListScreen = () => { const addOrUpdateItem = async (product: Product) => { if (!id) return; - const existing = items.find( + const timestamp = Date.now(); + const existing = itemState.find( (item) => item.product_id === product.id && item.is_carton === false, ); if (existing) { + setItemState((current) => + current.map((item) => + item.id === existing.id + ? { ...item, quantity: item.quantity + 1, updated_at: timestamp } + : item, + ), + ); await db.pickItems.update(existing.id, { quantity: existing.quantity + 1, - updated_at: Date.now(), + updated_at: timestamp, }); } else { - await db.pickItems.add({ + const newItem: PickItem = { id: uuidv4(), pick_list_id: id, product_id: product.id, quantity: 1, is_carton: false, status: 'pending', - created_at: Date.now(), - updated_at: Date.now(), + created_at: timestamp, + updated_at: timestamp, + }; + + setItemState((current) => [...current, newItem]); + await db.pickItems.add({ + ...newItem, }); } diff --git a/src/screens/ManageProductsScreen.tsx b/src/screens/ManageProductsScreen.tsx index 7eccc1f..4d99956 100644 --- a/src/screens/ManageProductsScreen.tsx +++ b/src/screens/ManageProductsScreen.tsx @@ -261,12 +261,24 @@ export const ManageProductsScreen = () => { await assertUniqueName(updates.name, productId); await assertUniqueBarcode(updates.barcode, productId); - await db.products.update(productId, { + const existing = await db.products.get(productId); + if (!existing) return; + + const normalizedName = updates.name.trim(); + const oldNameKey = existing.name.trim().toLowerCase(); + const updatedProduct: Product = { + ...existing, ...updates, + name: normalizedName, unit_type: DEFAULT_UNIT_TYPE, bulk_name: DEFAULT_BULK_NAME, updated_at: Date.now(), - }); + }; + + await db.products.put(updatedProduct); + await db.products + .filter((product) => product.id !== productId && product.name.trim().toLowerCase() === oldNameKey) + .delete(); setFeedback({ text: 'Product updated.', severity: 'success' }); };