Build [v1.4.0] - Audit Dashboard & LDAP Restoration

This commit is contained in:
Daniel Bedeleanu
2026-04-12 09:39:17 +03:00
parent 5cceba21f4
commit f3d861b1a2
23 changed files with 973 additions and 382 deletions

View File

@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
import { db, Item } from '@/lib/db';
import { inventoryApi } from '@/lib/api';
import PageShell from '@/components/PageShell';
import { History, X, Search, Filter } from 'lucide-react';
import { History, X, Search, Filter, Activity, ArrowDownCircle, ArrowUpCircle, User } from 'lucide-react';
import { cn } from '@/lib/utils';
import { fetchAndCacheItems } from '@/lib/sync';
@@ -61,16 +61,16 @@ export default function LogsPage() {
return (
<PageShell>
<main className="p-4 md:p-8 max-w-5xl mx-auto space-y-12">
<main className="p-4 md:p-8 max-w-7xl 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="flex items-center gap-5">
<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>
<h1 className="text-3xl font-black tracking-tight text-white">Audit Dashboard</h1>
<p className="text-xs text-slate-500 font-bold mt-1">Real-time Intervention Tracking</p>
</div>
</div>
@@ -86,21 +86,25 @@ export default function LogsPage() {
{/* 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 className="bg-slate-900/80 border border-slate-700/40 p-2 px-4 rounded-xl flex items-center gap-4 shadow-lg backdrop-blur-sm">
<Activity size={18} className="text-primary shrink-0 opacity-80" />
<p className="text-sm font-bold text-slate-300 whitespace-nowrap">Total Events</p>
<p className="text-xl font-black text-white tabular-nums ml-auto">{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 className="bg-slate-900/80 border border-slate-700/40 p-2 px-4 rounded-xl flex items-center gap-4 shadow-lg backdrop-blur-sm">
<ArrowDownCircle size={18} className="text-green-500 shrink-0 opacity-80" />
<p className="text-sm font-bold text-slate-300 whitespace-nowrap">Check in</p>
<p className="text-xl font-black text-green-500 tabular-nums ml-auto">{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 className="bg-slate-900/80 border border-slate-700/40 p-2 px-4 rounded-xl flex items-center gap-4 shadow-lg backdrop-blur-sm">
<ArrowUpCircle size={18} className="text-rose-500 shrink-0 opacity-80" />
<p className="text-sm font-bold text-slate-300 whitespace-nowrap">Check out</p>
<p className="text-xl font-black text-rose-500 tabular-nums ml-auto">{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 className="bg-slate-900/80 border border-slate-700/40 p-2 px-4 rounded-xl flex items-center gap-4 shadow-lg backdrop-blur-sm overflow-hidden">
<User size={18} className="text-indigo-400 shrink-0 opacity-80" />
<p className="text-sm font-bold text-slate-300 whitespace-nowrap">Top Operator</p>
<p className="text-base font-black text-amber-500 truncate ml-auto" title={mostActiveUser}>{mostActiveUser}</p>
</div>
</div>
@@ -117,18 +121,24 @@ export default function LogsPage() {
</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 => (
{[
{ label: 'All', value: 'ALL' },
{ label: 'Check in', value: 'CHECK_IN' },
{ label: 'Check out', value: 'CHECK_OUT' },
{ label: 'Trash', value: 'TRASH' },
{ label: 'Create', value: 'CREATE' }
].map(action => (
<button
key={action}
onClick={() => setFilterAction(action)}
key={action.value}
onClick={() => setFilterAction(action.value)}
className={cn(
"px-4 py-2.5 rounded-xl text-[10px] font-black transition-all whitespace-nowrap",
filterAction === action
"px-4 py-2.5 rounded-xl text-xs font-bold transition-all whitespace-nowrap",
filterAction === action.value
? "bg-primary text-white shadow-lg shadow-primary/20"
: "text-slate-500 hover:text-slate-300 hover:bg-slate-800"
)}
>
{action.replace('_', ' ')}
{action.label}
</button>
))}
</div>
@@ -139,7 +149,7 @@ export default function LogsPage() {
{loading ? (
<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>
<p className="text-[10px] font-black tracking-widest">Securing Audit Stream...</p>
</div>
) : filteredLogs.length === 0 ? (
<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">
@@ -157,49 +167,45 @@ export default function LogsPage() {
<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"
className="w-full text-left bg-slate-900/30 border border-slate-800/20 p-2 px-4 rounded-xl flex items-center justify-between gap-4 hover:bg-slate-900/60 hover:border-slate-700/50 transition-all group active:scale-[0.99] relative overflow-hidden shadow-sm"
>
<div className="flex-1 min-w-0 z-10">
<div className="flex items-center gap-3 mb-3">
<div className={cn(
"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.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>
<div className="flex-1 min-w-0 z-10 flex items-center gap-4">
{/* Compact Action Badge */}
<div className={cn(
"text-[9px] font-black px-2 py-0.5 rounded-md border min-w-[70px] text-center",
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.replace('_', ' ')}
</div>
<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 className="flex-1 min-w-0">
<h3 className="text-sm font-bold 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 items-center gap-2 opacity-80">
<span className="text-[9px] font-black text-amber-500 tracking-tight">{log.username || 'System'}</span>
<span className="w-1 h-1 rounded-full bg-slate-700" />
<span className="text-[9px] text-slate-500 font-mono">
{new Date(log.timestamp).toLocaleDateString()} · {new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
</div>
{log.details && (
<div className="text-[10px] font-bold text-slate-400 truncate max-w-[200px] italic opacity-60">
"{log.details}"
</div>
)}
</div>
</div>
<div className="shrink-0 text-right z-10">
<div className="shrink-0 flex items-center gap-3 z-10">
{log.details && (
<span className="text-[10px] text-slate-600 italic hidden md:block truncate max-w-[150px]">
"{log.details}"
</span>
)}
<div className={cn(
"text-4xl font-black tabular-nums group-hover:scale-110 transition-transform flex items-center justify-end gap-1",
"text-xl font-black tabular-nums min-w-[40px] text-right",
log.quantity_change > 0 ? "text-green-500" : (log.quantity_change < 0 ? "text-rose-500" : "text-indigo-400")
)}>
{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>
{/* Glass background effect */}
@@ -234,11 +240,11 @@ export default function LogsPage() {
<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-[10px] font-black text-slate-600">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="text-[10px] font-black text-slate-600">Delta</p>
<p className={cn(
"text-xl font-black",
selectedLog.quantity_change > 0 ? "text-green-500" : "text-rose-500"
@@ -250,7 +256,7 @@ export default function LogsPage() {
<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-[10px] font-black text-slate-600">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>
@@ -258,7 +264,7 @@ export default function LogsPage() {
{selectedLog.details && (
<div className="space-y-1">
<p className="text-[10px] font-black text-slate-600 uppercase">Intervention Details</p>
<p className="text-[10px] font-black text-slate-600">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>