From 7543d85de41c6552fa58dcd1a7df2ec3f2666ac6 Mon Sep 17 00:00:00 2001 From: Craig Williams Date: Wed, 21 Oct 2026 15:14:57 +0100 Subject: [PATCH] Shortened the table's "Values" with k, m and bn suffixes --- web/src/components/OpportunityTable.tsx | 10 ++++++++-- web/src/lib/format.test.ts | 16 ++++++++++++++++ web/src/lib/format.ts | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) 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);