import { todayStr } from '@/lib/utils'; export const MONTHS = [ 'January','February','March','April','May','June', 'July','August','September','October','November','December', ]; export const FILTER_ALL = 'all'; // Sentinel for the "no method" select option — empty string crashes Radix Select export const METHOD_NONE = 'none'; export const ROW_STATUS_CLS = { paid: 'bg-emerald-500/[0.04] dark:bg-emerald-400/[0.02]', autodraft: 'bg-sky-500/[0.04] dark:bg-sky-400/[0.018]', upcoming: '', due_soon: 'bg-amber-400/[0.07] dark:bg-amber-300/[0.016]', late: 'border-l-4 border-l-orange-400 bg-orange-500/[0.16] ring-1 ring-inset ring-orange-400/25 dark:bg-orange-400/[0.11] dark:ring-orange-300/25', missed: 'border-l-4 border-l-rose-400 bg-rose-500/[0.18] ring-1 ring-inset ring-rose-400/30 dark:bg-rose-400/[0.13] dark:ring-rose-300/30', }; export const STATUS_META = { paid: { label: 'Paid', cls: 'bg-emerald-500/15 text-emerald-500 border border-emerald-500/30 dark:bg-emerald-300/10 dark:text-emerald-200 dark:border-emerald-300/30' }, upcoming: { label: 'Upcoming', cls: 'bg-secondary text-muted-foreground border border-border' }, due_soon: { label: 'Due Soon', cls: 'bg-amber-400/15 text-amber-500 border border-amber-400/30 dark:bg-amber-300/10 dark:text-amber-200 dark:border-amber-300/28' }, late: { label: 'Late', cls: 'bg-orange-500/30 text-orange-800 border border-orange-500/60 shadow-sm shadow-orange-950/10 dark:bg-orange-400/25 dark:text-orange-100 dark:border-orange-300/60' }, missed: { label: 'Missed', cls: 'bg-rose-500/30 text-rose-800 border border-rose-500/70 shadow-sm shadow-rose-950/10 dark:bg-rose-400/25 dark:text-rose-100 dark:border-rose-300/60' }, autodraft: { label: 'Autodraft', cls: 'bg-sky-400/15 text-sky-500 border border-sky-400/30 dark:bg-sky-300/10 dark:text-sky-200 dark:border-sky-300/28' }, skipped: { label: 'Skipped', cls: 'bg-muted text-muted-foreground border border-border' }, }; export function paymentDateForTrackerMonth(year, month, dueDay) { const now = new Date(); if (year === now.getFullYear() && month === now.getMonth() + 1) { return todayStr(); } const daysInMonth = new Date(year, month, 0).getDate(); const day = Number.isInteger(Number(dueDay)) ? Math.min(Math.max(Number(dueDay), 1), daysInMonth) : 1; return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; } export function amountSearchText(...values) { return values .filter(value => value !== null && value !== undefined && Number.isFinite(Number(value))) .flatMap(value => { const num = Number(value); return [String(num), num.toFixed(2), `$${num.toFixed(2)}`]; }) .join(' '); } export function rowThreshold(row) { return row.actual_amount != null ? row.actual_amount : row.expected_amount; } export function rowEffectiveStatus(row) { if (row.is_skipped) return 'skipped'; const threshold = rowThreshold(row); const isPaidByThreshold = row.total_paid > 0 && row.total_paid >= threshold; return (isPaidByThreshold && row.status !== 'paid' && row.status !== 'autodraft') ? 'paid' : row.status; } export function rowIsPaid(row) { const status = rowEffectiveStatus(row); if (row.autopay_suggestion && status === 'autodraft') return false; return status === 'paid' || status === 'autodraft'; } export function rowIsDebt(row) { const category = String(row.category_name || '').toLowerCase(); return Number(row.current_balance) > 0 || row.minimum_payment != null || ['credit card', 'credit cards', 'loan', 'loans', 'debt', 'mortgage'].some(token => category.includes(token)); } export function moveInArray(items, fromIndex, toIndex) { const next = [...items]; const [moved] = next.splice(fromIndex, 1); next.splice(toIndex, 0, moved); return next; } export function paymentSummary(row, threshold) { const target = Number(threshold) || 0; const paid = Number(row.total_paid) || 0; const paidTowardDue = Number.isFinite(Number(row.paid_toward_due)) ? Number(row.paid_toward_due) : Math.min(paid, target); const overpaid = Number.isFinite(Number(row.overpaid_amount)) ? Number(row.overpaid_amount) : Math.max(paid - target, 0); const remaining = Math.max(target - paidTowardDue, 0); const percent = target > 0 ? Math.min(100, Math.round((paidTowardDue / target) * 100)) : 0; return { target, paid, paidTowardDue, overpaid, remaining, percent, count: Array.isArray(row.payments) ? row.payments.length : 0, partial: paid > 0 && remaining > 0, }; }