90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { Cell, Legend, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
|
||
|
|
|
||
|
|
import { CardSkeleton } from '@/components/shared/loading-skeleton';
|
||
|
|
import { EmptyState } from '@/components/shared/empty-state';
|
||
|
|
import { ChartCard } from './chart-card';
|
||
|
|
import { useLeadSource } from './use-analytics';
|
||
|
|
import type { DateRange } from '@/lib/services/analytics.service';
|
||
|
|
|
||
|
|
interface Props {
|
||
|
|
range: DateRange;
|
||
|
|
}
|
||
|
|
|
||
|
|
const COLORS = [
|
||
|
|
'hsl(var(--chart-1))',
|
||
|
|
'hsl(var(--chart-2))',
|
||
|
|
'hsl(var(--chart-3))',
|
||
|
|
'hsl(var(--chart-4))',
|
||
|
|
'hsl(var(--chart-5))',
|
||
|
|
];
|
||
|
|
|
||
|
|
const SOURCE_LABELS: Record<string, string> = {
|
||
|
|
website: 'Website',
|
||
|
|
referral: 'Referral',
|
||
|
|
manual: 'Manual',
|
||
|
|
social: 'Social',
|
||
|
|
unspecified: 'Unspecified',
|
||
|
|
};
|
||
|
|
|
||
|
|
export function LeadSourceChart({ range }: Props) {
|
||
|
|
const { data, isLoading } = useLeadSource(range);
|
||
|
|
const slices = data?.slices ?? [];
|
||
|
|
|
||
|
|
function toCsv(): string | null {
|
||
|
|
if (!slices.length) return null;
|
||
|
|
const header = 'source,count';
|
||
|
|
const rows = slices.map((s) => `${s.source},${s.count}`);
|
||
|
|
return [header, ...rows].join('\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
const chartData = slices.map((s) => ({
|
||
|
|
name: SOURCE_LABELS[s.source] ?? s.source,
|
||
|
|
value: s.count,
|
||
|
|
}));
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ChartCard
|
||
|
|
title="Lead Source Attribution"
|
||
|
|
description="Where new interests came from"
|
||
|
|
exportFilename={`lead-source-${range}`}
|
||
|
|
toCsv={toCsv}
|
||
|
|
>
|
||
|
|
{isLoading ? (
|
||
|
|
<CardSkeleton />
|
||
|
|
) : !slices.length ? (
|
||
|
|
<EmptyState title="No interests in range" />
|
||
|
|
) : (
|
||
|
|
<ResponsiveContainer width="100%" height={260}>
|
||
|
|
<PieChart>
|
||
|
|
<Pie
|
||
|
|
data={chartData}
|
||
|
|
dataKey="value"
|
||
|
|
nameKey="name"
|
||
|
|
cx="50%"
|
||
|
|
cy="50%"
|
||
|
|
outerRadius={90}
|
||
|
|
innerRadius={50}
|
||
|
|
paddingAngle={2}
|
||
|
|
>
|
||
|
|
{chartData.map((_, i) => (
|
||
|
|
<Cell key={i} fill={COLORS[i % COLORS.length]} />
|
||
|
|
))}
|
||
|
|
</Pie>
|
||
|
|
<Tooltip
|
||
|
|
contentStyle={{
|
||
|
|
background: 'hsl(var(--popover))',
|
||
|
|
border: '1px solid hsl(var(--border))',
|
||
|
|
borderRadius: '6px',
|
||
|
|
fontSize: 12,
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Legend wrapperStyle={{ fontSize: 12 }} />
|
||
|
|
</PieChart>
|
||
|
|
</ResponsiveContainer>
|
||
|
|
)}
|
||
|
|
</ChartCard>
|
||
|
|
);
|
||
|
|
}
|