diff --git a/Agents.md b/Agents.md index 0628625..c06d11e 100644 --- a/Agents.md +++ b/Agents.md @@ -199,7 +199,7 @@ Use a checkbox in each pick item row to switch between `"pending"` and `"picked" ### Increment Controls -Provide explicit controls to increase `quantity_units` and `quantity_bulk` (no long-press). Avoid swipe gestures for status changes or deletion on pick item rows. +Provide explicit controls to adjust the pick item's `quantity` and to toggle between unit and carton counts (no long-press). Avoid swipe gestures for status changes or deletion on pick item rows. ### Barcode Scanning diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index 6baae4b..2c076cc 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -1,5 +1,5 @@ import { Add, Delete } from '@mui/icons-material'; -import { Button, Checkbox, Stack, Typography } from '@mui/material'; +import { Button, Checkbox, IconButton, Stack, Typography } from '@mui/material'; import { PickItem } from '../models/PickItem'; import { Product } from '../models/Product'; @@ -8,8 +8,8 @@ interface PickItemRowProps { product?: Product | null; onIncrementQuantity: () => void; onToggleCarton: () => void; - onSwipeLeft: () => void; - onSwipeRight: () => void; + onStatusChange: (status: PickItem['status']) => void; + onDelete: () => void; } export const PickItemRow = ({ @@ -17,31 +17,56 @@ export const PickItemRow = ({ product, onIncrementQuantity, onToggleCarton, - onSwipeLeft, - onSwipeRight, + onStatusChange, + onDelete, }: PickItemRowProps) => { - const longPressHandlers = useLongPress({ onLongPress: onToggleCarton, onClick: onIncrementQuantity }); - const swipeHandlers = useSwipe({ onSwipeLeft, onSwipeRight }); + const packagingLabel = item.is_carton + ? product?.bulk_name ?? 'Carton' + : product?.unit_type ?? 'Unit'; - const isCarton = item.quantity_bulk > 0; + const toggleStatus = (checked: boolean) => { + onStatusChange(checked ? 'picked' : 'pending'); + }; return ( -
- {product?.name ?? 'Unknown product'} - - Qty: {item.quantity_units} - -
+ + toggleStatus(event.target.checked)} + inputProps={{ 'aria-label': 'Toggle picked status' }} + /> + + + {product?.name ?? 'Unknown product'} + + + Qty: {item.quantity} {packagingLabel} + + + + - {isCarton ? : null} - + + + + +
); diff --git a/src/db/index.ts b/src/db/index.ts index 4b07329..5a78de7 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -112,8 +112,12 @@ export class StockFillDB extends Dexie { if (hasNewFields) return undefined; - const legacyUnits = Number((item as PickItem).quantity_units ?? 0); - const legacyBulk = Number((item as PickItem).quantity_bulk ?? 0); + const legacyUnits = Number( + (item as PickItem & { quantity_units?: number }).quantity_units ?? 0, + ); + const legacyBulk = Number( + (item as PickItem & { quantity_bulk?: number }).quantity_bulk ?? 0, + ); if (legacyUnits > 0 && legacyBulk > 0) { await tx.table('pickItems').update(item.id, { @@ -148,6 +152,56 @@ export class StockFillDB extends Dexie { }), ); }); + + this.version(6) + .stores({ + products: 'id, name, category, &barcode, archived, created_at, updated_at', + areas: 'id, name, created_at, updated_at', + pickLists: 'id, area_id, created_at, completed_at', + pickItems: + 'id, pick_list_id, product_id, status, is_carton, quantity, created_at, updated_at', + categories: 'id, name, created_at, updated_at', + }) + .upgrade(async (tx) => { + const items = await tx.table('pickItems').toArray(); + const now = Date.now(); + + await Promise.all( + items.map(async (item) => { + const legacyUnits = Number((item as PickItem & { quantity_units?: number }).quantity_units ?? 0); + const legacyBulk = Number((item as PickItem & { quantity_bulk?: number }).quantity_bulk ?? 0); + const hasQuantity = typeof (item as PickItem).quantity === 'number'; + const hasCartonFlag = typeof (item as PickItem).is_carton === 'boolean'; + + const updateData: Partial & { + quantity_units?: undefined; + quantity_bulk?: undefined; + } = {}; + + if (!hasQuantity) { + updateData.quantity = legacyBulk > 0 ? legacyBulk : legacyUnits; + } + + if (!hasCartonFlag) { + updateData.is_carton = legacyBulk > 0 && legacyUnits === 0; + } + + if ('quantity_units' in item) { + updateData.quantity_units = undefined; + } + + if ('quantity_bulk' in item) { + updateData.quantity_bulk = undefined; + } + + if (Object.keys(updateData).length === 0) return undefined; + + updateData.updated_at = now; + + return tx.table('pickItems').update(item.id, updateData); + }), + ); + }); } } diff --git a/src/models/PickItem.ts b/src/models/PickItem.ts index ab0ca96..c6e3237 100644 --- a/src/models/PickItem.ts +++ b/src/models/PickItem.ts @@ -9,7 +9,4 @@ export interface PickItem { status: PickItemStatus; created_at: number; updated_at: number; - // Legacy fields retained for backward compatibility with pre-v5 data. - quantity_units?: number; - quantity_bulk?: number; } diff --git a/src/screens/ActivePickListScreen.tsx b/src/screens/ActivePickListScreen.tsx index 861e0b3..4c41850 100644 --- a/src/screens/ActivePickListScreen.tsx +++ b/src/screens/ActivePickListScreen.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import { useAreas, usePickItems, usePickList, useProducts } from '../hooks/dataHooks'; import { useDatabase } from '../context/DBProvider'; import { PickItemRow } from '../components/PickItemRow'; +import { PickItem } from '../models/PickItem'; export const ActivePickListScreen = () => { const { id } = useParams(); @@ -31,14 +32,17 @@ export const ActivePickListScreen = () => { const handleToggleCarton = async (itemId: string) => { const existing = await db.pickItems.get(itemId); if (!existing) return; + await db.pickItems.update(itemId, { - quantity_bulk: existing.quantity_bulk > 0 ? 0 : 1, + is_carton: !existing.is_carton, + quantity: existing.quantity || 1, updated_at: Date.now(), }); }; - const handleSwipeLeft = async (itemId: string) => { - await db.pickItems.update(itemId, { status: 'picked', updated_at: Date.now() }); + const handleStatusChange = async (itemId: string, status: PickItem['status']) => { + const nextStatus = status === 'picked' ? 'picked' : 'pending'; + await db.pickItems.update(itemId, { status: nextStatus, updated_at: Date.now() }); }; const handleDeleteItem = async (itemId: string) => { @@ -70,8 +74,8 @@ export const ActivePickListScreen = () => { product={products.find((p) => p.id === item.product_id)} onIncrementQuantity={() => handleIncrementQuantity(item.id)} onToggleCarton={() => handleToggleCarton(item.id)} - onSwipeLeft={() => handleSwipeLeft(item.id)} - onSwipeRight={() => handleSwipeRight(item.id)} + onStatusChange={(status) => handleStatusChange(item.id, status)} + onDelete={() => handleDeleteItem(item.id)} /> ))} diff --git a/src/screens/AddItemScreen.tsx b/src/screens/AddItemScreen.tsx index 0985e7f..d4c3f8c 100644 --- a/src/screens/AddItemScreen.tsx +++ b/src/screens/AddItemScreen.tsx @@ -1,20 +1,11 @@ -import { - Autocomplete, - Button, - Checkbox, - Container, - FormControlLabel, - InputAdornment, - Stack, - TextField, - Typography, -} from '@mui/material'; +import { Autocomplete, Button, Container, InputAdornment, Stack, TextField, Typography } from '@mui/material'; import SearchIcon from '@mui/icons-material/Search'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { v4 as uuidv4 } from 'uuid'; import { usePickItems, useProducts } from '../hooks/dataHooks'; import { useDatabase } from '../context/DBProvider'; +import { NumericStepper } from '../components/NumericStepper'; export const AddItemScreen = () => { const { id } = useParams(); @@ -23,8 +14,8 @@ export const AddItemScreen = () => { const products = useProducts(); const [selectedProduct, setSelectedProduct] = useState(null); const [query, setQuery] = useState(''); - const [quantity, setQuantity] = useState(1); - const [isCarton, setIsCarton] = useState(false); + const [units, setUnits] = useState(0); + const [cartons, setCartons] = useState(0); const navigate = useNavigate(); const unitLabel = selectedProduct?.unit_type ?? 'Units'; const cartonLabel = selectedProduct?.bulk_name ?? 'Cartons'; @@ -47,15 +38,10 @@ export const AddItemScreen = () => { }, [filteredProducts, selectedProduct]); useEffect(() => { - setQuantity(1); - setIsCarton(false); + setUnits(0); + setCartons(0); }, [selectedProduct]); - const quantityHelperText = selectedProduct?.unit_type - ? `Enter ${selectedProduct.unit_type} to pick` - : 'Enter the quantity to pick'; - const cartonLabel = selectedProduct?.bulk_name ? `Carton (${selectedProduct.bulk_name})` : 'Carton'; - const addItem = async () => { const productId = selectedProduct?.id; @@ -87,7 +73,7 @@ export const AddItemScreen = () => { }); }; - await Promise.all([addOrUpdateItem(false, units), addOrUpdateItem(true, bulk)]); + await Promise.all([addOrUpdateItem(false, units), addOrUpdateItem(true, cartons)]); navigate(`/pick-lists/${id}`); }; @@ -135,7 +121,7 @@ export const AddItemScreen = () => { )} /> - +