import React, { useMemo, useState } from 'react';
import { CheckCircle2, ChevronDown, Circle, Clock, Pause, Play, TrendingUp, 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 } from '@/lib/money';
function fmt(v) {
return formatUSDWhole(v);
}
function dateLabel(iso) {
if (!iso) return '—';
return new Date(iso).toLocaleDateString(undefined, { month: 'short', year: 'numeric' });
}
function months(n) {
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 ───────────────────────────────────────────────────────
function computeOnTrack(debt, monthsElapsed) {
if (!debt.projected_payoff_month || debt.current_balance === null) return null;
if (debt.starting_balance <= 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 = debt.starting_balance > 0
? 1 - (debt.current_balance / debt.starting_balance)
: 0;
const diff = progressActual - progressExpected;
if (diff > 0.05) return 'ahead';
if (diff < -0.05) return 'behind';
return 'on_track';
}
function OnTrackPill({ status }) {
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] ?? map.on_track;
return (
{label}
);
}
// ─── Per-debt progress row ────────────────────────────────────────────────────
function DebtProgressRow({ debt, snapshotDebt, monthsElapsed }) {
const startBal = debt.starting_balance ?? 0;
const curBal = debt.current_balance ?? startBal;
const pct = debt.progress_pct ?? 0;
const trackStatus = computeOnTrack({ ...debt, ...snapshotDebt }, monthsElapsed);
return (
{debt.current_balance !== null ? (
<>
{fmt(curBal)}
{startBal > 0 &&
{fmt(startBal)} start
}
>
) : (
removed
)}
{snapshotDebt?.projected_payoff_date && (
proj.
{snapshotDebt.projected_payoff_date.slice(0, 7)}
)}
);
}
// ─── PlanStatusBanner ─────────────────────────────────────────────────────────
export default function PlanStatusBanner({ plan, onPause, onResume, onComplete, onAbandon, onNewPlan }) {
const [open, setOpen] = useState(true);
const [confirmDialog, setConfirmDialog] = useState(null);
const snapshot = plan?.plan_snapshot ?? {};
const snapshotMap = useMemo(() => {
const m = {};
(snapshot.debts ?? []).forEach(d => { m[d.bill_id] = d; });
return m;
}, [snapshot.debts]);
const currentDebts = plan?.current_debts ?? [];
const monthsElapsed = plan?.months_elapsed ?? 0;
const totalStart = currentDebts.reduce((s, d) => s + (d.starting_balance ?? 0), 0);
const totalCur = currentDebts.reduce((s, d) => s + (d.current_balance ?? d.starting_balance ?? 0), 0);
const totalPaid = Math.max(0, totalStart - totalCur);
const overallPct = totalStart > 0 ? Math.min(100, Math.round(totalPaid / totalStart * 100)) : 0;
const isActive = plan?.status === 'active';
const isPaused = plan?.status === 'paused';
function confirm(action, title, description, onConfirm) {
setConfirmDialog({ title, description, onConfirm });
}
if (!plan) return null;
return (
<>
{/* Header */}
{/* Header row. The name/progress area and the chevron are the collapsible
toggles; the action buttons are siblings (not nested inside a trigger
button) so they don't trip axe nested-interactive (a11y QA-B14-02). */}
{/* Status dot + name + progress — toggle */}
{isActive ? (
) : (
)}
{plan.name}
{isActive ? 'Active' : 'Paused'}
Started {dateLabel(plan.started_at)} · {months(monthsElapsed)} in
{/* Overall progress bar */}
{/* Actions — siblings of the triggers, not nested inside them */}
{isActive && (
<>
Pause
confirm('complete', 'Mark plan as complete?', 'This will record your plan as successfully completed.', onComplete)}>
Complete
>
)}
{isPaused && (
<>
Resume
confirm('abandon', 'Abandon this plan?', 'This plan will be moved to history. Your debt data stays unchanged.', onAbandon)}>
Abandon
>
)}
confirm('new', 'Start a new plan?', 'Your current plan will be abandoned and moved to history. Your debt data stays unchanged.', onNewPlan)}>
New Plan
{/* Chevron — also a toggle */}
{/* Collapsible body — per-debt rows */}
{currentDebts.length === 0 ? (
No debt data in this plan.
) : (
currentDebts.map(debt => (
))
)}
{/* Summary row */}
{totalStart > 0 && (
Total progress
{fmt(totalPaid)} paid of {fmt(totalStart)}
{snapshot.interest_saved > 0 && (
{fmt(snapshot.interest_saved)} interest saved vs minimum
)}
)}
{/* Confirmation dialog */}
{ if (!open) setConfirmDialog(null); }}>
{confirmDialog?.title}
{confirmDialog?.description}
setConfirmDialog(null)}>Cancel
{ confirmDialog?.onConfirm(); setConfirmDialog(null); }}>
Confirm
>
);
}