diff --git a/web/src/components/OpportunityTable.tsx b/web/src/components/OpportunityTable.tsx
index d2281f5..5b5ecf9 100644
--- a/web/src/components/OpportunityTable.tsx
+++ b/web/src/components/OpportunityTable.tsx
@@ -9,7 +9,7 @@ import { createMemo, createSignal, For, Match, Show, Switch } from 'solid-js';
import { api } from '@/api/client';
import { errorText } from '@/lib/errors';
import { statusLabel } from '@/lib/filterOpportunities';
-import { formatDate, formatDateTime, formatMoney, parseDate } from '@/lib/format';
+import { formatDate, formatDateTime, formatMoney, formatMoneyShort, parseDate } from '@/lib/format';
import type { NoticeSite } from '@/lib/opportunities';
import { latestRelease, noticeSite } from '@/lib/opportunities';
import type { OpportunitySort, SortColumn } from '@/lib/sortOpportunities';
@@ -298,7 +298,13 @@ function OpportunityRow(props: RowProps): JSX.Element {
{formatDate(opportunity().start_date)} |
{formatDate(opportunity().end_date)} |
- {formatMoney(opportunity().gross_value)} |
+ {/* Shortened to scan down the column; the exact amount is in the tooltip. */}
+ 0 ? formatMoney(opportunity().gross_value) : undefined}
+ >
+ {formatMoneyShort(opportunity().gross_value)}
+ |
{
expect(formatMoney(-5)).toBe('—');
});
+test.each([
+ { value: 1, want: '£1' },
+ { value: 500, want: '£500' },
+ { value: 3563, want: '£3.6k' },
+ { value: 12500, want: '£13k' },
+ { value: 583333, want: '£583k' },
+ { value: 1500000, want: '£1.5m' },
+ { value: 9621987, want: '£9.6m' },
+ { value: 36250000000, want: '£36bn' },
+ { value: 0, want: '—' },
+ { value: -5, want: '—' },
+])('formatMoneyShort shows $value as $want', ({ value, want }) => {
+ expect(formatMoneyShort(value)).toBe(want);
+});
+
test('formatCount groups thousands', () => {
expect(formatCount(4854)).toBe('4,854');
expect(formatCount(7)).toBe('7');
diff --git a/web/src/lib/format.ts b/web/src/lib/format.ts
index e5ed286..c8798ac 100644
--- a/web/src/lib/format.ts
+++ b/web/src/lib/format.ts
@@ -22,6 +22,14 @@ const moneyFormat = new Intl.NumberFormat('en-GB', {
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". */
@@ -48,6 +56,14 @@ 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);
|