"use client" import { useCallback, useEffect, useState } from 'react' import { createClient } from '@/lib/supabase/client' import { MessageSquare, UserPlus, DollarSign, Send, } from 'lucide-react' import { loadActivity, loadConversationsSeries, loadMetrics, loadPipelineDonut, loadResponseTime, } from '@/lib/dashboard/queries' import type { ActivityItem, ConversationsSeriesPoint, MetricsBundle, PipelineDonutData, ResponseTimeSummary, } from '@/lib/dashboard/types' import { MetricCard } from '@/components/dashboard/metric-card' import { SkeletonCard } from '@/components/dashboard/skeleton' import { QuickActions } from '@/components/dashboard/quick-actions' import { ConversationsChart } from '@/components/dashboard/conversations-chart' import { PipelineDonut } from '@/components/dashboard/pipeline-donut' import { ResponseTimeChart } from '@/components/dashboard/response-time-chart' import { ActivityFeed } from '@/components/dashboard/activity-feed' type RangeDays = 7 | 30 | 90 export default function DashboardPage() { const [metrics, setMetrics] = useState(null) const [metricsLoading, setMetricsLoading] = useState(true) const [range, setRange] = useState(30) // Keep a cache per range so switching tabs doesn't re-fetch what we // already have. Ranges the user hasn't opened yet stay null and // trigger a fetch on first view. const [series, setSeries] = useState>({ 7: null, 30: null, 90: null, }) const [seriesLoading, setSeriesLoading] = useState(true) const [pipeline, setPipeline] = useState(null) const [pipelineLoading, setPipelineLoading] = useState(true) const [responseTime, setResponseTime] = useState(null) const [responseTimeLoading, setResponseTimeLoading] = useState(true) const [activity, setActivity] = useState(null) const [activityLoading, setActivityLoading] = useState(true) const loadAll = useCallback(() => { const db = createClient() // Kick everything off in parallel. Each block has its own // setState + finally so a slow query doesn't hold up faster // sections — each widget shows its own skeleton independently. void loadMetrics(db) .then((m) => setMetrics(m)) .catch((err) => console.error('[dashboard] metrics failed:', err)) .finally(() => setMetricsLoading(false)) void loadConversationsSeries(db, 30) .then((s) => setSeries((prev) => ({ ...prev, 30: s }))) .catch((err) => console.error('[dashboard] series failed:', err)) .finally(() => setSeriesLoading(false)) void loadPipelineDonut(db) .then((p) => setPipeline(p)) .catch((err) => console.error('[dashboard] pipeline failed:', err)) .finally(() => setPipelineLoading(false)) void loadResponseTime(db) .then((r) => setResponseTime(r)) .catch((err) => console.error('[dashboard] response time failed:', err)) .finally(() => setResponseTimeLoading(false)) void loadActivity(db, 20) .then((a) => setActivity(a)) .catch((err) => console.error('[dashboard] activity failed:', err)) .finally(() => setActivityLoading(false)) }, []) useEffect(() => { loadAll() }, [loadAll]) // Range switch handler — kept in an event callback (not an effect) // so the setState calls stay out of the react-hooks/set-state-in-effect // rule's way. The cached bucket check means switching back to a // previously-viewed range is instant and doesn't re-fetch. const handleRangeChange = useCallback( (r: RangeDays) => { setRange(r) if (series[r] !== null) return setSeriesLoading(true) const db = createClient() loadConversationsSeries(db, r) .then((s) => setSeries((prev) => ({ ...prev, [r]: s }))) .catch((err) => console.error('[dashboard] series failed:', err)) .finally(() => setSeriesLoading(false)) }, [series], ) return (
{/* Header */}

Dashboard

Live analytics across conversations, contacts, deals, broadcasts, and automations.

{/* Metric cards */}
{metricsLoading || !metrics ? ( Array.from({ length: 4 }).map((_, i) => ) ) : ( <> )}
{/* Quick actions */} {/* Charts row */}
{/* Response time */} {/* Activity feed */}
) } // ------------------------------------------------------------ function formatCurrency(v: number): string { return new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(v) } function deltaLabel(delta: number, suffix: string): string { if (delta === 0) return `No change ${suffix}` const sign = delta > 0 ? '+' : '' return `${sign}${delta.toLocaleString()} ${suffix}` }