Files
Rpa_Skills/rpa-dashboard-ai/templates/web/src/components/Overview.tsx
T
VasanthandClaude Opus 5.5 d4d3f83fb1 AI web starter: bands, review split pane, approve gate, server stub (checkpoint)
React + Vite + Tailwind starter with the no-AI shell, band legend, review
split pane with drafts and verify, and a server stub that refuses approval
while flagged fields are unverified. Band self-check and AI page check.
Both page checks share one Chromium via tests/conftest.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:06:44 +05:30

82 lines
3.9 KiB
TypeScript

import { useStore } from '../store'
// batch state -> [label, colour token, pill colour]
export const STATES: Record<string, [string, string, string]> = {
completed: ['Completed', 'var(--ok)', 'bg-ok'],
inprogress: ['In progress', 'var(--warn)', 'bg-warn'],
notcompleted: ['Not completed', 'var(--bad)', 'bg-bad'],
cleared: ['Cleared', 'var(--muted)', 'bg-muted'],
}
function Kpi({ label, n, colour, live }: { label: string; n: number; colour: string; live?: boolean }) {
return (
<div role="group" aria-label={label} className="flex items-center gap-3 rounded-2xl border border-line bg-glass-strong px-4 py-4 shadow-sm">
<div className={`h-[42px] w-[42px] shrink-0 rounded-xl ${live ? 'animate-pulse' : ''}`} style={{ background: colour }} />
<div>
<div className="font-head text-[26px] leading-tight font-extrabold tabular-nums">{n}</div>
<div className="mt-0.5 text-[11.5px] font-semibold text-muted">{label}</div>
</div>
</div>
)
}
export function Overview() {
const { batches } = useStore()
const count = Object.fromEntries(Object.keys(STATES).map((k) => [k, batches.filter((b) => b.state === k).length]))
const total = Object.values(count).reduce((a, x) => a + x, 0)
let acc = 0
const stops = Object.keys(STATES).flatMap((k) => {
if (!count[k]) return []
const from = (acc / total) * 100
acc += count[k]
return [`${STATES[k][1]} ${from}% ${(acc / total) * 100}%`]
})
return (
<div>
<div className="mb-4 grid grid-cols-4 gap-3.5 max-[820px]:grid-cols-2">
<Kpi label="Total batches" n={batches.length} colour="var(--accent)" />
<Kpi label="Completed" n={count.completed} colour="var(--ok)" />
<Kpi label="In progress" n={count.inprogress} colour="var(--warn)" live={count.inprogress > 0} />
<Kpi label="Not completed" n={count.notcompleted} colour="var(--bad)" />
</div>
<div className="grid grid-cols-2 gap-3.5 max-[820px]:grid-cols-1">
<div className="card">
<div className="card-title">Batch status split</div>
<div className="flex flex-wrap items-center gap-6">
<div role="img" className="relative h-[150px] w-[150px] rounded-full"
aria-label={'Batch status: ' + (total ? Object.keys(STATES).map((k) => `${STATES[k][0]} ${count[k]}`).join(', ') : 'no batches yet')}
style={{ background: total ? `conic-gradient(${stops.join(',')})` : 'conic-gradient(var(--border) 0 100%)' }}>
<div className="absolute inset-[22px] flex flex-col items-center justify-center rounded-full" style={{ background: 'var(--bg-bottom)' }}>
<div className="font-head text-[26px] font-extrabold">{batches.length}</div>
<div className="text-[10px] font-bold uppercase text-muted">batches</div>
</div>
</div>
<ul aria-label="Batch status">
{Object.keys(STATES).map((k) => (
<li key={k} className="mb-2 flex items-center gap-2 text-[12.5px] font-semibold">
<span className="h-2.5 w-2.5 rounded-[3px]" style={{ background: STATES[k][1] }} />
{STATES[k][0]} · {count[k]}
</li>
))}
</ul>
</div>
</div>
<div className="card">
<div className="card-title">Recent batches</div>
{batches.length === 0 && <div className="py-10 text-center text-[13px] text-muted">No batches yet</div>}
{batches.slice(0, 6).map((b) => {
const s = STATES[b.state] ?? [b.state, '', 'bg-warn']
return (
<div key={b.batch_id} className="mb-2 flex items-center gap-2 text-[12.5px] font-semibold">
#{b.batch_id} · {b.date}
<span className={`pill ml-auto ${s[2]}`}>{s[0]}</span>
</div>
)
})}
</div>
</div>
</div>
)
}