diff --git a/apps/dashboard/app/analytics/page.tsx b/apps/dashboard/app/analytics/page.tsx new file mode 100644 index 0000000..414b7c3 --- /dev/null +++ b/apps/dashboard/app/analytics/page.tsx @@ -0,0 +1,462 @@ +'use client'; + +import React, { useState, useMemo } from 'react'; +import Link from 'next/link'; +import { + LineChart, + Line, + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + AreaChart, + Area, +} from 'recharts'; + +// Mock data for demonstration +const MOCK_METRICS = { + totalExecutions24h: 12453, + totalExecutions7d: 87234, + totalExecutions30d: 356789, + successRate: 94.7, + successRateTrend: 2.3, + avgResponseTime: 1.2, + avgResponseTimeP50: 0.8, + avgResponseTimeP95: 2.4, + totalRevenue: 4523.67, + revenueTrend: 12.5, +}; + +const MOCK_EXECUTION_DATA = [ + { time: '00:00', executions: 420, success: 398, failure: 22 }, + { time: '04:00', executions: 380, success: 361, failure: 19 }, + { time: '08:00', executions: 890, success: 842, failure: 48 }, + { time: '12:00', executions: 1450, success: 1372, failure: 78 }, + { time: '16:00', executions: 1680, success: 1591, failure: 89 }, + { time: '20:00', executions: 1320, success: 1250, failure: 70 }, + { time: '24:00', executions: 680, success: 645, failure: 35 }, +]; + +const MOCK_RESPONSE_TIME_DATA = [ + { range: '0-0.5s', count: 45000 }, + { range: '0.5-1s', count: 82000 }, + { range: '1-1.5s', count: 65000 }, + { range: '1.5-2s', count: 48000 }, + { range: '2-3s', count: 32000 }, + { range: '3s+', count: 18000 }, +]; + +const MOCK_REVENUE_DATA = [ + { date: 'Jan', revenue: 1200 }, + { date: 'Feb', revenue: 1800 }, + { date: 'Mar', revenue: 2400 }, + { date: 'Apr', revenue: 2100 }, + { date: 'May', revenue: 3200 }, + { date: 'Jun', revenue: 3800 }, + { date: 'Jul', revenue: 4524 }, +]; + +const MOCK_AGENTS = [ + { id: 1, name: 'Airdrop Agent', executions: 45234, successRate: 96.2, revenue: 1234.56, category: 'DeFi' }, + { id: 2, name: 'Gas Optimizer', executions: 38765, successRate: 94.8, revenue: 987.32, category: 'Analytics' }, + { id: 3, name: 'Treasury Reporter', executions: 29345, successRate: 93.5, revenue: 765.43, category: 'Analytics' }, + { id: 4, name: 'Vesting Agent', executions: 21567, successRate: 97.1, revenue: 654.21, category: 'DeFi' }, + { id: 5, name: 'Liquidation Monitor', executions: 18432, successRate: 91.2, revenue: 543.21, category: 'Security' }, + { id: 6, name: 'Rate Optimizer', executions: 15234, successRate: 95.5, revenue: 432.10, category: 'DeFi' }, + { id: 7, name: 'Cross-chain Arbitrage', executions: 12345, successRate: 88.9, revenue: 321.45, category: 'DeFi' }, + { id: 8, name: 'Reputation Scorer', executions: 8765, successRate: 92.3, revenue: 210.87, category: 'Analytics' }, +]; + +// Time range selector +function TimeRangeSelector({ value, onChange }: { value: string; onChange: (v: string) => void }) { + return ( +
+ {['24h', '7d', '30d'].map((range) => ( + + ))} +
+ ); +} + +// Stat Card Component +function StatCard({ + title, + value, + trend, + trendLabel, + icon, + color, +}: { + title: string; + value: string | number; + trend?: number; + trendLabel?: string; + icon: string; + color: 'emerald' | 'cyan' | 'fuchsia' | 'amber'; +}) { + const colors = { + emerald: 'from-emerald-500/40 to-teal-500/10 border-emerald-500/20 text-emerald-400', + cyan: 'from-cyan-500/40 to-blue-500/10 border-cyan-500/20 text-cyan-400', + fuchsia: 'from-fuchsia-500/40 to-purple-500/10 border-fuchsia-500/20 text-fuchsia-400', + amber: 'from-amber-500/40 to-orange-500/10 border-amber-500/20 text-amber-400', + }; + + const colorStyles = colors[color]; + + return ( +
+
+
+
+
+
+
+ {icon} +
+ {trend !== undefined && ( + = 0 + ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20' + : 'bg-red-500/10 text-red-400 border-red-500/20' + }`}> + {trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}% + + )} +
+

{title}

+

{value}

+ {trendLabel &&

{trendLabel}

} +
+
+ ); +} + +// Chart Card Component +function ChartCard({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +// Custom Tooltip +function CustomTooltip({ active, payload, label }: any) { + if (active && payload && payload.length) { + return ( +
+

