import { useState } from 'react' import { usePoller } from '../hooks/usePoller.ts' import { useI18n } from '../hooks/useI18n.ts' import { batchApi } from '../api/batchApi.ts' import { monitorApi } from '../api/monitorApi.ts' import type { BatchStatistics, CacheStats, DailyStats, MetricsSummary, ProcessingDelay, RunningJob, } from '../api/types.ts' import MetricCard from '../components/charts/MetricCard.tsx' import StatusBadge from '../components/common/StatusBadge.tsx' import BarChart from '../components/charts/BarChart.tsx' import TimeRangeSelector from '../components/common/TimeRangeSelector.tsx' import { formatDuration, formatNumber, formatDateTime, formatPercent } from '../utils/formatters.ts' const POLL_INTERVAL = 30_000 export default function Dashboard() { const { t } = useI18n() const [stats, setStats] = useState(null) const [metrics, setMetrics] = useState(null) const [cache, setCache] = useState(null) const [delay, setDelay] = useState(null) const [daily, setDaily] = useState(null) const [running, setRunning] = useState([]) const [days, setDays] = useState(7) usePoller(async () => { const [s, m, c, d, ds, r] = await Promise.allSettled([ batchApi.getStatistics(days), monitorApi.getMetricsSummary(), monitorApi.getCacheStats(), monitorApi.getDelay(), batchApi.getDailyStats(), batchApi.getRunningJobs(), ]) if (s.status === 'fulfilled') setStats(s.value) if (m.status === 'fulfilled') setMetrics(m.value) if (c.status === 'fulfilled') setCache(c.value) if (d.status === 'fulfilled') setDelay(d.value) if (ds.status === 'fulfilled') setDaily(ds.value) if (r.status === 'fulfilled') setRunning(r.value) }, POLL_INTERVAL, [days]) const memUsage = metrics ? Math.round((metrics.memory.used / metrics.memory.max) * 100) : 0 return (
{/* Header */}

{t('dashboard.title')}

{/* Status Cards */}
= 95 ? 'up' : stats && stats.summary.successRate < 80 ? 'down' : 'neutral'} />
{/* Running Jobs + Processing Delay */}
{t('dashboard.runningJobs')}
{running.length === 0 ? (
{t('dashboard.noRunningJobs')}
) : (
{running.map(job => (
{job.jobName}
#{job.executionId} · {formatDateTime(job.startTime)}
))}
)}
{t('dashboard.delay')}
{delay ? (
{delay.delayMinutes ?? 0} {t('dashboard.delayMin')}
{t('dashboard.aisLatest')}
{formatDateTime(delay.aisLatestTime)}
{t('dashboard.processLatest')}
{formatDateTime(delay.queryLatestTime)}
{t('dashboard.aisReceived')}
{formatNumber(delay.recentAisCount)}{t('common.items')}
{t('dashboard.vesselsProcessed')}
{formatNumber(delay.processedVessels)}{t('common.items')}
) : (
{t('common.loading')}
)}
{/* System Metrics + Cache */}
{t('dashboard.systemMetrics')}
{metrics ? (
{t('dashboard.memory')} ({metrics.memory.used}MB / {metrics.memory.max}MB) 85 ? 'text-danger' : memUsage > 70 ? 'text-warning' : 'text-success'}> {memUsage}%
85 ? 'bg-danger' : memUsage > 70 ? 'bg-warning' : 'bg-success'}`} style={{ width: `${memUsage}%` }} />
{metrics.threads}
{t('dashboard.threads')}
{metrics.database.activeConnections}
{t('dashboard.dbConn')}
{(metrics.processing?.recordsPerSecond ?? 0).toFixed(0)}
{t('dashboard.recordsSec')}
) : (
{t('common.loading')}
)}
{t('dashboard.cacheStatus')}
{cache ? (
{cache.hitRate} {t('dashboard.hitRate')}
{formatNumber(cache.currentSize)}
{t('dashboard.size')}
{formatNumber(cache.hitCount)}
{t('dashboard.hits')}
{formatNumber(cache.missCount)}
{t('dashboard.misses')}
) : (
{t('common.loading')}
)}
{/* Daily Throughput Chart */} {daily && daily.dailyStats.length > 0 && (
{t('dashboard.dailyVolume')}
({ date: d.date.slice(5), processed: d.totalProcessed, }))} dataKey="processed" xKey="date" height={280} />
)}
) }