Improve pick list item interactions and manage products search
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user