import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { ArrowRight, TrendingUp, TrendingDown, RefreshCw, CreditCard, Pencil, Check, X, } from 'lucide-react'; import api, { type Account, type Transaction } from '../api'; function formatAmount(amount: number, currency: string) { const abs = Math.abs(amount); if (currency === 'BTC') return abs.toFixed(8); if (currency === 'XMR') return abs.toFixed(4); if (currency === 'USD') { return `$${abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } return `₡${abs.toLocaleString('es-CR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } function formatDate(dateStr: string) { return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } // --- Reusable card for an account balance --- function AccountCard({ account, editingId, editValue, setEditValue, startEditing, saveBalance, cancelEditing, }: { account: Account; editingId: number | null; editValue: string; setEditValue: (v: string) => void; startEditing: (a: Account) => void; saveBalance: (id: number) => void; cancelEditing: () => void; }) { const badgeLabel = account.account_type === 'CRYPTO' ? account.label : (account.bank === 'DAVIVIENDA' ? 'DAV' : account.bank); const isEditing = editingId === account.id; return (
{badgeLabel}
{isEditing ? (
setEditValue(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') saveBalance(account.id); if (e.key === 'Escape') cancelEditing(); }} autoFocus className="w-full text-2xl font-bold font-mono tracking-tight bg-input-bg border border-[#606C38]/40 rounded-lg px-2 py-1 focus:outline-none focus:border-[#606C38] transition-colors" />
) : (
startEditing(account)}>

{formatAmount(account.balance, account.currency)}

)}
); } // --- Total card --- function TotalCard({ total, currency }: { total: number; currency: string }) { return (
Total

{formatAmount(total, currency)}

); } export default function Dashboard() { const [accounts, setAccounts] = useState([]); const [recent, setRecent] = useState([]); const [loading, setLoading] = useState(true); const [editingId, setEditingId] = useState(null); const [editValue, setEditValue] = useState(''); const [exchangeRate, setExchangeRate] = useState<{ buy_rate: number; sell_rate: number } | null>(null); const fetchData = async () => { setLoading(true); try { const [accRes, txRes] = await Promise.all([ api.get('/accounts/'), api.get('/transactions/recent?limit=5'), ]); setAccounts(accRes.data); setRecent(txRes.data); } catch (e) { console.error(e); } finally { setLoading(false); } api.get('/exchange-rate/').then((r) => setExchangeRate(r.data)).catch(() => {}); }; useEffect(() => { fetchData(); }, []); const startEditing = (account: Account) => { setEditingId(account.id); setEditValue(String(account.balance)); }; const cancelEditing = () => { setEditingId(null); setEditValue(''); }; const saveBalance = async (accountId: number) => { const parsed = parseFloat(editValue); if (isNaN(parsed)) return cancelEditing(); try { await api.patch(`/accounts/${accountId}`, { balance: parsed }); setEditingId(null); setEditValue(''); fetchData(); } catch (e) { console.error(e); } }; const cardProps = { editingId, editValue, setEditValue, startEditing, saveBalance, cancelEditing }; // Group accounts by type const bankAccounts = accounts.filter((a) => a.account_type === 'BANK'); const pensionAccounts = accounts.filter((a) => a.account_type === 'PENSION'); const savingsAccounts = accounts.filter((a) => a.account_type === 'SAVINGS'); const liabilityAccounts = accounts.filter((a) => a.account_type === 'LIABILITY'); const cryptoAccounts = accounts.filter((a) => a.account_type === 'CRYPTO'); const bankOrder = ['BAC', 'BCR', 'DAVIVIENDA']; // Bank totals for exchange rate combined total const bankCRC = bankAccounts.filter((a) => a.currency === 'CRC').reduce((s, a) => s + a.balance, 0); const bankUSD = bankAccounts.filter((a) => a.currency === 'USD').reduce((s, a) => s + a.balance, 0); return (
{/* Header */}

Dashboard

Financial overview

{/* Bank accounts — grouped by currency */} {(['CRC', 'USD'] as const).map((currency) => { const accts = bankAccounts .filter((a) => a.currency === currency) .sort((a, b) => bankOrder.indexOf(a.bank) - bankOrder.indexOf(b.bank)); if (accts.length === 0) return null; const total = accts.reduce((s, a) => s + a.balance, 0); return (

{currency} Accounts

{accts.map((a) => )}
); })} {/* Pension accounts */} {pensionAccounts.length > 0 && (

Pension

{pensionAccounts.map((a) => )} s + a.balance, 0)} currency="CRC" />
)} {/* Savings accounts */} {savingsAccounts.length > 0 && (

Savings

{savingsAccounts.map((a) => )} s + a.balance, 0)} currency="CRC" />
)} {/* Liabilities */} {liabilityAccounts.length > 0 && (

Liabilities

{liabilityAccounts.map((account) => { const bankShort = account.bank === 'DAVIVIENDA' ? 'DAV' : account.bank; return (
Balance {bankShort}

startEditing(account)} > {editingId === account.id ? ( setEditValue(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') saveBalance(account.id); if (e.key === 'Escape') cancelEditing(); }} autoFocus className="w-full text-2xl font-bold font-mono tracking-tight bg-input-bg border border-red-500/40 rounded-lg px-2 py-1 focus:outline-none focus:border-red-500 transition-colors text-text-primary" onClick={(e) => e.stopPropagation()} /> ) : ( formatAmount(account.balance, account.currency) )}

{account.next_payment != null && (

Next payment: {formatAmount(account.next_payment, account.currency)}

)}
); })}
)} {/* Crypto */} {cryptoAccounts.length > 0 && (

Crypto

{cryptoAccounts.map((a) => )}
)} {/* Exchange rate + combined total */} {exchangeRate && (
USD/CRC Exchange Rate
Buy: ₡{exchangeRate.buy_rate.toFixed(2)} Sell: ₡{exchangeRate.sell_rate.toFixed(2)}
{accounts.length > 0 && (
Combined Total (CRC)

{formatAmount(bankCRC + bankUSD * exchangeRate.sell_rate, 'CRC')}

)}
)} {/* Recent transactions */}

Recent Charges

View all
{recent.length === 0 && !loading ? (
No transactions yet. Add your first one!
) : (
{recent.map((tx) => (
{tx.transaction_type === 'DEVOLUCION' ? : }

{tx.merchant}

{formatDate(tx.date)} {tx.category && {tx.category.name}}

{tx.transaction_type === 'DEVOLUCION' ? '+' : '-'}{formatAmount(tx.amount, tx.currency)}
))}
)}
); }