Outreach.jsx
1 import { useQuery } from '@tanstack/react-query'; 2 import { fetchOutreach } from '../api'; 3 import MetricCard from '../components/MetricCard'; 4 import DataTable from '../components/DataTable'; 5 import { BarChart, LineChart, PieChart, FunnelChart } from '../components/charts'; 6 7 const PROVIDER_COLS = [ 8 { accessorKey: 'provider', header: 'Provider' }, 9 { accessorKey: 'model', header: 'Model' }, 10 { 11 accessorKey: 'total_tokens', 12 header: 'Tokens', 13 cell: i => i.getValue()?.toLocaleString() ?? '—', 14 }, 15 { accessorKey: 'total_cost', header: 'Cost', cell: i => `$${(i.getValue() ?? 0).toFixed(4)}` }, 16 { accessorKey: 'request_count', header: 'Requests' }, 17 ]; 18 19 export default function Outreach() { 20 const { data, isLoading, error } = useQuery({ queryKey: ['outreach'], queryFn: fetchOutreach }); 21 22 if (isLoading) return <div className="text-slate-400 p-8">Loading…</div>; 23 if (error) return <div className="text-red-400 p-8">Error: {error.message}</div>; 24 25 const d = data ?? {}; 26 const rr = d.response_rates ?? {}; 27 const ss = d.sales_data ?? {}; 28 const lu = d.llm_usage_by_stage ?? []; 29 const dc = d.llm_daily_costs_30d ?? []; 30 const prov = d.llm_usage_by_provider ?? []; 31 const funnel = d.outreach_funnel ?? []; 32 33 // Build channel response chart data 34 const channelData = Object.entries(rr.by_channel ?? {}).map(([k, v]) => ({ 35 channel: k, 36 sent: v.sent ?? 0, 37 replied: v.replied ?? 0, 38 rate: (((v.replied ?? 0) / Math.max(v.sent ?? 1, 1)) * 100).toFixed(1), 39 })); 40 41 return ( 42 <div className="space-y-6"> 43 {/* Top metrics */} 44 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 45 <MetricCard label="Total Sent" value={(rr.total_sent ?? 0).toLocaleString()} /> 46 <MetricCard 47 label="Response Rate" 48 value={`${(rr.response_rate ?? 0).toFixed(1)}%`} 49 variant={ 50 (rr.response_rate ?? 0) >= 2 51 ? 'success' 52 : (rr.response_rate ?? 0) >= 1 53 ? 'warning' 54 : 'error' 55 } 56 /> 57 <MetricCard label="Total Sales" value={ss.total_sales ?? 0} variant="success" /> 58 <MetricCard 59 label="Revenue" 60 value={`$${(ss.total_revenue ?? 0).toLocaleString('en-AU', { minimumFractionDigits: 0 })}`} 61 variant="success" 62 /> 63 </div> 64 65 {/* Delivery funnel + channel response rates */} 66 <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 67 {funnel.length > 0 && ( 68 <div className="bg-slate-800 rounded-xl p-4 border border-slate-700"> 69 <h3 className="text-sm font-medium text-slate-400 mb-3">Delivery Funnel</h3> 70 <FunnelChart data={funnel} nameKey="status" valueKey="count" /> 71 </div> 72 )} 73 {channelData.length > 0 && ( 74 <div className="bg-slate-800 rounded-xl p-4 border border-slate-700"> 75 <h3 className="text-sm font-medium text-slate-400 mb-3">Replies by Channel</h3> 76 <BarChart 77 data={channelData} 78 xKey="channel" 79 bars={[ 80 { key: 'sent', name: 'Sent', color: '#60a5fa' }, 81 { key: 'replied', name: 'Replied', color: '#34d399' }, 82 ]} 83 /> 84 </div> 85 )} 86 </div> 87 88 {/* Sales metrics */} 89 {ss.total_sales > 0 && ( 90 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 91 <MetricCard label="Avg Deal Value" value={`$${(ss.avg_deal_value ?? 297).toFixed(0)}`} /> 92 <MetricCard label="Conversion Rate" value={`${(ss.conversion_rate ?? 0).toFixed(2)}%`} /> 93 <MetricCard 94 label="Cost per Sale" 95 value={`$${(ss.cost_per_sale ?? 0).toFixed(2)}`} 96 variant="warning" 97 /> 98 <MetricCard 99 label="Gross Margin" 100 value={`${(ss.gross_margin ?? 99.3).toFixed(1)}%`} 101 variant="success" 102 /> 103 </div> 104 )} 105 106 {/* LLM cost by stage */} 107 {lu.length > 0 && ( 108 <div className="bg-slate-800 rounded-xl p-4 border border-slate-700"> 109 <h3 className="text-sm font-medium text-slate-400 mb-3">LLM Cost by Pipeline Stage</h3> 110 <BarChart 111 data={lu} 112 xKey="stage" 113 bars={[{ key: 'total_cost', name: 'Cost ($)', color: '#fbbf24' }]} 114 /> 115 </div> 116 )} 117 118 {/* Daily LLM costs 30d */} 119 {dc.length > 0 && ( 120 <div className="bg-slate-800 rounded-xl p-4 border border-slate-700"> 121 <h3 className="text-sm font-medium text-slate-400 mb-3"> 122 Daily LLM Costs (Last 30 Days) 123 </h3> 124 <LineChart 125 data={dc} 126 xKey="date" 127 lines={[{ key: 'daily_cost', name: 'Cost ($)', color: '#fbbf24' }]} 128 /> 129 </div> 130 )} 131 132 {/* Provider breakdown */} 133 {prov.length > 0 && ( 134 <div className="bg-slate-800 rounded-xl p-4 border border-slate-700"> 135 <h3 className="text-sm font-medium text-slate-400 mb-3">Cost by Provider & Model</h3> 136 <DataTable columns={PROVIDER_COLS} data={prov} /> 137 </div> 138 )} 139 </div> 140 ); 141 }