'use client'; import { useState, useMemo } from 'react'; import { useSearchParams } from 'next/navigation'; import Price from '@/components/Price'; import { generateLedgerCSV, downloadCSV, penceToPounds } from '@/lib/csvExport'; interface ReportData { orders: Array<{ id: string; total: number; createdAt: Date }>; manualSales: Array<{ id: string; total: number; soldAt: Date; createdAt: Date }>; expenses: Array<{ id: string; amount: number; date: Date; category: string }>; purchases: Array<{ id: string; total: number; purchasedAt: Date }>; } export default function ReportsPageClient({ data }: { data: ReportData }) { const searchParams = useSearchParams(); const [tab, setTab] = useState(searchParams.get('tab') ?? 'monthly-profit'); const [fromDate, setFromDate] = useState(''); const [toDate, setToDate] = useState(''); // Ensure dates are parsed as Date objects (they might be strings from server) const normalizedData = { orders: data.orders.map(o => ({ ...o, createdAt: typeof o.createdAt === 'string' ? new Date(o.createdAt) : o.createdAt, })), manualSales: data.manualSales.map(m => ({ ...m, soldAt: typeof m.soldAt === 'string' ? new Date(m.soldAt) : m.soldAt, createdAt: typeof m.createdAt === 'string' ? new Date(m.createdAt) : m.createdAt, })), expenses: data.expenses.map(e => ({ ...e, date: typeof e.date === 'string' ? new Date(e.date) : e.date, })), purchases: data.purchases.map(p => ({ ...p, purchasedAt: typeof p.purchasedAt === 'string' ? new Date(p.purchasedAt) : p.purchasedAt, })), }; const monthlyProfit = useMemo(() => { const months = new Map(); [...normalizedData.orders, ...normalizedData.manualSales].forEach((sale: any) => { const date = new Date(sale.soldAt || sale.createdAt); const key = date.toISOString().slice(0, 7); if (!months.has(key)) months.set(key, { revenue: 0, expenses: 0 }); const m = months.get(key)!; m.revenue += sale.total; }); normalizedData.expenses.forEach((exp) => { const date = new Date(exp.date); const key = date.toISOString().slice(0, 7); if (!months.has(key)) months.set(key, { revenue: 0, expenses: 0 }); const m = months.get(key)!; m.expenses += exp.amount; }); return Array.from(months.entries()) .sort((a, b) => b[0].localeCompare(a[0])) .map(([month, { revenue, expenses }]) => ({ month, revenue, expenses, profit: revenue - expenses, })); }, [normalizedData]); const expenseBreakdown = useMemo(() => { const byCategory = new Map(); normalizedData.expenses.forEach((exp) => { byCategory.set(exp.category, (byCategory.get(exp.category) ?? 0) + exp.amount); }); return Array.from(byCategory.entries()) .sort((a, b) => b[1] - a[1]) .map(([category, total]) => ({ category, total })); }, [normalizedData]); const cashFlow = useMemo(() => { const flow = new Map(); [...normalizedData.orders, ...normalizedData.manualSales].forEach((sale: any) => { const date = new Date(sale.soldAt || sale.createdAt); const key = date.toISOString().slice(0, 7); if (!flow.has(key)) flow.set(key, { in: 0, out: 0 }); flow.get(key)!.in += sale.total; }); normalizedData.expenses.forEach((exp) => { const date = new Date(exp.date); const key = date.toISOString().slice(0, 7); if (!flow.has(key)) flow.set(key, { in: 0, out: 0 }); flow.get(key)!.out += exp.amount; }); let cumIn = 0, cumOut = 0; return Array.from(flow.entries()) .sort((a, b) => a[0].localeCompare(b[0])) .map(([month, { in: inFlow, out: outFlow }]) => { cumIn += inFlow; cumOut += outFlow; return { month, in: inFlow, out: outFlow, cumNet: cumIn - cumOut }; }); }, [normalizedData]); const hmrcReport = useMemo(() => { const totalRevenue = [...normalizedData.orders, ...normalizedData.manualSales].reduce((sum, s: any) => sum + s.total, 0); const costOfGoodsSold = normalizedData.purchases.reduce((sum, p) => sum + p.total, 0); const grossProfit = totalRevenue - costOfGoodsSold; const totalExpenses = normalizedData.expenses.reduce((sum, e) => sum + e.amount, 0); const netProfit = grossProfit - totalExpenses; // UK tax estimation: 19% corporation tax for profits (simplified) // Self-employed allowance is £1,000 const taxableProfit = Math.max(0, netProfit - 100000); // Simplified threshold const estimatedTax = Math.max(0, Math.round(taxableProfit * 0.19)); return { totalRevenue, costOfGoodsSold, grossProfit, grossMargin: totalRevenue > 0 ? (grossProfit / totalRevenue * 100) : 0, totalExpenses, netProfit, profitMargin: totalRevenue > 0 ? (netProfit / totalRevenue * 100) : 0, estimatedTax, }; }, [normalizedData]); const handleExportLedger = () => { const from = fromDate ? new Date(fromDate) : null; const to = toDate ? new Date(`${toDate}T23:59:59`) : null; const ledgerRows = [ ...normalizedData.orders .filter((o) => (!from || o.createdAt >= from) && (!to || o.createdAt <= to)) .map((o) => ({ date: o.createdAt, type: 'INCOME' as const, description: 'Website order', amount: o.total, category: 'Sales', })), ...normalizedData.manualSales .filter((s) => (!from || s.soldAt >= from) && (!to || s.soldAt <= to)) .map((s) => ({ date: s.soldAt, type: 'INCOME' as const, description: 'Manual sale', amount: s.total, category: 'Sales', })), ...normalizedData.purchases .filter((p) => (!from || p.purchasedAt >= from) && (!to || p.purchasedAt <= to)) .map((p) => ({ date: p.purchasedAt, type: 'EXPENSE' as const, description: 'Stock purchase', amount: p.total, category: 'Cost of goods', })), ...normalizedData.expenses .filter((e) => (!from || e.date >= from) && (!to || e.date <= to)) .map((e) => ({ date: e.date, type: 'EXPENSE' as const, description: e.category, amount: e.amount, category: e.category, })), ]; const dateRange = fromDate || toDate ? ` (${fromDate || 'start'} to ${toDate || 'today'})` : ''; const csv = generateLedgerCSV(ledgerRows, `Ledger Export${dateRange}`); downloadCSV(csv, `ledger-${new Date().toISOString().split('T')[0]}.csv`); }; return ( <> {/* Date Filtering */}

Filter & Export

{(fromDate || toDate) && ( )}
{[ { id: 'trends', label: 'Trends' }, { id: 'monthly-profit', label: 'Monthly Profit' }, { id: 'expense-breakdown', label: 'Expenses' }, { id: 'cash-flow', label: 'Cash Flow' }, { id: 'hmrc', label: 'HMRC Report' }, ].map((t) => ( ))}
{tab === 'trends' && (

Monthly Profit Trend

{/* Y-axis */} {/* X-axis */} {monthlyProfit.slice(-12).map((row, i) => { const maxProfit = Math.max(...monthlyProfit.map((r) => Math.abs(r.profit)), 1); const x = 50 + (i / 11) * 900; const barHeight = (Math.abs(row.profit) / maxProfit) * 200; const y = 260 - barHeight; const isPositive = row.profit >= 0; return ( {/* Bar */} {/* Month label */} {row.month.slice(5)} {/* Value label */} £{(row.profit / 100).toFixed(0)} ); })}

Avg Monthly Profit

sum + m.profit, 0) / Math.max(monthlyProfit.length, 1))} />

