import { useMemo, useState } from 'react'; import { CheckCircle2, ChevronDown, Pause, Play, X, Zap } from 'lucide-react'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { formatUSDWhole, asDollars, type Dollars } from '@/lib/money'; interface CurrentDebt { bill_id: number; name: string; starting_balance?: Dollars; current_balance?: Dollars | null; progress_pct?: number; projected_payoff_month?: number; } interface SnapshotDebt { bill_id: number; projected_payoff_date?: string | null; projected_payoff_month?: number; } export interface ActivePlan { name: string; status: string; started_at?: string | null; months_elapsed?: number; current_debts?: CurrentDebt[]; plan_snapshot?: { debts?: SnapshotDebt[]; interest_saved?: Dollars }; } interface ConfirmState { title: string; description: string; onConfirm?: () => void; } function fmt(v: Dollars | null | undefined): string { return formatUSDWhole(v); } function dateLabel(iso: string | null | undefined): string { if (!iso) return '—'; return new Date(iso).toLocaleDateString(undefined, { month: 'short', year: 'numeric' }); } function months(n: number | null | undefined): string { if (!n || n <= 0) return 'just started'; const y = Math.floor(n / 12); const m = n % 12; if (y === 0) return `${m} mo`; if (m === 0) return `${y} yr`; return `${y} yr ${m} mo`; } // ─── On-track indicator ─────────────────────────────────────────────────────── interface OnTrackDebt { projected_payoff_month?: number; current_balance?: Dollars | null; starting_balance?: Dollars; } function computeOnTrack(debt: OnTrackDebt, monthsElapsed: number): string | null { if (!debt.projected_payoff_month || debt.current_balance == null) return null; const startBal = debt.starting_balance ?? asDollars(0); if (startBal <= 0) return null; const remaining = debt.projected_payoff_month - monthsElapsed; if (remaining <= 0) return debt.current_balance <= 0 ? 'done' : 'behind'; const progressExpected = monthsElapsed / debt.projected_payoff_month; const progressActual = startBal > 0 ? 1 - (debt.current_balance / startBal) : 0; const diff = progressActual - progressExpected; if (diff > 0.05) return 'ahead'; if (diff < -0.05) return 'behind'; return 'on_track'; } function OnTrackPill({ status }: { status: string | null }) { if (!status) return null; const map = { ahead: { label: '↑ Ahead', cls: 'bg-teal-500/12 text-teal-600 dark:text-teal-400' }, on_track: { label: '→ On track', cls: 'bg-muted/60 text-muted-foreground' }, behind: { label: '↓ Behind', cls: 'bg-amber-500/12 text-amber-600 dark:text-amber-400' }, done: { label: '✓ Paid off', cls: 'bg-emerald-500/12 text-emerald-600 dark:text-emerald-400' }, }; const { label, cls } = map[status as keyof typeof map] ?? map.on_track; return ( {label} ); } // ─── Per-debt progress row ──────────────────────────────────────────────────── function DebtProgressRow({ debt, snapshotDebt, monthsElapsed }: { debt: CurrentDebt; snapshotDebt?: SnapshotDebt; monthsElapsed: number }) { const startBal = debt.starting_balance ?? asDollars(0); const curBal = debt.current_balance ?? startBal; const pct = debt.progress_pct ?? 0; const trackStatus = computeOnTrack({ ...debt, ...snapshotDebt }, monthsElapsed); return (
{fmt(curBal)}
{startBal > 0 &&{fmt(startBal)} start
} > ) : (removed
)}proj.
{snapshotDebt.projected_payoff_date.slice(0, 7)}
No debt data in this plan.
) : ( currentDebts.map(debt => (