import { useState } from 'react'; import { ChevronDown, History } from 'lucide-react'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { cn } from '@/lib/utils'; import { formatUSD, formatUSDWhole } from '@/lib/money'; import type { Dollars } from '@/lib/money'; interface SnowballPlanDebt { bill_id: number; name: string; starting_balance: Dollars; projected_payoff_date?: string | null; projected_total_interest?: Dollars | null; } interface SnowballPlanSnapshot { debts?: SnowballPlanDebt[]; projected_months?: number; projected_payoff_date?: string | null; interest_saved?: Dollars; minimum_only_months?: number; } interface CurrentDebt { bill_id: number; current_balance?: Dollars | null; } export interface SnowballPlan { id: number; name: string; status: string; method: string; started_at?: string | null; completed_at?: string | null; paused_at?: string | null; extra_payment?: Dollars; notes?: string | null; plan_snapshot?: SnowballPlanSnapshot; current_debts?: CurrentDebt[]; } function fmt(v: Dollars | null | undefined): string { return formatUSDWhole(v); } function fmtFull(v: Dollars | null | undefined): string { return formatUSD(v); } function dateRange(plan: SnowballPlan): string { const start = plan.started_at ? new Date(plan.started_at).toLocaleDateString(undefined, { month: 'short', year: 'numeric' }) : '—'; const endDate = plan.completed_at || plan.paused_at; const end = endDate ? new Date(endDate).toLocaleDateString(undefined, { month: 'short', year: 'numeric' }) : plan.status === 'abandoned' ? 'abandoned' : 'present'; return `${start} – ${end}`; } function StatusBadge({ status }: { status: string }) { const map = { active: 'bg-emerald-500/12 text-emerald-600 dark:text-emerald-400', paused: 'bg-amber-500/12 text-amber-600 dark:text-amber-400', completed: 'bg-indigo-500/12 text-indigo-600 dark:text-indigo-400', abandoned: 'bg-rose-500/12 text-rose-600 dark:text-rose-400', }; const labels: Record = { active: 'Active', paused: 'Paused', completed: 'Completed', abandoned: 'Abandoned' }; return ( {labels[status] ?? status} ); } function MethodBadge({ method }: { method: string }) { const map: Record = { snowball: 'Snowball', avalanche: 'Avalanche', custom: 'Custom' }; return ( {map[method] ?? method} ); } // ─── Expanded plan detail ───────────────────────────────────────────────────── function PlanDetail({ plan }: { plan: SnowballPlan }) { const snapshot = plan.plan_snapshot ?? {}; const debts = snapshot.debts ?? []; return (
{/* Summary stats */}
{snapshot.projected_months && (

Projected payoff

{snapshot.projected_payoff_date ?? '—'}

)} {(snapshot.interest_saved ?? 0) > 0 && (

Interest saved

{fmt(snapshot.interest_saved)}

)} {snapshot.minimum_only_months && (

Minimum-only months

{snapshot.minimum_only_months}

)} {(plan.extra_payment ?? 0) > 0 && (

Extra/mo

{fmtFull(plan.extra_payment)}

)}
{/* Per-debt snapshot table */} {debts.length > 0 && (

Debt snapshot at start of plan

{debts.map(d => { const current = plan.current_debts?.find(c => c.bill_id === d.bill_id); const curBal = current?.current_balance; const isPaidOff = curBal !== null && curBal !== undefined && curBal <= 0; return ( ); })}
Debt Starting balance Projected payoff Projected interest Current balance
{d.name} {fmtFull(d.starting_balance)} {d.projected_payoff_date ?? '—'} {d.projected_total_interest != null ? fmtFull(d.projected_total_interest) : '—'} {isPaidOff ? ( Paid off ✓ ) : curBal != null ? ( fmtFull(curBal) ) : ( removed )}
)} {plan.notes && (

{plan.notes}

)}
); } // ─── Single plan row ────────────────────────────────────────────────────────── function PlanRow({ plan }: { plan: SnowballPlan }) { const [expanded, setExpanded] = useState(false); const snapshot = plan.plan_snapshot ?? {}; const debtCount = (snapshot.debts ?? []).length; return (
); } // ─── PlanHistoryPanel ───────────────────────────────────────────────────────── export default function PlanHistoryPanel({ plans = [] }: { plans?: SnowballPlan[] }) { const [open, setOpen] = useState(false); const historical = plans.filter(p => !['active', 'paused'].includes(p.status)); if (historical.length === 0) return null; return (
{historical.map(plan => ( ))}
); }