Files
pn-new-crm/src/components/dashboard/lead-source-chart.tsx
Matt Ciaccio 8699f81879
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
chore(style): codebase em-dash sweep + minor layout polish
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).

Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
  pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
  port switcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:57:01 +02:00

100 lines
2.8 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"
description="Lights up once new interests are created - tracks where each came from (website, referral, broker)."
/>
) : (
// Percentage radii + center-anchored chart so the pie scales with
// the container instead of being clipped to a constant 90px ring at
// narrow widths. Legend is reserved a fixed footer height.
<ResponsiveContainer width="100%" height={280}>
<PieChart>
<Pie
data={chartData}
dataKey="value"
nameKey="name"
cx="50%"
cy="45%"
outerRadius="70%"
innerRadius="40%"
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
verticalAlign="bottom"
height={40}
wrapperStyle={{ fontSize: 12, paddingTop: 4 }}
/>
</PieChart>
</ResponsiveContainer>
)}
</ChartCard>
);
}