import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Wand2, Tag } from "lucide-react"; import { PageHelp } from "../components/shared/PageHelp"; import { useTransactions } from "../hooks/useTransactions"; import TransactionFilterBar from "../components/transactions/TransactionFilterBar"; import TransactionSummaryBar from "../components/transactions/TransactionSummaryBar"; import TransactionTable from "../components/transactions/TransactionTable"; import TransactionPagination from "../components/transactions/TransactionPagination"; import ContextMenu from "../components/shared/ContextMenu"; import AddKeywordDialog from "../components/categories/AddKeywordDialog"; import { listAllLinkedTransfersForTooltip, type LinkedTransferTooltipRow, } from "../services/balance.service"; import type { TransactionRow } from "../shared/types"; export default function TransactionsPage() { const { t } = useTranslation(); const { state, setFilter, setSort, setPage, updateCategory, saveNotes, autoCategorize, addKeywordToCategory, loadSplitChildren, saveSplit, deleteSplit } = useTransactions(); const [resultMessage, setResultMessage] = useState(null); const [menu, setMenu] = useState<{ x: number; y: number; row: TransactionRow } | null>(null); const [pending, setPending] = useState(null); // Issue #142 — single batch lookup for the inlined transfer icon. One // SELECT on mount gives us a Map the table consults via // `.has()` per row. Avoids an N+1 hit on the rendered page. const [linkedTransfersByTxId, setLinkedTransfersByTxId] = useState< Map >(new Map()); useEffect(() => { listAllLinkedTransfersForTooltip() .then(setLinkedTransfersByTxId) .catch(() => setLinkedTransfersByTxId(new Map())); }, []); const handleRowContextMenu = (e: React.MouseEvent, row: TransactionRow) => { e.preventDefault(); setMenu({ x: e.clientX, y: e.clientY, row }); }; const handleAutoCategorize = async () => { setResultMessage(null); const count = await autoCategorize(); if (count > 0) { setResultMessage(t("transactions.autoCategorizeResult", { count })); } else { setResultMessage(t("transactions.autoCategorizeNone")); } setTimeout(() => setResultMessage(null), 4000); }; return (

{t("transactions.title")}

{resultMessage && ( {resultMessage} )}
{state.error && (
{state.error}
)} {state.isLoading ? (
{t("common.loading")}
) : ( <> )} {menu && ( setMenu(null)} items={[ { icon: , label: t("reports.keyword.addFromTransaction"), onClick: () => setPending(menu.row), }, ]} /> )} {pending && ( setPending(null)} onApplied={() => setPending(null)} /> )}
); }