Improve pick list item interactions and manage products search

This commit is contained in:
beatz174-bit
2025-11-24 08:56:26 +10:00
parent aa38a99eb4
commit f14a1ffd01
4 changed files with 72 additions and 19 deletions
+3 -1
View File
@@ -123,7 +123,9 @@ test.describe('Active pick list', () => {
await expect(page.getByText('Product updated.')).toBeVisible(); await expect(page.getByText('Product updated.')).toBeVisible();
await expect(page.getByRole('button', { name: `Edit ${updatedListProductName}` })).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(); await page.getByRole('button', { name: `Delete ${updatedListProductName}` }).click();
-1
View File
@@ -130,7 +130,6 @@ export const PickItemRow = ({
color="error" color="error"
variant="contained" variant="contained"
onClick={handleConfirmDelete} onClick={handleConfirmDelete}
aria-label="Confirm delete"
> >
Delete Delete
</Button> </Button>
+55 -15
View File
@@ -37,10 +37,15 @@ export const ActivePickListScreen = () => {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [itemFilter, setItemFilter] = useState<'all' | 'cartons' | 'units'>('all'); const [itemFilter, setItemFilter] = useState<'all' | 'cartons' | 'units'>('all');
const [showPicked, setShowPicked] = useState(true); const [showPicked, setShowPicked] = useState(true);
const [itemState, setItemState] = useState(items);
useEffect(() => {
setItemState(items);
}, [items]);
const allItemsPicked = useMemo( const allItemsPicked = useMemo(
() => items.length > 0 && items.every((item) => item.status === 'picked'), () => itemState.length > 0 && itemState.every((item) => item.status === 'picked'),
[items], [itemState],
); );
const productMap = useMemo(() => { const productMap = useMemo(() => {
@@ -58,8 +63,8 @@ export const ActivePickListScreen = () => {
); );
const visibleItemsByStatus = useMemo( const visibleItemsByStatus = useMemo(
() => (showPicked ? items : items.filter((item) => item.status !== 'picked')), () => (showPicked ? itemState : itemState.filter((item) => item.status !== 'picked')),
[items, showPicked], [itemState, showPicked],
); );
const sortedItems = useMemo(() => { const sortedItems = useMemo(() => {
@@ -138,8 +143,8 @@ export const ActivePickListScreen = () => {
const singlePackagingType = packagingTypeCount === 1; const singlePackagingType = packagingTypeCount === 1;
const productIdsInList = useMemo( const productIdsInList = useMemo(
() => new Set(items.map((item) => item.product_id)), () => new Set(itemState.map((item) => item.product_id)),
[items], [itemState],
); );
const filteredProducts = useMemo(() => { const filteredProducts = useMemo(() => {
@@ -201,11 +206,18 @@ export const ActivePickListScreen = () => {
return filteredItems; return filteredItems;
}, [itemFilter, showPicked, sortedItems]); }, [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 handleIncrementQuantity = async (itemId: string) => {
const existing = await db.pickItems.get(itemId); const existing = await db.pickItems.get(itemId);
if (!existing) return; if (!existing) return;
const nextQuantity = existing.quantity + 1;
updateItemState(itemId, (item) => ({ ...item, quantity: nextQuantity, updated_at: Date.now() }));
await db.pickItems.update(itemId, { await db.pickItems.update(itemId, {
quantity: existing.quantity + 1, quantity: nextQuantity,
updated_at: Date.now(), updated_at: Date.now(),
}); });
}; };
@@ -216,6 +228,7 @@ export const ActivePickListScreen = () => {
const nextQuantity = Math.max(1, (existing.quantity || 1) - 1); const nextQuantity = Math.max(1, (existing.quantity || 1) - 1);
updateItemState(itemId, (item) => ({ ...item, quantity: nextQuantity, updated_at: Date.now() }));
await db.pickItems.update(itemId, { await db.pickItems.update(itemId, {
quantity: nextQuantity, quantity: nextQuantity,
updated_at: Date.now(), updated_at: Date.now(),
@@ -226,27 +239,41 @@ export const ActivePickListScreen = () => {
const existing = await db.pickItems.get(itemId); const existing = await db.pickItems.get(itemId);
if (!existing) return; 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, { await db.pickItems.update(itemId, {
is_carton: !existing.is_carton, is_carton: nextCartonFlag,
quantity: existing.quantity || 1, quantity: nextQuantity,
updated_at: Date.now(), updated_at: Date.now(),
}); });
}; };
const handleStatusChange = async (itemId: string, status: PickItem['status']) => { const handleStatusChange = async (itemId: string, status: PickItem['status']) => {
const nextStatus = status === 'picked' ? 'picked' : 'pending'; 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() }); await db.pickItems.update(itemId, { status: nextStatus, updated_at: Date.now() });
}; };
const handleDeleteItem = async (itemId: string) => { const handleDeleteItem = async (itemId: string) => {
setItemState((current) => current.filter((item) => item.id !== itemId));
await db.pickItems.delete(itemId); await db.pickItems.delete(itemId);
}; };
const handleMarkAllPicked = async () => { const handleMarkAllPicked = async () => {
setShowPicked(true); setShowPicked(true);
const timestamp = Date.now(); const timestamp = Date.now();
setItemState((current) =>
current.map((item) => ({ ...item, status: 'picked', updated_at: timestamp })),
);
await Promise.all( await Promise.all(
items.map((item) => itemState.map((item) =>
db.pickItems.update(item.id, { status: 'picked', updated_at: timestamp }), db.pickItems.update(item.id, { status: 'picked', updated_at: timestamp }),
), ),
); );
@@ -255,25 +282,38 @@ export const ActivePickListScreen = () => {
const addOrUpdateItem = async (product: Product) => { const addOrUpdateItem = async (product: Product) => {
if (!id) return; 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, (item) => item.product_id === product.id && item.is_carton === false,
); );
if (existing) { 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, { await db.pickItems.update(existing.id, {
quantity: existing.quantity + 1, quantity: existing.quantity + 1,
updated_at: Date.now(), updated_at: timestamp,
}); });
} else { } else {
await db.pickItems.add({ const newItem: PickItem = {
id: uuidv4(), id: uuidv4(),
pick_list_id: id, pick_list_id: id,
product_id: product.id, product_id: product.id,
quantity: 1, quantity: 1,
is_carton: false, is_carton: false,
status: 'pending', status: 'pending',
created_at: Date.now(), created_at: timestamp,
updated_at: Date.now(), updated_at: timestamp,
};
setItemState((current) => [...current, newItem]);
await db.pickItems.add({
...newItem,
}); });
} }
+14 -2
View File
@@ -261,12 +261,24 @@ export const ManageProductsScreen = () => {
await assertUniqueName(updates.name, productId); await assertUniqueName(updates.name, productId);
await assertUniqueBarcode(updates.barcode, 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, ...updates,
name: normalizedName,
unit_type: DEFAULT_UNIT_TYPE, unit_type: DEFAULT_UNIT_TYPE,
bulk_name: DEFAULT_BULK_NAME, bulk_name: DEFAULT_BULK_NAME,
updated_at: Date.now(), 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' }); setFeedback({ text: 'Product updated.', severity: 'success' });
}; };