mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
0269273d01
The `| 0` truncation in colorForUserId/colorFor existed to coerce an undefined user_id to 0, but the settlement API always returns user_id — only the two BudgetPanel test fixtures omitted it. Add user_id to the fixtures, switch both helpers to Math.trunc (with a Number.isFinite guard in colorForUserId preserving the old undefined->0 behaviour), and drop nothing else: TravelerPicker only ever receives required numbers.
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
import { currencyDecimals } from '../../utils/formatters'
|
|
import { SYMBOLS, SPLIT_COLORS } from './BudgetPanel.constants'
|
|
|
|
export function widgetTheme(dark: boolean) {
|
|
if (dark) return {
|
|
bg: 'linear-gradient(180deg, #17171d 0%, #0d0d12 100%)',
|
|
border: 'rgba(255,255,255,0.07)',
|
|
text: '#ffffff',
|
|
sub: 'rgba(255,255,255,0.6)',
|
|
faint: 'rgba(255,255,255,0.4)',
|
|
track: 'rgba(255,255,255,0.04)',
|
|
divider: 'rgba(255,255,255,0.07)',
|
|
iconBg: 'rgba(255,255,255,0.08)',
|
|
iconBorder: 'rgba(255,255,255,0.12)',
|
|
iconColor: 'rgba(255,255,255,0.9)',
|
|
centerBg: '#17171d',
|
|
flowBg: 'rgba(255,255,255,0.05)',
|
|
flowBorder: 'rgba(255,255,255,0.07)',
|
|
flowHoverBg: 'rgba(255,255,255,0.08)',
|
|
flowHoverBorder: 'rgba(255,255,255,0.12)',
|
|
rowHover: 'rgba(255,255,255,0.03)',
|
|
shadow: '0 20px 50px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.04)',
|
|
donutShadow: 'drop-shadow(0 0 20px rgba(0,0,0,0.3))',
|
|
}
|
|
return {
|
|
bg: 'linear-gradient(180deg, #ffffff 0%, #f9fafb 100%)',
|
|
border: 'rgba(15,23,42,0.08)',
|
|
text: '#111827',
|
|
sub: 'rgba(17,24,39,0.6)',
|
|
faint: 'rgba(17,24,39,0.4)',
|
|
track: 'rgba(15,23,42,0.05)',
|
|
divider: 'rgba(15,23,42,0.08)',
|
|
iconBg: 'rgba(15,23,42,0.05)',
|
|
iconBorder: 'rgba(15,23,42,0.1)',
|
|
iconColor: 'rgba(17,24,39,0.75)',
|
|
centerBg: '#ffffff',
|
|
flowBg: 'rgba(15,23,42,0.03)',
|
|
flowBorder: 'rgba(15,23,42,0.08)',
|
|
flowHoverBg: 'rgba(15,23,42,0.06)',
|
|
flowHoverBorder: 'rgba(15,23,42,0.14)',
|
|
rowHover: 'rgba(15,23,42,0.04)',
|
|
shadow: '0 12px 32px rgba(15,23,42,0.08), 0 2px 6px rgba(0,0,0,0.04)',
|
|
donutShadow: 'drop-shadow(0 4px 18px rgba(15,23,42,0.12))',
|
|
}
|
|
}
|
|
|
|
export function hexLighten(hex: string, amount: number): string {
|
|
const m = hex.replace('#', '').match(/.{2}/g)
|
|
if (!m || m.length !== 3) return hex
|
|
const mix = (c: number) => Math.min(255, Math.round(c + (255 - c) * amount))
|
|
const [r, g, b] = m.map(x => Number.parseInt(x, 16))
|
|
return `#${[mix(r), mix(g), mix(b)].map(v => v.toString(16).padStart(2, '0')).join('')}`
|
|
}
|
|
|
|
export const fmtNum = (v: number | null | undefined, locale: string, cur: string) => {
|
|
if (v == null || Number.isNaN(v)) return '-'
|
|
const d = currencyDecimals(cur)
|
|
return Number(v).toLocaleString(locale, { minimumFractionDigits: d, maximumFractionDigits: d }) + ' ' + (SYMBOLS[cur] || cur)
|
|
}
|
|
|
|
type NumOrNull = number | null | undefined
|
|
|
|
export const calcPP = (p: NumOrNull, n: NumOrNull) => (n! > 0 ? (p as number) / (n as number) : null)
|
|
export const calcPD = (p: NumOrNull, d: NumOrNull) => (d! > 0 ? (p as number) / (d as number) : null)
|
|
export const calcPPD = (p: NumOrNull, n: NumOrNull, d: NumOrNull) => (n! > 0 && d! > 0 ? (p as number) / ((n as number) * (d as number)) : null)
|
|
|
|
// A custom (uneven) split has no single "per person" figure — one member's share
|
|
// differs from another's — so the averaged per-person columns are meaningless for it
|
|
// (the per-member amounts are shown via the member chips instead). #1458
|
|
export const hasCustomMemberSplit = (item: { members?: { amount?: number | null }[] }) =>
|
|
(item.members || []).some(m => m.amount != null)
|
|
|
|
export function splitColorFor(userId: number, order: number) {
|
|
return SPLIT_COLORS[order % SPLIT_COLORS.length]
|
|
}
|
|
|
|
export function colorForUserId(userId: number) {
|
|
const id = Number.isFinite(userId) ? Math.trunc(userId) : 0
|
|
return SPLIT_COLORS[(id - 1 + SPLIT_COLORS.length * 1000) % SPLIT_COLORS.length]
|
|
}
|
|
|
|
/**
|
|
* Normalises a pasted amount to a plain `1234.56` string: drops currency
|
|
* symbols and spaces, treats the last comma/dot as the decimal separator and
|
|
* removes every thousand separator before it.
|
|
*/
|
|
export function normalizePastedAmount(raw: string): string {
|
|
const text = raw.trim().replace(/[^\d.,-]/g, '')
|
|
const decimalPos = Math.max(text.lastIndexOf(','), text.lastIndexOf('.'))
|
|
if (decimalPos === -1) return text.replace(/[.,]/g, '')
|
|
return text.substring(0, decimalPos).replace(/[.,]/g, '') + '.' + text.substring(decimalPos + 1)
|
|
}
|