import { Autocomplete, Button, Checkbox, Container, FormControl, FormControlLabel, IconButton, InputAdornment, Radio, RadioGroup, Stack, TextField, Tooltip, Typography, } from '@mui/material'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import SearchIcon from '@mui/icons-material/Search'; import { Link as RouterLink, useParams, useNavigate } from 'react-router-dom'; import { useEffect, useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { useAreas, usePickItems, usePickList, useProducts } from '../hooks/dataHooks'; import { useDatabase } from '../context/DBProvider'; import { PickItemRow } from '../components/PickItemRow'; import { PickItem } from '../models/PickItem'; import { Product } from '../models/Product'; export const ActivePickListScreen = () => { const { id } = useParams(); const pickList = usePickList(id); const items = usePickItems(id); const products = useProducts(); const areas = useAreas(); const db = useDatabase(); const navigate = useNavigate(); const [selectedProduct, setSelectedProduct] = useState(null); const [query, setQuery] = useState(''); const [itemFilter, setItemFilter] = useState<'all' | 'cartons' | 'units'>('all'); const [showPicked, setShowPicked] = useState(true); const allItemsPicked = useMemo( () => items.length > 0 && items.every((item) => item.status === 'picked'), [items], ); const productMap = useMemo(() => { const map = new Map(); products.forEach((product) => { map.set(product.id, product); }); return map; }, [products]); const areaName = useMemo( () => areas.find((area) => area.id === pickList?.area_id)?.name ?? 'Area', [areas, pickList?.area_id], ); const sortedItems = useMemo(() => { return [...items].sort((a, b) => { const productA = productMap.get(a.product_id); const productB = productMap.get(b.product_id); const nameA = productA?.name.trim().toLowerCase() ?? ''; const nameB = productB?.name.trim().toLowerCase() ?? ''; const nameComparison = nameA.localeCompare(nameB, undefined, { sensitivity: 'base' }); if (nameComparison !== 0) { return nameComparison; } if (a.is_carton !== b.is_carton) { return a.is_carton ? 1 : -1; } const timeA = a.updated_at ?? a.created_at; const timeB = b.updated_at ?? b.created_at; return timeA - timeB; }); }, [items, productMap]); const sortedProducts = useMemo(() => { const uniqueProducts = new Map(); products.forEach((product) => { const normalizedName = product.name.trim().toLowerCase(); const existing = uniqueProducts.get(normalizedName); if (!existing || product.updated_at > existing.updated_at) { uniqueProducts.set(normalizedName, product); } }); return Array.from(uniqueProducts.values()).sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }), ); }, [products]); const hasCartonItems = useMemo(() => items.some((item) => item.is_carton), [items]); const hasUnitItems = useMemo(() => items.some((item) => !item.is_carton), [items]); const filteredProducts = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); if (!normalizedQuery) return sortedProducts; return sortedProducts.filter((product) => { const searchableText = `${product.name} ${product.category} ${product.barcode ?? ''}`.toLowerCase(); return searchableText.includes(normalizedQuery); }); }, [sortedProducts, query]); useEffect(() => { if (!selectedProduct) return; if (!filteredProducts.some((product) => product.id === selectedProduct.id)) { setSelectedProduct(null); } }, [filteredProducts, selectedProduct]); useEffect(() => { if (allItemsPicked && !showPicked) { setShowPicked(true); } }, [allItemsPicked, showPicked]); useEffect(() => { if (itemFilter === 'cartons' && !hasCartonItems) { setItemFilter(hasUnitItems ? 'units' : 'all'); } else if (itemFilter === 'units' && !hasUnitItems) { setItemFilter(hasCartonItems ? 'cartons' : 'all'); } }, [itemFilter, hasCartonItems, hasUnitItems]); const visibleItems = useMemo(() => { let filteredItems = showPicked ? sortedItems : sortedItems.filter((item) => item.status !== 'picked'); if (itemFilter === 'cartons') { return filteredItems.filter((item) => item.is_carton); } if (itemFilter === 'units') { return filteredItems.filter((item) => !item.is_carton); } return filteredItems; }, [itemFilter, showPicked, sortedItems]); const handleIncrementQuantity = async (itemId: string) => { const existing = await db.pickItems.get(itemId); if (!existing) return; await db.pickItems.update(itemId, { quantity: existing.quantity + 1, updated_at: Date.now(), }); }; const handleDecrementQuantity = async (itemId: string) => { const existing = await db.pickItems.get(itemId); if (!existing) return; const nextQuantity = Math.max(1, (existing.quantity || 1) - 1); await db.pickItems.update(itemId, { quantity: nextQuantity, updated_at: Date.now(), }); }; const handleToggleCarton = async (itemId: string) => { const existing = await db.pickItems.get(itemId); if (!existing) return; await db.pickItems.update(itemId, { is_carton: !existing.is_carton, quantity: existing.quantity || 1, 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) => { await db.pickItems.delete(itemId); }; const handleMarkAllPicked = async () => { setShowPicked(true); const timestamp = Date.now(); await Promise.all( items.map((item) => db.pickItems.update(item.id, { status: 'picked', updated_at: timestamp }), ), ); }; const addOrUpdateItem = async (product: Product) => { if (!id) return; const existing = items.find( (item) => item.product_id === product.id && item.is_carton === false, ); if (existing) { await db.pickItems.update(existing.id, { quantity: existing.quantity + 1, updated_at: Date.now(), }); } else { await db.pickItems.add({ 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(), }); } setSelectedProduct(null); setQuery(''); }; const returnToLists = () => { navigate('/pick-lists'); }; return ( {areaName} List Add products to this list `${option.name} (${option.category})`} isOptionEqualToValue={(option, value) => option.id === value.id} value={selectedProduct} onChange={(_, value) => { setSelectedProduct(value); if (value) { void addOrUpdateItem(value); } }} inputValue={query} onInputChange={(_, value, reason) => { if (reason === 'input') { setQuery(value); } if (reason === 'clear') { setQuery(''); } }} filterOptions={(options) => options} noOptionsText={query.trim() ? 'No matching products' : 'No products available'} fullWidth renderInput={(params) => ( {params.InputProps.startAdornment} ), endAdornment: ( <> {params.InputProps.endAdornment} ), }} /> )} /> Selecting a product immediately adds it to the pick list. Filter list by packaging setItemFilter(event.target.value as 'all' | 'cartons' | 'units') } sx={{ flexGrow: 1 }} > } label="All" /> } label="Cartons" disabled={!hasCartonItems} /> } label="Units" disabled={!hasUnitItems} /> setShowPicked(event.target.checked)} disabled={allItemsPicked} /> } label="Show picked" /> {pickList?.notes ? ( {pickList.notes} ) : null} {visibleItems.map((item) => ( handleIncrementQuantity(item.id)} onDecrementQuantity={() => handleDecrementQuantity(item.id)} onToggleCarton={() => handleToggleCarton(item.id)} onStatusChange={(status) => handleStatusChange(item.id, status)} onDelete={() => handleDeleteItem(item.id)} /> ))} ); };