'use client'; import { useState, useEffect } from 'react'; interface Props { companyId?: string; selectedCompany: 'rmp' | 'jtr'; companies: any[]; } function fmt(n: number) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n); } export default function ReportsTab({ companyId, selectedCompany, companies }: Props) { const [invoices, setInvoices] = useState([]); const [loading, setLoading] = useState(true); const [reportType, setReportType] = useState<'monthly' | 'quarterly' | 'yearly'>('monthly'); useEffect(() => { setLoading(true); const params = companyId ? `?companyId=${companyId}` : ''; fetch(`/api/billing/invoices${params}`) .then(r => r.json()) .then(d => setInvoices(d.data || [])) .finally(() => setLoading(false)); }, [companyId]); // AR Aging const today = new Date(); const aging = { current: [] as any[], '1-30': [] as any[], '31-60': [] as any[], '61-90': [] as any[], '90+': [] as any[], }; invoices .filter(inv => !['paid', 'cancelled', 'draft'].includes(inv.status)) .forEach(inv => { if (!inv.due_date) return; const due = new Date(inv.due_date); const days = Math.floor((today.getTime() - due.getTime()) / (1000 * 60 * 60 * 24)); if (days <= 0) aging.current.push(inv); else if (days <= 30) aging['1-30'].push(inv); else if (days <= 60) aging['31-60'].push(inv); else if (days <= 90) aging['61-90'].push(inv); else aging['90+'].push(inv); }); // Top clients const clientTotals: Record = {}; invoices .filter(inv => inv.status === 'paid') .forEach(inv => { const name = inv.billing_clients?.name || 'Unknown'; if (!clientTotals[name]) clientTotals[name] = { name, total: 0 }; clientTotals[name].total += Number(inv.total || 0); }); const topClients = Object.values(clientTotals).sort((a, b) => b.total - a.total).slice(0, 10); // Revenue by period const now = new Date(); const currentYear = now.getFullYear(); const yearlyRevenue = invoices .filter(inv => inv.status === 'paid' && inv.paid_at?.startsWith(String(currentYear))) .reduce((s, inv) => s + Number(inv.total || 0), 0); const totalInvoiced = invoices.reduce((s, inv) => s + Number(inv.total || 0), 0); const handleExportCSV = () => { const rows = [ ['Invoice #', 'Client', 'Amount', 'Status', 'Issue Date', 'Due Date', 'Paid At'], ...invoices.map(inv => [ inv.invoice_number, inv.billing_clients?.name || '', inv.total, inv.status, inv.issue_date || '', inv.due_date || '', inv.paid_at || '', ]), ]; const csv = rows.map(r => r.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `billing-report-${selectedCompany}-${new Date().toISOString().split('T')[0]}.csv`; a.click(); URL.revokeObjectURL(url); }; return (
{/* Header */}
{(['monthly', 'quarterly', 'yearly'] as const).map(t => ( ))}
{/* Summary */}

Total Invoiced ({currentYear})

{fmt(yearlyRevenue)}

Total Invoiced (All Time)

{fmt(totalInvoiced)}

Tax Summary ({currentYear})

{fmt(yearlyRevenue)}

Total invoiced — for accounting export

{/* AR Aging */}

Accounts Receivable Aging

{[ { label: 'Current (not yet due)', key: 'current', color: 'text-green-400' }, { label: '1–30 days overdue', key: '1-30', color: 'text-yellow-400' }, { label: '31–60 days overdue', key: '31-60', color: 'text-orange-400' }, { label: '61–90 days overdue', key: '61-90', color: 'text-red-400' }, { label: '90+ days overdue (bad debt risk)', key: '90+', color: 'text-red-600' }, ].map(bucket => { const items = aging[bucket.key as keyof typeof aging]; const total = items.reduce((s, inv) => s + Number(inv.total || 0) - Number(inv.amount_paid || 0), 0); return (
{bucket.label} ({items.length} invoice{items.length !== 1 ? 's' : ''})
{fmt(total)}
); })}
{/* Top Clients */}

Top Clients by Revenue

{topClients.length === 0 ? (

No paid invoices yet.

) : (
{topClients.map((c, i) => (
{i + 1}
{c.name} {fmt(c.total)}
))}
)}
); }