90 lines
3.9 KiB
TypeScript
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>
|
|
);
|
|
}
|