2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Card } from '../ui/card';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { StatCard } from '../dashboard/StatCard';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
Users,
|
|
|
|
|
DollarSign,
|
|
|
|
|
Activity,
|
|
|
|
|
AlertTriangle,
|
|
|
|
|
HardDrive,
|
|
|
|
|
ShoppingBag,
|
|
|
|
|
ShieldAlert,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
Loader2,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { adminService } from '../../services/adminService';
|
|
|
|
|
import { Report } from '../../types';
|
|
|
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
|
import { logger } from '@/utils/logger';
|
|
|
|
|
|
|
|
|
|
export const AdminDashboardView: React.FC = () => {
|
|
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [stats, setStats] = useState<any>({});
|
|
|
|
|
const [reports, setReports] = useState<Report[]>([]);
|
|
|
|
|
const [uploads, setUploads] = useState<any[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-01-13 18:47:57 +00:00
|
|
|
const fetchData = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const [statsData, reportsData, uploadsData] = await Promise.all([
|
|
|
|
|
adminService.getDashboardStats(),
|
|
|
|
|
adminService.getModerationQueue('pending'),
|
|
|
|
|
adminService.getRecentUploads(),
|
|
|
|
|
]);
|
|
|
|
|
setStats(statsData);
|
|
|
|
|
setReports(reportsData);
|
|
|
|
|
setUploads(uploadsData);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.error('Error loading admin dashboard data', {
|
|
|
|
|
error: e instanceof Error ? e.message : String(e),
|
|
|
|
|
stack: e instanceof Error ? e.stack : undefined,
|
|
|
|
|
});
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
fetchData();
|
2026-01-07 09:31:02 +00:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleAction = async (id: string, action: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
await adminService.resolveReport(id, action);
|
|
|
|
|
setReports(reports.filter((r) => r.id !== id));
|
|
|
|
|
addToast(`Report ${action}`, 'success');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
if (loading)
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex justify-center py-20">
|
|
|
|
|
<Loader2 className="w-10 h-10 text-kodo-cyan animate-spin" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-8 animate-fadeIn pb-20">
|
2026-01-13 18:47:57 +00:00
|
|
|
<h2 className="text-3xl font-display font-bold text-white mb-6">
|
|
|
|
|
SYSTEM OVERVIEW
|
|
|
|
|
</h2>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Stats Grid */}
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
|
|
|
<StatCard
|
|
|
|
|
label="Total Users"
|
|
|
|
|
value={stats.totalUsers?.toLocaleString()}
|
|
|
|
|
icon={<Users className="w-5 h-5" />}
|
|
|
|
|
trend={stats.trends?.users}
|
|
|
|
|
color="cyan"
|
|
|
|
|
/>
|
|
|
|
|
<StatCard
|
|
|
|
|
label="Monthly Revenue"
|
|
|
|
|
value={`$${stats.monthlyRevenue?.toLocaleString()}`}
|
|
|
|
|
icon={<DollarSign className="w-5 h-5" />}
|
|
|
|
|
trend={stats.trends?.revenue}
|
|
|
|
|
color="gold"
|
|
|
|
|
/>
|
|
|
|
|
<StatCard
|
|
|
|
|
label="Active Sessions"
|
|
|
|
|
value={stats.activeSessions?.toLocaleString()}
|
|
|
|
|
icon={<Activity className="w-5 h-5" />}
|
|
|
|
|
trend={stats.trends?.sessions}
|
|
|
|
|
color="lime"
|
|
|
|
|
/>
|
|
|
|
|
<StatCard
|
|
|
|
|
label="Pending Reports"
|
|
|
|
|
value={stats.pendingReports}
|
|
|
|
|
icon={<ShieldAlert className="w-5 h-5" />}
|
|
|
|
|
trend={stats.trends?.reports}
|
|
|
|
|
color="red"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
|
{/* Main Chart Area (Mock) */}
|
|
|
|
|
<Card variant="default" className="lg:col-span-2">
|
|
|
|
|
<div className="flex justify-between items-center mb-6">
|
|
|
|
|
<h3 className="font-bold text-white">Traffic & Server Load</h3>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<span className="text-xs text-gray-400 flex items-center gap-1">
|
|
|
|
|
<div className="w-2 h-2 bg-kodo-cyan rounded-full"></div>{' '}
|
|
|
|
|
Traffic
|
|
|
|
|
</span>
|
|
|
|
|
<span className="text-xs text-gray-400 flex items-center gap-1">
|
|
|
|
|
<div className="w-2 h-2 bg-kodo-magenta rounded-full"></div> CPU
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="h-64 flex items-end gap-1">
|
|
|
|
|
{Array.from({ length: 40 }).map((_, i) => (
|
|
|
|
|
<div
|
|
|
|
|
key={i}
|
|
|
|
|
className="flex-1 flex flex-col justify-end gap-1 h-full group"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className="w-full bg-kodo-magenta/30 rounded-t"
|
|
|
|
|
style={{ height: `${Math.random() * 30 + 10}%` }}
|
|
|
|
|
></div>
|
|
|
|
|
<div
|
|
|
|
|
className="w-full bg-kodo-cyan/30 rounded-t"
|
|
|
|
|
style={{ height: `${Math.random() * 50 + 20}%` }}
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Quick Actions */}
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<Card variant="default">
|
|
|
|
|
<h3 className="font-bold text-white mb-4 text-sm uppercase tracking-wider">
|
|
|
|
|
Quick Actions
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
icon={<AlertTriangle className="w-4 h-4" />}
|
|
|
|
|
>
|
|
|
|
|
Lockdown
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
icon={<HardDrive className="w-4 h-4" />}
|
|
|
|
|
>
|
|
|
|
|
Clear Cache
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
icon={<ShoppingBag className="w-4 h-4" />}
|
|
|
|
|
>
|
|
|
|
|
Sales Rep
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
size="sm"
|
|
|
|
|
icon={<ShieldAlert className="w-4 h-4" />}
|
|
|
|
|
>
|
|
|
|
|
Audit Log
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<Card variant="default">
|
|
|
|
|
<h3 className="font-bold text-white mb-4 text-sm uppercase tracking-wider">
|
|
|
|
|
System Health
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-gray-400">Database</span>
|
|
|
|
|
<span className="text-kodo-lime font-bold">Healthy</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-gray-400">Storage</span>
|
|
|
|
|
<span className="text-kodo-lime font-bold">65% Used</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between text-sm">
|
|
|
|
|
<span className="text-gray-400">API Latency</span>
|
|
|
|
|
<span className="text-white font-mono">45ms</span>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</Card>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
|
|
|
{/* Recent Reports */}
|
|
|
|
|
<Card variant="default">
|
|
|
|
|
<div className="flex justify-between items-center mb-4">
|
|
|
|
|
<h3 className="font-bold text-white flex items-center gap-2">
|
|
|
|
|
<AlertTriangle className="w-4 h-4 text-kodo-red" /> Recent Reports
|
|
|
|
|
</h3>
|
|
|
|
|
<Button variant="ghost" size="sm">
|
|
|
|
|
View All
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
{reports.map((report) => (
|
|
|
|
|
<div
|
|
|
|
|
key={report.id}
|
|
|
|
|
className="flex items-center justify-between p-3 bg-kodo-ink rounded border border-kodo-steel/30"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="text-sm font-bold text-white">
|
|
|
|
|
{report.targetName}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-400">
|
|
|
|
|
{report.targetType} • {report.reason}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="h-8 w-8 p-0 text-kodo-lime"
|
|
|
|
|
onClick={() => handleAction(report.id, 'resolved')}
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="h-8 w-8 p-0 text-kodo-red"
|
|
|
|
|
onClick={() => handleAction(report.id, 'banned')}
|
|
|
|
|
>
|
|
|
|
|
<AlertTriangle className="w-4 h-4" />
|
|
|
|
|
</Button>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
{reports.length === 0 && (
|
|
|
|
|
<div className="text-center text-gray-500 py-4">
|
|
|
|
|
No pending reports.
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{/* Recent Uploads */}
|
|
|
|
|
<Card variant="default">
|
|
|
|
|
<div className="flex justify-between items-center mb-4">
|
|
|
|
|
<h3 className="font-bold text-white flex items-center gap-2">
|
|
|
|
|
<HardDrive className="w-4 h-4 text-kodo-cyan" /> Moderation Queue
|
|
|
|
|
</h3>
|
|
|
|
|
<Button variant="ghost" size="sm">
|
|
|
|
|
View All
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
{uploads.map((upload) => (
|
|
|
|
|
<div
|
|
|
|
|
key={upload.id}
|
|
|
|
|
className="flex items-center justify-between p-3 bg-kodo-ink rounded border border-kodo-steel/30"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="text-sm font-bold text-white">
|
|
|
|
|
{upload.name}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-400">
|
|
|
|
|
{upload.user} • {upload.size}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="h-8 w-8 p-0 text-kodo-lime"
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="h-8 w-8 p-0 text-kodo-red"
|
|
|
|
|
>
|
|
|
|
|
<AlertTriangle className="w-4 h-4" />
|
|
|
|
|
</Button>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|