From fb3e5ceb5b0b17274dd3e4c6bd215c4474df1b65 Mon Sep 17 00:00:00 2001 From: senke Date: Fri, 6 Feb 2026 17:54:02 +0100 Subject: [PATCH] refactor(web): split AdminDashboardView into admin-dashboard-view module - types: DashboardStats, UploadItem, AuditLogItem, StatCardProps, Report - useAdminDashboardView: fetchData, handleAction, triggerProtocol - Header, StatCard, TrafficCard, ProtocolsCard, NodeHealthCard, Tabs - AdminDashboardSkeleton for Loading state - max-w-layout-content, text-xs, gap-0.5 (no arbitrary values) - Stories: Default, Loading (Skeleton). Decorator min-h-layout-page - Re-export from AdminDashboardView.tsx Co-authored-by: Cursor --- .../admin/AdminDashboardView.stories.tsx | 63 ++-- .../components/admin/AdminDashboardView.tsx | 290 +----------------- .../AdminDashboardHeader.tsx | 57 ++++ .../AdminDashboardNodeHealthCard.tsx | 40 +++ .../AdminDashboardProtocolsCard.tsx | 73 +++++ .../AdminDashboardSkeleton.tsx | 69 +++++ .../AdminDashboardStatCard.tsx | 69 +++++ .../AdminDashboardTabs.tsx | 181 +++++++++++ .../AdminDashboardTrafficCard.tsx | 66 ++++ .../AdminDashboardView.tsx | 88 ++++++ .../admin/admin-dashboard-view/index.ts | 10 + .../admin/admin-dashboard-view/types.ts | 41 +++ .../useAdminDashboardView.ts | 72 +++++ 13 files changed, 797 insertions(+), 322 deletions(-) create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardHeader.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardNodeHealthCard.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardProtocolsCard.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardSkeleton.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardStatCard.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTabs.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTrafficCard.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/AdminDashboardView.tsx create mode 100644 apps/web/src/components/admin/admin-dashboard-view/index.ts create mode 100644 apps/web/src/components/admin/admin-dashboard-view/types.ts create mode 100644 apps/web/src/components/admin/admin-dashboard-view/useAdminDashboardView.ts diff --git a/apps/web/src/components/admin/AdminDashboardView.stories.tsx b/apps/web/src/components/admin/AdminDashboardView.stories.tsx index dd155cac0..7cc8e8fa2 100644 --- a/apps/web/src/components/admin/AdminDashboardView.stories.tsx +++ b/apps/web/src/components/admin/AdminDashboardView.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import { AdminDashboardView } from './AdminDashboardView'; +import { AdminDashboardSkeleton } from './admin-dashboard-view'; import { ToastProvider } from '../../components/feedback/ToastProvider'; /** @@ -9,48 +10,44 @@ import { ToastProvider } from '../../components/feedback/ToastProvider'; * visualisation du trafic, queue de modération et logs système. */ const meta: Meta = { - title: 'Components/Features/Admin/AdminDashboardView', - component: AdminDashboardView, - parameters: { - layout: 'fullscreen', - docs: { - description: { - component: 'Dashboard admin avec métriques, graphiques de trafic et contrôles système.', - }, - }, + title: 'Components/Features/Admin/AdminDashboardView', + component: AdminDashboardView, + parameters: { + layout: 'fullscreen', + docs: { + description: { + component: + 'Dashboard admin avec métriques, graphiques de trafic et contrôles système.', + }, }, - tags: ['autodocs'], - decorators: [ - (Story) => ( - -
- -
-
- ), - ], + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( + +
+ +
+
+ ), + ], }; export default meta; type Story = StoryObj; -/** - * État par défaut avec données chargées. - */ export const Default: Story = { - name: 'Par défaut', + name: 'Par défaut', }; -/** - * État de chargement initial. - */ export const Loading: Story = { - name: 'Chargement', - parameters: { - docs: { - description: { - story: 'Affiche le spinner pendant le chargement des données admin.', - }, - }, + name: 'Chargement', + render: () => , + parameters: { + docs: { + description: { + story: 'Skeleton pendant le chargement des données admin.', + }, }, + }, }; diff --git a/apps/web/src/components/admin/AdminDashboardView.tsx b/apps/web/src/components/admin/AdminDashboardView.tsx index 5f576213b..f454b3bbc 100644 --- a/apps/web/src/components/admin/AdminDashboardView.tsx +++ b/apps/web/src/components/admin/AdminDashboardView.tsx @@ -1,289 +1 @@ -import React, { useState, useEffect } from 'react'; -import { Card } from '../ui/card'; -import { Button } from '../ui/button'; -import { - Users, DollarSign, Activity, AlertTriangle, HardDrive, ShoppingBag, ShieldAlert, Loader2, Server, Database, Lock, RefreshCw, Eye, ShieldCheck, History -} from 'lucide-react'; -import { adminService } from '../../services/adminService'; -import { Report } from '../../types'; -import { useToast } from '../../components/feedback/ToastProvider'; -import { logger } from '@/utils/logger'; -import { cn } from '@/lib/utils'; -import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; - -const NebulaStatCard = ({ label, value, icon, color, trend }: any) => ( - -
-
-
{icon}
- {trend !== undefined && ( - 0 ? "text-lime-500 border-lime-500/20 bg-lime-500/10" : "text-red-500 border-red-500/20 bg-red-500/10")}> - {trend > 0 ? '+' : ''}{trend}% - - )} -
-
{value}
-
{label}
- -); - -export const AdminDashboardView: React.FC = () => { - const { addToast } = useToast(); - const [stats, setStats] = useState({}); - const [reports, setReports] = useState([]); - const [uploads, setUploads] = useState([]); - const [auditLogs, setAuditLogs] = useState([]); - const [loading, setLoading] = useState(true); - const [protocolActive, setProtocolActive] = useState(null); - - useEffect(() => { - const fetchData = async () => { - setLoading(true); - try { - const [statsData, reportsData, uploadsData, logsData] = await Promise.all([ - adminService.getDashboardStats(), - adminService.getModerationQueue('pending'), - adminService.getRecentUploads(), - adminService.getAuditLogs({ limit: 10 }), - ]); - setStats(statsData); - setReports(reportsData); - setUploads(uploadsData); - setAuditLogs(logsData.logs || []); - } catch (e) { - logger.error('Error loading admin dashboard', { error: e }); - } finally { - setLoading(false); - } - }; - fetchData(); - }, []); - - const handleAction = async (id: string, action: string) => { - await adminService.resolveReport(id, action); - setReports(reports.filter((r) => r.id !== id)); - addToast(`Protocol "${action.toUpperCase()}" executed successfully.`, 'success'); - }; - - const triggerProtocol = (name: string, color: string) => { - setProtocolActive(name); - addToast(`INITIALIZING ${name.toUpperCase()}...`, 'info'); - setTimeout(() => { - addToast(`${name.toUpperCase()} DEPLOYED`, color as any); - setProtocolActive(null); - }, 2000); - }; - - if (loading) return
; - - return ( -
-
-
-

- COMMAND CENTER -

-
-
-
- Nodes Online -
-
- Sector: 00-ALPHA -
-
-
- - -
-
- - {/* Primary Metrics */} -
- } trend={stats.trends?.users} color="cyan" /> - } trend={stats.trends?.revenue} color="gold" /> - } trend={stats.trends?.sessions} color="lime" /> - } trend={stats.trends?.reports} color="red" /> -
- -
- {/* Real-time Traffic Visualizer */} - -
-
-

