mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(ui): restore click-through navigation on home KPI cards
PR-Agent caught a regression: pre-migration the four home stats had onClick handlers navigating to /providers, /cliproxy, /accounts, and /health. The Phase 2 migration dropped those handlers, removing the shortcuts for all users. Add an optional onClick prop to KpiCard. When supplied, the tile promotes from a static <div> to a <button> with hover lift, keyboard focus ring, and an accessible aria-label derived from the label. Wire all four home KPIs back to their original navigation targets.
This commit is contained in:
@@ -27,6 +27,14 @@ interface KpiCardProps {
|
||||
/** Tone for the hint line (e.g. positive delta, warning, etc.). */
|
||||
tone?: 'default' | 'positive' | 'warning' | 'negative';
|
||||
icon?: ReactNode;
|
||||
/**
|
||||
* Optional click handler. When set, the tile renders as a button with
|
||||
* keyboard + hover affordances — used by pages where each KPI is also a
|
||||
* navigation entry point (e.g. home dashboard).
|
||||
*/
|
||||
onClick?: () => void;
|
||||
/** Accessible label override when onClick is set. Defaults to `label`. */
|
||||
ariaLabel?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -39,10 +47,22 @@ const TONE_CLASSES: Record<NonNullable<KpiCardProps['tone']>, string> = {
|
||||
|
||||
/**
|
||||
* KpiCard - Single hero stat tile inside a KpiRow.
|
||||
*
|
||||
* Renders as a static <div> by default; promotes to a clickable <button>
|
||||
* when `onClick` is supplied so the tile gains keyboard focus + hover state.
|
||||
*/
|
||||
export function KpiCard({ label, value, hint, tone = 'default', icon, className }: KpiCardProps) {
|
||||
return (
|
||||
<div className={cn('rounded-xl border bg-card p-4 shadow-sm', className)}>
|
||||
export function KpiCard({
|
||||
label,
|
||||
value,
|
||||
hint,
|
||||
tone = 'default',
|
||||
icon,
|
||||
onClick,
|
||||
ariaLabel,
|
||||
className,
|
||||
}: KpiCardProps) {
|
||||
const content = (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{label}
|
||||
@@ -51,6 +71,27 @@ export function KpiCard({ label, value, hint, tone = 'default', icon, className
|
||||
</div>
|
||||
<p className="mt-2 text-2xl font-semibold tracking-tight tabular-nums">{value}</p>
|
||||
{hint && <p className={cn('mt-1 text-xs', TONE_CLASSES[tone])}>{hint}</p>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const baseClasses = 'rounded-xl border bg-card p-4 shadow-sm';
|
||||
|
||||
if (onClick) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel ?? (typeof label === 'string' ? label : undefined)}
|
||||
className={cn(
|
||||
baseClasses,
|
||||
'text-left transition-all hover:bg-card/80 hover:shadow-md hover:-translate-y-0.5 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={cn(baseClasses, className)}>{content}</div>;
|
||||
}
|
||||
|
||||
@@ -67,22 +67,26 @@ export function HomePage() {
|
||||
label={t('home.profiles')}
|
||||
value={overview?.profiles ?? 0}
|
||||
icon={<KeyIcon className="size-4" />}
|
||||
onClick={() => navigate('/providers')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('home.cliproxy')}
|
||||
value={overview?.cliproxy ?? 0}
|
||||
icon={<Zap className="size-4" />}
|
||||
onClick={() => navigate('/cliproxy')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('home.accounts')}
|
||||
value={overview?.accounts ?? 0}
|
||||
icon={<UsersIcon className="size-4" />}
|
||||
onClick={() => navigate('/accounts')}
|
||||
/>
|
||||
<KpiCard
|
||||
label={t('home.health')}
|
||||
value={overview?.health ? `${overview.health.passed}/${overview.health.total}` : '—'}
|
||||
tone={healthTone}
|
||||
icon={<ActivityIcon className="size-4" />}
|
||||
onClick={() => navigate('/health')}
|
||||
/>
|
||||
</KpiRow>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user