diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index d9038162..aed004a2 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -5,6 +5,7 @@ import { db, Item } from '@/lib/db'; import { inventoryApi } from '@/lib/api'; import { fetchAndCacheItems, syncOfflineOperations } from '@/lib/sync'; import { useScanner } from '@/hooks/useScanner'; +import { useStockAdjustment } from '@/hooks/useStockAdjustment'; import Scanner from '@/components/Scanner'; import AIOnboarding from '@/components/AIOnboarding'; import PageShell from '@/components/PageShell'; @@ -53,8 +54,6 @@ export default function Home() { const [boxMatches, setBoxMatches] = useState([]); const [isEditing, setIsEditing] = useState(false); const [editedItem, setEditedItem] = useState>({}); - const [adjustQty, setAdjustQty] = useState(1); - const [adjustType, setAdjustType] = useState<'ADD' | 'REMOVE' | 'TRASH'>('ADD'); const [trashReason, setTrashReason] = useState('Damaged'); const [syncing, setSyncing] = useState(false); const [currentUser, setCurrentUser] = useState(null); @@ -91,6 +90,22 @@ export default function Home() { } }); + const { + adjustQty, + setAdjustQty, + adjustType, + setAdjustType, + handleAdjustStock + } = useStockAdjustment({ + selectedItem, + isOnline, + currentUser, + onAdjustmentComplete: () => { + setSelectedItem(null); + }, + onInventoryUpdate: setInventory + }); + useEffect(() => { if (!localStorage.getItem('inventory_token')) { window.location.href = '/login'; @@ -291,50 +306,6 @@ export default function Home() { } }; - const handleAdjustStock = async () => { - if (!selectedItem) return; - - const toastId = toast.loading("Processing..."); - - try { - const finalAdjustQty = adjustQty; - const newQty = selectedItem.quantity + (adjustType === 'ADD' ? adjustQty : -adjustQty); - - // 1. Create a unique ID for this operation to prevent double-counting on server - const operationId = crypto.randomUUID(); - - // 2. Queue local operation - const opType = adjustType === 'ADD' ? 'CHECK_IN' : (adjustType === 'TRASH' ? 'TRASH' : 'CHECK_OUT'); - - await db.pendingOperations.add({ - type: opType as any, - barcode: selectedItem.barcode, - quantity: finalAdjustQty, - timestamp: Date.now(), - synced: 0, - // We add this to our DB even if it doesn't have it yet, Dexie handles it - uuid: operationId - } as any); - - // 3. Update local UI & DB - await db.items.update(selectedItem.id!, { quantity: newQty }); - setInventory(prev => prev.map(i => i.id === selectedItem.id ? { ...i, quantity: newQty } : i)); - - // 4. Trigger Sync - if (isOnline) { - await handleSync(); - toast.success("Inventory updated & synced", { id: toastId }); - } else { - toast.success("Saved locally (Offline)", { id: toastId }); - } - - setSelectedItem(null); - setAdjustQty(1); - } catch (error: any) { - console.error("Adjustment failure:", error); - toast.error("Error saving operation", { id: toastId }); - } - }; const [searchQuery, setSearchQuery] = useState(''); diff --git a/frontend/hooks/useStockAdjustment.ts b/frontend/hooks/useStockAdjustment.ts new file mode 100644 index 00000000..3b7aca0a --- /dev/null +++ b/frontend/hooks/useStockAdjustment.ts @@ -0,0 +1,86 @@ +import { useState, useCallback } from 'react'; +import { db, Item } from '@/lib/db'; +import { toast } from 'react-hot-toast'; +import { syncOfflineOperations } from '@/lib/sync'; + +interface UseStockAdjustmentOptions { + selectedItem: Item | null; + isOnline: boolean; + currentUser: any; + onAdjustmentComplete?: () => void; + onInventoryUpdate?: (items: Item[]) => void; +} + +export function useStockAdjustment(options: UseStockAdjustmentOptions) { + const { selectedItem, isOnline, currentUser, onAdjustmentComplete, onInventoryUpdate } = options; + const [adjustQty, setAdjustQty] = useState(1); + const [adjustType, setAdjustType] = useState<'ADD' | 'REMOVE' | 'TRASH'>('ADD'); + + const handleAdjustStock = useCallback(async () => { + if (!selectedItem) return; + + const toastId = toast.loading("Processing..."); + + try { + const finalAdjustQty = adjustQty; + const newQty = selectedItem.quantity + (adjustType === 'ADD' ? adjustQty : -adjustQty); + + // Create a unique ID for this operation to prevent double-counting on server + const operationId = crypto.randomUUID(); + + // Queue local operation + const opType = adjustType === 'ADD' ? 'CHECK_IN' : (adjustType === 'TRASH' ? 'TRASH' : 'CHECK_OUT'); + + await db.pendingOperations.add({ + type: opType as any, + barcode: selectedItem.barcode, + quantity: finalAdjustQty, + timestamp: Date.now(), + synced: 0, + uuid: operationId + } as any); + + // Update local UI & DB + await db.items.update(selectedItem.id!, { quantity: newQty }); + + // Get updated inventory + const updated = await db.items.toArray(); + if (onInventoryUpdate) { + onInventoryUpdate(updated); + } + + // Trigger Sync + if (isOnline && currentUser) { + try { + const result = await syncOfflineOperations(currentUser.id); + if (result.success > 0) { + toast.success("Inventory updated & synced", { id: toastId }); + } else { + toast.success("Saved locally", { id: toastId }); + } + } catch (syncError) { + console.error("Sync failed:", syncError); + toast.success("Saved locally (Sync pending)", { id: toastId }); + } + } else { + toast.success("Saved locally (Offline)", { id: toastId }); + } + + setAdjustQty(1); + if (onAdjustmentComplete) { + onAdjustmentComplete(); + } + } catch (error: any) { + console.error("Adjustment failure:", error); + toast.error("Error saving operation", { id: toastId }); + } + }, [selectedItem, adjustQty, adjustType, isOnline, currentUser, onAdjustmentComplete, onInventoryUpdate]); + + return { + adjustQty, + setAdjustQty, + adjustType, + setAdjustType, + handleAdjustStock + }; +}