89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import type { DateTimeString } from '@/types/Opportunities';
|
|
|
|
const ZERO_TIME_PREFIX = '0001-01-01';
|
|
|
|
const dateFormat = new Intl.DateTimeFormat('en-GB', {
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
});
|
|
|
|
const dateTimeFormat = new Intl.DateTimeFormat('en-GB', {
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
|
|
const moneyFormat = new Intl.NumberFormat('en-GB', {
|
|
style: 'currency',
|
|
currency: 'GBP',
|
|
maximumFractionDigits: 0,
|
|
});
|
|
|
|
// en-GB's compact suffixes are k, m and bn; amounts under £1,000 are left
|
|
// whole.
|
|
const shortMoneyFormat = new Intl.NumberFormat('en-GB', {
|
|
style: 'currency',
|
|
currency: 'GBP',
|
|
notation: 'compact',
|
|
});
|
|
|
|
const countFormat = new Intl.NumberFormat('en-GB');
|
|
|
|
/** Treats null and Go's zero time.Time as "no date". */
|
|
export function parseDate(value: DateTimeString | null | undefined): Date | null {
|
|
if (!value || value.startsWith(ZERO_TIME_PREFIX)) return null;
|
|
const d = new Date(value);
|
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
}
|
|
|
|
/** A date for display, e.g. "3 Mar 2026", or a dash for no date. */
|
|
export function formatDate(value: DateTimeString | null | undefined): string {
|
|
const d = parseDate(value);
|
|
return d ? dateFormat.format(d) : '—';
|
|
}
|
|
|
|
/** A date and time for display, e.g. "3 Mar 2026, 09:30", or a dash for none. */
|
|
export function formatDateTime(value: DateTimeString | null | undefined): string {
|
|
const d = parseDate(value);
|
|
return d ? dateTimeFormat.format(d) : '—';
|
|
}
|
|
|
|
/** An amount in pounds for display, e.g. "£125,001", or a dash for none. */
|
|
export function formatMoney(value: number): string {
|
|
return value > 0 ? moneyFormat.format(value) : '—';
|
|
}
|
|
|
|
/**
|
|
* An amount in pounds shortened with a suffix, e.g. "£250k", "£1.5m" or
|
|
* "£36bn", or a dash for none. Amounts under £1,000 are shown whole.
|
|
*/
|
|
export function formatMoneyShort(value: number): string {
|
|
return value > 0 ? shortMoneyFormat.format(value) : '—';
|
|
}
|
|
|
|
/** A count for display, e.g. "4,854". */
|
|
export function formatCount(value: number): string {
|
|
return countFormat.format(value);
|
|
}
|
|
|
|
/**
|
|
* An amount in its currency for display, e.g. "€1,201", or a dash for none.
|
|
* A currency code Intl doesn't know is shown after the number.
|
|
*/
|
|
export function formatValue(amount: number, currency: string): string {
|
|
if (amount <= 0) return '—';
|
|
if (currency === '' || currency === 'GBP') return moneyFormat.format(amount);
|
|
try {
|
|
return new Intl.NumberFormat('en-GB', {
|
|
style: 'currency',
|
|
currency,
|
|
maximumFractionDigits: 0,
|
|
}).format(amount);
|
|
} catch {
|
|
return `${countFormat.format(Math.round(amount))} ${currency}`;
|
|
}
|
|
}
|