All files / src/pages/crm crm-analytics.tsx

100% Statements 12/12
100% Branches 6/6
100% Functions 4/4
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                    1x                     10x 10x 10x 10x   10x 1x               9x   1x       45x             9x     1x                                                                                                                
import { useState } from "react";
import { Link, useNavigate } from "@tanstack/react-router";
import { ArrowLeft, BarChart3 } from "lucide-react";
import { StickyPageHeader } from "../../components/ui/sticky-page-header";
import { MobileFullPage } from "../../components/ui/mobile-full-page";
import { usePro } from "../../lib/pro-context";
import { useCrmAnalytics } from "../../hooks/queries/useCrmQueries";
import {
	KpiCards,
	PipelineOverview,
	SourcePerformance,
	FinancialSummary,
	LossReasonBreakdown,
	FollowupHealth,
} from "./analytics-panels";
 
// ─── Constants ───────────────────────────────────────────────────────────────
 
const PERIOD_OPTIONS = [
	{ value: "this_week", label: "This Week" },
	{ value: "this_month", label: "This Month" },
	{ value: "last_month", label: "Last Month" },
	{ value: "this_quarter", label: "This Quarter" },
	{ value: "all_time", label: "All Time" },
];
 
// ─── Page ────────────────────────────────────────────────────────────────────
 
export function CrmAnalyticsPage() {
	const { proId } = usePro();
	const navigate = useNavigate();
	const [period, setPeriod] = useState("this_month");
	const { data, isLoading } = useCrmAnalytics(proId, period);
 
	if (isLoading || !proId) {
		return (
			<div className="flex items-center justify-center h-64">
				<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600" />
			</div>
		);
	}
 
	const periodSelect = (
		<select
			value={period}
			onChange={(e) => setPeriod(e.target.value)}
			className="h-9 px-3 rounded-md border border-border-default bg-background-elevated text-sm text-foreground-default focus:ring-2 focus:ring-primary-500"
		>
			{PERIOD_OPTIONS.map((opt) => (
				<option key={opt.value} value={opt.value}>
					{opt.label}
				</option>
			))}
		</select>
	);
 
	return (
		<MobileFullPage
			title="CRM Analytics"
			onBack={() => navigate({ to: "/crm" })}
			headerActions={periodSelect}
		>
		<div className="space-y-6 max-w-5xl p-4 sm:p-0">
			{/* Header — desktop only (MobileFullPage provides the mobile header) */}
			<div className="hidden sm:block">
			<StickyPageHeader>
				<div className="flex items-center justify-between gap-3 w-full">
					<div className="flex items-center gap-3">
						<Link
							to="/crm"
							className="text-foreground-muted hover:text-foreground-default transition-colors"
						>
							<ArrowLeft className="h-5 w-5" />
						</Link>
						<h1 className="text-xl font-bold text-foreground-default">
							CRM Analytics
						</h1>
					</div>
					{periodSelect}
				</div>
			</StickyPageHeader>
			</div>
 
			{!data ? (
				<div className="text-center py-16 rounded-lg border border-border-default bg-background-elevated">
					<BarChart3 className="h-10 w-10 text-foreground-subtle mx-auto mb-3" />
					<p className="text-foreground-subtle">
						No analytics data available yet. Start adding leads to see insights.
					</p>
				</div>
			) : (
				<>
					{/* KPI summary cards */}
					<KpiCards data={data} />
 
					{/* Pipeline overview */}
					<PipelineOverview data={data} />
 
					{/* Two-column: Sources + Financial */}
					<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
						<SourcePerformance data={data} />
						<FinancialSummary data={data} />
					</div>
 
					{/* Two-column: Loss Reasons + Follow-up */}
					<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
						<LossReasonBreakdown data={data} />
						<FollowupHealth data={data} />
					</div>
				</>
			)}
		</div>
		</MobileFullPage>
	);
}