money: wishlist tier totals fit on one line (compact €, scales with card, exact amount in tooltip)
All checks were successful
Deploy production / deploy (push) Successful in 2m54s

This commit is contained in:
2026-10-24 14:09:07 +00:00
parent 427f4ed095
commit 85da5afdfe
4 changed files with 66 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { sortWishlist, tierClass } from './wishlist';
import { compactAmount, compactEuro, sortWishlist, tierClass } from './wishlist';
describe('wish list order', () => {
it('sorts by tier descending, then price descending, unpriced and untiered last', () => {
@@ -23,4 +23,19 @@ describe('wish list order', () => {
expect(tierClass(null)).toBe('tier-none');
expect(tierClass(9)).toBe('tier-none');
});
it('formats tier totals compactly', () => {
expect(compactEuro(0)).toBe('€\u00a00');
expect(compactEuro(500_00)).toBe('€\u00a0500');
expect(compactEuro(8_865_00)).toBe('€\u00a08.865');
expect(compactEuro(9_365_49)).toBe('€\u00a09.365');
expect(compactEuro(9_999_60)).toBe('€\u00a010k');
expect(compactEuro(17_580_00)).toBe('€\u00a017,6k');
expect(compactEuro(28_080_00)).toBe('€\u00a028,1k');
expect(compactEuro(999_960_00)).toBe('€\u00a01M');
expect(compactEuro(1_234_567_00)).toBe('€\u00a01,2M');
expect(compactEuro(-1_500_00)).toBe('−€\u00a01.500');
expect(compactAmount(28_080_00)).toBe('28,1k');
expect(compactAmount(-40)).toBe('0');
});
});

View File

@@ -23,3 +23,27 @@ export function sortWishlist<T extends WishSortable>(items: readonly T[]): T[] {
export function tierClass(priority: number | null): string {
return priority != null && priority >= 1 && priority <= 5 ? `tier-${priority}` : 'tier-none';
}
const oneDecimal = (value: number) => {
const rounded = Math.round(value * 10) / 10;
return (Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)).replace('.', ',');
};
/**
* Short amount for the wish list tier cards, so a total fits on one line:
* whole euros below €10k ("8.865"), then thousands ("28,1k") and millions ("1,2M").
* Uses the same en-NL separators as the rest of the tracker. No currency symbol.
*/
export function compactAmount(cents: number): string {
const euros = Math.round(Math.abs(cents) / 100);
const sign = cents < 0 && euros > 0 ? '−' : '';
if (euros >= 999_950) return `${sign}${oneDecimal(euros / 1_000_000)}M`;
if (euros >= 10_000) return `${sign}${oneDecimal(euros / 1_000)}k`;
return sign + String(euros).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
}
/** compactAmount with the euro sign: "€ 8.865", "€ 28,1k", "−€ 1.500". */
export function compactEuro(cents: number): string {
const body = compactAmount(cents);
return body.startsWith('−') ? `−€\u00a0${body.slice(1)}` : `€\u00a0${body}`;
}