spine-framework-cortex 0.1.19 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/{functions/custom_cortex-handler.ts → api/cortex-handler.ts} +9 -9
  2. package/components/CliInstancesCard.tsx +144 -0
  3. package/components/CortexSidebar.tsx +27 -53
  4. package/hooks/useTypeRegistry.ts +74 -0
  5. package/index.tsx +13 -24
  6. package/manifest.json +1 -13
  7. package/package.json +11 -20
  8. package/pages/courses/CoursesPage.tsx +14 -4
  9. package/pages/crm/AccountDetailPage.tsx +149 -194
  10. package/pages/crm/ContactsPage.tsx +7 -7
  11. package/pages/intelligence/IntelligencePage.tsx +24 -31
  12. package/pages/kb/KBEditorPage.tsx +9 -2
  13. package/pages/operations/AuditFunnelPage.tsx +378 -0
  14. package/pages/operations/InstallFunnelPage.tsx +410 -0
  15. package/pages/operations/OperationsDashboard.tsx +275 -0
  16. package/pages/support/RedactionReview.tsx +11 -2
  17. package/seed/link-types.json +8 -42
  18. package/seed/package.json +27 -0
  19. package/seed/roles.json +1 -1
  20. package/seed/types.json +2711 -596
  21. package/CHANGELOG.md +0 -46
  22. package/LICENSE.md +0 -223
  23. package/README.md +0 -69
  24. package/functions/custom_anonymous-sessions.ts +0 -356
  25. package/functions/custom_case_analysis.ts +0 -507
  26. package/functions/custom_community-escalation.ts +0 -234
  27. package/functions/custom_cortex-chunks.ts +0 -52
  28. package/functions/custom_funnel-scoring.ts +0 -256
  29. package/functions/custom_funnel-signal.ts +0 -446
  30. package/functions/custom_funnel-timers.ts +0 -449
  31. package/functions/custom_kb-chunker-test.ts +0 -364
  32. package/functions/custom_kb-chunker.ts +0 -576
  33. package/functions/custom_kb-embeddings.ts +0 -481
  34. package/functions/custom_kb-ingestion.ts +0 -448
  35. package/functions/custom_support-triage.ts +0 -649
  36. package/functions/custom_tag_management.ts +0 -314
  37. package/functions/webhook-handlers.ts +0 -29
  38. package/lib/resolveTypeId.ts +0 -16
  39. package/pages/crm/ContactDetailPage.tsx +0 -184
  40. package/pages/ops/AuditFunnelPage.tsx +0 -191
  41. package/pages/ops/CommandCenterPage.tsx +0 -377
  42. package/pages/ops/InstallFunnelPage.tsx +0 -226
  43. package/seed/accounts.json +0 -9
  44. package/seed/integrations.json +0 -24
  45. package/seed/pipelines.json +0 -59
  46. package/seed/triggers.json +0 -125
