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 (
);
})}
{/* 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.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)}
))}
)}
);
}