67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
const usd = new Intl.NumberFormat("en-US", {
|
|
style: "currency",
|
|
currency: "USD",
|
|
maximumFractionDigits: 0,
|
|
});
|
|
|
|
const usdPrecise = new Intl.NumberFormat("en-US", {
|
|
style: "currency",
|
|
currency: "USD",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
|
|
const number = new Intl.NumberFormat("en-US");
|
|
|
|
const date = new Intl.DateTimeFormat("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
|
|
const dateShort = new Intl.DateTimeFormat("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
});
|
|
|
|
const time = new Intl.DateTimeFormat("en-US", {
|
|
hour: "numeric",
|
|
minute: "2-digit",
|
|
});
|
|
|
|
const relative = new Intl.RelativeTimeFormat("en-US", { numeric: "auto" });
|
|
|
|
/**
|
|
* Coerce a backend Decimal-serialized value (string) or a number to a finite
|
|
* number. Returns the supplied fallback (default 0) on null/undefined/NaN.
|
|
* Used everywhere the FastAPI `Decimal` field needs to flow into a UI that
|
|
* expects `number`.
|
|
*/
|
|
export const toNum = (v: string | number | null | undefined, fallback = 0): number => {
|
|
if (v === null || v === undefined) return fallback;
|
|
const n = typeof v === "number" ? v : Number(v);
|
|
return Number.isFinite(n) ? n : fallback;
|
|
};
|
|
|
|
export const fmt = {
|
|
usd: (n: number) => usd.format(n),
|
|
usdPrecise: (n: number) => usdPrecise.format(n),
|
|
usdDecimal: (v: string | number | null | undefined) =>
|
|
usdPrecise.format(toNum(v)),
|
|
num: (n: number) => number.format(n),
|
|
date: (iso: string) => date.format(new Date(iso)),
|
|
dateShort: (iso: string) => dateShort.format(new Date(iso)),
|
|
time: (iso: string) => time.format(new Date(iso)),
|
|
pct: (n: number, digits = 1) => `${n.toFixed(digits)}%`,
|
|
relative: (iso: string) => {
|
|
const ms = new Date(iso).getTime() - Date.now();
|
|
const minutes = Math.round(ms / 60_000);
|
|
if (Math.abs(minutes) < 60) return relative.format(minutes, "minute");
|
|
const hours = Math.round(minutes / 60);
|
|
if (Math.abs(hours) < 24) return relative.format(hours, "hour");
|
|
const days = Math.round(hours / 24);
|
|
if (Math.abs(days) < 7) return relative.format(days, "day");
|
|
return fmt.date(iso);
|
|
},
|
|
};
|