import { useState, useEffect, useMemo, useRef } from 'react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; import { DashboardBanner } from '../../lib/dashboardBanner'; import { useLocation, useNavigate } from 'react-router-dom'; import Layout from '../../components/Layout'; import SortTh from '../../components/SortTh'; import StatusBadge from '../../components/StatusBadge'; import { useSortable } from '../../hooks/useSortable'; import { supabase } from '../../lib/supabase'; import { readPageCache, writePageCache } from '../../lib/pageCache'; import { exportCPAPackage, generateSubcontractorPOPDF } from '../../lib/invoice'; import { blobToEmailAttachment, sendEmail } from '../../lib/email'; import { popupOverlayStyle, popupSurfaceStyle } from '../../lib/popupStyles'; const CATEGORIES = ['Software', 'Contractor', 'Advertising', 'Office', 'Travel', 'Meals', 'Equipment', 'Other']; const statusColor = { draft: 'not_started', sent: 'in_progress', paid: 'client_approved' }; const poStatusColor = { draft: 'not_started', sent: 'in_progress', approved: 'client_approved', ready_to_pay: 'in_progress', paid: 'client_approved', cancelled: 'needs_revision', }; const poStatusLabel = { draft: 'Draft', sent: 'Sent', approved: 'Approved', ready_to_pay: 'Ready to Pay', paid: 'Paid', cancelled: 'Cancelled', }; const invoiceStatusBadgeLabel = (status) => { if (status === 'sent') return 'Invoiced'; return status ? status.charAt(0).toUpperCase() + status.slice(1) : '—'; }; const RECEIPT_BUCKET = 'expense-receipts'; const FIELD_LABEL_STYLE = { fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: 0.8, display: 'block', marginBottom: 4 }; const FIELD_INPUT_STYLE = { minHeight: 42, margin: 0 }; const blankExpense = () => ({ date: new Date().toISOString().slice(0, 10), description: '', category: 'Other', amount: '', notes: '', receipt: null, receipt_path: null, receipt_name: null, removeReceipt: false, }); function getFileExt(name = '') { const clean = String(name).split('.').pop()?.toLowerCase(); return clean && clean !== name ? clean.replace(/[^a-z0-9]/g, '') : 'bin'; } const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; function FinancesChartTooltip({ active, payload, label, year }) { if (!active || !payload?.length) return null; return (
| {fmtDate(exp.date)} | {exp.category || '—'} | {exp.notes || '—'} | {fmtAmt(exp.amount)} |
| {inv.profile?.name || '—'} | {fmtDate(inv.submitted_at)} | {fmtDate(inv.paid_at)} |
|
{fmtAmt(total)} |
| {fmtDate(inv.invoice_date)} | {fmtDate(inv.due_date)} |
|
{fmtAmt(inv.total)} |
Loading...
) : filtered.length === 0 ? (No invoices.
) : (| {inv.invoice_number} | {inv.bill_to || inv.company?.name} | {new Date(inv.invoice_date).toLocaleDateString()} | {new Date(inv.due_date).toLocaleDateString()} |
|
${Number(inv.total).toFixed(2)} |
Loading...
) : expensesError ? ({expensesError}
) : filteredExpenses.length === 0 ? (No expenses yet.
) : (| Action | |||||
|---|---|---|---|---|---|
| {new Date(exp.date).toLocaleDateString()} | {exp.description} | {exp.category} |
{exp.notes || exp.receipt_path ? (
{exp.notes || '—'}
{exp.receipt_path && (
)}
) : '—'}
|
${Number(exp.amount).toFixed(2)} |
Loading...
) : subInvoicesError ? ({subInvoicesError}
) : subInvoices.length === 0 ? (No sub invoices yet.
) : (| Actions | |||||
|---|---|---|---|---|---|
| {inv.invoice_number} |
{inv.profile?.name || 'External'}
{inv.profile?.email || '—'}
|
{inv.submitted_at ? new Date(inv.submitted_at).toLocaleDateString() : '—'} |
|
${total.toFixed(2)} |