diff --git a/src/app/(app)/dashboard2/page.tsx b/src/app/(app)/dashboard2/page.tsx index 8d81146..f6d78c4 100644 --- a/src/app/(app)/dashboard2/page.tsx +++ b/src/app/(app)/dashboard2/page.tsx @@ -11,6 +11,7 @@ import { TrendingUp, AlertCircle, DollarSign, + RefreshCw, } from "lucide-react"; import { toast } from "sonner"; import getPocketBase from "@/lib/pocketbase"; @@ -43,12 +44,13 @@ export default function RepDashboardPage() { const router = useRouter(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { const pb = getPocketBase(); const pbToken = pb.authStore?.token; - const headers: Record = {}; + const headers: Record = { "content-type": "application/json" }; if (pbToken) headers.Authorization = pbToken; const response = await fetch("/api/v1/rep-dashboard", { @@ -60,23 +62,36 @@ export default function RepDashboardPage() { throw new Error(body.error || `Rep dashboard failed (HTTP ${response.status})`); } setData((await response.json()) as RepDashboardData); + setError(null); } catch (err: unknown) { - toast.error(err instanceof Error ? err.message : "Failed to load rep dashboard"); + const message = err instanceof Error ? err.message : "Failed to load rep dashboard"; + setError(message); + toast.error(message); } finally { setLoading(false); } }, []); useEffect(() => { - void fetchData(); + const handle = window.setTimeout(() => { + void fetchData(); + }, 0); + return () => window.clearTimeout(handle); }, [fetchData]); + // Realtime subscription with a single trailing-edge debounce. A burst of + // changes collapses to a single refetch — and we only subscribe AFTER + // the initial load completes, so a slow first fetch doesn't compete + // with the realtime subscription firing. useEffect(() => { + if (!data) return; const pb = getPocketBase(); - let handle = 0; + const debounceRef = { handle: 0 as number }; const trigger = () => { - if (handle) window.clearTimeout(handle); - handle = window.setTimeout(() => void fetchData(), REALTIME_DEBOUNCE_MS); + if (debounceRef.handle) window.clearTimeout(debounceRef.handle); + debounceRef.handle = window.setTimeout(() => { + void fetchData(); + }, REALTIME_DEBOUNCE_MS); }; const subscribe = async () => { await pb.collection("quotes").subscribe("*", trigger); @@ -85,14 +100,44 @@ export default function RepDashboardPage() { }; void subscribe(); return () => { - if (handle) window.clearTimeout(handle); + if (debounceRef.handle) window.clearTimeout(debounceRef.handle); void pb.collection("quotes").unsubscribe("*"); void pb.collection("orders").unsubscribe("*"); void pb.collection("order_items").unsubscribe("*"); }; - }, [fetchData]); + }, [fetchData, data]); - if (loading || !data) { + if (error && !data) { + return ( +
+
+

My Dashboard

+

+ Your quotes, outstanding work, and this month’s numbers. +

+
+
+ +

Couldn’t load your dashboard

+

{error}

+ +
+
+ ); + } + + if (loading && !data) { return (
Loading your dashboard… @@ -100,6 +145,8 @@ export default function RepDashboardPage() { ); } + if (!data) return null; + const { quotesLoaded, quoteVolumeTrend, outstandingQuotes, outstandingOrders, monthRevenue } = data; return (