'use client'; import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter } from 'next/navigation'; function fmt(cents: number) { return '$' + (cents / 100).toLocaleString('en-US', { minimumFractionDigits: 2 }); } export default function ReconciliationPage() { const { user, loading } = useAuth(); const router = useRouter(); const [transactions, setTransactions] = useState([]); const [loadingData, setLoadingData] = useState(false); const [uploading, setUploading] = useState(false); const [summary, setSummary] = useState<{ matched: number; unmatched: number } | null>(null); useEffect(() => { if (!loading && !user) router.push('/login?next=' + encodeURIComponent(window.location.pathname)); }, [user, loading, router]); const load = useCallback(async () => { setLoadingData(true); const r = await fetch('/api/accounting/reconciliation'); const d = await r.json(); setTransactions(d.transactions ?? []); setSummary(d.summary ?? null); setLoadingData(false); }, []); useEffect(() => { if (user) load(); }, [user, load]); async function handleUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setUploading(true); const text = await file.text(); const r = await fetch('/api/accounting/reconciliation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csv: text }), }); const d = await r.json(); setSummary(d.summary ?? null); setUploading(false); load(); } async function handleManualMatch(txId: string, invoiceId: string) { await fetch('/api/accounting/reconciliation/match', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction_id: txId, invoice_id: invoiceId }), }); load(); } if (loading) return
; if (!user) return null; return (

Bank Reconciliation

← Accounting
{summary && (

{summary.matched}

Auto-matched transactions

{summary.unmatched}

Need review

)}
{['Date','Description','Amount','Type','Matched To','Reconciled','Action'].map(h => ( ))} {loadingData ? ( ) : transactions.length === 0 ? ( ) : transactions.map((tx: any) => ( ))}
{h}
Loading…
No transactions. Upload a CSV to begin.
{tx.transaction_date} {tx.description} {fmt(Math.abs(tx.amount_cents ?? 0))} {tx.type} {tx.matched_invoice_id ? `Invoice ${tx.matched_invoice_id.slice(0, 8)}…` : tx.matched_expense_id ? `Expense` : '—'} {tx.reconciled ? 'Yes' : 'No'} {!tx.reconciled && Manual match}
); }