- Traffic Flux -

-

HOLOGRAPHIC STREAMING INTERFACE

-
-
-
Uplink -
Downlink -
-
- -
- {/* Holographic scanning line */} -
- -
- {[...Array(4)].map((_, i) =>
)} -
- - {Array.from({ length: 60 }).map((_, i) => { - const h1 = Math.random() * 50 + 5; - const h2 = Math.random() * 30 + 10; - return ( -
-
-
-
- ); - })} -
-
- SYS_INIT - BUFFERING_NODES... - LIVE_DATA -
- - - {/* Global Control Terminal */} -
- -

Protocols

-
- {[ - { id: 'purge', label: 'PURGE CACHE', icon: , color: 'gold' }, - { id: 'index', label: 'REINDEX DB', icon: , color: 'cyan' }, - { id: 'sales', label: 'SALES RT', icon: , color: 'lime' }, - { id: 'logs', label: 'SEC LOGS', icon: , color: 'primary' } - ].map((act) => ( - - ))} -
-
- - -

Node Health

-
- {[{ l: 'CORE_KERNEL', s: 'STABLE', c: 'text-lime-500' }, - { l: 'STORAGE_HIVE', s: '88% CAPACITY', c: 'text-gold-500' }, - { l: 'REST_UPLINK', s: '12ms', c: 'text-cyan-500' }, - { l: 'SECURITY_GRID', s: 'ACTIVE', c: 'text-lime-500' }] - .map((m, i) => ( -
- {m.l} - {m.s} -
- ))} -
-
-
-
- - {/* Multi-Tab Interaction Terminal */} - - - -
- MODERATION - {reports.length} -
-
- -
- SIGNALS - {uploads.length} -
-
- -
- SYSTEM LOGS - -
-
-
- - - -
- {reports.length === 0 ? ( -
No pending reports detected.
- ) : reports.map(r => ( -
-
-
-
-
{r.targetName}
-
- {r.targetType}{r.reason}{r.timestamp} -
-
-
-
- - -
-
- ))} -
-
-
- - - -
- {uploads.map(u => ( -
-
-
-
-
{u.name}
-
User: {u.user} • Payload: {u.size} • Handshake: {u.date}
-
-
- -
- ))} -
-
-
- - - -
- Timestamp - Action - Node - Payload Data -
-
- {auditLogs.map((log, i) => ( -
- {new Date(log.timestamp).toLocaleTimeString()} - {log.action || 'AUTH_VAL'} - USER_{log.user_id?.slice(0, 4)} - {JSON.stringify(log.details || log.metadata || {})} -
- ))} -
-
-
-
-
- ); -}; +export { AdminDashboardView } from './admin-dashboard-view'; diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardHeader.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardHeader.tsx new file mode 100644 index 000000000..2df7207f7 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardHeader.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { RefreshCw, Lock, ShieldCheck } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface AdminDashboardHeaderProps { + protocolActive: string | null; + onRescan: () => void; + onLockdown: () => void; +} + +export function AdminDashboardHeader({ + protocolActive, + onRescan, + onLockdown, +}: AdminDashboardHeaderProps) { + return ( +
+
+

+ COMMAND CENTER +

+
+
+
+ + Nodes Online + +
+
+ + Sector: 00-ALPHA + +
+
+
+ + +
+
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardNodeHealthCard.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardNodeHealthCard.tsx new file mode 100644 index 000000000..d5b11e918 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardNodeHealthCard.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; + +const NODES = [ + { l: 'CORE_KERNEL', s: 'STABLE', c: 'text-lime-500' }, + { l: 'STORAGE_HIVE', s: '88% CAPACITY', c: 'text-gold-500' }, + { l: 'REST_UPLINK', s: '12ms', c: 'text-cyan-500' }, + { l: 'SECURITY_GRID', s: 'ACTIVE', c: 'text-lime-500' }, +]; + +export function AdminDashboardNodeHealthCard() { + return ( + +

+ Node Health +

+
+ {NODES.map((m, i) => ( +
+ + {m.l} + + + {m.s} + +
+ ))} +
+
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardProtocolsCard.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardProtocolsCard.tsx new file mode 100644 index 000000000..b69a18937 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardProtocolsCard.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { HardDrive, Database, ShoppingBag, Eye } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface AdminDashboardProtocolsCardProps { + onTrigger: (name: string, color: string) => void; +} + +const PROTOCOLS = [ + { + id: 'purge', + label: 'PURGE CACHE', + icon: , + color: 'gold', + }, + { + id: 'index', + label: 'REINDEX DB', + icon: , + color: 'cyan', + }, + { + id: 'sales', + label: 'SALES RT', + icon: , + color: 'lime', + }, + { + id: 'logs', + label: 'SEC LOGS', + icon: , + color: 'primary', + }, +]; + +export function AdminDashboardProtocolsCard({ + onTrigger, +}: AdminDashboardProtocolsCardProps) { + return ( + +

+ Protocols +

+
+ {PROTOCOLS.map((act) => ( + + ))} +
+
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardSkeleton.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardSkeleton.tsx new file mode 100644 index 000000000..033f2a54d --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardSkeleton.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; + +export function AdminDashboardSkeleton() { + return ( +
+
+
+ +
+ + +
+
+
+ + +
+
+ +
+ {[1, 2, 3, 4].map((i) => ( + + + + + + ))} +
+ +
+ + + + +
+ + +
+ {[1, 2, 3, 4].map((i) => ( + + ))} +
+
+ + +
+ {[1, 2, 3, 4].map((i) => ( + + ))} +
+
+
+
+ +
+ + +
+ {[1, 2, 3].map((i) => ( + + ))} +
+
+
+
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardStatCard.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardStatCard.tsx new file mode 100644 index 000000000..2958fff03 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardStatCard.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; +import type { StatCardProps } from './types'; + +const colorClasses: Record = { + cyan: 'bg-cyan-500', + gold: 'bg-gold-500', + lime: 'bg-lime-500', + red: 'bg-red-500', +}; + +const textColorClasses: Record = { + cyan: 'text-cyan-500', + gold: 'text-gold-500', + lime: 'text-lime-500', + red: 'text-red-500', +}; + +export function AdminDashboardStatCard({ + label, + value, + icon, + color, + trend, +}: StatCardProps) { + return ( + +
+
+
+ {icon} +
+ {trend !== undefined && ( + 0 + ? 'text-lime-500 border-lime-500/20 bg-lime-500/10' + : 'text-red-500 border-red-500/20 bg-red-500/10', + )} + > + {trend > 0 ? '+' : ''} + {trend}% + + )} +
+
+ {value ?? '—'} +
+
+ {label} +
+ + ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTabs.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTabs.tsx new file mode 100644 index 000000000..a3863270b --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTabs.tsx @@ -0,0 +1,181 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { AlertTriangle, HardDrive, History } from 'lucide-react'; +import type { Report } from '@/types'; +import type { UploadItem, AuditLogItem } from './types'; + +interface AdminDashboardTabsProps { + reports: Report[]; + uploads: UploadItem[]; + auditLogs: AuditLogItem[]; + onReportAction: (id: string, action: string) => void; +} + +export function AdminDashboardTabs({ + reports, + uploads, + auditLogs, + onReportAction, +}: AdminDashboardTabsProps) { + return ( + + + +
+ MODERATION + + {reports.length} + +
+
+ +
+ SIGNALS + + {uploads.length} + +
+
+ +
+ SYSTEM LOGS + +
+
+
+ + + +
+ {reports.length === 0 ? ( +
+ No pending reports detected. +
+ ) : ( + reports.map((r) => ( +
+
+
+ +
+
+
+ {r.targetName} +
+
+ {r.targetType} •{' '} + {r.reason} •{' '} + {r.timestamp} +
+
+
+
+ + +
+
+ )) + )} +
+
+
+ + + +
+ {uploads.map((u) => ( +
+
+
+ +
+
+
{u.name}
+
+ User: {u.user} • Payload: {u.size} • Handshake: {u.date} +
+
+
+ +
+ ))} +
+
+
+ + + +
+ Timestamp + Action + Node + Payload Data +
+
+ {auditLogs.map((log, i) => ( +
+ + {log.timestamp + ? new Date(log.timestamp).toLocaleTimeString() + : '—'} + + + {log.action ?? 'AUTH_VAL'} + + + USER_{log.user_id != null ? String(log.user_id).slice(0, 4) : '???'} + + + {JSON.stringify(log.details ?? log.metadata ?? {})} + +
+ ))} +
+
+
+
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTrafficCard.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTrafficCard.tsx new file mode 100644 index 000000000..14e36e43b --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTrafficCard.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Server } from 'lucide-react'; + +export function AdminDashboardTrafficCard() { + return ( + +
+
+

+ Traffic Flux +

+

+ HOLOGRAPHIC STREAMING INTERFACE +

+
+
+ +
+ Uplink + + +
+ Downlink + +
+
+ +
+
+
+ {[...Array(4)].map((_, i) => ( +
+ ))} +
+ {Array.from({ length: 60 }).map((_, i) => { + const h1 = Math.random() * 50 + 5; + const h2 = Math.random() * 30 + 10; + return ( +
+
+
+
+ ); + })} +
+
+ SYS_INIT + BUFFERING_NODES... + LIVE_DATA +
+ + ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardView.tsx b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardView.tsx new file mode 100644 index 000000000..cab20cf09 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/AdminDashboardView.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Users, DollarSign, Activity, ShieldAlert } from 'lucide-react'; +import { useAdminDashboardView } from './useAdminDashboardView'; +import { AdminDashboardHeader } from './AdminDashboardHeader'; +import { AdminDashboardStatCard } from './AdminDashboardStatCard'; +import { AdminDashboardTrafficCard } from './AdminDashboardTrafficCard'; +import { AdminDashboardProtocolsCard } from './AdminDashboardProtocolsCard'; +import { AdminDashboardNodeHealthCard } from './AdminDashboardNodeHealthCard'; +import { AdminDashboardTabs } from './AdminDashboardTabs'; +import { AdminDashboardSkeleton } from './AdminDashboardSkeleton'; +import { Loader2 } from 'lucide-react'; + +export function AdminDashboardView() { + const { + stats, + reports, + uploads, + auditLogs, + loading, + protocolActive, + handleAction, + triggerProtocol, + } = useAdminDashboardView(); + + if (loading) { + return ( +
+ +
+ ); + } + + return ( +
+ triggerProtocol('RESCAN', 'success')} + onLockdown={() => triggerProtocol('LOCKDOWN', 'error')} + /> + +
+ } + trend={stats.trends?.users} + color="cyan" + /> + } + trend={stats.trends?.revenue} + color="gold" + /> + } + trend={stats.trends?.sessions} + color="lime" + /> + } + trend={stats.trends?.reports} + color="red" + /> +
+ +
+ +
+ + +
+
+ + +
+ ); +} diff --git a/apps/web/src/components/admin/admin-dashboard-view/index.ts b/apps/web/src/components/admin/admin-dashboard-view/index.ts new file mode 100644 index 000000000..921429fc3 --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/index.ts @@ -0,0 +1,10 @@ +export type { + DashboardStats, + UploadItem, + AuditLogItem, + StatCardProps, + Report, +} from './types'; +export { AdminDashboardView } from './AdminDashboardView'; +export { AdminDashboardSkeleton } from './AdminDashboardSkeleton'; +export { useAdminDashboardView } from './useAdminDashboardView'; diff --git a/apps/web/src/components/admin/admin-dashboard-view/types.ts b/apps/web/src/components/admin/admin-dashboard-view/types.ts new file mode 100644 index 000000000..9ce68a61f --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/types.ts @@ -0,0 +1,41 @@ +import type { ReactNode } from 'react'; +import type { Report } from '@/types'; + +export interface DashboardStats { + totalUsers?: number; + monthlyRevenue?: number; + activeSessions?: number; + pendingReports?: number; + trends?: { + users?: number; + revenue?: number; + sessions?: number; + reports?: number; + }; +} + +export interface UploadItem { + id: string; + name: string; + user: string; + size: string; + date: string; +} + +export interface AuditLogItem { + timestamp?: string; + action?: string; + user_id?: string | number; + details?: unknown; + metadata?: unknown; +} + +export interface StatCardProps { + label: string; + value: string | number | undefined; + icon: ReactNode; + color: 'cyan' | 'gold' | 'lime' | 'red'; + trend?: number; +} + +export type { Report }; diff --git a/apps/web/src/components/admin/admin-dashboard-view/useAdminDashboardView.ts b/apps/web/src/components/admin/admin-dashboard-view/useAdminDashboardView.ts new file mode 100644 index 000000000..c759e18eb --- /dev/null +++ b/apps/web/src/components/admin/admin-dashboard-view/useAdminDashboardView.ts @@ -0,0 +1,72 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useToast } from '@/components/feedback/ToastProvider'; +import { adminService } from '@/services/adminService'; +import { logger } from '@/utils/logger'; +import type { Report } from '@/types'; +import type { DashboardStats, UploadItem, AuditLogItem } from './types'; + +export function useAdminDashboardView() { + const { addToast } = useToast(); + const [stats, setStats] = useState({}); + const [reports, setReports] = useState([]); + const [uploads, setUploads] = useState([]); + const [auditLogs, setAuditLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [protocolActive, setProtocolActive] = useState(null); + + const fetchData = useCallback(async () => { + setLoading(true); + try { + const [statsData, reportsData, uploadsData, logsData] = await Promise.all([ + adminService.getDashboardStats(), + adminService.getModerationQueue('pending'), + adminService.getRecentUploads(), + adminService.getAuditLogs({ limit: 10 }), + ]); + setStats(statsData as DashboardStats); + setReports((reportsData as Report[]) || []); + setUploads((uploadsData as UploadItem[]) || []); + setAuditLogs(logsData?.logs || []); + } catch (e) { + logger.error('Error loading admin dashboard', { error: e }); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleAction = useCallback( + async (id: string, action: string) => { + await adminService.resolveReport(id, action); + setReports((prev) => prev.filter((r) => r.id !== id)); + addToast(`Protocol "${action.toUpperCase()}" executed successfully.`, 'success'); + }, + [addToast], + ); + + const triggerProtocol = useCallback( + (name: string, color: string) => { + setProtocolActive(name); + addToast(`INITIALIZING ${name.toUpperCase()}...`, 'info'); + setTimeout(() => { + addToast(`${name.toUpperCase()} DEPLOYED`, color as 'success' | 'error'); + setProtocolActive(null); + }, 2000); + }, + [addToast], + ); + + return { + stats, + reports, + uploads, + auditLogs, + loading, + protocolActive, + handleAction, + triggerProtocol, + }; +}