Files
daily_console_web/src/components/KpiCard.tsx
2026-09-17 17:57:21 +05:30

90 lines
3.9 KiB
TypeScript

import type { ReactNode } from 'react';
import './kpiCard.css';
/**
* A KPI tile — the console's headline figure.
*
* ── There were two of these, and neither worked ─────────────────────────────
*
* This component put an icon inline with the label and CENTRED the value
* underneath it, which is why a row of tiles never lined up: a centred number
* sits in a different place in every tile depending on how long it is.
* `console.css` had a second, unrelated implementation — `.kpi` — with a
* brand-tinted icon tile on the left and the text stacked beside it. The two
* appeared on the same screen. The second was the better design and this is
* now that design, for both.
*
* ── `note` is rendered again, and that is the real change ───────────────────
*
* The line under the figure — "142 app · 38 counter", "12% of app orders" —
* was removed from the tile at some point but never removed from the CALLERS:
* 86 of them still compute and pass it, and it was being thrown away. That is
* the half of a KPI that carries the meaning. A bare `0` under "Unsynced
* bills" reads as "no data" when it means everything is in the books, and `42`
* under "Total orders" says nothing about which channel they came through.
*
* ── `tone` does something now ───────────────────────────────────────────────
*
* All five tones used to map to `var(--color-brand)`, so the prop was
* decorative — every tile was purple whether it reported takings or a failure.
* The tone now colours the icon tile only: the figure itself stays in ink,
* because a number that changes colour with its own value is hard to compare
* against the tile beside it. Brand remains the default, so a tile that is
* merely reporting is not shouting.
*/
export type KpiTone = 'accent' | 'success' | 'warning' | 'error' | 'neutral';
export interface KpiCardProps {
/** Small-caps label. Say what it is, not what it means. */
label: string;
value: string;
/**
* The line under the figure — what the number is made of, or what it is a
* share of. This is where a KPI gets its meaning; see the note above.
*/
note?: string;
/**
* Colours the icon tile. `neutral` and `accent` are both the brand — a tile
* reports by default and only says more when something is actually wrong.
*/
tone?: KpiTone;
icon?: ReactNode;
/**
* Accepted and ignored.
*
* The 2px underline bar this drove was removed from the tile deliberately —
* a proportion bar under five tiles turned the strip into a chart. Six call
* sites still pass it, so the prop stays declared to keep them compiling.
* Drop it from those callers and this can go.
*/
fill?: number;
}
export function KpiCard({ label, value, note, tone = 'neutral', icon }: KpiCardProps) {
return (
<div className="kpi-card" data-tone={tone}>
{/* Label and icon share the top row; the figure gets the full width of
the card underneath them.
The icon used to sit in a column of its own to the LEFT, and at the
width these tiles actually get — five across a 1,224px column, less
340px whenever Nearle Buddy is open — a 40px tile plus its gutter
left about 134px for the number. "₹1,24,500" does not fit in 134px at
24px bold, so it wrapped, and it wrapped mid-figure: the first tile
on the Console read "₹1,24,50" on one line and "0" on the next. */}
<div className="kpi-card-head">
<span className="kpi-card-label">{label}</span>
{icon ? (
<span className="kpi-card-icon" aria-hidden>
{icon}
</span>
) : null}
</div>
<span className="kpi-card-value">{value}</span>
{note ? <span className="kpi-card-note">{note}</span> : null}
</div>
);
}