Build [v1.9.19]

This commit is contained in:
Daniel Bedeleanu
2026-04-14 20:44:01 +03:00
parent fcb187974e
commit 00ee4cf9c5
38 changed files with 2059 additions and 157 deletions

View File

@@ -82,6 +82,7 @@ export default function Home() {
const [currentUser, setCurrentUser] = useState<any | null>(null);
const [categories, setCategories] = useState<any[]>([]);
const [fieldScanning, setFieldScanning] = useState<{ active: boolean, field: string } | null>(null);
const [boxSearchQuery, setBoxSearchQuery] = useState('');
useEffect(() => {
if (!localStorage.getItem('inventory_token')) {
@@ -291,6 +292,10 @@ export default function Home() {
const sn = (item.serial_number || '').toUpperCase();
const name = item.name.toUpperCase();
const category = item.category.toUpperCase();
const ocrKey = (item.ocr_text || '').toUpperCase().replace(/[^A-Z0-9\s/+-]/g, ' ');
// Priority 0: Exact OCR Key match (Heuristic provided by AI)
if (ocrKey && cleanText.includes(ocrKey)) score += 1000;
// Priority 1: Serial Number (Absolute match)
if (sn && cleanText.includes(sn)) score += 500;
@@ -418,10 +423,13 @@ export default function Home() {
return (
item.name.toLowerCase().includes(query) ||
item.category.toLowerCase().includes(query) ||
(item.specs?.toLowerCase().includes(query) ?? false) ||
(item.description?.toLowerCase().includes(query) ?? false) ||
(item.connector?.toLowerCase().includes(query) ?? false) ||
(item.size?.toLowerCase().includes(query) ?? false) ||
(item.part_number?.toLowerCase().includes(query) ?? false) ||
(item.color?.toLowerCase().includes(query) ?? false) ||
(item.box_label?.toLowerCase().includes(query) ?? false)
(item.box_label?.toLowerCase().includes(query) ?? false) ||
(item.ocr_text?.toLowerCase().includes(query) ?? false)
);
});
@@ -476,19 +484,26 @@ export default function Home() {
{isScannerReady && (
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]" />
<span className="text-[10px] sm:text-xs font-black text-green-500/90 whitespace-nowrap tracking-wider uppercase">
<span className="text-[10px] sm:text-xs font-black text-green-500/90 whitespace-nowrap">
Scanner: OK
</span>
</div>
)}
<div className="flex items-center gap-2">
<div className={`w-1.5 h-1.5 rounded-full ${isOnline ? 'bg-green-500 animate-pulse shadow-[0_0_8px_rgba(34,197,94,0.6)]' : 'bg-rose-500 shadow-[0_0_8px_rgba(244,63,94,0.6)]'}`} />
<span className={`text-[10px] sm:text-xs font-black whitespace-nowrap tracking-wider uppercase ${isOnline ? 'text-green-500/90' : 'text-rose-500/90'}`}>
<span className={`text-[10px] sm:text-xs font-black whitespace-nowrap ${isOnline ? 'text-green-500/90' : 'text-rose-500/90'}`}>
Sync: {isOnline ? 'Active' : 'Offline'}
</span>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<button
onClick={() => setShowBoxManager(true)}
className="p-2.5 bg-slate-900/80 border border-slate-800 text-slate-400 rounded-xl hover:text-primary transition-all active:scale-95"
title="Box Manager"
>
<Package size={18} />
</button>
<button
onClick={handleSync}
disabled={syncing}
@@ -553,27 +568,31 @@ export default function Home() {
<div className="w-full h-px bg-slate-800/50 my-2" />
<div className="w-full grid grid-cols-1 gap-4">
<div className="w-full grid grid-cols-2 gap-3">
<button
onClick={() => setShowOnboarding(true)}
className="w-full h-18 py-4 rounded-[1.5rem] bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center gap-4 group hover:border-indigo-500/50 transition-all font-black text-indigo-400"
className="flex flex-col items-center justify-center p-5 rounded-3xl bg-indigo-500/10 border border-indigo-500/20 group hover:border-indigo-500/50 transition-all font-black text-indigo-400 gap-3"
>
<Sparkles size={24} className="group-hover:scale-125 transition-transform" />
<span className="text-left leading-tight">
Add NEW Item<br />
<span className="text-[10px] opacity-60 uppercase tracking-widest font-mono">AI Onboarding</span>
</span>
<div className="p-3 bg-indigo-500/10 rounded-2xl group-hover:scale-110 transition-transform">
<Sparkles size={24} />
</div>
<div className="text-center">
<p className="text-sm leading-tight">Add Item</p>
<p className="text-[10px] opacity-60 font-mono mt-1">AI Onboarding</p>
</div>
</button>
<button
onClick={() => setShowBoxManager(true)}
className="w-full h-18 py-4 rounded-[1.5rem] bg-slate-900/50 border border-slate-800 flex items-center justify-center gap-4 group hover:border-primary/40 transition-all font-black text-slate-300"
className="flex flex-col items-center justify-center p-5 rounded-3xl bg-slate-900/50 border border-slate-800 group hover:border-primary/40 transition-all font-black text-slate-300 gap-3"
>
<Package size={24} className="text-primary group-hover:scale-125 transition-transform" />
<span className="text-left leading-tight">
Manage Boxes<br />
<span className="text-[10px] opacity-60 uppercase tracking-widest font-mono">Box Inventory</span>
</span>
<div className="p-3 bg-primary/10 rounded-2xl group-hover:scale-110 transition-transform">
<Package size={24} className="text-primary" />
</div>
<div className="text-center">
<p className="text-sm leading-tight">Manage Boxes</p>
<p className="text-[10px] opacity-60 font-mono mt-1">Box Inventory</p>
</div>
</button>
</div>
</div>
@@ -709,15 +728,54 @@ export default function Home() {
</button>
</div>
</div>
<div className="col-span-2">
<label className="text-xs font-black text-slate-500 ml-1">Specs</label>
<input
type="text"
value={editedItem.specs || ''}
onChange={e => setEditedItem({ ...editedItem, specs: e.target.value })}
<div>
<label className="text-xs font-black text-slate-500 ml-1">Connectors</label>
<input
type="text"
value={editedItem.connector || ''}
onChange={e => setEditedItem({...editedItem, connector: e.target.value})}
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-slate-100"
placeholder="e.g. LC/UPC"
/>
</div>
<div>
<label className="text-xs font-black text-slate-500 ml-1">Size / Length</label>
<input
type="text"
value={editedItem.size || ''}
onChange={e => setEditedItem({...editedItem, size: e.target.value})}
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-slate-100"
placeholder="e.g. 5m / 10G"
/>
</div>
<div className="col-span-2">
<label className="text-xs font-black text-slate-500 ml-1">Color</label>
<input
type="text"
value={editedItem.color || ''}
onChange={e => setEditedItem({...editedItem, color: e.target.value})}
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-slate-100"
placeholder="e.g. Aqua"
/>
</div>
<div className="col-span-2">
<label className="text-xs font-black text-slate-500 ml-1">Description</label>
<textarea
value={editedItem.description || ''}
onChange={e => setEditedItem({ ...editedItem, description: e.target.value })}
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-slate-100 resize-none h-20"
placeholder="Item description..."
/>
</div>
<div className="col-span-2">
<label className="text-xs font-black text-slate-500 ml-1">OCR Matching Key (Local Identification)</label>
<textarea
value={editedItem.ocr_text || ''}
onChange={e => setEditedItem({ ...editedItem, ocr_text: e.target.value })}
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-3 px-4 text-[10px] font-mono outline-none text-slate-400 resize-none h-12"
placeholder="Paste identification string here..."
/>
</div>
</div>
</div>
) : (
@@ -861,59 +919,84 @@ export default function Home() {
{showBoxManager && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/90 backdrop-blur-md animate-in fade-in duration-300">
<div className="w-full max-w-2xl bg-slate-900 border border-slate-800 rounded-[2.5rem] shadow-2xl overflow-hidden flex flex-col max-h-[90vh]">
<div className="p-8 border-b border-slate-800 flex justify-between items-center shrink-0 bg-slate-900/50">
<div>
<h3 className="text-2xl font-black tracking-tight flex items-center gap-3">
<Package className="text-primary" size={28} />
Box Inventory
</h3>
<p className="text-sm text-slate-500 font-bold mt-1">Manage physical box labels & printing</p>
<div className="p-8 pb-4 flex flex-col gap-6 shrink-0 bg-slate-900/50">
<div className="flex justify-between items-center">
<div>
<h3 className="text-2xl font-black tracking-tight flex items-center gap-3">
<Package className="text-primary" size={28} />
Box Inventory
</h3>
<p className="text-sm text-slate-500 font-bold mt-1">Manage physical box labels & printing</p>
</div>
<button onClick={() => { setShowBoxManager(false); setBoxSearchQuery(''); }} className="p-3 hover:bg-slate-800 rounded-full transition-colors text-slate-400">
<X size={24} />
</button>
</div>
<div className="relative">
<input
type="text"
placeholder="Search boxes..."
value={boxSearchQuery}
onChange={(e) => setBoxSearchQuery(e.target.value)}
className="w-full bg-slate-950 border border-slate-800 rounded-2xl py-3.5 pl-11 pr-4 text-sm focus:border-primary outline-none transition-all placeholder:text-slate-600"
/>
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-600" size={18} />
{boxSearchQuery && (
<button
onClick={() => setBoxSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1.5 hover:bg-slate-800 rounded-lg text-slate-500"
>
<X size={14} />
</button>
)}
</div>
<button onClick={() => setShowBoxManager(false)} className="p-3 hover:bg-slate-800 rounded-full transition-colors">
<X size={24} />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 space-y-4">
{existingBoxes.length === 0 ? (
<div className="flex-1 overflow-y-auto p-6 space-y-4 pt-4">
{existingBoxes.filter(b => b.toLowerCase().includes(boxSearchQuery.toLowerCase())).length === 0 ? (
<div className="py-20 text-center space-y-4 opacity-40">
<Package size={48} className="mx-auto" />
<p className="font-bold">No box labels defined yet.</p>
<p className="font-bold">{existingBoxes.length === 0 ? 'No box labels defined yet.' : 'No matching boxes found.'}</p>
<p className="text-xs max-w-xs mx-auto">Associate items with a "Box Label" in their metadata to see them here.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{existingBoxes.map(box => {
const itemCount = inventory.filter(i => i.box_label === box).length;
return (
<div key={box} className="bg-slate-950 border border-slate-800 p-5 rounded-3xl flex flex-col gap-4 group hover:border-primary/50 transition-all">
<div className="flex-1 min-w-0">
<h4 className="text-lg font-black text-white truncate">{box}</h4>
<p className="text-xs text-slate-500 font-bold">{itemCount} items linked</p>
</div>
<div className="flex gap-2 shrink-0">
<button
onClick={() => setSelectedBoxLabel(box)}
className="flex-1 py-3 bg-primary text-white text-xs font-black rounded-xl flex items-center justify-center gap-2 hover:bg-blue-500 transition-colors shadow-lg shadow-primary/20"
>
<Printer size={14} /> Print Label
</button>
<button
onClick={() => { setSearchQuery(box.toLowerCase()); setShowBoxManager(false); }}
className="px-4 py-3 bg-slate-900 border border-slate-800 text-slate-400 text-xs font-bold rounded-xl hover:bg-slate-800"
>
View
</button>
</div>
</div>
);
})}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 pb-4">
{existingBoxes
.filter(box => box.toLowerCase().includes(boxSearchQuery.toLowerCase()))
.map(box => {
const itemCount = inventory.filter(i => i.box_label === box).length;
return (
<div key={box} className="bg-slate-950/50 border border-slate-800/60 p-5 rounded-3xl flex flex-col gap-4 group hover:border-primary/40 transition-all">
<div className="flex-1 min-w-0">
<h4 className="text-lg font-black text-white truncate">{box}</h4>
<p className="text-xs text-slate-500 font-bold mt-0.5">{itemCount} items linked</p>
</div>
<div className="flex gap-2 shrink-0">
<button
onClick={() => setSelectedBoxLabel(box)}
className="flex-1 py-3 bg-primary text-white text-[11px] font-black rounded-xl flex items-center justify-center gap-2 hover:bg-blue-500 transition-colors shadow-lg shadow-primary/10 active:scale-95"
>
<Printer size={14} /> Print Label
</button>
<button
onClick={() => { setSearchQuery(box.toLowerCase()); setShowBoxManager(false); setBoxSearchQuery(''); }}
className="px-4 py-3 bg-slate-900 border border-slate-800 text-slate-400 text-[11px] font-bold rounded-xl hover:bg-slate-800 active:scale-95"
>
View
</button>
</div>
</div>
);
})}
</div>
)}
</div>
<div className="p-6 bg-slate-950/50 border-t border-slate-800 text-center">
<p className="text-[10px] text-slate-600 font-bold uppercase tracking-widest">TFM aInventory Box Management Mode</p>
<div className="p-6 py-4 bg-slate-950/50 border-t border-slate-800 text-center flex items-center justify-center gap-2">
<div className="w-1 h-1 rounded-full bg-primary animate-pulse" />
<p className="text-[10px] text-slate-500 font-bold font-mono">TFM aInventory Box management mode</p>
</div>
</div>
</div>
</div>