Shared UI kit
format.js 96 lines
Files · 11
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596export const number = (value) =>
new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(value);
export function displayDate(value, withTime = false) {
if (!value) return "No date";
const parts = value.match(
/^(\d{4})[/-](\d{2})[/-](\d{2})(?:T(\d{2}):(\d{2}))?/,
);
if (parts) {
const date = new Date(
Number(parts[1]),
Number(parts[2]) - 1,
Number(parts[3]),
);
return (
date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
year: "numeric",
}) + (withTime && parts[4] ? ` · ${parts[4]}:${parts[5]}` : "")
);
}
const date = new Date(value);
return Number.isNaN(date.getTime())
? value
: date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
year: "numeric",
});
}
export const readingDate = () => localISO().slice(0, 10).replaceAll("-", "/");
export function localISO() {
const date = new Date();
return new Date(date.getTime() - date.getTimezoneOffset() * 60000)
.toISOString()
.slice(0, 19);
}
export function money(cost, currency) {
const [whole, fraction = ""] = cost.split(".");
return `${currency} ${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}.${fraction.padEnd(2, "0")}`;
}
export function totalsByCurrency(rows, payment) {
const totals = new Map();
for (const row of rows.filter((row) => row.payment === payment)) {
const [whole, fraction = ""] = row.cost.split(".");
const cents = BigInt(whole) * 100n + BigInt(fraction.padEnd(2, "0"));
totals.set(
row.currency_code,
(totals.get(row.currency_code) || 0n) + cents,
);
}
return (
[...totals]
.map(([currency, cents]) =>
money(
`${cents / 100n}.${String(cents % 100n).padStart(2, "0")}`,
currency,
),
)
.join(" · ") || "—"
);
}
export function addDays(date, amount) {
const d = new Date(`${date}T12:00:00Z`);
d.setUTCDate(d.getUTCDate() + amount);
return d.toISOString().slice(0, 10);
}
export const nullable = (value) =>
value === "" || value === undefined ? null : value;
export function durationLabel(value) {
if (!value) return "—";
const match =
/^P(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(
value,
);
if (!match) return value;
return (
match
.slice(1)
.map((v, i) =>
v === undefined
? ""
: `${v} ${["yr", "mo", "wk", "day", "hr", "min", "sec"][i]}`,
)
.filter(Boolean)
.join(" ") || value
);
}
export const isDuration = (value) =>
!value ||
/^P(?=\d|T\d)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?$/.test(
value,
);