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
All checks were successful
Deploy production / deploy (push) Successful in 2m54s
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user