@@ -0,0 +1,410 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@core/components/ui/card'
3
+ import { Badge } from '@core/components/ui/badge'
4
+ import { Button } from '@core/components/ui/button'
5
+ import { Progress } from '@core/components/ui/progress'
6
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@core/components/ui/tabs'
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@core/components/ui/table'
8
+ import { useApi } from '@core/hooks/useApi'
9
+ import { formatDistanceToNow } from 'date-fns'
10
+ import { Download, TrendingUp, Users, Package } from 'lucide-react'
11
+
12
+ interface InstallMetrics {
13
+ total_downloads: number
14
+ unique_installers: number
15
+ active_instances: number
16
+ conversion_funnel: {
17
+ downloads: number
18
+ installations: number
19
+ first_use: number
20
+ claimed: number
21
+ }
22
+ install_trends: Array<{
23
+ date: string
24
+ downloads: number
25
+ installations: number
26
+ }>
27
+ recent_installs: Array<{
28
+ id: string
29
+ serial: string
30
+ app_version: string
31
+ environment: string
32
+ first_seen_at: string
33
+ last_signal_at: string
34
+ claimed_by?: string
35
+ claimed_at?: string
36
+ account_name?: string
37
+ }>
38
+ top_versions: Array<{
39
+ version: string
40
+ count: number
41
+ percentage: number
42
+ }>
43
+ }
44
+
45
+ export default function InstallFunnelPage() {
46
+ const [metrics, setMetrics] = useState<InstallMetrics | null>(null)
47
+ const [loading, setLoading] = useState(true)
48
+ const { apiFetch } = useApi()
49
+
50
+ useEffect(() => {
51
+ loadMetrics()
52
+ }, [])
53
+
54
+ const loadMetrics = async () => {
55
+ try {
56
+ const data = await apiFetch('/admin/install-funnel/metrics')
57
+ setMetrics(data)
58
+ } catch (error) {
59
+ console.error('Failed to load install metrics:', error)
60
+ } finally {
61
+ setLoading(false)
62
+ }
63
+ }
64
+
65
+ const exportData = async () => {
66
+ try {
67
+ const response = await apiFetch('/admin/install-funnel/exportData')
68
+
69
+ // Create download link
70
+ const blob = new Blob([JSON.stringify(response, null, 2)], { type: 'application/json' })
71
+ const url = window.URL.createObjectURL(blob)
72
+ const a = document.createElement('a')
73
+ a.href = url
74
+ a.download = `install-funnel-${new Date().toISOString().split('T')[0]}.json`
75
+ document.body.appendChild(a)
76
+ a.click()
77
+ window.URL.revokeObjectURL(url)
78
+ document.body.removeChild(a)
79
+ } catch (error) {
80
+ console.error('Failed to export data:', error)
81
+ }
82
+ }
83
+
84
+ if (loading) {
85
+ return <div className="p-6">Loading install funnel data...</div>
86
+ }
87
+
88
+ const conversionRates = metrics ? {
89
+ download_to_install: (metrics.conversion_funnel.installations / metrics.conversion_funnel.downloads * 100) || 0,
90
+ install_to_use: (metrics.conversion_funnel.first_use / metrics.conversion_funnel.installations * 100) || 0,
91
+ use_to_claim: (metrics.conversion_funnel.claimed / metrics.conversion_funnel.first_use * 100) || 0
92
+ } : { download_to_install: 0, install_to_use: 0, use_to_claim: 0 }
93
+
94
+ return (
95
+ <div className="p-6 space-y-6">
96
+ <div className="flex justify-between items-center">
97
+ <div>
98
+ <h1 className="text-3xl font-bold">Install Funnel</h1>
99
+ <p className="text-muted-foreground">
100
+ Track downloads, installations, and adoption metrics
101
+ </p>
102
+ </div>
103
+ <div className="flex space-x-2">
104
+ <Button variant="outline" onClick={exportData}>
105
+ <Download className="w-4 h-4 mr-2" />
106
+ Export Data
107
+ </Button>
108
+ <Button onClick={loadMetrics}>Refresh</Button>
109
+ </div>
110
+ </div>
111
+
112
+ {/* Key Metrics */}
113
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
114
+ <Card>
115
+ <CardHeader className="pb-2">
116
+ <CardTitle className="text-sm font-medium flex items-center">
117
+ <Download className="w-4 h-4 mr-2" />
118
+ Total Downloads
119
+ </CardTitle>
120
+ </CardHeader>
121
+ <CardContent>
122
+ <div className="text-2xl font-bold">{metrics?.total_downloads || 0}</div>
123
+ <p className="text-xs text-muted-foreground">
124
+ All-time downloads
125
+ </p>
126
+ </CardContent>
127
+ </Card>
128
+
129
+ <Card>
130
+ <CardHeader className="pb-2">
131
+ <CardTitle className="text-sm font-medium flex items-center">
132
+ <Users className="w-4 h-4 mr-2" />
133
+ Unique Installers
134
+ </CardTitle>
135
+ </CardHeader>
136
+ <CardContent>
137
+ <div className="text-2xl font-bold">{metrics?.unique_installers || 0}</div>
138
+ <p className="text-xs text-muted-foreground">
139
+ Distinct installations
140
+ </p>
141
+ </CardContent>
142
+ </Card>
143
+
144
+ <Card>
145
+ <CardHeader className="pb-2">
146
+ <CardTitle className="text-sm font-medium flex items-center">
147
+ <Package className="w-4 h-4 mr-2" />
148
+ Active Instances
149
+ </CardTitle>
150
+ </CardHeader>
151
+ <CardContent>
152
+ <div className="text-2xl font-bold">{metrics?.active_instances || 0}</div>
153
+ <p className="text-xs text-muted-foreground">
154
+ Used in last 7 days
155
+ </p>
156
+ </CardContent>
157
+ </Card>
158
+
159
+ <Card>
160
+ <CardHeader className="pb-2">
161
+ <CardTitle className="text-sm font-medium flex items-center">
162
+ <TrendingUp className="w-4 h-4 mr-2" />
163
+ Overall Conversion
164
+ </CardTitle>
165
+ </CardHeader>
166
+ <CardContent>
167
+ <div className="text-2xl font-bold">
168
+ {conversionRates.download_to_install.toFixed(1)}%
169
+ </div>
170
+ <p className="text-xs text-muted-foreground">
171
+ Download → Install
172
+ </p>
173
+ </CardContent>
174
+ </Card>
175
+ </div>
176
+
177
+ <Tabs defaultValue="funnel" className="space-y-4">
178
+ <TabsList>
179
+ <TabsTrigger value="funnel">Conversion Funnel</TabsTrigger>
180
+ <TabsTrigger value="instances">Instance Details</TabsTrigger>
181
+ <TabsTrigger value="trends">Installation Trends</TabsTrigger>
182
+ <TabsTrigger value="versions">Version Analytics</TabsTrigger>
183
+ </TabsList>
184
+
185
+ <TabsContent value="funnel" className="space-y-4">
186
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
187
+ <Card>
188
+ <CardHeader>
189
+ <CardTitle>Conversion Funnel</CardTitle>
190
+ <CardDescription>User progression from download to claim</CardDescription>
191
+ </CardHeader>
192
+ <CardContent>
193
+ <div className="space-y-4">
194
+ {metrics?.conversion_funnel && (
195
+ <>
196
+ <div className="space-y-2">
197
+ <div className="flex justify-between">
198
+ <span>Downloads</span>
199
+ <span className="font-medium">{metrics.conversion_funnel.downloads}</span>
200
+ </div>
201
+ <Progress value={100} className="h-2" />
202
+ </div>
203
+
204
+ <div className="space-y-2">
205
+ <div className="flex justify-between">
206
+ <span>Installations</span>
207
+ <span className="font-medium">{metrics.conversion_funnel.installations}</span>
208
+ </div>
209
+ <Progress value={conversionRates.download_to_install} className="h-2" />
210
+ <p className="text-xs text-muted-foreground">
211
+ {conversionRates.download_to_install.toFixed(1)}% conversion
212
+ </p>
213
+ </div>
214
+
215
+ <div className="space-y-2">
216
+ <div className="flex justify-between">
217
+ <span>First Use</span>
218
+ <span className="font-medium">{metrics.conversion_funnel.first_use}</span>
219
+ </div>
220
+ <Progress value={conversionRates.install_to_use} className="h-2" />
221
+ <p className="text-xs text-muted-foreground">
222
+ {conversionRates.install_to_use.toFixed(1)}% conversion
223
+ </p>
224
+ </div>
225
+
226
+ <div className="space-y-2">
227
+ <div className="flex justify-between">
228
+ <span>Claimed</span>
229
+ <span className="font-medium">{metrics.conversion_funnel.claimed}</span>
230
+ </div>
231
+ <Progress value={conversionRates.use_to_claim} className="h-2" />
232
+ <p className="text-xs text-muted-foreground">
233
+ {conversionRates.use_to_claim.toFixed(1)}% conversion
234
+ </p>
235
+ </div>
236
+ </>
237
+ )}
238
+ </div>
239
+ </CardContent>
240
+ </Card>
241
+
242
+ <Card>
243
+ <CardHeader>
244
+ <CardTitle>Conversion Insights</CardTitle>
245
+ <CardDescription>Key metrics and drop-off points</CardDescription>
246
+ </CardHeader>
247
+ <CardContent>
248
+ <div className="space-y-4">
249
+ <div className="flex justify-between items-center p-3 bg-blue-50 rounded-lg">
250
+ <div>
251
+ <div className="font-medium">Download to Install</div>
252
+ <div className="text-sm text-muted-foreground">
253
+ {conversionRates.download_to_install.toFixed(1)}% conversion
254
+ </div>
255
+ </div>
256
+ <Badge variant={conversionRates.download_to_install > 50 ? 'default' : 'destructive'}>
257
+ {conversionRates.download_to_install > 50 ? 'Good' : 'Needs Attention'}
258
+ </Badge>
259
+ </div>
260
+
261
+ <div className="flex justify-between items-center p-3 bg-green-50 rounded-lg">
262
+ <div>
263
+ <div className="font-medium">Install to Use</div>
264
+ <div className="text-sm text-muted-foreground">
265
+ {conversionRates.install_to_use.toFixed(1)}% conversion
266
+ </div>
267
+ </div>
268
+ <Badge variant={conversionRates.install_to_use > 70 ? 'default' : 'destructive'}>
269
+ {conversionRates.install_to_use > 70 ? 'Good' : 'Needs Attention'}
270
+ </Badge>
271
+ </div>
272
+
273
+ <div className="flex justify-between items-center p-3 bg-purple-50 rounded-lg">
274
+ <div>
275
+ <div className="font-medium">Use to Claim</div>
276
+ <div className="text-sm text-muted-foreground">
277
+ {conversionRates.use_to_claim.toFixed(1)}% conversion
278
+ </div>
279
+ </div>
280
+ <Badge variant={conversionRates.use_to_claim > 30 ? 'default' : 'destructive'}>
281
+ {conversionRates.use_to_claim > 30 ? 'Good' : 'Needs Attention'}
282
+ </Badge>
283
+ </div>
284
+ </div>
285
+ </CardContent>
286
+ </Card>
287
+ </div>
288
+ </TabsContent>
289
+
290
+ <TabsContent value="instances" className="space-y-4">
291
+ <Card>
292
+ <CardHeader>
293
+ <CardTitle>Recent Installations</CardTitle>
294
+ <CardDescription>Latest instance installations and their status</CardDescription>
295
+ </CardHeader>
296
+ <CardContent>
297
+ <Table>
298
+ <TableHeader>
299
+ <TableRow>
300
+ <TableHead>Instance ID</TableHead>
301
+ <TableHead>Version</TableHead>
302
+ <TableHead>Environment</TableHead>
303
+ <TableHead>First Seen</TableHead>
304
+ <TableHead>Last Activity</TableHead>
305
+ <TableHead>Status</TableHead>
306
+ <TableHead>Owner</TableHead>
307
+ </TableRow>
308
+ </TableHeader>
309
+ <TableBody>
310
+ {metrics?.recent_installs?.map((instance) => (
311
+ <TableRow key={instance.id}>
312
+ <TableCell>
313
+ <code className="text-xs bg-muted px-1 py-0.5 rounded">
314
+ {instance.serial.slice(0, 12)}...
315
+ </code>
316
+ </TableCell>
317
+ <TableCell>
318
+ <Badge variant="outline">{instance.app_version}</Badge>
319
+ </TableCell>
320
+ <TableCell>
321
+ <Badge
322
+ variant={instance.environment === 'production' ? 'default' : 'secondary'}
323
+ >
324
+ {instance.environment}
325
+ </Badge>
326
+ </TableCell>
327
+ <TableCell>
328
+ <div className="text-sm">
329
+ {formatDistanceToNow(new Date(instance.first_seen_at), { addSuffix: true })}
330
+ </div>
331
+ </TableCell>
332
+ <TableCell>
333
+ <div className="text-sm">
334
+ {formatDistanceToNow(new Date(instance.last_signal_at), { addSuffix: true })}
335
+ </div>
336
+ </TableCell>
337
+ <TableCell>
338
+ <Badge variant={instance.claimed_by ? 'default' : 'secondary'}>
339
+ {instance.claimed_by ? 'Claimed' : 'Unclaimed'}
340
+ </Badge>
341
+ </TableCell>
342
+ <TableCell>
343
+ {instance.account_name ? (
344
+ <div>
345
+ <div className="font-medium text-sm">{instance.account_name}</div>
346
+ <div className="text-xs text-muted-foreground">{instance.claimed_by}</div>
347
+ </div>
348
+ ) : (
349
+ <span className="text-sm text-muted-foreground">Unclaimed</span>
350
+ )}
351
+ </TableCell>
352
+ </TableRow>
353
+ ))}
354
+ </TableBody>
355
+ </Table>
356
+ </CardContent>
357
+ </Card>
358
+ </TabsContent>
359
+
360
+ <TabsContent value="trends" className="space-y-4">
361
+ <Card>
362
+ <CardHeader>
363
+ <CardTitle>Installation Trends</CardTitle>
364
+ <CardDescription>Daily download and installation activity</CardDescription>
365
+ </CardHeader>
366
+ <CardContent>
367
+ <div className="text-center py-8 text-muted-foreground">
368
+ <TrendingUp className="w-12 h-12 mx-auto mb-4 opacity-50" />
369
+ <p>Chart visualization would be implemented here</p>
370
+ <p className="text-sm">Showing {metrics?.install_trends?.length || 0} days of data</p>
371
+ </div>
372
+ </CardContent>
373
+ </Card>
374
+ </TabsContent>
375
+
376
+ <TabsContent value="versions" className="space-y-4">
377
+ <Card>
378
+ <CardHeader>
379
+ <CardTitle>Version Distribution</CardTitle>
380
+ <CardDescription>Active instances by version</CardDescription>
381
+ </CardHeader>
382
+ <CardContent>
383
+ <div className="space-y-3">
384
+ {metrics?.top_versions?.map((version) => (
385
+ <div key={version.version} className="space-y-1">
386
+ <div className="flex justify-between items-center">
387
+ <div className="flex items-center space-x-2">
388
+ <Badge variant="outline">{version.version}</Badge>
389
+ <span className="text-sm text-muted-foreground">
390
+ {version.count} instances
391
+ </span>
392
+ </div>
393
+ <span className="font-medium">{version.percentage.toFixed(1)}%</span>
394
+ </div>
395
+ <div className="w-full bg-secondary rounded-full h-2">
396
+ <div
397
+ className="bg-primary h-2 rounded-full"
398
+ style={{ width: `${version.percentage}%` }}
399
+ />
400
+ </div>
401
+ </div>
402
+ ))}
403
+ </div>
404
+ </CardContent>
405
+ </Card>
406
+ </TabsContent>
407
+ </Tabs>
408
+ </div>
409
+ )
410
+ }
@@ -0,0 +1,275 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@core/components/ui/card'
3
+ import { Badge } from '@core/components/ui/badge'
4
+ import { Button } from '@core/components/ui/button'
5
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@core/components/ui/tabs'
6
+ import { useApi } from '@core/hooks/useApi'
7
+ import { formatDistanceToNow } from 'date-fns'
8
+
9
+ interface SignalMetrics {
10
+ total_signals: number
11
+ signals_today: number
12
+ signals_this_week: number
13
+ conversion_rate: number
14
+ top_sources: Array<{ source: string; count: number }>
15
+ recent_signals: Array<{
16
+ id: string
17
+ action_type: string
18
+ source: string
19
+ stage: string
20
+ created_at: string
21
+ account_name?: string
22
+ }>
23
+ }
24
+
25
+ interface FunnelMetrics {
26
+ anonymous_visitors: number
27
+ identified_users: number
28
+ installed_instances: number
29
+ conversion_rates: {
30
+ anonymous_to_identified: number
31
+ identified_to_installed: number
32
+ }
33
+ stage_distribution: Array<{
34
+ stage: string
35
+ count: number
36
+ percentage: number
37
+ }>
38
+ }
39
+
40
+ export default function OperationsDashboard() {
41
+ const [signalMetrics, setSignalMetrics] = useState<SignalMetrics | null>(null)
42
+ const [funnelMetrics, setFunnelMetrics] = useState<FunnelMetrics | null>(null)
43
+ const [loading, setLoading] = useState(true)
44
+ const { apiFetch } = useApi()
45
+
46
+ useEffect(() => {
47
+ loadMetrics()
48
+ }, [])
49
+
50
+ const loadMetrics = async () => {
51
+ try {
52
+ const [signals, funnel] = await Promise.all([
53
+ apiFetch('/admin/signals/metrics'),
54
+ apiFetch('/admin/funnel/metrics')
55
+ ])
56
+ setSignalMetrics(signals)
57
+ setFunnelMetrics(funnel)
58
+ } catch (error) {
59
+ console.error('Failed to load metrics:', error)
60
+ } finally {
61
+ setLoading(false)
62
+ }
63
+ }
64
+
65
+ if (loading) {
66
+ return <div className="p-6">Loading operations dashboard...</div>
67
+ }
68
+
69
+ return (
70
+ <div className="p-6 space-y-6">
71
+ <div className="flex justify-between items-center">
72
+ <div>
73
+ <h1 className="text-3xl font-bold">Operations Dashboard</h1>
74
+ <p className="text-muted-foreground">
75
+ Monitor signal tracking, funnel performance, and system health
76
+ </p>
77
+ </div>
78
+ <Button onClick={loadMetrics}>Refresh</Button>
79
+ </div>
80
+
81
+ {/* Key Metrics */}
82
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
83
+ <Card>
84
+ <CardHeader className="pb-2">
85
+ <CardTitle className="text-sm font-medium">Total Signals</CardTitle>
86
+ </CardHeader>
87
+ <CardContent>
88
+ <div className="text-2xl font-bold">{signalMetrics?.total_signals || 0}</div>
89
+ <p className="text-xs text-muted-foreground">
90
+ +{signalMetrics?.signals_today || 0} today
91
+ </p>
92
+ </CardContent>
93
+ </Card>
94
+
95
+ <Card>
96
+ <CardHeader className="pb-2">
97
+ <CardTitle className="text-sm font-medium">Conversion Rate</CardTitle>
98
+ </CardHeader>
99
+ <CardContent>
100
+ <div className="text-2xl font-bold">{(signalMetrics?.conversion_rate || 0).toFixed(1)}%</div>
101
+ <p className="text-xs text-muted-foreground">
102
+ Last 30 days
103
+ </p>
104
+ </CardContent>
105
+ </Card>
106
+
107
+ <Card>
108
+ <CardHeader className="pb-2">
109
+ <CardTitle className="text-sm font-medium">Active Users</CardTitle>
110
+ </CardHeader>
111
+ <CardContent>
112
+ <div className="text-2xl font-bold">{funnelMetrics?.identified_users || 0}</div>
113
+ <p className="text-xs text-muted-foreground">
114
+ Identified users
115
+ </p>
116
+ </CardContent>
117
+ </Card>
118
+
119
+ <Card>
120
+ <CardHeader className="pb-2">
121
+ <CardTitle className="text-sm font-medium">Installations</CardTitle>
122
+ </CardHeader>
123
+ <CardContent>
124
+ <div className="text-2xl font-bold">{funnelMetrics?.installed_instances || 0}</div>
125
+ <p className="text-xs text-muted-foreground">
126
+ Active instances
127
+ </p>
128
+ </CardContent>
129
+ </Card>
130
+ </div>
131
+
132
+ <Tabs defaultValue="signals" className="space-y-4">
133
+ <TabsList>
134
+ <TabsTrigger value="signals">Signal Analytics</TabsTrigger>
135
+ <TabsTrigger value="funnel">Funnel Performance</TabsTrigger>
136
+ <TabsTrigger value="health">System Health</TabsTrigger>
137
+ </TabsList>
138
+
139
+ <TabsContent value="signals" className="space-y-4">
140
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
141
+ <Card>
142
+ <CardHeader>
143
+ <CardTitle>Top Signal Sources</CardTitle>
144
+ <CardDescription>Most active signal sources</CardDescription>
145
+ </CardHeader>
146
+ <CardContent>
147
+ <div className="space-y-2">
148
+ {signalMetrics?.top_sources?.map((source, index) => (
149
+ <div key={source.source} className="flex justify-between items-center">
150
+ <div className="flex items-center space-x-2">
151
+ <Badge variant="outline">{index + 1}</Badge>
152
+ <span className="capitalize">{source.source}</span>
153
+ </div>
154
+ <span className="font-medium">{source.count}</span>
155
+ </div>
156
+ ))}
157
+ </div>
158
+ </CardContent>
159
+ </Card>
160
+
161
+ <Card>
162
+ <CardHeader>
163
+ <CardTitle>Recent Signals</CardTitle>
164
+ <CardDescription>Latest signal activity</CardDescription>
165
+ </CardHeader>
166
+ <CardContent>
167
+ <div className="space-y-2">
168
+ {signalMetrics?.recent_signals?.slice(0, 10).map((signal) => (
169
+ <div key={signal.id} className="flex justify-between items-center text-sm">
170
+ <div>
171
+ <span className="font-medium">{signal.action_type}</span>
172
+ <div className="text-muted-foreground">
173
+ {signal.account_name || 'Anonymous'} • {signal.source}
174
+ </div>
175
+ </div>
176
+ <div className="text-right">
177
+ <Badge variant="outline" className="text-xs">
178
+ {signal.stage}
179
+ </Badge>
180
+ <div className="text-xs text-muted-foreground">
181
+ {formatDistanceToNow(new Date(signal.created_at), { addSuffix: true })}
182
+ </div>
183
+ </div>
184
+ </div>
185
+ ))}
186
+ </div>
187
+ </CardContent>
188
+ </Card>
189
+ </div>
190
+ </TabsContent>
191
+
192
+ <TabsContent value="funnel" className="space-y-4">
193
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
194
+ <Card>
195
+ <CardHeader>
196
+ <CardTitle>Funnel Stages</CardTitle>
197
+ <CardDescription>User progression through funnel</CardDescription>
198
+ </CardHeader>
199
+ <CardContent>
200
+ <div className="space-y-3">
201
+ {funnelMetrics?.stage_distribution?.map((stage) => (
202
+ <div key={stage.stage} className="space-y-1">
203
+ <div className="flex justify-between text-sm">
204
+ <span className="capitalize">{stage.stage}</span>
205
+ <span>{stage.count} ({stage.percentage.toFixed(1)}%)</span>
206
+ </div>
207
+ <div className="w-full bg-secondary rounded-full h-2">
208
+ <div
209
+ className="bg-primary h-2 rounded-full"
210
+ style={{ width: `${stage.percentage}%` }}
211
+ />
212
+ </div>
213
+ </div>
214
+ ))}
215
+ </div>
216
+ </CardContent>
217
+ </Card>
218
+
219
+ <Card>
220
+ <CardHeader>
221
+ <CardTitle>Conversion Rates</CardTitle>
222
+ <CardDescription>Stage-to-stage conversion</CardDescription>
223
+ </CardHeader>
224
+ <CardContent>
225
+ <div className="space-y-4">
226
+ <div className="text-center">
227
+ <div className="text-2xl font-bold text-green-600">
228
+ {(funnelMetrics?.conversion_rates.anonymous_to_identified || 0).toFixed(1)}%
229
+ </div>
230
+ <p className="text-sm text-muted-foreground">
231
+ Anonymous → Identified
232
+ </p>
233
+ </div>
234
+ <div className="text-center">
235
+ <div className="text-2xl font-bold text-blue-600">
236
+ {(funnelMetrics?.conversion_rates.identified_to_installed || 0).toFixed(1)}%
237
+ </div>
238
+ <p className="text-sm text-muted-foreground">
239
+ Identified → Installed
240
+ </p>
241
+ </div>
242
+ </div>
243
+ </CardContent>
244
+ </Card>
245
+ </div>
246
+ </TabsContent>
247
+
248
+ <TabsContent value="health" className="space-y-4">
249
+ <Card>
250
+ <CardHeader>
251
+ <CardTitle>System Health</CardTitle>
252
+ <CardDescription>Overall system status and performance</CardDescription>
253
+ </CardHeader>
254
+ <CardContent>
255
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
256
+ <div className="text-center">
257
+ <div className="text-2xl font-bold text-green-600">Healthy</div>
258
+ <p className="text-sm text-muted-foreground">Signal Processing</p>
259
+ </div>
260
+ <div className="text-center">
261
+ <div className="text-2xl font-bold text-green-600">Operational</div>
262
+ <p className="text-sm text-muted-foreground">Database</p>
263
+ </div>
264
+ <div className="text-center">
265
+ <div className="text-2xl font-bold text-green-600">Normal</div>
266
+ <p className="text-sm text-muted-foreground">API Response</p>
267
+ </div>
268
+ </div>
269
+ </CardContent>
270
+ </Card>
271
+ </TabsContent>
272
+ </Tabs>
273
+ </div>
274
+ )
275
+ }