mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { Banknote, FileSignature, FileText, Package } from "lucide-react";
|
|
|
|
import { KpiStrip, type KpiItem } from "@/components/page";
|
|
import type { IOverviewKpis, IOverviewPeriodTotals } from "@/types/overview";
|
|
import { CountUp } from "./CountUp";
|
|
|
|
function formatCurrency(amount: number, currency: "ETB" | "USD") {
|
|
return new Intl.NumberFormat("en-US", {
|
|
style: "currency",
|
|
currency,
|
|
maximumFractionDigits: 0,
|
|
}).format(amount);
|
|
}
|
|
|
|
/** Compact form ("ETB 58.6M") — the hero cell is too narrow for nine digits. */
|
|
function formatCompactCurrency(amount: number, currency: "ETB" | "USD") {
|
|
return new Intl.NumberFormat("en-US", {
|
|
style: "currency",
|
|
currency,
|
|
notation: "compact",
|
|
maximumFractionDigits: 1,
|
|
}).format(amount);
|
|
}
|
|
|
|
/** Period-over-period % change, or undefined when there's no prior baseline to compare against. */
|
|
function pctDelta(current: number, previous: number): number | undefined {
|
|
if (previous === 0) return undefined;
|
|
return Math.round(((current - previous) / previous) * 100);
|
|
}
|
|
|
|
interface OverviewHeroKpisProps {
|
|
kpis: IOverviewKpis;
|
|
current: IOverviewPeriodTotals;
|
|
previous: IOverviewPeriodTotals;
|
|
rangeLabel: string;
|
|
}
|
|
|
|
/**
|
|
* The four numbers an executive reads first: money and volume for the
|
|
* selected range, plus what's currently in flight. Revenue and cargo carry a
|
|
* real vs-prior-period delta; the two workflow snapshots don't, because
|
|
* "active bookings/contracts" is a point-in-time gauge, not a period total —
|
|
* showing a delta for it would mean inventing a comparison that isn't real.
|
|
*/
|
|
export function OverviewHeroKpis({ kpis, current, previous, rangeLabel }: OverviewHeroKpisProps) {
|
|
const items: KpiItem[] = [
|
|
// An API deployed before the overview revamp omits the period totals —
|
|
// drop the two tiles that need them rather than crash (or hide the strip).
|
|
...(current
|
|
? [
|
|
{
|
|
label: `Revenue (${rangeLabel})`,
|
|
value: <CountUp value={current.revenueEtb} format={(n) => formatCompactCurrency(n, "ETB")} />,
|
|
hint: formatCurrency(current.revenueUsd, "USD"),
|
|
icon: Banknote,
|
|
color: "yellow",
|
|
delta: pctDelta(current.revenueEtb, previous?.revenueEtb ?? 0),
|
|
},
|
|
{
|
|
label: "Cargo moved",
|
|
value: <CountUp value={current.tons} format={(n) => `${Math.round(n).toLocaleString()} t`} />,
|
|
icon: Package,
|
|
color: "edr-green",
|
|
delta: pctDelta(current.tons, previous?.tons ?? 0),
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
label: "Active bookings",
|
|
value: <CountUp value={kpis.bookings.totalActive} />,
|
|
icon: FileText,
|
|
color: "edr-green",
|
|
},
|
|
{
|
|
label: "Active contracts",
|
|
value: <CountUp value={kpis.contracts.totalActive} />,
|
|
icon: FileSignature,
|
|
color: "edr-green",
|
|
},
|
|
];
|
|
|
|
return <KpiStrip items={items} />;
|
|
}
|