import { useEffect, useState, useCallback } from 'react'; import { Plus, Search, Pencil, Trash2, TrendingUp, TrendingDown, ChevronDown, ClipboardPaste, } from 'lucide-react'; import api, { type Transaction, type Category } from '../api'; import TransactionModal from '../components/TransactionModal'; import PasteImportModal from '../components/PasteImportModal'; import ConfirmDialog from '../components/ConfirmDialog'; import BillingCycleSelector from '../components/BillingCycleSelector'; function formatAmount(amount: number, currency: string) { const abs = Math.abs(amount); if (currency === 'USD') { return `$${abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } return `₡${abs.toLocaleString('es-CR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } export default function Transactions() { const [transactions, setTransactions] = useState([]); const [categories, setCategories] = useState([]); const [search, setSearch] = useState(''); const [categoryFilter, setCategoryFilter] = useState(''); const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleteId, setDeleteId] = useState(null); const [deleting, setDeleting] = useState(false); const [cycle, setCycle] = useState<{ year: number; month: number } | null>(null); const fetchTransactions = useCallback(async () => { setLoading(true); try { const params: Record = { source: 'CREDIT_CARD', limit: '200' }; if (search) params.search = search; if (categoryFilter) params.category_id = categoryFilter; if (cycle) { params.cycle_year = String(cycle.year); params.cycle_month = String(cycle.month); } const { data } = await api.get('/transactions/', { params }); setTransactions(data); } finally { setLoading(false); } }, [search, categoryFilter, cycle]); useEffect(() => { api.get('/categories/').then((r) => setCategories(r.data)); }, []); useEffect(() => { const timer = setTimeout(fetchTransactions, 300); return () => clearTimeout(timer); }, [fetchTransactions]); const handleDelete = async () => { if (deleteId === null) return; setDeleting(true); try { await api.delete(`/transactions/${deleteId}`); setDeleteId(null); fetchTransactions(); } finally { setDeleting(false); } }; const totalCRC = transactions .filter((tx) => tx.currency === 'CRC') .reduce((sum, tx) => sum + (tx.transaction_type === 'DEVOLUCION' ? -tx.amount : tx.amount), 0); const totalUSD = transactions .filter((tx) => tx.currency === 'USD') .reduce((sum, tx) => sum + (tx.transaction_type === 'DEVOLUCION' ? -tx.amount : tx.amount), 0); return (

Credit Card Transactions

{transactions.length} transactions {totalCRC !== 0 && ( <> · {formatAmount(totalCRC, 'CRC')} )} {totalUSD !== 0 && ( <> · {formatAmount(totalUSD, 'USD')} )}

{/* Billing cycle */} {/* Filters */}
setSearch(e.target.value)} className="w-full bg-input-bg border border-border rounded-lg pl-10 pr-4 py-2.5 text-sm text-text-primary placeholder-text-faint focus:outline-none focus:border-[#606C38]/50 transition-colors" placeholder="Search merchants..." />
{/* Table */}
{transactions.map((tx) => ( ))}
Date Merchant Category Amount Actions
{new Date(tx.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', })}
{tx.transaction_type === 'DEVOLUCION' ? ( ) : ( )}
{tx.merchant}
{tx.category ? ( {tx.category.name} ) : ( )} {tx.transaction_type === 'DEVOLUCION' ? '+' : '-'} {formatAmount(tx.amount, tx.currency)}
{transactions.length === 0 && !loading && (
No transactions found
)}
{modalOpen && ( setModalOpen(false)} onSaved={fetchTransactions} /> )} {importOpen && ( setImportOpen(false)} onImported={fetchTransactions} /> )} {deleteId !== null && ( setDeleteId(null)} loading={deleting} /> )}
); }