Build [v.1.3.6]
This commit is contained in:
@@ -157,8 +157,8 @@ export default function AdminPage() {
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('inventory_user');
|
||||
window.location.href = '/';
|
||||
import('@/lib/auth').then(m => m.clearAuth());
|
||||
window.location.href = '/login';
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function InventoryPage() {
|
||||
setInventory(res);
|
||||
// Sync local DB
|
||||
await db.items.clear();
|
||||
await db.items.bulkAdd(res);
|
||||
await db.items.bulkPut(res);
|
||||
} catch (err) {
|
||||
console.error("Failed to load backend data", err);
|
||||
}
|
||||
@@ -189,11 +189,17 @@ export default function InventoryPage() {
|
||||
inventory.some(i => i.category === c && i.name.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
);
|
||||
|
||||
// Extract unique item types for suggestions
|
||||
const existingTypes = Array.from(new Set(inventory.map(i => i.type).filter(Boolean))).sort() as string[];
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<div className="p-3 md:p-8 max-w-4xl mx-auto space-y-6">
|
||||
<datalist id="existing-types">
|
||||
{existingTypes.map(t => <option key={t} value={t} />)}
|
||||
</datalist>
|
||||
|
||||
<header className="max-w-4xl mx-auto w-full mb-8">
|
||||
<h1 className="text-2xl font-black flex items-center gap-3">
|
||||
@@ -402,6 +408,7 @@ export default function InventoryPage() {
|
||||
<label className="text-xs font-black text-slate-500 ml-1">Item Type</label>
|
||||
<input
|
||||
type="text"
|
||||
list="existing-types"
|
||||
value={editedItem.type || ''}
|
||||
onChange={e => setEditedItem({...editedItem, type: 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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef, memo } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { User, Shield, X, Lock, ChevronRight } from 'lucide-react';
|
||||
import { inventoryApi } from '@/lib/api';
|
||||
import { saveToken } from '@/lib/auth';
|
||||
@@ -56,15 +56,8 @@ export default function LoginPage() {
|
||||
password
|
||||
});
|
||||
|
||||
console.log('[LoginPage] Login response received:', {
|
||||
access_token: tokenResponse.access_token?.substring(0, 20),
|
||||
user_id: tokenResponse.user_id,
|
||||
username: tokenResponse.username
|
||||
});
|
||||
|
||||
// Save JWT token and user info
|
||||
saveToken(tokenResponse);
|
||||
console.log('[LoginPage] saveToken called. Checking localStorage:', localStorage.getItem('inventory_token')?.substring(0, 20));
|
||||
toast.success(`Welcome back, ${tokenResponse.username}`);
|
||||
|
||||
// Delay slightly to show toast then redirect
|
||||
|
||||
@@ -13,6 +13,8 @@ export default function LogsPage() {
|
||||
const [inventory, setInventory] = useState<Item[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterAction, setFilterAction] = useState('ALL');
|
||||
const [selectedLog, setSelectedLog] = useState<any | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
@@ -26,7 +28,7 @@ export default function LogsPage() {
|
||||
setInventory(cached);
|
||||
|
||||
// Fetch fresh logs
|
||||
const logs = await inventoryApi.getAuditLogs(50);
|
||||
const logs = await inventoryApi.getAuditLogs(100);
|
||||
setAuditLogs(logs);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -37,101 +39,242 @@ export default function LogsPage() {
|
||||
|
||||
const filteredLogs = auditLogs.filter(log => {
|
||||
const itemName = inventory.find(i => i.id === log.target_item_id)?.name || '';
|
||||
return (
|
||||
itemName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
log.action.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(log.username || '').toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
const matchesSearch = itemName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
log.action.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(log.username || '').toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesAction = filterAction === 'ALL' || log.action.includes(filterAction);
|
||||
|
||||
return matchesSearch && matchesAction;
|
||||
});
|
||||
|
||||
// Calculate stats
|
||||
const totalCount = auditLogs.length;
|
||||
const inCount = auditLogs.filter(l => l.action.includes('IN')).length;
|
||||
const outCount = auditLogs.filter(l => l.action.includes('OUT') || l.action.includes('TRASH')).length;
|
||||
|
||||
const mostActiveUser = auditLogs.length > 0 ?
|
||||
Object.entries(auditLogs.reduce((acc: any, curr) => {
|
||||
acc[curr.username] = (acc[curr.username] || 0) + 1;
|
||||
return acc;
|
||||
}, {})).sort((a: any, b: any) => b[1] - a[1])[0]?.[0] : 'N/A';
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<main className="p-4 md:p-8 max-w-4xl mx-auto space-y-8">
|
||||
<header className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-primary/10 rounded-2xl text-primary border border-primary/20">
|
||||
<History size={24} />
|
||||
<main className="p-4 md:p-8 max-w-5xl mx-auto space-y-12">
|
||||
<header className="space-y-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-4 bg-primary/10 rounded-[2rem] text-primary border border-primary/20 shadow-xl shadow-primary/5">
|
||||
<History size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-black tracking-tight text-white italic">Audit Dashboard</h1>
|
||||
<p className="text-xs text-slate-500 font-bold tracking-widest uppercase mt-1">Real-time Intervention Tracking</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-black tracking-tight text-white">Audit History</h1>
|
||||
<p className="text-xs text-slate-500 font-bold">Centralized Transaction Logs</p>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={loadData}
|
||||
className="px-4 py-2 bg-slate-900 border border-slate-800 text-slate-400 hover:text-white rounded-xl text-xs font-black transition-all active:scale-95"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative group">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-500 group-focus-within:text-primary transition-colors" size={18} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search logs by item, action or user..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-slate-900/50 border border-slate-800 focus:border-primary/50 rounded-2xl py-4 pl-12 pr-4 text-sm text-white placeholder:text-slate-600 outline-none transition-all shadow-inner"
|
||||
/>
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="bg-slate-900/40 border border-slate-800/50 p-6 rounded-[2rem] space-y-2">
|
||||
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Total Events</p>
|
||||
<p className="text-3xl font-black text-white tabular-nums">{totalCount}</p>
|
||||
</div>
|
||||
<div className="bg-slate-900/40 border border-slate-800/50 p-6 rounded-[2rem] space-y-2">
|
||||
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Flow In</p>
|
||||
<p className="text-3xl font-black text-green-500 tabular-nums">{inCount}</p>
|
||||
</div>
|
||||
<div className="bg-slate-900/40 border border-slate-800/50 p-6 rounded-[2rem] space-y-2">
|
||||
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Flow Out</p>
|
||||
<p className="text-3xl font-black text-rose-500 tabular-nums">{outCount}</p>
|
||||
</div>
|
||||
<div className="bg-slate-900/40 border border-slate-800/50 p-6 rounded-[2rem] space-y-2">
|
||||
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Top Operator</p>
|
||||
<p className="text-xl font-black text-primary truncate" title={mostActiveUser}>{mostActiveUser}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="relative group flex-1">
|
||||
<Search className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-500 group-focus-within:text-primary transition-colors" size={20} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search logs by item name, user, or action details..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-slate-950/50 border border-slate-900 focus:border-primary/50 rounded-3xl py-5 pl-14 pr-6 text-sm text-white placeholder:text-slate-700 outline-none transition-all shadow-2xl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 p-1.5 bg-slate-900/50 border border-slate-900 rounded-[1.5rem] overflow-x-auto no-scrollbar">
|
||||
{['ALL', 'CHECK_IN', 'CHECK_OUT', 'TRASH', 'CREATE'].map(action => (
|
||||
<button
|
||||
key={action}
|
||||
onClick={() => setFilterAction(action)}
|
||||
className={cn(
|
||||
"px-4 py-2.5 rounded-xl text-[10px] font-black transition-all whitespace-nowrap",
|
||||
filterAction === action
|
||||
? "bg-primary text-white shadow-lg shadow-primary/20"
|
||||
: "text-slate-500 hover:text-slate-300 hover:bg-slate-800"
|
||||
)}
|
||||
>
|
||||
{action.replace('_', ' ')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="space-y-4">
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center justify-center p-20 text-slate-500 animate-pulse">
|
||||
<History size={48} className="opacity-20 mb-4" />
|
||||
<p className="text-xs font-black">Retrieving Logs From Cloud...</p>
|
||||
<div className="flex flex-col items-center justify-center py-32 text-slate-600 gap-4 animate-pulse">
|
||||
<div className="w-12 h-12 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
|
||||
<p className="text-[10px] uppercase font-black tracking-widest">Securing Audit Stream...</p>
|
||||
</div>
|
||||
) : filteredLogs.length === 0 ? (
|
||||
<div className="bg-slate-900/30 border border-slate-800 border-dashed rounded-[2.5rem] p-16 flex flex-col items-center justify-center text-center gap-4">
|
||||
<div className="w-16 h-16 bg-slate-800 rounded-full flex items-center justify-center text-slate-600">
|
||||
<Search size={32} />
|
||||
<div className="bg-slate-900/20 border border-slate-800/50 border-dashed rounded-[3rem] py-24 flex flex-col items-center justify-center text-center gap-6">
|
||||
<div className="w-20 h-20 bg-slate-900 rounded-[2rem] flex items-center justify-center text-slate-700 border border-slate-800 shadow-inner">
|
||||
<Search size={40} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-lg font-bold text-slate-300">No transactions found</p>
|
||||
<p className="text-sm text-slate-500">Try a different search term or check back later</p>
|
||||
<p className="text-xl font-black text-slate-300">No interventions found</p>
|
||||
<p className="text-xs text-slate-600 font-bold mt-2">Try adjusting your filters or search query</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-3">
|
||||
<div className="grid gap-4">
|
||||
{filteredLogs.map((log) => (
|
||||
<div key={log.id} className="bg-slate-900/40 border border-slate-800/50 p-5 rounded-3xl flex items-center justify-between gap-4 hover:bg-slate-900/60 transition-colors group">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<button
|
||||
key={log.id}
|
||||
onClick={() => setSelectedLog(log)}
|
||||
className="w-full text-left bg-slate-900/30 border border-slate-800/40 p-6 rounded-[2.5rem] flex flex-col sm:flex-row sm:items-center justify-between gap-6 hover:bg-slate-900/60 hover:border-slate-700/50 transition-all group active:scale-[0.99] relative overflow-hidden"
|
||||
>
|
||||
<div className="flex-1 min-w-0 z-10">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className={cn(
|
||||
"text-xs font-black px-2 py-0.5 rounded-md",
|
||||
log.action.includes('CHECK_IN') ? "bg-green-500/10 text-green-500" :
|
||||
(log.action.includes('TRASH') ? "bg-rose-500/10 text-rose-500" : "bg-amber-500/10 text-amber-500")
|
||||
"text-[10px] font-black px-3 py-1 rounded-full border shadow-sm",
|
||||
log.action.includes('CHECK_IN') ? "bg-green-500/5 text-green-500 border-green-500/20" :
|
||||
(log.action.includes('TRASH') ? "bg-rose-500/5 text-rose-500 border-rose-500/20" :
|
||||
(log.action.includes('CREATE') ? "bg-indigo-500/5 text-indigo-400 border-indigo-500/20" : "bg-amber-500/5 text-amber-500 border-amber-500/20"))
|
||||
)}>
|
||||
{log.action}
|
||||
{log.action.replace('_', ' ')}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1 h-1 rounded-full bg-slate-700" />
|
||||
<span className="text-[10px] font-black text-slate-500 uppercase tracking-tight">{log.username || 'System'}</span>
|
||||
</div>
|
||||
<span className="text-xs font-bold text-slate-600">by</span>
|
||||
<span className="text-xs font-black text-slate-400">{log.username || 'System'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-base font-bold text-slate-100 group-hover:text-primary transition-colors">
|
||||
{inventory.find(i => i.id === log.target_item_id)?.name || `Item #${log.target_item_id}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-3">
|
||||
<div className="text-xs text-slate-600 font-mono flex items-center gap-1.5">
|
||||
<div className="w-1 h-1 rounded-full bg-slate-700" />
|
||||
{new Date(log.timestamp).toLocaleString()}
|
||||
|
||||
<h3 className="text-lg font-black text-white group-hover:text-primary transition-colors truncate">
|
||||
{inventory.find(i => i.id === log.target_item_id)?.name || `Item #${log.target_item_id}`}
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-4 mt-4">
|
||||
<div className="text-[10px] text-slate-500 font-mono flex items-center gap-2 bg-slate-950/50 px-3 py-1.5 rounded-xl border border-slate-800/50">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-primary/40" />
|
||||
{new Date(log.timestamp).toLocaleDateString()} · {new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</div>
|
||||
{log.details && (
|
||||
<span className="text-xs bg-slate-800/50 text-slate-500 px-3 py-1 rounded-full border border-slate-700/50 font-mono">
|
||||
{log.details}
|
||||
</span>
|
||||
<div className="text-[10px] font-bold text-slate-400 truncate max-w-[200px] italic opacity-60">
|
||||
"{log.details}"
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className={cn(
|
||||
"text-2xl font-black tabular-nums group-hover:scale-110 transition-transform",
|
||||
log.quantity_change > 0 ? "text-green-400" : "text-rose-400"
|
||||
|
||||
<div className="shrink-0 text-right z-10">
|
||||
<div className={cn(
|
||||
"text-4xl font-black tabular-nums group-hover:scale-110 transition-transform flex items-center justify-end gap-1",
|
||||
log.quantity_change > 0 ? "text-green-500" : (log.quantity_change < 0 ? "text-rose-500" : "text-indigo-400")
|
||||
)}>
|
||||
{log.quantity_change > 0 ? '+' : ''}{log.quantity_change}
|
||||
</p>
|
||||
{log.quantity_change > 0 ? '+' : ''}{log.quantity_change === 0 ? '±' : log.quantity_change}
|
||||
</div>
|
||||
<p className="text-[10px] font-black text-slate-600 uppercase tracking-widest mt-1">Quantity</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Glass background effect */}
|
||||
<div className="absolute inset-x-0 bottom-0 h-1 bg-gradient-to-r from-transparent via-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Selected Log Modal */}
|
||||
{selectedLog && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-xl animate-in fade-in duration-300">
|
||||
<div className="bg-slate-900 border border-slate-800 rounded-[3rem] p-10 max-w-lg w-full shadow-2xl space-y-8 animate-in zoom-in-95 duration-300">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="space-y-1">
|
||||
<div className={cn(
|
||||
"text-[10px] font-black px-4 py-1.5 rounded-full border inline-block",
|
||||
selectedLog.action.includes('CHECK_IN') ? "bg-green-500/10 text-green-500 border-green-500/30" :
|
||||
(selectedLog.action.includes('TRASH') ? "bg-rose-500/10 text-rose-500 border-rose-500/30" : "bg-amber-500/10 text-amber-500 border-amber-500/30")
|
||||
)}>
|
||||
{selectedLog.action}
|
||||
</div>
|
||||
<h2 className="text-3xl font-black text-white tracking-tight leading-tight pt-2">
|
||||
{inventory.find(i => i.id === selectedLog.target_item_id)?.name || `Item #${selectedLog.target_item_id}`}
|
||||
</h2>
|
||||
</div>
|
||||
<button onClick={() => setSelectedLog(null)} className="p-3 hover:bg-slate-800 rounded-2xl text-slate-500 transition-colors border border-slate-800">
|
||||
<X size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-1 bg-slate-950/50 p-4 rounded-2xl border border-slate-800">
|
||||
<p className="text-[10px] font-black text-slate-600 uppercase">Operator</p>
|
||||
<p className="text-sm font-black text-white">{selectedLog.username || 'System Profile'}</p>
|
||||
</div>
|
||||
<div className="space-y-1 bg-slate-950/50 p-4 rounded-2xl border border-slate-800">
|
||||
<p className="text-[10px] font-black text-slate-600 uppercase">Delta</p>
|
||||
<p className={cn(
|
||||
"text-xl font-black",
|
||||
selectedLog.quantity_change > 0 ? "text-green-500" : "text-rose-500"
|
||||
)}>
|
||||
{selectedLog.quantity_change > 0 ? '+' : ''}{selectedLog.quantity_change} Units
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-black text-slate-600 uppercase">Timestamp</p>
|
||||
<p className="text-sm font-bold text-slate-300 bg-slate-800/30 p-4 rounded-2xl border border-slate-800/50">
|
||||
{new Date(selectedLog.timestamp).toLocaleString(undefined, { dateStyle: 'full', timeStyle: 'medium' })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{selectedLog.details && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-black text-slate-600 uppercase">Intervention Details</p>
|
||||
<div className="bg-primary/5 text-primary/80 p-6 rounded-[2rem] border border-primary/10 text-sm font-bold leading-relaxed italic">
|
||||
"{selectedLog.details}"
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setSelectedLog(null)}
|
||||
className="w-full bg-slate-800 hover:bg-slate-700 text-white font-black py-5 rounded-[2rem] transition-all active:scale-95 border border-slate-700"
|
||||
>
|
||||
Close Insights
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</PageShell>
|
||||
);
|
||||
|
||||
@@ -71,17 +71,25 @@ export default function Home() {
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('inventory_token')) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
setMounted(true);
|
||||
const savedUser = localStorage.getItem('inventory_user');
|
||||
if (savedUser) {
|
||||
setCurrentUser(JSON.parse(savedUser));
|
||||
}
|
||||
|
||||
|
||||
// Initial categories fetch
|
||||
inventoryApi.getCategories().then(c => setCategories(c)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('inventory_token')) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
setMounted(true);
|
||||
setIsOnline(navigator.onLine);
|
||||
const handleOnline = () => {
|
||||
@@ -366,11 +374,18 @@ export default function Home() {
|
||||
);
|
||||
});
|
||||
|
||||
// Extract unique item types for suggestions
|
||||
const existingTypes = Array.from(new Set(inventory.map(i => i.type).filter(Boolean))).sort() as string[];
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<div className="p-3 md:p-8 overflow-x-hidden w-full">
|
||||
{/* Search datalist for types */}
|
||||
<datalist id="existing-types">
|
||||
{existingTypes.map(t => <option key={t} value={t} />)}
|
||||
</datalist>
|
||||
|
||||
{/* Header */}
|
||||
<header className="flex flex-col sm:flex-row justify-between sm:items-center gap-4 mb-6 w-full max-w-4xl mx-auto px-1">
|
||||
@@ -487,7 +502,7 @@ export default function Home() {
|
||||
className="w-full h-16 rounded-[1.5rem] bg-slate-900/50 border border-slate-800 flex items-center justify-center gap-3 group hover:border-primary/40 transition-all font-bold"
|
||||
>
|
||||
<Sparkles size={18} className="text-primary group-hover:scale-110 transition-transform" />
|
||||
<span className="text-sm">Add New Item (AI Onboarding)</span>
|
||||
<span className="text-sm">Add NEW Item<br />(AI Onboarding)</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -498,6 +513,7 @@ export default function Home() {
|
||||
{showOnboarding && (
|
||||
<AIOnboarding
|
||||
categories={categories}
|
||||
inventory={inventory}
|
||||
onCancel={() => setShowOnboarding(false)}
|
||||
onComplete={handleOnboardingComplete}
|
||||
/>
|
||||
@@ -587,6 +603,7 @@ export default function Home() {
|
||||
<label className="text-xs font-black text-slate-500 ml-1">Item Type (e.g. SFP, Patch Cord)</label>
|
||||
<input
|
||||
type="text"
|
||||
list="existing-types"
|
||||
value={editedItem.type || ''}
|
||||
onChange={e => setEditedItem({...editedItem, type: 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"
|
||||
|
||||
Reference in New Issue
Block a user