'use client'; import { useState, useEffect, useCallback } from 'react'; import { db, Item } from '@/lib/db'; import { inventoryApi } from '@/lib/api'; import { fetchAndCacheItems } from '@/lib/sync'; import { useScanner } from '@/hooks/useScanner'; import { useStockAdjustment } from '@/hooks/useStockAdjustment'; import { useSync } from '@/hooks/useSync'; import Scanner from '@/components/Scanner'; import AIOnboarding from '@/components/AIOnboarding'; import PageShell from '@/components/PageShell'; import ItemComparisonModal from '@/components/ItemComparisonModal'; import StockAdjustmentPanel from '@/components/StockAdjustmentPanel'; import ScannerSection from '@/components/ScannerSection'; import SearchModal from '@/components/inventory/SearchModal'; import { toast } from 'react-hot-toast'; import { Package, X, ChevronRight, RefreshCw, Search } from 'lucide-react'; import { cn } from '@/lib/utils'; import versionData from '../VERSION.json'; export default function Home() { const [mounted, setMounted] = useState(false); const [isOnline, setIsOnline] = useState(true); const [inventory, setInventory] = useState([]); const [showOnboarding, setShowOnboarding] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const [boxMatches, setBoxMatches] = useState([]); const [currentUser, setCurrentUser] = useState(null); const [categories, setCategories] = useState([]); const [comparisonModal, setComparisonModal] = useState<{ show: boolean, newItem: any, existingItem: any, existingId: number | null }>({ show: false, newItem: null, existingItem: null, existingId: null }); const [comparisonLoading, setComparisonLoading] = useState(false); const [showSearch, setShowSearch] = useState(false); const { syncing, handleSync } = useSync({ isOnline, currentUser, onInventoryUpdate: setInventory }); const { mode, setMode, showScanner, setShowScanner, isScannerReady, fieldScanning, setFieldScanning, onScanSuccess, onOCRMatch, } = useScanner({ inventory, isOnline, onSync: handleSync, onMatchFound: (item, adjustType) => { setSelectedItem(item); setAdjustType(adjustType); }, onMultipleMatches: (items) => { setBoxMatches(items); }, onFieldCapture: (field, value) => { if (field === 'box_label') { // Handle via editedItem logic in panels if needed } } }); 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'; return; } setMounted(true); setIsOnline(navigator.onLine); const savedUser = localStorage.getItem('inventory_user'); if (savedUser) { setCurrentUser(JSON.parse(savedUser)); } inventoryApi.getCategories().then(c => setCategories(c)).catch(() => { }); loadInventory(); const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); const interval = setInterval(() => { setIsOnline(navigator.onLine); if (navigator.onLine) { loadInventory(); } }, 30000); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); clearInterval(interval); }; }, []); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); setShowSearch(!showSearch); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [showSearch]); const loadInventory = async () => { const cached = await db.items.toArray(); setInventory(cached); if (navigator.onLine) { const fresh = await fetchAndCacheItems(); setInventory(fresh); } }; const handleOnboardingComplete = async (itemData: any) => { try { if (itemData.part_number) { itemData.part_number = itemData.part_number.toLowerCase(); } const backendData = { ...itemData }; delete backendData.extractedImageBlob; if (itemData.extractedImageBlob) { const reader = new FileReader(); reader.readAsDataURL(itemData.extractedImageBlob); await new Promise((resolve, reject) => { reader.onload = () => { const base64 = (reader.result as string).split(',')[1]; backendData.extracted_image_bytes = base64; backendData.image_processing = itemData.imageProcessing; delete backendData.imageProcessing; resolve(null); }; reader.onerror = reject; }); } await db.items.add(itemData); if (isOnline && currentUser) { try { await inventoryApi.createItem(currentUser.id, backendData); toast.success("Item saved to cloud catalog!"); setShowOnboarding(false); await loadInventory(); } catch (err: any) { const status = err.response?.status; const responseData = err.response?.data; if (status === 409 && responseData?.detail) { const detail = responseData.detail; setComparisonModal({ show: true, newItem: backendData, existingItem: detail.existing_item, existingId: detail.existing_id }); return; } else { console.error("Cloud sync failed:", err.message); toast.error("Item saved locally. Cloud sync failed."); setShowOnboarding(false); await loadInventory(); } } } else { toast.success("Saved locally (Offline mode)"); setShowOnboarding(false); await loadInventory(); } } catch (error) { console.error(error); toast.error("Failed to save item"); } }; const handleComparisonUpdate = async () => { if (!comparisonModal.existingId) return; setComparisonLoading(true); try { const updateData = { ...comparisonModal.newItem }; if (updateData.extractedImageBlob) { const reader = new FileReader(); reader.readAsDataURL(updateData.extractedImageBlob); await new Promise((resolve, reject) => { reader.onload = () => { const base64 = (reader.result as string).split(',')[1]; updateData.extracted_image_bytes = base64; updateData.image_processing = updateData.imageProcessing; delete updateData.extractedImageBlob; delete updateData.imageProcessing; resolve(null); }; reader.onerror = reject; }); } await inventoryApi.updateItem(comparisonModal.existingId, updateData); toast.success("Item updated successfully!"); setComparisonModal({ show: false, newItem: null, existingItem: null, existingId: null }); setShowOnboarding(false); await loadInventory(); } catch (err: any) { console.error("Update failed:", err.message); toast.error("Failed to update item"); } finally { setComparisonLoading(false); } }; const handleComparisonSkip = () => { setComparisonModal({ show: false, newItem: null, existingItem: null, existingId: null }); setShowOnboarding(false); toast("Local copy saved.", { icon: '💾' }); loadInventory(); }; if (!mounted) return null; return (
{/* Header */}
TFM aInventory

TFM aInventory

Industrial Asset Management

{isScannerReady && (
Scanner: Ready
)}
Sync: {isOnline ? 'Active' : 'Offline'}
setMode(newMode as any)} showScanner={showScanner} onShowScanner={setShowScanner} onScanSuccess={onScanSuccess} onOCRMatch={onOCRMatch} onAddItemClick={() => setShowOnboarding(true)} /> {/* Overlays & Modals */} {showOnboarding && ( setShowOnboarding(false)} onComplete={handleOnboardingComplete} /> )} setShowSearch(false)} onSelectItem={(item) => { setSelectedItem(item); setShowSearch(false); }} /> {/* TODO: Fix StockAdjustmentPanel prop types setSelectedItem(null)} onQuantityChange={setAdjustQty} onTypeChange={setAdjustType} onAdjustStock={handleAdjustStock} /> */} {boxMatches.length > 0 && !selectedItem && (

Box Contents

Select item for {mode.toLowerCase().replace('_', ' ')}

{boxMatches.map(item => ( ))}
)}
); }