File size: 11,051 Bytes
7200823
f667d47
de40b1a
 
 
f667d47
7200823
de40b1a
 
 
 
f667d47
de40b1a
 
7200823
 
 
 
 
 
 
 
 
f667d47
 
 
 
 
 
 
7200823
de40b1a
f667d47
 
 
 
 
 
 
 
 
7200823
 
 
de40b1a
7200823
 
de40b1a
 
 
 
 
 
 
 
 
 
7200823
 
 
de40b1a
f667d47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de40b1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7200823
 
de40b1a
7200823
de40b1a
 
 
 
 
 
 
 
 
 
 
f667d47
de40b1a
 
 
7200823
f667d47
7200823
 
de40b1a
7200823
 
de40b1a
7200823
de40b1a
 
7200823
 
 
 
 
 
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
'use client'
import { TrendingUp, TrendingDown, Target, Zap, Award, Clock, CheckCircle2, XCircle, BarChart2 } from 'lucide-react'
import { cn, fmtNum } from '@/lib/utils'
import { cohorts, eventBreakdown, dailyEvents, roiData } from '@/lib/mock-data'
import { BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
import { useState, useEffect } from 'react'

const Tip = ({ active, payload, label }: any) => {
  if (!active || !payload?.length) return null
  return (<div className="bg-surface-elevated border border-hairline-dark rounded-lg p-3 shadow-lg">
    <p className="text-caption text-muted mb-1">{label}</p>
    {payload.map((p: any, i: number) => <p key={i} className="font-mono text-num-sm" style={{color:p.color}}>{p.name}: {typeof p.value==='number' && p.value>1000 ? fmtNum(p.value) : p.value}{p.unit||''}</p>)}
  </div>)
}

function getColor(v: number) {
  if (v === 0) return 'bg-surface-elevated text-muted'
  if (v >= 75) return 'bg-trading-up/20 text-trading-up'
  if (v >= 50) return 'bg-brand-yellow/15 text-brand-yellow'
  if (v >= 30) return 'bg-[#ff9500]/15 text-[#ff9500]'
  return 'bg-trading-down/15 text-trading-down'
}

interface AttributionStats {
  total: number; recovered: number; pending: number; successRate: number
  avgDaysToRecover: number
  weeklyTrend: { week: string; rate: number }[]
  interventions: { wallet: string; eventFired: string; score: number; recovered: boolean; daysToRecover?: number }[]
}

export default function AnalyticsPage() {
  const maxEvt = Math.max(...eventBreakdown.map(e => e.count))
  const [attr, setAttr] = useState<AttributionStats | null>(null)

  useEffect(() => {
    fetch('/api/torque/attribution')
      .then(r => r.ok ? r.json() : null)
      .then(d => d && setAttr(d))
      .catch(() => {})
  }, [])

  return (
    <div className="p-6 space-y-6">
      <div><h1 className="text-display-sm text-[#eaecef]">Analytics</h1><p className="text-body-md text-muted mt-1">Deep retention insights, cohort analysis & ROI tracking</p></div>

      <div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
        {[
          { l:'Avg Retention', v:'67.3%', c:'+9.6%', up:true, i:Target },
          { l:'Churn Rate', v:'4.2%', c:'-4.3%', up:false, i:TrendingDown },
          { l:'Campaign ROI', v:'847%', c:'+15.2%', up:true, i:TrendingUp },
          { l:'Saved Wallets', v:'15.2K', c:'+23.4%', up:true, i:Award },
          { l:'Agent Actions', v:'3,847', c:'+12.1%', up:true, i:Zap },
        ].map(k => { const I = k.i; return (
          <div key={k.l} className="rounded-xl bg-surface-card border border-hairline-dark p-4">
            <div className="flex items-center justify-between mb-2"><span className="text-caption text-muted">{k.l}</span><I className="w-4 h-4 text-muted"/></div>
            <div className="font-mono text-title-lg text-[#eaecef] tabular-nums">{k.v}</div>
            <span className={cn('font-mono text-num-sm tabular-nums', k.up?'text-trading-up':'text-trading-down')}>{k.c}</span>
          </div>
        )})}
      </div>

      {/* Recovery Attribution Dashboard */}
      {attr && (
        <div className="rounded-xl bg-surface-card border border-hairline-dark overflow-hidden">
          <div className="px-5 py-4 border-b border-hairline-dark flex items-center justify-between">
            <div className="flex items-center gap-2">
              <BarChart2 className="w-4 h-4 text-trading-up" />
              <h3 className="text-title-sm">Recovery Attribution</h3>
              <span className="text-[10px] px-2 py-0.5 rounded-pill bg-trading-up/10 text-trading-up font-semibold border border-trading-up/20">LIVE</span>
            </div>
            <p className="text-caption text-muted">Did interventions actually work?</p>
          </div>
          <div className="p-5 space-y-5">
            <div className="grid grid-cols-4 gap-4">
              <div className="rounded-xl bg-trading-up/5 border border-trading-up/20 p-4 text-center">
                <p className="text-caption text-trading-up/70 mb-1">Success Rate</p>
                <p className="font-mono text-display-sm text-trading-up tabular-nums">{attr.successRate}%</p>
                <p className="text-caption text-muted mt-0.5">{attr.recovered}/{attr.total} rescued</p>
              </div>
              <div className="rounded-xl bg-surface-elevated border border-hairline-dark p-4 text-center">
                <p className="text-caption text-muted mb-1">Avg Recovery</p>
                <p className="font-mono text-title-lg text-[#eaecef] tabular-nums">{attr.avgDaysToRecover}d</p>
                <p className="text-caption text-muted mt-0.5">days to return</p>
              </div>
              <div className="rounded-xl bg-brand-yellow/5 border border-brand-yellow/20 p-4 text-center">
                <p className="text-caption text-brand-yellow/70 mb-1">Pending</p>
                <p className="font-mono text-title-lg text-brand-yellow tabular-nums">{attr.pending}</p>
                <p className="text-caption text-muted mt-0.5">awaiting return</p>
              </div>
              <div className="rounded-xl bg-surface-elevated border border-hairline-dark p-4">
                <p className="text-caption text-muted mb-2">Weekly trend</p>
                <ResponsiveContainer width="100%" height={52}>
                  <LineChart data={attr.weeklyTrend}>
                    <Line type="monotone" dataKey="rate" stroke="#0ecb81" strokeWidth={2} dot={false} />
                  </LineChart>
                </ResponsiveContainer>
              </div>
            </div>

            <div>
              <p className="text-caption text-muted uppercase tracking-wider mb-3">Recent interventions</p>
              <div className="space-y-1.5">
                {attr.interventions.map((item, i) => (
                  <div key={i} className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-surface-elevated/50 transition">
                    {item.recovered
                      ? <CheckCircle2 className="w-4 h-4 text-trading-up flex-shrink-0" />
                      : <XCircle className="w-4 h-4 text-muted flex-shrink-0" />}
                    <span className="font-mono text-body-sm text-[#eaecef] w-32">{item.wallet}</span>
                    <code className="text-[10px] text-muted bg-surface-elevated px-1.5 py-0.5 rounded">{item.eventFired}</code>
                    <span className="font-mono text-num-sm text-muted tabular-nums">score={item.score}</span>
                    <span className={cn('ml-auto text-caption font-semibold', item.recovered ? 'text-trading-up' : 'text-muted')}>
                      {item.recovered ? `✓ returned in ${item.daysToRecover}d` : 'pending'}
                    </span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      )}

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <div className="rounded-xl bg-surface-card border border-hairline-dark p-5">
          <h3 className="text-title-sm mb-4">Daily Custom Events</h3>
          <ResponsiveContainer width="100%" height={250}>
            <BarChart data={dailyEvents}><CartesianGrid strokeDasharray="3 3" stroke="#2b3139"/><XAxis dataKey="date" tick={{fill:'#707a8a',fontSize:11}} axisLine={{stroke:'#2b3139'}}/><YAxis tick={{fill:'#707a8a',fontSize:11}} axisLine={{stroke:'#2b3139'}}/><Tooltip content={<Tip/>}/><Bar dataKey="value" fill="#FCD535" radius={[4,4,0,0]} name="Events"/></BarChart>
          </ResponsiveContainer>
        </div>
        <div className="rounded-xl bg-surface-card border border-hairline-dark p-5">
          <h3 className="text-title-sm mb-4">Campaign ROI Over Time</h3>
          <ResponsiveContainer width="100%" height={250}>
            <LineChart data={roiData}><CartesianGrid strokeDasharray="3 3" stroke="#2b3139"/><XAxis dataKey="date" tick={{fill:'#707a8a',fontSize:11}} axisLine={{stroke:'#2b3139'}}/><YAxis tick={{fill:'#707a8a',fontSize:11}} axisLine={{stroke:'#2b3139'}} unit="%"/><Tooltip content={<Tip/>}/><Line type="monotone" dataKey="value" stroke="#0ecb81" strokeWidth={2.5} dot={{fill:'#0ecb81',r:4}} name="ROI %"/></LineChart>
          </ResponsiveContainer>
        </div>
      </div>

      <div className="rounded-xl bg-surface-card border border-hairline-dark p-5">
        <div className="flex items-center justify-between mb-4">
          <div><h3 className="text-title-sm">Retention Cohorts</h3><p className="text-caption text-muted mt-0.5">Weekly cohort heatmap — FlowState launched Mar 31</p></div>
        </div>
        <table className="w-full"><thead><tr className="border-b border-hairline-dark">
          <th className="text-left text-caption text-muted uppercase px-4 py-2">Cohort</th>
          <th className="text-center text-caption text-muted uppercase px-4 py-2">Day 1</th>
          <th className="text-center text-caption text-muted uppercase px-4 py-2">Day 7</th>
          <th className="text-center text-caption text-muted uppercase px-4 py-2">Day 14</th>
          <th className="text-center text-caption text-muted uppercase px-4 py-2">Day 30</th>
          <th className="text-center text-caption text-muted uppercase px-4 py-2">Day 60</th>
        </tr></thead><tbody>{cohorts.map(c => (
          <tr key={c.week} className="border-b border-hairline-dark/50">
            <td className="px-4 py-2"><div className="flex items-center gap-2"><Clock className="w-3.5 h-3.5 text-muted"/><span className="text-body-sm text-muted font-mono">{c.week}</span></div></td>
            {[c.d1,c.d7,c.d14,c.d30,c.d60].map((v,i) => (
              <td key={i} className="px-4 py-2 text-center"><span className={cn('inline-block min-w-[48px] px-2 py-1 rounded font-mono text-num-sm tabular-nums', getColor(v))}>{v===0?'—':v+'%'}</span></td>
            ))}
          </tr>
        ))}</tbody></table>
        <div className="mt-4 p-3 rounded-lg bg-trading-up/5 border border-trading-up/20">
          <p className="text-body-sm text-trading-up">📈 <strong>FlowState Impact:</strong> Cohorts after Mar 31 show +7pp higher day-7 retention and +11pp higher day-14 retention vs pre-launch.</p>
        </div>
      </div>

      <div className="rounded-xl bg-surface-card border border-hairline-dark p-5">
        <h3 className="text-title-sm mb-4">Torque Custom Events Breakdown</h3>
        <div className="space-y-4">{eventBreakdown.map(e => (
          <div key={e.event} className="space-y-1.5">
            <div className="flex items-center justify-between"><code className="font-mono text-caption text-muted">{e.event}</code><span className="font-mono text-num-sm text-[#eaecef] tabular-nums">{fmtNum(e.count)}</span></div>
            <div className="h-2 rounded-full bg-surface-elevated overflow-hidden"><div className="h-full rounded-full transition-all duration-700" style={{width:(e.count/maxEvt*100)+'%',backgroundColor:e.color}}/></div>
          </div>
        ))}</div>
      </div>
    </div>
  )
}