Best Month

m.profit), 0)} />

Worst Month

m.profit), 0)} />

)} {tab === 'monthly-profit' && (
{monthlyProfit.map((row) => ( ))}
Month Revenue Expenses Profit
{row.month} = 0 ? 'text-green-600' : 'text-splash-pink'}`}>
)} {tab === 'expense-breakdown' && (
{expenseBreakdown.length === 0 ? (

No expenses recorded yet.

) : ( expenseBreakdown.map((row) => (
{row.category}
)) )}
)} {tab === 'cash-flow' && (
{cashFlow.map((row) => ( ))}
Month Cash In Cash Out Cumulative
{row.month} = 0 ? 'text-green-600' : 'text-splash-pink'}`}>
)} {tab === 'hmrc' && (
ⓘ Summary for HMRC Self Assessment. Keep invoices and receipts as evidence.

Total Turnover

Cost of Goods Sold

Gross Profit

{hmrcReport.grossMargin.toFixed(1)}% margin

Operating Expenses

Net Profit (before tax)

= 0 ? 'text-green-600' : 'text-splash-pink'}`}>

{hmrcReport.profitMargin.toFixed(1)}% of revenue

Estimated Tax Liability

Based on 19% corporation tax on profits. Self-employed? Your actual rate may differ. Consult an accountant for accurate figures.

What to report to HMRC:

  • Total turnover:
  • Cost of sales (purchases):
  • Running expenses:
  • Profit before tax:
)}
); }