import React, { useState } from 'react'; import { AlertCircle, ChevronDown, BellOff, SkipForward, CreditCard } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api.js'; import { cn, fmt } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, } from '@/components/ui/dropdown-menu'; function snoozeUntil(days) { const d = new Date(); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); } function daysOverdueLabel(dueDate) { if (!dueDate) return 'overdue'; const today = new Date(); today.setHours(0, 0, 0, 0); const due = new Date(`${dueDate}T00:00:00`); const days = Math.floor((today - due) / 86_400_000); if (days <= 0) return 'due today'; return `${days} ${days === 1 ? 'day' : 'days'} overdue`; } function OverdueRow({ row, year, month, onPayNow, onRefresh }) { const [loading, setLoading] = useState(false); const threshold = row.actual_amount ?? row.expected_amount; async function handleSkip() { setLoading(true); try { await api.saveBillMonthlyState(row.id, { year, month, is_skipped: true, actual_amount: row.actual_amount, notes: row.monthly_notes, }); onRefresh(); } catch { toast.error('Failed to skip bill'); } finally { setLoading(false); } } async function handleSnooze(days) { setLoading(true); try { await api.snoozeOverdue(row.id, { year, month, snoozed_until: snoozeUntil(days), actual_amount: row.actual_amount, notes: row.monthly_notes, is_skipped: false, }); toast.success(`Snoozed for ${days} ${days === 1 ? 'day' : 'days'}`); onRefresh(); } catch { toast.error('Failed to snooze bill'); } finally { setLoading(false); } } return (
{/* Bill info */}
{row.name} {row.category_name && ( {row.category_name} )}
{daysOverdueLabel(row.due_date)}
{/* Amount */} {fmt(threshold)} {/* Actions */}
handleSnooze(1)}>1 day handleSnooze(3)}>3 days handleSnooze(7)}>7 days
); } export default function OverdueCommandCenter({ rows, year, month, refresh, onPayNow }) { const todayStr = new Date().toISOString().slice(0, 10); const [isOpen, setIsOpen] = useState(true); const overdueRows = rows.filter(r => (r.status === 'late' || r.status === 'missed') && !r.is_skipped && (!r.snoozed_until || r.snoozed_until <= todayStr) ); const snoozedRows = rows.filter(r => (r.status === 'late' || r.status === 'missed') && !r.is_skipped && r.snoozed_until && r.snoozed_until > todayStr ); if (overdueRows.length === 0 && snoozedRows.length === 0) return null; const totalOverdue = overdueRows.reduce((sum, r) => { const threshold = r.actual_amount ?? r.expected_amount; const paid = r.total_paid ?? 0; return sum + Math.max(0, threshold - paid); }, 0); return (
{/* Header */} {/* Bill rows */} {overdueRows.length > 0 ? (
{overdueRows.map(row => ( ))}
) : (

All overdue bills are snoozed.

)}
); }