"use client"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { useAuth } from "@/hooks/useAuth"; import { useRouter } from "next/navigation"; import { apiFetch } from "@/lib/api"; import { useStableState } from "@/hooks/useStableState"; import { useVisiblePolling } from "@/hooks/useVisiblePolling"; import { QrCode, Ticket, Activity, UserCheck, Clock } from "lucide-react"; import { StatCard, StatCardRow } from "@/components/shared/StatCard"; import { QuickActionTile, QuickActionGrid } from "@/components/shared/QuickActionTile"; const QUICK_ACTIONS = [ { href: "/dashboard/staff/ticket-scanning", label: "Scan tickets", description: "Use your device camera to validate tickets", icon: QrCode }, { href: "/dashboard/staff/event-tickets", label: "Event tickets & printing", description: "Browse event tickets and print lists", icon: Ticket }, ] as const; export default function StaffDashboardPage() { const { user, loading, token } = useAuth(); const router = useRouter(); // Access control (staff/supervisor/admin) const canView = useMemo(() => { const role = user?.role; return role === "admin" || role === "supervisor" || role === "staff"; }, [user]); useEffect(() => { if (loading) return; if (!user) router.replace("/login"); }, [user, loading, router]); // Stats and recent scans for quick overview — both come from one endpoint // (/api/stats/staff) instead of two separate calls. useStableState skips re-renders when // a poll returns identical data, and hasLoadedOnce below means "Refreshing…" only shows // on the very first load — together these stop the stats panel from flickering. const [stats, setStats] = useStableState(null); const [recentScans, setRecentScans] = useStableState([]); const [loadingStats, setLoadingStats] = useState(false); const hasLoadedOnce = useRef(false); const loadStats = async () => { if (!token) return; const isFirstLoad = !hasLoadedOnce.current; try { if (isFirstLoad) setLoadingStats(true); const data = await apiFetch("/api/stats/staff", { authToken: token }); setStats(data.scanStats); setRecentScans(Array.isArray(data.recentScans) ? data.recentScans : []); } catch (e) { // ignore } finally { hasLoadedOnce.current = true; if (isFirstLoad) setLoadingStats(false); } }; useEffect(() => { if (!token) return; loadStats(); }, [token]); // Poll every 10s while the tab is visible; pause in the background and refetch // immediately on return instead of leaving stale numbers up. useVisiblePolling(() => { if (!token) return; loadStats(); }, 10000, !!token); return (

Welcome back{user ? `, ${user.name}` : ""} 👋

Here's today's scanning activity.

{!canView && (
You need staff, supervisor, or admin access to use staff tools.
)}
Quick actions
{QUICK_ACTIONS.map(a => ( ))}

Recent scans

{loadingStats && Refreshing…}
    {recentScans.map((u: any) => (
  • {u.ticket?.event?.title || u.ticket?.eventId || 'Event'}
    {new Date(u.scannedAt).toLocaleString()}
    {u.ticket?.registrationOption?.eventOption?.name || 'Ticket'} — #{String(u.ticket?.id || '').slice(0, 8)}
    Scanned by: {u.scannedBy?.name || u.scannedById}
  • ))} {recentScans.length === 0 &&
  • No scans yet.
  • }

Today by staff

{stats?.byStaff?.length > 0 ? (
    {stats.byStaff.map((s: any) => (
  • {s.name || 'Staff'} {s.count}
  • ))}
) : (
No scans recorded yet today.
)}
); }