/** * Component: Admin Dashboard Page * Documentation: documentation/admin-dashboard.md */ 'use client'; import useSWR, { mutate } from 'swr'; import Link from 'next/link'; import { authenticatedFetcher, fetchJSON } from '@/lib/utils/api'; import { MetricCard } from './components/MetricCard'; import { ActiveDownloadsTable } from './components/ActiveDownloadsTable'; import { RecentRequestsTable } from './components/RecentRequestsTable'; import { ToastProvider, useToast } from '@/components/ui/Toast'; import { ReportedIssuesSection } from './components/ReportedIssuesSection'; import { formatDistanceToNow } from 'date-fns'; import { useState } from 'react'; interface PendingApprovalRequest { id: string; createdAt: string; type: 'audiobook' | 'ebook'; audiobook: { title: string; author: string; coverArtUrl: string | null; }; user: { id: string; plexUsername: string; avatarUrl: string | null; }; } function PendingApprovalSection({ requests }: { requests: PendingApprovalRequest[] }) { const toast = useToast(); const [loadingStates, setLoadingStates] = useState>({}); const handleApproveRequest = async (requestId: string) => { setLoadingStates((prev) => ({ ...prev, [requestId]: true })); try { await fetchJSON(`/api/admin/requests/${requestId}/approve`, { method: 'POST', body: JSON.stringify({ action: 'approve' }), }); toast.success('Request approved'); // Mutate both pending requests and recent requests caches await mutate('/api/admin/requests/pending-approval'); await mutate('/api/admin/requests/recent'); await mutate('/api/admin/metrics'); } catch (error) { console.error('[Admin] Failed to approve request:', error); toast.error( `Failed to approve request: ${error instanceof Error ? error.message : 'Unknown error'}` ); } finally { setLoadingStates((prev) => ({ ...prev, [requestId]: false })); } }; const handleDenyRequest = async (requestId: string) => { setLoadingStates((prev) => ({ ...prev, [requestId]: true })); try { await fetchJSON(`/api/admin/requests/${requestId}/approve`, { method: 'POST', body: JSON.stringify({ action: 'deny' }), }); toast.success('Request denied'); // Mutate pending requests cache await mutate('/api/admin/requests/pending-approval'); await mutate('/api/admin/metrics'); } catch (error) { console.error('[Admin] Failed to deny request:', error); toast.error( `Failed to deny request: ${error instanceof Error ? error.message : 'Unknown error'}` ); } finally { setLoadingStates((prev) => ({ ...prev, [requestId]: false })); } }; return (
{/* Section Header */}

Requests Awaiting Approval

{requests.length}
{/* Requests Grid */}
{requests.map((request) => { const isLoading = loadingStates[request.id] || false; return (
{/* Card Content */}
{/* Cover Image */}
{request.audiobook.coverArtUrl ? ( {request.audiobook.title} ) : (
)}
{/* Book Info */}

{request.audiobook.title}

{request.type === 'ebook' && ( Ebook )}

{request.audiobook.author}

{/* User Info */}
{request.user.avatarUrl ? ( {request.user.plexUsername} ) : (
)} {request.user.plexUsername}
{/* Timestamp */}

{formatDistanceToNow(new Date(request.createdAt), { addSuffix: true })}

{/* Action Buttons */}
); })}
); } function AdminDashboardContent() { // Fetch data with auto-refresh every 10 seconds const { data: metrics, error: metricsError } = useSWR( '/api/admin/metrics', authenticatedFetcher, { refreshInterval: 10000, } ); const { data: downloadsData, error: downloadsError } = useSWR( '/api/admin/downloads/active', authenticatedFetcher, { refreshInterval: 5000, // Refresh downloads more frequently } ); // Note: RecentRequestsTable now fetches its own data with filtering/pagination const { data: pendingApprovalData } = useSWR( '/api/admin/requests/pending-approval', authenticatedFetcher, { refreshInterval: 10000, } ); const { data: reportedIssuesData } = useSWR( '/api/admin/reported-issues', authenticatedFetcher, { refreshInterval: 10000, } ); const { data: settingsData } = useSWR( '/api/admin/settings', authenticatedFetcher, { refreshInterval: 60000, // Settings change infrequently } ); const isLoading = !metrics || !downloadsData; const hasError = metricsError || downloadsError; if (hasError) { return (

Error Loading Dashboard

{metricsError?.message || downloadsError?.message || 'Failed to load dashboard data'}

); } return (
{/* Header */}

Admin Dashboard

Monitor system health, active downloads, and recent requests

Back to Home Home
{isLoading ? (
) : ( <> {/* Metrics Grid */}
} variant="default" /> } variant={metrics.activeDownloads > 0 ? 'info' : 'default'} /> } variant="success" /> } variant={metrics.failedLast30Days > 0 ? 'error' : 'default'} /> } variant="default" /> } variant={ metrics.systemHealth.status === 'healthy' ? 'success' : metrics.systemHealth.status === 'degraded' ? 'warning' : 'error' } subtitle={ metrics.systemHealth.issues.length > 0 ? metrics.systemHealth.issues.join(', ') : 'All systems operational' } />
{/* Quick Actions */}
Settings
Users
Scheduled Jobs
System Logs
{/* Requests Awaiting Approval */} {pendingApprovalData?.requests && pendingApprovalData.requests.length > 0 && ( )} {/* Reported Issues */} {reportedIssuesData?.issues && reportedIssuesData.issues.length > 0 && ( )} {/* Active Downloads */}

Active Downloads

{/* Request Management */}

Request Management

)}
); } export default function AdminDashboard() { return ( ); }