refactor(ui): comprehensive light mode fixes and dashboard cleanup

- Light mode: fix 40+ missing CSS overrides (solid emerald/cyan bg-950,
  300-level text colours, border opacity variants, hover states, violet
  accent, bg-slate-900/30 and /90, bg-rose-950/30, red-950/50)
- Light mode: fix broad bg-gradient-to-br override to only target dark
  banner cards (from-[#1E293B]), preserving coloured user avatar gradients
- Light mode: BookingDetailsModal JSON panel switched to GitHub-Light style
  (bg #f6f8fa) including <pre> override so the general 'pre' rule cannot
  darken it back
- Dashboard: simplify banner (flat card, no gradient/watermark/time-widget)
- Dashboard: reduce visual noise (shorter titles, remove LIVE animated badge,
  remove italic notes quote, neutral checklist items, no footer jargon)
- Dashboard: normalise section-icon colours to slate-400 except Active (emerald)
- Dashboard: replace non-standard Tailwind classes (slate-101/350/905/1000,
  indigo-405, emerald-990) with valid equivalents
- Dashboard: standardise button style to rounded-lg + text-xs across
  Active Reservations and Upcoming cards; add visible borders on Cancel/Purge
This commit is contained in:
Brückner
2026-06-05 11:08:34 +02:00
parent 33c7b2ba65
commit 7afb4829bc
2 changed files with 340 additions and 146 deletions

View File

@ -65,10 +65,10 @@ export default function Dashboard({
// Quick state checklist for the user to mark items as done as they test their lab!
// (kept deliberately nerdy - morale is a critical infrastructure dependency)
const [todoList, setTodoList] = useState([
{ id: 't1', text: 'Ping 8.8.8.8 to confirm the Internet still exists', checked: false },
{ id: 't2', text: 'Coffee level nominal ☕ - packets route faster on caffeine', checked: true },
{ id: 't3', text: "Rule out DNS first (narrator: it was, in fact, always DNS)", checked: false },
{ id: 't4', text: 'Layer-1 check: is it actually plugged in? (PEBKAC defense)', checked: false }
{ id: 't1', text: 'Verify network connectivity (ping gateway)', checked: false },
{ id: 't2', text: 'Coffee ready ☕', checked: true },
{ id: 't3', text: 'Check DNS resolution', checked: false },
{ id: 't4', text: 'Confirm physical connections are in place', checked: false }
]);
const toggleTodo = (id: string) => {
@ -91,58 +91,30 @@ export default function Dashboard({
return (
<div className="space-y-6" id="dashboard-cockpit-root">
{/* Banner Card Grid */}
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 bg-gradient-to-br from-[#1E293B] to-[#111827] border border-slate-800 rounded-2xl p-6 shadow-sm overflow-hidden relative">
<div className="absolute top-0 right-0 p-8 text-slate-800 pointer-events-none select-none font-mono text-9xl font-extrabold opacity-10">
NET
</div>
<div className="md:col-span-8 space-y-4">
<h2 className="text-2xl font-bold tracking-tight text-white leading-tight font-sans">
Welcome back, <span className="text-emerald-400">{currentUser.name}</span>!
{/* Banner */}
<div className="bg-[#1E293B] border border-slate-800 rounded-2xl p-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="space-y-1.5">
<h2 className="text-xl font-bold tracking-tight text-white font-sans">
Welcome back, <span className="text-emerald-400">{currentUser.name}</span>
</h2>
<p className="text-xs text-slate-300 leading-relaxed font-sans max-w-xl">
Your lab cockpit. Grab some hardware, block a time slot, and keep the rescue runbooks one click away for when a switch decides to packet-storm itself at 16:59 on a Friday. root@ghostgrid:~# have fun, break things (in the lab).
<p className="text-xs text-slate-400 font-sans">
Your lab management hub. Reserve hardware, track active sessions, and keep runbooks close.
</p>
<div className="pt-2 flex items-center gap-3">
<button
onClick={onNavigateToCalendar}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-slate-950 font-sans font-bold text-xs rounded-lg transition-colors flex items-center gap-1.5 hover:cursor-pointer"
>
<Zap className="w-4 h-4 text-slate-950 fill-slate-950" />
Book Your Lab
</button>
<button
onClick={onNavigateToDevices}
className="px-4 py-2 bg-slate-900 hover:bg-slate-800 text-slate-200 border border-slate-800 hover:border-slate-700 rounded-lg text-xs font-semibold transition-all flex items-center gap-1 hover:cursor-pointer"
>
Browse Inventory
</button>
</div>
</div>
<div className="md:col-span-4 bg-slate-950/60 p-4 rounded-xl border border-slate-850 flex flex-col justify-between font-sans">
<div>
<span className="text-[10px] font-mono uppercase tracking-widest text-slate-500 block">System Time</span>
<div className="text-2xl font-mono text-emerald-400 font-bold mt-1 tabular-nums">
{now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
</div>
<p className="text-[10px] text-slate-400 font-sans mt-0.5">
{now.toLocaleDateString([], { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
</p>
</div>
<div className="mt-4 pt-4 border-t border-slate-850 grid grid-cols-2 gap-2 text-center text-[10px] text-slate-350">
<div className="bg-slate-900 p-2 rounded border border-slate-850">
<span className="block font-bold text-slate-100 font-mono">{devices.length}</span>
<span>Hardware Nodes</span>
</div>
<div className="bg-slate-900 p-2 rounded border border-slate-850">
<span className="block font-bold text-slate-100 font-mono">{labs.length}</span>
<span>Available Labs</span>
</div>
</div>
<div className="flex items-center gap-3 shrink-0">
<button
onClick={onNavigateToCalendar}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-slate-950 font-sans font-bold text-xs rounded-lg transition-colors flex items-center gap-1.5 hover:cursor-pointer"
>
<Zap className="w-4 h-4 text-slate-950 fill-slate-950" />
Book Your Lab
</button>
<button
onClick={onNavigateToDevices}
className="px-4 py-2 bg-slate-900 hover:bg-slate-800 text-slate-200 border border-slate-800 hover:border-slate-700 rounded-lg text-xs font-semibold transition-all flex items-center gap-1 hover:cursor-pointer"
>
Browse Inventory
</button>
</div>
</div>
@ -156,28 +128,16 @@ export default function Dashboard({
<div className="bg-[#1E293B] border border-slate-800 rounded-xl p-5 shadow-sm">
<h3 className="font-bold text-sm text-slate-100 flex items-center gap-2 mb-4 font-sans justify-between">
<span className="flex items-center gap-2">
<Clock className="w-4.5 h-4.5 text-emerald-400" />
Active Reservations (your boxes, right now)
</span>
<span className="inline-flex items-center gap-1.5 text-[10px] font-mono font-bold text-emerald-400 bg-emerald-950/40 border border-emerald-900/50 rounded-full px-2.5 py-0.5">
<span className="relative flex h-1.5 w-1.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500"></span>
</span>
LIVE
<Clock className="w-4 h-4 text-emerald-400" />
Active Reservations
</span>
<span className="w-2 h-2 rounded-full bg-emerald-500 shrink-0" />
</h3>
{activeBookings.length === 0 ? (
<div className="text-center py-10 bg-slate-900/35 rounded-lg border border-slate-850 p-6 font-sans">
<PlayCircle className="w-8 h-8 text-slate-700 mx-auto mb-2 opacity-50" />
<p className="text-xs text-slate-400">No boxes checked out right now. idle hands, idle hardware.</p>
<button
onClick={onNavigateToCalendar}
className="text-xs text-emerald-400 font-semibold underline mt-2 hover:text-emerald-300"
>
grab a slot -&gt;
</button>
<div className="text-center py-8 bg-slate-900/35 rounded-lg border border-slate-800 font-sans">
<PlayCircle className="w-7 h-7 text-slate-600 mx-auto mb-2 opacity-50" />
<p className="text-xs text-slate-400">No active sessions.</p>
</div>
) : (
<div className="space-y-4 font-sans">
@ -209,28 +169,24 @@ export default function Dashboard({
</span>
</div>
<p className="text-xs text-slate-300 leading-relaxed font-sans mb-3 italic">
"{booking.notes || 'no notes - running blind'}"
</p>
<div className="pt-3 border-t border-slate-900/60 flex justify-between items-center text-[10px]">
<div className="pt-3 border-t border-slate-800/60 flex justify-between items-center text-[10px]">
<span className="font-mono text-slate-400">
Active window: {startF} - {endF}
{startF} {endF}
</span>
<div className="flex gap-2">
<button
onClick={() => onSelectBookingDetails(booking)}
className="px-2.5 py-1 bg-emerald-950/80 border border-emerald-500/30 text-emerald-400 hover:text-emerald-300 rounded flex items-center gap-1 transition-all font-semibold hover:cursor-pointer"
className="px-3 py-1.5 bg-emerald-950/80 border border-emerald-500/30 text-emerald-400 hover:text-emerald-300 rounded-lg text-xs font-semibold transition-all hover:cursor-pointer"
>
Inspect Details (Rest / Ansible)
Details
</button>
<button
onClick={() => {
if (confirm('Are you sure you want to release these nodes early? Hardware holds will terminate immediately.')) {
if (confirm('Release this reservation early?')) {
onCancelBooking(booking.id);
}
}}
className="px-2.5 py-1 bg-rose-950/40 border border-rose-700/50 text-rose-400 hover:bg-rose-900/60 hover:border-rose-500 hover:text-rose-300 rounded flex items-center gap-1 transition-all font-semibold hover:cursor-pointer"
className="px-3 py-1.5 bg-rose-950/40 border border-rose-700/50 text-rose-400 hover:bg-rose-900/60 hover:border-rose-500 hover:text-rose-300 rounded-lg text-xs font-semibold transition-all hover:cursor-pointer"
>
Release
</button>
@ -245,13 +201,13 @@ export default function Dashboard({
{/* Upcoming Sessions */}
<div className="bg-[#1E293B] border border-slate-800 rounded-xl p-5 shadow-sm font-sans">
<h3 className="font-bold text-sm text-slate-101 flex items-center gap-2 mb-3.5">
<Calendar className="w-4.5 h-4.5 text-indigo-400" />
Upcoming in the Queue ({upcomingBookings.length})
<h3 className="font-bold text-sm text-slate-100 flex items-center gap-2 mb-3.5">
<Calendar className="w-4 h-4 text-slate-400" />
Upcoming ({upcomingBookings.length})
</h3>
{upcomingBookings.length === 0 ? (
<p className="text-xs text-slate-400 py-4 italic text-center">Queue is empty. crontab clean, nothing scheduled.</p>
<p className="text-xs text-slate-400 py-4 text-center">No upcoming reservations.</p>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{upcomingBookings.map((booking) => {
@ -260,14 +216,14 @@ export default function Dashboard({
const startF = new Date(booking.startDateTime).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
const endF = new Date(booking.endDateTime).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
return (
<div key={booking.id} className="p-3 bg-slate-905/30 border border-slate-850 hover:border-slate-800 rounded-lg flex flex-col justify-between">
<div key={booking.id} className="p-3 bg-slate-900/30 border border-slate-800 hover:border-slate-700 rounded-lg flex flex-col justify-between">
<div>
<div className="flex justify-between items-start mb-1">
<span className="font-mono font-bold text-[10px] text-indigo-405 bg-indigo-950/50 border border-indigo-900 px-2 py-0.5 rounded">
<span className="font-mono font-bold text-[10px] text-indigo-400 bg-indigo-950/50 border border-indigo-900/50 px-2 py-0.5 rounded">
{dayStr}
</span>
<span className="text-[10px] font-mono text-slate-500">
{startF} - {endF}
{startF} {endF}
</span>
</div>
<h4 className="text-xs font-bold text-slate-200 mt-1 font-sans">{lab?.name}</h4>
@ -276,32 +232,32 @@ export default function Dashboard({
</p>
</div>
<div className="pt-2 mt-2 border-t border-slate-850 flex justify-end gap-1.5 pt-2">
<div className="pt-2 mt-2 border-t border-slate-800 flex justify-end gap-1.5">
<button
onClick={() => onSelectBookingDetails(booking)}
className="px-2.5 py-1 text-[9px] text-emerald-400 hover:text-emerald-350 bg-emerald-950/40 border border-emerald-990/30 rounded font-semibold transition hover:cursor-pointer"
className="px-2.5 py-1 text-xs text-emerald-400 hover:text-emerald-300 bg-emerald-950/40 border border-emerald-900/30 rounded-lg font-semibold transition hover:cursor-pointer"
>
Specs / REST API
Details
</button>
<button
onClick={() => {
if (confirm('Cancel this upcoming reservation? Linked SMTP alerts will notify appropriate parties.')) {
if (confirm('Cancel this upcoming reservation?')) {
onCancelBooking(booking.id);
}
}}
className="px-2 py-1 text-[9px] text-slate-400 hover:text-white hover:bg-slate-800 rounded border border-transparent hover:cursor-pointer"
className="px-2.5 py-1 text-xs text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-lg border border-slate-700/50 hover:cursor-pointer transition"
>
Cancel Slot
Cancel
</button>
<button
onClick={() => {
if (confirm('Are you sure you want to permanently delete this reservation from SQLite storage? This action cannot be reversed.')) {
if (confirm('Permanently delete this reservation?')) {
onDeleteBooking(booking.id);
}
}}
className="px-2 py-1 text-[9px] text-rose-400 hover:text-rose-300 hover:bg-rose-950/30 rounded border border-transparent hover:cursor-pointer"
className="px-2.5 py-1 text-xs text-rose-400 hover:text-rose-300 hover:bg-rose-950/30 rounded-lg border border-rose-900/30 hover:cursor-pointer transition"
>
Purge SQLite
Purge
</button>
</div>
</div>
@ -316,62 +272,58 @@ export default function Dashboard({
{/* RIGHT COLUMN: Checklist and simulated action panel */}
<div className="lg:col-span-4 space-y-6">
{/* Workflows Checklist */}
{/* Lab Checklist */}
<div className="bg-[#1E293B] border border-slate-800 rounded-xl p-5 shadow-sm font-sans">
<h3 className="font-bold text-sm text-slate-101 flex items-center gap-2 mb-3.5">
<ListTodo className="w-4.5 h-4.5 text-amber-500" />
Pre-Flight Checklist (before you blame the network)
<h3 className="font-bold text-sm text-slate-100 flex items-center gap-2 mb-3.5">
<ListTodo className="w-4 h-4 text-slate-400" />
Lab Checklist
</h3>
<div className="space-y-2.5">
<div className="space-y-2">
{todoList.map((item) => (
<div
key={item.id}
<div
key={item.id}
onClick={() => toggleTodo(item.id)}
className="flex items-start gap-2.5 p-2 bg-slate-1000/40 hover:bg-slate-900 rounded-lg cursor-pointer transition-all border border-slate-850/60"
className="flex items-start gap-2.5 p-2 bg-slate-900/40 hover:bg-slate-900 rounded-lg cursor-pointer transition-all border border-slate-800/60"
>
<input
type="checkbox"
checked={item.checked}
onChange={() => {}}
className="mt-0.5 rounded border-slate-800 text-emerald-500 focus:ring-emerald-450 w-3.5 h-3.5 shrink-0"
className="mt-0.5 rounded border-slate-700 text-emerald-500 w-3.5 h-3.5 shrink-0"
/>
<span className={`text-[11px] leading-tight ${item.checked ? 'text-slate-500 line-through' : 'text-slate-200'}`}>
<span className={`text-xs leading-tight ${item.checked ? 'text-slate-500 line-through' : 'text-slate-300'}`}>
{item.text}
</span>
</div>
))}
</div>
<div className="mt-4 pt-3 border-t border-slate-850 text-[10px] text-slate-450 text-center">
Works on my machine (TM). check the boxes anyway.
</div>
</div>
{/* Quick Links - shortcut into the shared tooling dashboard */}
{/* Quick Links */}
<div className="bg-[#1E293B] border border-slate-800 rounded-xl p-5 shadow-sm font-sans">
<h3 className="font-bold text-sm text-slate-101 flex items-center gap-2 mb-3.5 justify-between">
<h3 className="font-bold text-sm text-slate-100 flex items-center gap-2 mb-3.5 justify-between">
<span className="flex items-center gap-2">
<LinkIcon className="w-4.5 h-4.5 text-cyan-400" />
<LinkIcon className="w-4 h-4 text-slate-400" />
Quick Links
</span>
<button
onClick={onNavigateToLinks}
className="text-[10px] text-cyan-400 hover:text-cyan-300 font-semibold flex items-center gap-0.5 hover:cursor-pointer"
className="text-[10px] text-slate-400 hover:text-slate-200 font-semibold flex items-center gap-0.5 hover:cursor-pointer"
>
Manage <ArrowRight className="w-3 h-3" />
</button>
</h3>
{links.length === 0 ? (
<div className="text-center py-6 bg-slate-900/35 rounded-lg border border-slate-850 p-5">
<Globe className="w-7 h-7 text-slate-700 mx-auto mb-2 opacity-50" />
<p className="text-[11px] text-slate-400">No shared links yet.</p>
<div className="text-center py-6 bg-slate-900/35 rounded-lg border border-slate-800">
<Globe className="w-7 h-7 text-slate-600 mx-auto mb-2 opacity-50" />
<p className="text-xs text-slate-400">No shared links yet.</p>
<button
onClick={onNavigateToLinks}
className="text-[11px] text-cyan-400 font-semibold underline mt-1.5 hover:text-cyan-300 hover:cursor-pointer"
className="text-xs text-slate-400 font-semibold underline mt-1.5 hover:text-slate-200 hover:cursor-pointer"
>
Add CheckMK, Semaphore & co.
Add links
</button>
</div>
) : (
@ -386,23 +338,23 @@ export default function Dashboard({
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-2.5 p-2 bg-slate-1000/40 hover:bg-slate-900 rounded-lg border border-slate-850/60 hover:border-slate-800 transition-all"
className="group flex items-center gap-2.5 p-2 bg-slate-900/40 hover:bg-slate-900 rounded-lg border border-slate-800/60 hover:border-slate-700 transition-all"
>
<span className={`w-7 h-7 rounded-md bg-slate-950 border border-slate-800 flex items-center justify-center shrink-0 ${accent}`}>
<Globe className="w-3.5 h-3.5" />
</span>
<span className="min-w-0 flex-1">
<span className="block text-[11px] font-semibold text-slate-200 group-hover:text-white truncate">{link.title}</span>
<span className={`block text-[9px] font-mono truncate ${accent}`}>{host}</span>
<span className="block text-xs font-semibold text-slate-200 group-hover:text-white truncate">{link.title}</span>
<span className={`block text-[10px] font-mono truncate ${accent}`}>{host}</span>
</span>
<ExternalLink className="w-3.5 h-3.5 text-slate-600 group-hover:text-slate-300 shrink-0" />
<ExternalLink className="w-3.5 h-3.5 text-slate-600 group-hover:text-slate-400 shrink-0" />
</a>
);
})}
{links.length > 6 && (
<button
onClick={onNavigateToLinks}
className="w-full text-center text-[10px] text-slate-500 hover:text-cyan-400 pt-1.5 font-semibold hover:cursor-pointer"
className="w-full text-center text-[10px] text-slate-500 hover:text-slate-300 pt-1.5 font-semibold hover:cursor-pointer"
>
+{links.length - 6} more links
</button>