{label}

+ {payload.map((entry: any, index: number) => ( +

+ {entry.name}: {entry.value.toLocaleString()} +

+ ))} +
+ ); + } + return null; +} + +// Leaderboard Table +function Leaderboard({ + data, + sortBy, + onSortBy, +}: { + data: typeof MOCK_AGENTS; + sortBy: string; + onSortBy: (s: string) => void; +}) { + const sortedData = useMemo(() => { + return [...data].sort((a, b) => { + if (sortBy === 'executions') return b.executions - a.executions; + if (sortBy === 'successRate') return b.successRate - a.successRate; + if (sortBy === 'revenue') return b.revenue - a.revenue; + return 0; + }); + }, [data, sortBy]); + + const SortIcon = ({ column }: { column: string }) => ( + + {sortBy === column ? '↓' : '↕'} + + ); + + return ( +
+
+

Agent Leaderboard

+
+
+ + + + + + + + + + + + {sortedData.map((agent, idx) => ( + + + + + + + + ))} + +
#Agent onSortBy('executions')} + > + Executions + onSortBy('successRate')} + > + Success Rate + onSortBy('revenue')} + > + Revenue +
{idx + 1} +
+

{agent.name}

+

{agent.category}

+
+
{agent.executions.toLocaleString()} + = 95 ? 'text-emerald-400' : + agent.successRate >= 90 ? 'text-amber-400' : 'text-red-400' + }`}> + {agent.successRate}% + + ${agent.revenue.toLocaleString()}
+
+
+ ); +} + +// Category Filter +function CategoryFilter({ value, onChange, options }: { value: string; onChange: (v: string) => void; options: string[] }) { + return ( + + ); +} + +// Main Analytics Dashboard +export default function AnalyticsDashboard() { + const [timeRange, setTimeRange] = useState('7d'); + const [sortBy, setSortBy] = useState('executions'); + const [category, setCategory] = useState('All'); + + const filteredAgents = useMemo(() => { + if (category === 'All') return MOCK_AGENTS; + return MOCK_AGENTS.filter((a) => a.category === category); + }, [category]); + + const categories = ['All', ...new Set(MOCK_AGENTS.map((a) => a.category))]; + + return ( +
+ {/* Header */} +
+
+
+
+ + + + + +
+

Agent Analytics

+

+ Agent performance metrics, execution stats, and revenue tracking +

+
+
+ +
+
+
+ + {/* Content */} +
+ {/* Stats Cards */} +
+ + + + +
+ + {/* Charts Grid */} +
+ {/* Execution Volume Chart */} + + + + + + + + + + + + + } /> + + + + + + + {/* Success vs Failure Chart */} + + + + + + + } /> + + + + + +
+ + {/* Response Time Distribution */} + + + + + + + } /> + + + + + + {/* Revenue Chart */} + + + + + + + + + + + + `$${v}`} /> + } /> + + + + + + {/* Agent Leaderboard */} +
+
+

Agent Performance

+ +
+ +
+
+
+ ); +} diff --git a/apps/dashboard/app/components/Navbar.tsx b/apps/dashboard/app/components/Navbar.tsx index 2d11715..0585c3e 100644 --- a/apps/dashboard/app/components/Navbar.tsx +++ b/apps/dashboard/app/components/Navbar.tsx @@ -43,6 +43,11 @@ const navLinks = [ )}, + { href: '/analytics', label: 'Analytics', icon: ( + + + + )}, ]; function truncateAddress(addr: string): string {