280 lines
16 KiB
TypeScript
280 lines
16 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import axios from 'axios';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
Activity, Users, DollarSign, Server, Cpu,
|
|
ArrowUpRight, ArrowDownRight, RefreshCw, Layers
|
|
} from 'lucide-react';
|
|
import Layout from '../components/Layout';
|
|
import { useAuth } from '../AuthContext';
|
|
|
|
// Simple simulated chart component
|
|
const MicroChart = ({ color, data }: { color: string, data: number[] }) => (
|
|
<div className="flex items-end gap-1 h-12 w-full mt-2 opacity-50">
|
|
{data.map((val, i) => (
|
|
<div
|
|
key={i}
|
|
style={{
|
|
height: `${val}%`,
|
|
backgroundColor: color
|
|
}}
|
|
className="flex-1 rounded-sm transition-all duration-500"
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
export default function AnalyticsPage() {
|
|
const { t } = useTranslation();
|
|
const { user } = useAuth();
|
|
const [stats, setStats] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
|
|
// Mock Chart Data
|
|
const [chartData] = useState(() => Array.from({ length: 12 }, () => Math.floor(Math.random() * 60) + 20));
|
|
|
|
useEffect(() => {
|
|
const fetchStats = async () => {
|
|
try {
|
|
const res = await axios.get('/api/admin/analytics');
|
|
setStats(res.data);
|
|
} catch (err) {
|
|
console.error("Failed to load analytics", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchStats();
|
|
|
|
// Auto-refresh every 30s
|
|
const interval = setInterval(fetchStats, 30000);
|
|
return () => clearInterval(interval);
|
|
}, [refreshKey]);
|
|
|
|
const formatCurrency = (val: number) => {
|
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(val);
|
|
};
|
|
|
|
const formatTime = (seconds: number) => {
|
|
const h = Math.floor(seconds / 3600);
|
|
const m = Math.floor((seconds % 3600) / 60);
|
|
return `${h}h ${m}m`;
|
|
};
|
|
|
|
if (loading && !stats) return (
|
|
<Layout>
|
|
<div className="flex h-[80vh] items-center justify-center">
|
|
<RefreshCw className="w-8 h-8 text-stone-300 animate-spin" />
|
|
</div>
|
|
</Layout>
|
|
);
|
|
|
|
return (
|
|
<Layout>
|
|
<div className="max-w-7xl mx-auto p-8 relative overflow-hidden min-h-screen">
|
|
|
|
{/* Background Ambient Glow */}
|
|
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-purple-500/5 rounded-full blur-[120px] -z-10" />
|
|
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-blue-500/5 rounded-full blur-[120px] -z-10" />
|
|
|
|
{/* Header Section */}
|
|
<div className="flex justify-between items-end mb-10">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className="bg-stone-900 text-white px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-widest">
|
|
v{stats.system.version}
|
|
</div>
|
|
<span className="flex items-center gap-1.5 text-green-600 text-[10px] font-bold uppercase tracking-widest">
|
|
<span className="relative flex h-2 w-2">
|
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
|
|
</span>
|
|
System Operational
|
|
</span>
|
|
</div>
|
|
<h1 className="text-4xl font-black text-stone-900 tracking-tighter">Engine Analytics</h1>
|
|
<p className="text-stone-500 font-medium">Mission Control & System Health</p>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => setRefreshKey(p => p + 1)}
|
|
className="flex items-center gap-2 px-4 py-2 bg-white/50 hover:bg-white border border-stone-200/50 rounded-xl transition-all shadow-sm backdrop-blur-sm text-sm font-bold text-stone-600 hover:text-stone-900"
|
|
>
|
|
<RefreshCw className="w-4 h-4" />
|
|
Refresh Data
|
|
</button>
|
|
</div>
|
|
|
|
{/* KPI Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
|
|
|
|
{/* KPI 1: Users */}
|
|
<div className="bg-white/60 backdrop-blur-xl border border-white/40 shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-3xl p-6 relative overflow-hidden group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="p-3 bg-indigo-50 rounded-2xl text-indigo-600 group-hover:scale-110 transition-transform">
|
|
<Users className="w-6 h-6" />
|
|
</div>
|
|
<span className="flex items-center text-xs font-bold text-green-600 bg-green-50 px-2 py-1 rounded-lg">
|
|
+{stats.users.new24h} <ArrowUpRight className="w-3 h-3 ml-1" />
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-stone-400 uppercase tracking-widest mb-1">Total Users</p>
|
|
<h3 className="text-3xl font-black text-stone-900 tracking-tight">{stats.users.total}</h3>
|
|
</div>
|
|
<MicroChart color="#6366f1" data={chartData} />
|
|
</div>
|
|
|
|
{/* KPI 2: Projects */}
|
|
<div className="bg-white/60 backdrop-blur-xl border border-white/40 shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-3xl p-6 relative overflow-hidden group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="p-3 bg-purple-50 rounded-2xl text-purple-600 group-hover:scale-110 transition-transform">
|
|
<Layers className="w-6 h-6" />
|
|
</div>
|
|
<span className="flex items-center text-xs font-bold text-purple-600 bg-purple-50 px-2 py-1 rounded-lg">
|
|
{stats.projects.completionRate}% Done
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-stone-400 uppercase tracking-widest mb-1">Total Projects</p>
|
|
<h3 className="text-3xl font-black text-stone-900 tracking-tight">{stats.projects.total}</h3>
|
|
</div>
|
|
<MicroChart color="#a855f7" data={[...chartData].reverse()} />
|
|
</div>
|
|
|
|
{/* KPI 3: Liability */}
|
|
<div className="bg-white/60 backdrop-blur-xl border border-white/40 shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-3xl p-6 relative overflow-hidden group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="p-3 bg-amber-50 rounded-2xl text-amber-600 group-hover:scale-110 transition-transform">
|
|
<DollarSign className="w-6 h-6" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-stone-400 uppercase tracking-widest mb-1">Credit Liability</p>
|
|
<h3 className="text-3xl font-black text-stone-900 tracking-tight">{stats.financials.creditsLiability.toLocaleString()}</h3>
|
|
<p className="text-xs text-stone-400 mt-1 font-mono">Value: {formatCurrency(stats.financials.estimatedValue)}</p>
|
|
</div>
|
|
<div className="absolute -right-6 -bottom-6 opacity-5">
|
|
<DollarSign className="w-40 h-40" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* KPI 4: System Health */}
|
|
<div className="bg-stone-900 backdrop-blur-xl border border-stone-800 shadow-[0_8px_30px_rgb(0,0,0,0.04)] rounded-3xl p-6 relative overflow-hidden text-white group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="p-3 bg-white/10 rounded-2xl text-white group-hover:scale-110 transition-transform">
|
|
<Server className="w-6 h-6" />
|
|
</div>
|
|
<span className="flex items-center text-xs font-bold text-green-400 bg-green-900/30 px-2 py-1 rounded-lg border border-green-800">
|
|
{stats.system.cpuLoad}% Load
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-stone-400 uppercase tracking-widest mb-1">Uptime</p>
|
|
<h3 className="text-3xl font-black tracking-tight">{formatTime(stats.system.uptime)}</h3>
|
|
<p className="text-xs text-stone-500 mt-1 font-mono">Mem: {stats.system.memory}MB</p>
|
|
</div>
|
|
<div className="absolute top-0 right-0 w-full h-full bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-10 mix-blend-overlay"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Detailed Sections */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
{/* Live Feed */}
|
|
<div className="lg:col-span-2 bg-white border border-stone-100 shadow-sm rounded-3xl p-8">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<Activity className="w-5 h-5 text-stone-400" />
|
|
<h3 className="text-lg font-black text-stone-900 tracking-tight">Live Activity Feed</h3>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{stats.activityLog.map((log: any) => (
|
|
<div key={log.id} className="flex items-center justify-between p-4 rounded-2xl bg-stone-50 border border-stone-100 hover:bg-white hover:shadow-md transition-all group">
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold text-xs">
|
|
{log.user.substring(0, 2).toUpperCase()}
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-stone-900">{log.action.replace('_', ' ')}</p>
|
|
<p className="text-xs text-stone-500">{log.user}</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-xs font-mono font-bold text-stone-400">{new Date(log.timestamp).toLocaleTimeString()}</p>
|
|
<p className="text-[10px] font-bold text-stone-300 uppercase tracking-widest">{log.details}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{stats.activityLog.length === 0 && (
|
|
<div className="text-center py-10 text-stone-400 text-sm">No recent activity found.</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* System Resources */}
|
|
<div className="bg-white border border-stone-100 shadow-sm rounded-3xl p-8">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<Cpu className="w-5 h-5 text-stone-400" />
|
|
<h3 className="text-lg font-black text-stone-900 tracking-tight">Resource Monitor</h3>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<div>
|
|
<div className="flex justify-between text-xs font-bold text-stone-500 mb-2 uppercase tracking-widest">
|
|
<span>CPU Usage</span>
|
|
<span>{stats.system.cpuLoad}%</span>
|
|
</div>
|
|
<div className="h-2 w-full bg-stone-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-stone-900 rounded-full transition-all duration-1000 ease-out"
|
|
style={{ width: `${stats.system.cpuLoad}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex justify-between text-xs font-bold text-stone-500 mb-2 uppercase tracking-widest">
|
|
<span>Memory Allocation</span>
|
|
<span>{stats.system.memory} MB</span>
|
|
</div>
|
|
<div className="h-2 w-full bg-stone-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-purple-500 rounded-full transition-all duration-1000 ease-out"
|
|
style={{ width: `${Math.min((stats.system.memory / 512) * 100, 100)}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex justify-between text-xs font-bold text-stone-500 mb-2 uppercase tracking-widest">
|
|
<span>Storage (Projects)</span>
|
|
<span>{stats.projects.total} Items</span>
|
|
</div>
|
|
<div className="h-2 w-full bg-stone-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-blue-500 rounded-full transition-all duration-1000 ease-out"
|
|
style={{ width: '45%' }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-8 p-4 bg-amber-50 rounded-2xl border border-amber-100">
|
|
<h4 className="flex items-center gap-2 text-xs font-black text-amber-700 uppercase tracking-widest mb-1">
|
|
<Activity className="w-3 h-3" /> System Status
|
|
</h4>
|
|
<p className="text-xs text-amber-600/80 leading-relaxed">
|
|
All services operate within nominal parameters. No critical warnings detected in the last 24 hours.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</Layout>
|
|
);
|
|
}
|