'use client' import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth' import AdminShell from '../ui/AdminShell' type ServiceState = { name: string status: string message?: string } type RecentRequest = { id: number title?: string | null year?: number | null statusLabel?: string | null requestedBy?: string | null createdAt?: string | null } type PortalOverview = { overview?: { total_items?: number total_comments?: number by_kind?: Record by_status?: Record } my_items?: number } const formatDateTime = (value?: string | null) => { if (!value) return 'Unknown' const date = new Date(value) if (Number.isNaN(date.valueOf())) return value return date.toLocaleString() } const normalizeRecent = (items: any[]): RecentRequest[] => items .filter((item) => item?.id) .map((item) => ({ id: Number(item.id), title: item.title ?? null, year: item.year ?? null, statusLabel: item.statusLabel ?? null, requestedBy: item.requestedBy ?? null, createdAt: item.createdAt ?? null, })) export default function AdminLandingPage() { const router = useRouter() const [services, setServices] = useState([]) const [serviceOverall, setServiceOverall] = useState('unknown') const [recent, setRecent] = useState([]) const [portalOverview, setPortalOverview] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { if (!getToken()) { router.push('/login') return } const load = async () => { setLoading(true) setError(null) try { const baseUrl = getApiBase() const [meResponse, serviceResponse, recentResponse, overviewResponse] = await Promise.all([ authFetch(`${baseUrl}/auth/me`), authFetch(`${baseUrl}/status/services`), authFetch(`${baseUrl}/requests/recent?take=8&days=0`), authFetch(`${baseUrl}/portal/overview`), ]) if (meResponse.status === 401) { clearToken() router.push('/login') return } if (meResponse.status === 403) { router.push('/') return } const me = await meResponse.json() if (me?.role !== 'admin') { router.push('/') return } if (serviceResponse.ok) { const data = await serviceResponse.json() setServiceOverall(data?.overall ?? 'unknown') setServices(Array.isArray(data?.services) ? data.services : []) } if (recentResponse.ok) { const data = await recentResponse.json() setRecent(Array.isArray(data?.results) ? normalizeRecent(data.results) : []) } if (overviewResponse.ok) { const data = await overviewResponse.json() setPortalOverview(data) } } catch (err) { console.error(err) setError('Unable to load the operations dashboard.') } finally { setLoading(false) } } void load() }, [router]) const serviceCounts = useMemo(() => { const up = services.filter((service) => service.status === 'up').length const down = services.filter((service) => service.status === 'down').length const degraded = services.filter((service) => service.status === 'degraded').length const notConfigured = services.filter((service) => service.status === 'not_configured').length return { up, down, degraded, notConfigured, total: services.length } }, [services]) const issueCount = Number(portalOverview?.overview?.by_kind?.issue ?? 0) const requestItemCount = Number(portalOverview?.overview?.by_kind?.request ?? 0) const commentCount = Number(portalOverview?.overview?.total_comments ?? 0) const rail = (
Service ecosystem
{services.length === 0 ? (
Service status is not available yet.
) : ( services.map((service) => ( {service.name} {service.message ?? 'No message reported'} {service.status} )) )}
) return ( router.push('/')}> View health } > {loading ?
Loading operations dashboard...
: null} {error ?
{error}
: null}
Services online {serviceCounts.up}/{serviceCounts.total || 0}

{serviceOverall === 'up' ? 'All configured services are responding.' : 'Some services need review.'}

Recent requests {recent.length}

Loaded from the live request cache.

Open issue items {issueCount}

{commentCount} portal comments recorded.

Portal requests {requestItemCount}

Tracked in the dedicated request workflow.

Recent activity

Live request cache entries, newest first.

{recent.length === 0 ? (
No recent requests were returned.
) : (
Request Status User Created
{recent.map((row) => ( ))}
)}

Attention states

Service states that affect request processing.

{serviceCounts.down} down {serviceCounts.degraded} degraded {serviceCounts.notConfigured} not configured
) }