export function formatCurrency(amount: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount); } export function formatBidAmount(amount: number): string { return amount > 0 ? formatCurrency(amount) : 'Not priced'; } export function formatDate(iso: string): string { return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } export function formatDateTime(iso: string | null): string { if (!iso) return '-'; return new Date(iso).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', }); } export function formatAge(iso: string): string { const diffMs = Date.now() - new Date(iso).getTime(); const diffMinutes = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); const diffWeeks = Math.floor(diffDays / 7); if (diffHours < 1) return `${Math.max(diffMinutes, 0)}m`; if (diffDays < 1) return `${diffHours}h`; if (diffDays < 7) return `${diffDays}d`; return `${diffWeeks}w`; } export function getAgeColor(iso: string): string | undefined { const diffDays = (Date.now() - new Date(iso).getTime()) / 86400000; if (diffDays > 5) return '#d32f2f'; if (diffDays > 2) return '#ed6c02'; return undefined; } export interface GroupedProposal { latest: T; revisions: T[]; hasRevisions: boolean; baseNumber: string; } export function groupRevisions(rows: T[]): GroupedProposal[] { const groups = new Map(); for (const row of rows) { const base = row.proposalNumber.replace(/-R\d+$/, ''); const existing = groups.get(base); if (existing) { existing.push(row); } else { groups.set(base, [row]); } } const result: GroupedProposal[] = []; for (const [baseNumber, items] of groups) { items.sort((a, b) => { const revA = extractRevision(a.proposalNumber); const revB = extractRevision(b.proposalNumber); return revB - revA; }); result.push({ latest: items[0]!, revisions: items.slice(1), hasRevisions: items.length > 1, baseNumber, }); } return result; } function extractRevision(proposalNumber: string): number { const match = proposalNumber.match(/-R(\d+)$/); return match ? parseInt(match[1]!, 10) : 0; }