veza/apps/web/src/components/admin/AdminDashboardView.tsx
senke fed22d40a5 aesthetic-improvements: reduce decorative cyan across multiple component categories (80/20 rule, batch 11)
- Social: FeedView, ConnectionsView, GroupsView, ExploreView, GroupDetailView loading spinners and decorative text, CreatePostModal decorative select text and hashtag links, PostCard decorative tag links and waveform bars and view comments link, CreateGroupModal decorative icon (9 instances)
- Settings: DataExportModal decorative icon, LoginHistory decorative IP text, AppearanceSettingsView decorative icon and selected theme checkmark, BackupsView decorative icon, CloudIntegrationView decorative icon, AccessibilitySettingsView decorative icon, SecuritySettings decorative icon, PasskeyModal decorative icon and loading spinner (8 instances)
- Studio: ProjectsManager loading spinner and progress percentage text, CloudFileBrowser decorative music icons, AIToolsView decorative music icon, ConnectivityView decorative icon, CreateProjectModal decorative icon, CloudSettingsView decorative icon (6 instances)
- Admin: AdminDashboardView loading spinner and decorative chart bars and icon, AdminSettingsView decorative icon, AdminModerationView loading spinner, AdminUsersView loading spinner (5 instances)
- Inventory: InventoryView loading spinner, EquipmentCard decorative price icon, EquipmentDetailView loading spinner and decorative icons and price text, AddEquipmentView decorative icon (5 instances)
- Seller: CreateProductView decorative icon, SellerDashboardView loading spinner and decorative icon and sales text (3 instances)
- Live: LiveStreamDetailView decorative streamer name text (1 instance)
- Developer: DeveloperDashboardView loading spinner, WebhooksView decorative icon (2 instances)
- Upload: BulkUploadModal decorative icon, FilePreviewCard decorative audio file icon, MetadataForm decorative button text, CoverArtUploadModal decorative icon, LyricsEditorModal decorative icon (5 instances)
- Notifications: NotificationItem decorative follow icon and mark as read button, NotificationBell decorative mark all read link (3 instances)
- Total: ~46 files, ~46 instances replaced
- Preserved: Active/selected states (CloudFileBrowser selected files checkmarks, CreatePostModal post type active state, GroupCard/GroupDetailView public/private badges - semantic indicators, DataExportModal checkbox accents - focus/interaction, AppearanceSettingsView selected theme - active state, PasskeyModal checkbox accent - focus/interaction, LyricsEditorModal checkbox accent - focus/interaction, FileUploadZone drag active state - active state, EquipmentDetailView support link - functional link, FlashSaleModal link - functional link, EquipmentDetailView image indicator dots - active state), primary actions, design system variants
- Action 11.3.1.3 in progress (eleventh batch: social, settings, studio, admin, inventory, seller, live, developer, upload, notifications components)
2026-01-16 11:26:33 +01:00

297 lines
10 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { StatCard } from '../dashboard/StatCard';
import {
Users,
DollarSign,
Activity,
AlertTriangle,
HardDrive,
ShoppingBag,
ShieldAlert,
CheckCircle,
Loader2,
} from 'lucide-react';
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(() => {
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();
}, []);
const handleAction = async (id: string, action: string) => {
await adminService.resolveReport(id, action);
setReports(reports.filter((r) => r.id !== id));
addToast(`Report ${action}`, 'success');
};
if (loading)
return (
<div className="flex justify-center py-20">
<Loader2 className="w-10 h-10 text-kodo-steel animate-spin" />
</div>
);
return (
<div className="space-y-8 animate-fadeIn pb-20">
<h2 className="text-2xl font-display font-bold text-white mb-6">
SYSTEM OVERVIEW
</h2>
{/* 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>
<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-kodo-content-dim flex items-center gap-1">
<div className="w-2 h-2 bg-kodo-steel rounded-full"></div>{' '}
Traffic
</span>
<span className="text-xs text-kodo-content-dim 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>
{/* 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>
<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-kodo-content-dim">Database</span>
<span className="text-kodo-lime font-bold">Healthy</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-kodo-content-dim">Storage</span>
<span className="text-kodo-lime font-bold">65% Used</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-kodo-content-dim">API Latency</span>
<span className="text-white font-mono">45ms</span>
</div>
</div>
</Card>
</div>
</div>
<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-kodo-content-dim">
{report.targetType} {report.reason}
</div>
</div>
<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>
</div>
</div>
))}
{reports.length === 0 && (
<div className="text-center text-kodo-content-dim py-4">
No pending reports.
</div>
)}
</div>
</Card>
{/* 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-steel" /> 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-kodo-content-dim">
{upload.user} {upload.size}
</div>
</div>
<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>
</div>
</div>
))}
</div>
</Card>
</div>
</div>
);
};