/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useEffect } from 'react'; import { Booking, LabTemplate, Device, User, QuickLink } from '../types'; import { Zap, Clock, PlayCircle, MapPin, ListTodo, Calendar, Link as LinkIcon, ExternalLink, Globe, ArrowRight } from 'lucide-react'; interface DashboardProps { currentUser: User; bookings: Booking[]; labs: LabTemplate[]; devices: Device[]; links: QuickLink[]; onCancelBooking: (id: string) => void; onDeleteBooking: (id: string) => void; onSelectBookingDetails: (booking: Booking) => void; onNavigateToCalendar: () => void; onNavigateToDevices: () => void; onNavigateToLabs: () => void; onNavigateToLinks: () => void; } const LINK_ACCENT: Record = { emerald: 'text-emerald-400', cyan: 'text-cyan-400', indigo: 'text-indigo-400', amber: 'text-amber-400', rose: 'text-rose-400', violet: 'text-violet-400', }; export default function Dashboard({ currentUser, bookings, labs, devices, links, onCancelBooking, onDeleteBooking, onSelectBookingDetails, onNavigateToCalendar, onNavigateToDevices, onNavigateToLabs, onNavigateToLinks }: DashboardProps) { const [now, setNow] = useState(new Date()); useEffect(() => { const timer = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(timer); }, []); const ONE_HOUR_MS = 60 * 60 * 1000; const personalBookings = bookings.filter(b => b.userId === currentUser.id && b.status !== 'cancelled'); // "Active" = currently running, plus a 1h grace window after the end so // freshly-finished sessions linger briefly instead of jumping to "Expired". const activeBookings = personalBookings.filter(b => { const start = new Date(b.startDateTime).getTime(); const end = new Date(b.endDateTime).getTime(); return start <= now.getTime() && end > now.getTime() - ONE_HOUR_MS; }); const upcomingBookings = personalBookings.filter(b => b.status === 'upcoming' && new Date(b.startDateTime) > now); // 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 } ]); const toggleTodo = (id: string) => { setTodoList(todoList.map(t => t.id === id ? { ...t, checked: !t.checked } : t)); }; const getRemainingTimeText = (endTimeStr: string) => { const diffMs = new Date(endTimeStr).getTime() - now.getTime(); if (diffMs <= 0) { // Within the 1h grace window - wrapping up rather than "expired". const agoMin = Math.max(1, Math.ceil(-diffMs / (1000 * 60))); return `Ended ${agoMin}m ago`; } const hours = Math.floor(diffMs / (1000 * 60 * 60)); const mins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); const secs = Math.floor((diffMs % (1000 * 60)) / 1000); return hours > 0 ? `${hours}h ${mins}m remaining` : `${mins}m ${secs}s remaining`; }; return (
{/* Banner Card Grid */}
NET

Welcome back, {currentUser.name}!

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).

System Time
{now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}

{now.toLocaleDateString([], { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}

{devices.length} Hardware Nodes
{labs.length} Available Labs
{/* Main Grid Content */}
{/* LEFT COMPONENT: Active / Upcoming Bookings */}
{/* Active Sessions */}

Active Reservations (your boxes, right now) LIVE

{activeBookings.length === 0 ? (

No boxes checked out right now. idle hands, idle hardware.

) : (
{activeBookings.map((booking) => { const lab = labs.find(l => l.id === booking.labId); const startDate = new Date(booking.startDateTime); const endDate = new Date(booking.endDateTime); const sameDay = startDate.toDateString() === endDate.toDateString(); const dayFmt: Intl.DateTimeFormatOptions = { weekday: 'short', day: 'numeric', month: 'short' }; const timeFmt: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' }; const startF = `${startDate.toLocaleDateString('en-US', dayFmt)}, ${startDate.toLocaleTimeString('en-US', timeFmt)}`; const endF = sameDay ? endDate.toLocaleTimeString('en-US', timeFmt) : `${endDate.toLocaleDateString('en-US', dayFmt)}, ${endDate.toLocaleTimeString('en-US', timeFmt)}`; return (

{lab?.name}

{lab?.location}
{/* Countdown Pill */} {getRemainingTimeText(booking.endDateTime)}

"{booking.notes || 'no notes - running blind'}"

Active window: {startF} - {endF}
); })}
)}
{/* Upcoming Sessions */}

Upcoming in the Queue ({upcomingBookings.length})

{upcomingBookings.length === 0 ? (

Queue is empty. crontab clean, nothing scheduled.

) : (
{upcomingBookings.map((booking) => { const lab = labs.find(l => l.id === booking.labId); const dayStr = new Date(booking.startDateTime).toLocaleDateString('en-US', { weekday: 'short', day: 'numeric', month: 'short' }); 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 (
{dayStr} {startF} - {endF}

{lab?.name}

{booking.notes}

); })}
)}
{/* RIGHT COLUMN: Checklist and simulated action panel */}
{/* Workflows Checklist */}

Pre-Flight Checklist (before you blame the network)

{todoList.map((item) => (
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="mt-0.5 rounded border-slate-800 text-emerald-500 focus:ring-emerald-450 w-3.5 h-3.5 shrink-0" /> {item.text}
))}
Works on my machine (TM). check the boxes anyway.
{/* Quick Links - shortcut into the shared tooling dashboard */}

Quick Links

{links.length === 0 ? (

No shared links yet.

) : (
{links.slice(0, 6).map(link => { let host = link.url; try { host = new URL(link.url).host; } catch { /* keep raw */ } const accent = LINK_ACCENT[link.color] ?? LINK_ACCENT.emerald; return ( {link.title} {host} ); })} {links.length > 6 && ( )}
)}
); }