import { useState } from 'react'; import { TrendingUp, TrendingDown, ChevronDown, ChevronUp, EyeOff } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api'; import { cn, fmt } from '@/lib/utils'; import { asDollars } from '@/lib/money'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; import type { DriftBill } from '@/types'; function DriftRow({ row, refresh }: { row: DriftBill; refresh: () => void }) { const [loading, setLoading] = useState(false); async function handleUpdate() { setLoading(true); const oldAmount = row.expected_amount; try { await api.updateBill(row.id, { expected_amount: row.recent_amount }); toast.success(`"${row.name}" updated to ${fmt(row.recent_amount)}`, { action: { label: 'Undo', onClick: async () => { try { await api.updateBill(row.id, { expected_amount: oldAmount }); toast.success('Reverted'); refresh(); } catch { toast.error('Failed to revert'); } }, }, }); refresh(); } catch { toast.error('Failed to update bill'); } finally { setLoading(false); } } async function handleDismiss() { setLoading(true); try { await api.snoozeBillDrift(row.id); toast.success('Hidden for 30 days'); refresh(); } catch { toast.error('Failed to dismiss'); } finally { setLoading(false); } } const isUp = row.direction === 'up'; const sign = isUp ? '+' : ''; return (
{/* Bill info */}
{row.name} {row.category_name && ( {row.category_name} )}
{isUp ? ( ) : ( )} {sign} {row.drift_pct}% over {row.months_sampled} months
{/* Amount change */}
{fmt(row.expected_amount)} {fmt(row.recent_amount)} {sign} {row.drift_pct}%
{/* Actions */}
); } interface DriftInsightPanelProps { driftBills?: DriftBill[]; refresh: () => void; // Controlled + persisted collapse state (falls back to local state when omitted). collapsed?: boolean; onCollapsedChange?: (collapsed: boolean) => void; // Fully hide the panel (writes the tracker_show_* setting on the page). onHide?: () => void; } export default function DriftInsightPanel({ driftBills, refresh, collapsed, onCollapsedChange, onHide, }: DriftInsightPanelProps) { const [localOpen, setLocalOpen] = useState(true); const isControlled = collapsed !== undefined; const isOpen = isControlled ? !collapsed : localOpen; const setOpen = (open: boolean) => { if (isControlled) onCollapsedChange?.(!open); else setLocalOpen(open); }; if (!driftBills?.length) return null; const totalNetDelta = driftBills.reduce( (sum, b) => sum + (b.recent_amount - b.expected_amount), 0, ); const sign = totalNetDelta >= 0 ? '+' : ''; const netColor = totalNetDelta >= 0 ? 'text-amber-500 dark:text-amber-400' : 'text-teal-500 dark:text-teal-400'; const hasIncrease = driftBills.some((b) => b.direction === 'up'); const ToggleIcon = isOpen ? ChevronDown : ChevronUp; return (
{/* Header */}
{onHide && ( )}
{/* Bill rows */}
{driftBills.map((row) => ( ))}
); }