import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { AppLayout } from '../../components/layout/AppLayout';
import { leadGenApi, type LeadAttribution } from '../../api/leadGen';
import { useAuth } from '../../contexts/AuthContext';
import {
ArrowLeft,
Loader2,
DollarSign,
TrendingUp,
TrendingDown,
Target,
Filter,
Search,
Download,
BarChart3,
PieChart,
} from 'lucide-react';
export function AttributionReportPage() {
const navigate = useNavigate();
const { user } = useAuth();
const companyId = user?.company_id;
const [utmSource, setUtmSource] = useState('');
const [statusFilter, setStatusFilter] = useState('');
const [days, setDays] = useState(30);
const [search, setSearch] = useState('');
const { data, isLoading } = useQuery({
queryKey: ['lead-gen-attribution', companyId, utmSource, statusFilter, days],
queryFn: () => leadGenApi.getAttributions(companyId!, {
utm_source: utmSource || undefined,
status: statusFilter || undefined,
days,
}),
enabled: !!companyId,
});
const rawAttributions = data?.data?.data?.attributions || [];
const summary = data?.data?.data?.summary;
// Filter by search
const attributions = rawAttributions.filter(a => {
if (!search) return true;
const s = search.toLowerCase();
return (
(a.utm_campaign || '').toLowerCase().includes(s) ||
(a.utm_source || '').toLowerCase().includes(s) ||
(a.utm_term || '').toLowerCase().includes(s) ||
(a.lead_source || '').toLowerCase().includes(s)
);
});
// Source breakdown
const sourceBreakdown = new Map<string, { count: number; revenue: number; spend: number; roas: number }>();
attributions.forEach(a => {
const src = a.utm_source || 'unknown';
const existing = sourceBreakdown.get(src) || { count: 0, revenue: 0, spend: 0, roas: 0 };
existing.count += 1;
existing.revenue += a.deal_amount || 0;
existing.spend += a.total_ad_spend || 0;
existing.roas = existing.spend > 0 ? existing.revenue / existing.spend : 0;
sourceBreakdown.set(src, existing);
});
// Status breakdown
const statusBreakdown = new Map<string, number>();
attributions.forEach(a => {
statusBreakdown.set(a.attribution_status, (statusBreakdown.get(a.attribution_status) || 0) + 1);
});
const totalRevenue = summary?.total_revenue || 0;
const totalSpend = summary?.total_spend || 0;
const overallRoas = summary?.overall_roas || 0;
return (
<AppLayout title="Attribution Report">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => navigate('/app/lead-gen')}
className="p-2 hover:bg-surface-100 rounded-lg transition-colors"
>
<ArrowLeft className="h-5 w-5" />
</button>
<div>
<h1 className="text-2xl font-bold text-surface-900">Attribution Report</h1>
<p className="text-sm text-surface-500">
Track leads from ad click to closed deal
</p>
</div>
</div>
<button
type="button"
onClick={() => {
const csv = exportCSV(attributions);
downloadCSV(csv, `attribution-report-${new Date().toISOString().slice(0, 10)}.csv`);
}}
disabled={attributions.length === 0}
className="flex items-center gap-2 px-4 py-2 rounded-lg border border-surface-200 text-sm font-medium text-surface-700 hover:bg-surface-50 disabled:opacity-50 transition-colors"
>
<Download className="h-4 w-4" />
Export CSV
</button>
</div>
{/* Summary KPIs */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="rounded-xl border border-surface-200 bg-white p-4">
<div className="flex items-center gap-2 mb-2">
<Target className="h-4 w-4 text-blue-600" />
<span className="text-xs font-medium uppercase tracking-wider text-surface-400">Total Leads</span>
</div>
<p className="text-xl font-bold text-surface-900">{summary?.attribution_count || 0}</p>
</div>
<div className="rounded-xl border border-surface-200 bg-white p-4">
<div className="flex items-center gap-2 mb-2">
<DollarSign className="h-4 w-4 text-red-600" />
<span className="text-xs font-medium uppercase tracking-wider text-surface-400">Ad Spend</span>
</div>
<p className="text-xl font-bold text-red-600">
${totalSpend.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p>
</div>
<div className="rounded-xl border border-surface-200 bg-white p-4">
<div className="flex items-center gap-2 mb-2">
<TrendingUp className="h-4 w-4 text-emerald-600" />
<span className="text-xs font-medium uppercase tracking-wider text-surface-400">Revenue</span>
</div>
<p className="text-xl font-bold text-emerald-600">
${totalRevenue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p>
</div>
<div className="rounded-xl border border-surface-200 bg-white p-4">
<div className="flex items-center gap-2 mb-2">
<BarChart3 className="h-4 w-4 text-purple-600" />
<span className="text-xs font-medium uppercase tracking-wider text-surface-400">Overall ROAS</span>
</div>
<p className={`text-xl font-bold ${overallRoas >= 3 ? 'text-emerald-600' : 'text-amber-600'}`}>
{overallRoas.toFixed(1)}x
</p>
</div>
</div>
{/* Breakdowns */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Source breakdown */}
<div className="rounded-xl border border-surface-200 bg-white p-6">
<h3 className="font-semibold text-surface-900 mb-4 flex items-center gap-2">
<BarChart3 className="h-4 w-4 text-blue-600" />
Performance by Source
</h3>
{sourceBreakdown.size === 0 ? (
<p className="text-sm text-surface-400">No data available</p>
) : (
<div className="space-y-3">
{Array.from(sourceBreakdown.entries()).map(([source, stats]) => (
<div key={source} className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-surface-900 capitalize">{source}</p>
<p className="text-xs text-surface-400">{stats.count} leads</p>
</div>
<div className="text-right">
<p className="text-sm font-medium text-emerald-600">
${stats.revenue.toLocaleString(undefined, { maximumFractionDigits: 0 })}
</p>
<p className={`text-xs ${stats.roas >= 3 ? 'text-emerald-600' : 'text-amber-600'}`}>
{stats.roas.toFixed(1)}x ROAS
</p>
</div>
</div>
))}
</div>
)}
</div>
{/* Status breakdown */}
<div className="rounded-xl border border-surface-200 bg-white p-6">
<h3 className="font-semibold text-surface-900 mb-4 flex items-center gap-2">
<PieChart className="h-4 w-4 text-purple-600" />
Funnel Status
</h3>
{statusBreakdown.size === 0 ? (
<p className="text-sm text-surface-400">No data available</p>
) : (
<div className="space-y-3">
{Array.from(statusBreakdown.entries()).map(([status, count]) => (
<div key={status} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
status === 'deal'
? 'bg-emerald-100 text-emerald-700'
: status === 'lead'
? 'bg-blue-100 text-blue-700'
: 'bg-surface-100 text-surface-600'
}`}>
{status}
</span>
<span className="text-xs text-surface-400">
{attributions.length > 0
? ((count / attributions.length) * 100).toFixed(0) + '%'
: '0%'}
</span>
</div>
<span className="text-sm font-medium text-surface-900">{count}</span>
</div>
))}
</div>
)}
</div>
</div>
{/* Filters */}
<div className="flex flex-wrap items-center gap-3 mb-6">
<div className="relative flex-1 min-w-[200px]">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-surface-400" />
<input
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search campaigns, keywords..."
className="w-full rounded-lg border border-surface-200 pl-10 pr-3 py-2 text-sm focus:border-brand-500 focus:ring-2 focus:ring-brand-100"
/>
</div>
<div className="flex items-center gap-2">
<Filter className="h-4 w-4 text-surface-400" />
<select
value={utmSource}
onChange={e => setUtmSource(e.target.value)}
className="rounded-lg border border-surface-200 px-3 py-2 text-sm bg-white focus:border-brand-500"
>
<option value="">All sources</option>
<option value="google">Google Ads</option>
<option value="facebook">Facebook Ads</option>
</select>
</div>
<select
value={statusFilter}
onChange={e => setStatusFilter(e.target.value)}
className="rounded-lg border border-surface-200 px-3 py-2 text-sm bg-white focus:border-brand-500"
>
<option value="">All statuses</option>
<option value="click">Click</option>
<option value="lead">Lead</option>
<option value="deal">Deal</option>
</select>
<select
value={days}
onChange={e => setDays(Number(e.target.value))}
className="rounded-lg border border-surface-200 px-3 py-2 text-sm bg-white focus:border-brand-500"
>
<option value={7}>Last 7 days</option>
<option value={14}>Last 14 days</option>
<option value={30}>Last 30 days</option>
<option value={60}>Last 60 days</option>
<option value={90}>Last 90 days</option>
</select>
</div>
{/* Attribution Table */}
{isLoading ? (
<div className="flex justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-brand-500" />
</div>
) : attributions.length === 0 ? (
<div className="rounded-xl border border-surface-200 bg-white p-12 text-center">
<Target className="h-8 w-8 mx-auto mb-3 text-surface-300" />
<p className="text-surface-500 font-medium">No attribution data</p>
<p className="text-sm text-surface-400 mt-1">
Leads will appear here when forms are submitted with UTM tracking parameters.
</p>
</div>
) : (
<div className="rounded-xl border border-surface-200 bg-white overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-surface-200 bg-surface-50">
<th className="text-left px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Source</th>
<th className="text-left px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Campaign</th>
<th className="text-left px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Keyword</th>
<th className="text-left px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Lead Source</th>
<th className="text-right px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Click Cost</th>
<th className="text-right px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Ad Spend</th>
<th className="text-right px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Revenue</th>
<th className="text-right px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">ROAS</th>
<th className="text-center px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Status</th>
<th className="text-right px-6 py-3 text-xs font-medium uppercase tracking-wider text-surface-500">Date</th>
</tr>
</thead>
<tbody>
{attributions.map(a => (
<AttributionRow key={a.id} attribution={a} />
))}
</tbody>
</table>
</div>
</div>
)}
</div>
</AppLayout>
);
}
function AttributionRow({ attribution }: { attribution: LeadAttribution }) {
return (
<tr className="border-b border-surface-100 last:border-b-0 hover:bg-surface-50/50 transition-colors">
<td className="px-6 py-3">
<span className="capitalize font-medium text-surface-900">{attribution.utm_source || attribution.lead_source}</span>
</td>
<td className="px-6 py-3 text-surface-700">{attribution.utm_campaign || '—'}</td>
<td className="px-6 py-3 text-surface-500">{attribution.utm_term || '—'}</td>
<td className="px-6 py-3 text-surface-500 capitalize">{attribution.lead_source}</td>
<td className="px-6 py-3 text-right tabular-nums text-surface-600">
{attribution.click_cost ? `$${attribution.click_cost.toFixed(2)}` : '—'}
</td>
<td className="px-6 py-3 text-right tabular-nums text-surface-600">
{attribution.total_ad_spend ? `$${attribution.total_ad_spend.toFixed(2)}` : '—'}
</td>
<td className="px-6 py-3 text-right tabular-nums font-medium text-emerald-600">
{attribution.deal_amount ? `$${attribution.deal_amount.toFixed(2)}` : '—'}
</td>
<td className="px-6 py-3 text-right tabular-nums">
{attribution.roas ? (
<span className={attribution.roas >= 3 ? 'text-emerald-600 font-medium' : 'text-amber-600'}>
{attribution.roas.toFixed(1)}x
</span>
) : (
<span className="text-surface-400">—</span>
)}
</td>
<td className="px-6 py-3 text-center">
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
attribution.attribution_status === 'deal'
? 'bg-emerald-100 text-emerald-700'
: attribution.attribution_status === 'lead'
? 'bg-blue-100 text-blue-700'
: 'bg-surface-100 text-surface-600'
}`}>
{attribution.attribution_status}
</span>
</td>
<td className="px-6 py-3 text-right tabular-nums text-surface-400 text-xs whitespace-nowrap">
{new Date(attribution.created_at).toLocaleDateString()}
</td>
</tr>
);
}
function exportCSV(attributions: LeadAttribution[]) {
const headers = ['Source', 'Campaign', 'Keyword', 'Lead Source', 'Click Cost', 'Ad Spend', 'Revenue', 'ROAS', 'Status', 'Date'];
const rows = attributions.map(a => [
a.utm_source || '',
a.utm_campaign || '',
a.utm_term || '',
a.lead_source,
a.click_cost?.toFixed(2) || '',
a.total_ad_spend?.toFixed(2) || '',
a.deal_amount?.toFixed(2) || '',
a.roas?.toFixed(2) || '',
a.attribution_status,
new Date(a.created_at).toLocaleDateString(),
]);
return [headers.join(','), ...rows.map(r => r.map(v => `"${v}"`).join(','))].join('\n');
}
function downloadCSV(csv: string, filename: string) {
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}