AVIS / frontend /src /pages /AnalyticsPage.jsx
X2-0's picture
HF Clean Deploy
1c0c94d
Raw
History Blame Contribute Delete
2.62 kB
import { useEffect, useState } from 'react'
import { getAnalytics } from '../api/client.js'
import ByDayChart from '../components/charts/ByDayChart.jsx'
import ByRouteChart from '../components/charts/ByRouteChart.jsx'
import ByTypeChart from '../components/charts/ByTypeChart.jsx'
import Spinner from '../components/common/Spinner.jsx'
function Kpi({ n, l, color = 'var(--blue)' }) {
return (
<div style={{
padding: '16px', borderRadius: '10px', border: '1px solid var(--line)', background: 'var(--card2)',
borderTop: `3px solid ${color}`, flex: '1 1 140px', display: 'flex', flexDirection: 'column', gap: '6px'
}}>
<span style={{ fontSize: '24px', fontWeight: 'bold', color: color }}>{n}</span>
<span className="muted">{l}</span>
</div>
)
}
export default function AnalyticsPage() {
const [a, setA] = useState(null)
const [err, setErr] = useState('')
useEffect(() => {
getAnalytics().then(setA).catch((e) => setErr(e.message))
}, [])
if (err) return <div className="card">Failed to load analytics: {err}</div>
if (!a) return <div className="card"><Spinner /> loading analytics…</div>
const confirmed = (a.by_route?.auto_confirmed || 0) + (a.by_route?.vlm_confirmed || 0)
const reduction = a.total ? Math.round((100 * confirmed) / a.total) : 0
const plateRate = a.plates_read ? Math.round((100 * (a.plates_valid || 0)) / a.plates_read) : 0
return (
<>
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginBottom: '16px' }}>
<Kpi n={a.total} l="Total violations" color="var(--blue)" />
<Kpi n={confirmed} l="Confirmed (no human)" color="var(--ok)" />
<Kpi n={a.pending_review} l="Pending review" color="var(--warn)" />
<Kpi n={a.plates_read ?? 0} l="Plates read" color="var(--txt)" />
</div>
<div className="card">
<div className="banner good">
🤖 <b>Human-review reduction: {reduction}%</b> — that share of violations was resolved
automatically (auto- or AI-confirmed), with no human needed.
</div>
<div className="muted">
Plate OCR: {a.plate_ocr_enabled ? 'on' : 'off'} · {a.plates_read || 0} read,{' '}
{a.plates_valid || 0} valid format ({plateRate}%).
</div>
</div>
<div className="charts">
<div className="card">
<ByTypeChart byType={a.by_type} />
</div>
<div className="card">
<ByRouteChart byRoute={a.by_route} />
</div>
</div>
<div className="card">
<ByDayChart byDay={a.by_day} />
</div>
</>
)
}