diff --git a/src/components/reports/HighlightsTopTransactionsList.tsx b/src/components/reports/HighlightsTopTransactionsList.tsx
index 06fe732..07ca79a 100644
--- a/src/components/reports/HighlightsTopTransactionsList.tsx
+++ b/src/components/reports/HighlightsTopTransactionsList.tsx
@@ -5,6 +5,7 @@ export interface HighlightsTopTransactionsListProps {
transactions: RecentTransaction[];
windowDays: 30 | 60 | 90;
onWindowChange: (days: 30 | 60 | 90) => void;
+ onContextMenuRow?: (event: React.MouseEvent, transaction: RecentTransaction) => void;
}
function formatAmount(amount: number, language: string): string {
@@ -18,6 +19,7 @@ export default function HighlightsTopTransactionsList({
transactions,
windowDays,
onWindowChange,
+ onContextMenuRow,
}: HighlightsTopTransactionsListProps) {
const { t, i18n } = useTranslation();
@@ -50,7 +52,11 @@ export default function HighlightsTopTransactionsList({
) : (
{transactions.map((tx) => (
- -
+
- onContextMenuRow(e, tx) : undefined}
+ className="flex items-center gap-3 px-4 py-2 text-sm"
+ >
Promise;
onSaveSplit: (parentId: number, entries: Array<{ category_id: number; amount: number; description: string }>) => Promise;
onDeleteSplit: (parentId: number) => Promise;
+ onRowContextMenu?: (event: React.MouseEvent, row: TransactionRow) => void;
}
function SortIcon({
@@ -50,6 +51,7 @@ export default function TransactionTable({
onLoadSplitChildren,
onSaveSplit,
onDeleteSplit,
+ onRowContextMenu,
}: TransactionTableProps) {
const { t } = useTranslation();
const [expandedId, setExpandedId] = useState(null);
@@ -135,6 +137,7 @@ export default function TransactionTable({
{rows.map((row) => (
onRowContextMenu(e, row) : undefined}
className="hover:bg-[var(--muted)] transition-colors"
>
| {row.date} |
diff --git a/src/pages/ReportsHighlightsPage.tsx b/src/pages/ReportsHighlightsPage.tsx
index b6d1a58..b84b58d 100644
--- a/src/pages/ReportsHighlightsPage.tsx
+++ b/src/pages/ReportsHighlightsPage.tsx
@@ -1,15 +1,18 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
-import { ArrowLeft } from "lucide-react";
+import { ArrowLeft, Tag } from "lucide-react";
import PeriodSelector from "../components/dashboard/PeriodSelector";
import HubNetBalanceTile from "../components/reports/HubNetBalanceTile";
import HighlightsTopMoversTable from "../components/reports/HighlightsTopMoversTable";
import HighlightsTopMoversChart from "../components/reports/HighlightsTopMoversChart";
import HighlightsTopTransactionsList from "../components/reports/HighlightsTopTransactionsList";
import ViewModeToggle, { readViewMode, type ViewMode } from "../components/reports/ViewModeToggle";
+import ContextMenu from "../components/shared/ContextMenu";
+import AddKeywordDialog from "../components/categories/AddKeywordDialog";
import { useHighlights } from "../hooks/useHighlights";
import { useReportsPeriod } from "../hooks/useReportsPeriod";
+import type { RecentTransaction } from "../shared/types";
const STORAGE_KEY = "reports-viewmode-highlights";
@@ -18,9 +21,16 @@ export default function ReportsHighlightsPage() {
const { period, setPeriod, from, to, setCustomDates } = useReportsPeriod();
const { data, isLoading, error, windowDays, setWindowDays } = useHighlights();
const [viewMode, setViewMode] = useState(() => readViewMode(STORAGE_KEY));
+ const [menu, setMenu] = useState<{ x: number; y: number; tx: RecentTransaction } | null>(null);
+ const [pending, setPending] = useState(null);
const preserveSearch = typeof window !== "undefined" ? window.location.search : "";
+ const handleContextMenu = (e: React.MouseEvent, tx: RecentTransaction) => {
+ e.preventDefault();
+ setMenu({ x: e.clientX, y: e.clientY, tx });
+ };
+
return (
@@ -87,10 +97,35 @@ export default function ReportsHighlightsPage() {
transactions={data.topTransactions}
windowDays={windowDays}
onWindowChange={setWindowDays}
+ onContextMenuRow={handleContextMenu}
/>
)}
+
+ {menu && (
+
setMenu(null)}
+ items={[
+ {
+ icon: ,
+ label: t("reports.keyword.addFromTransaction"),
+ onClick: () => setPending(menu.tx),
+ },
+ ]}
+ />
+ )}
+
+ {pending && (
+ setPending(null)}
+ onApplied={() => setPending(null)}
+ />
+ )}
);
}
diff --git a/src/pages/TransactionsPage.tsx b/src/pages/TransactionsPage.tsx
index 604baf2..9d425a4 100644
--- a/src/pages/TransactionsPage.tsx
+++ b/src/pages/TransactionsPage.tsx
@@ -1,18 +1,28 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { Wand2 } from "lucide-react";
+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 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);
+
+ const handleRowContextMenu = (e: React.MouseEvent, row: TransactionRow) => {
+ e.preventDefault();
+ setMenu({ x: e.clientX, y: e.clientY, row });
+ };
const handleAutoCategorize = async () => {
setResultMessage(null);
@@ -84,6 +94,7 @@ export default function TransactionsPage() {
onLoadSplitChildren={loadSplitChildren}
onSaveSplit={saveSplit}
onDeleteSplit={deleteSplit}
+ onRowContextMenu={handleRowContextMenu}
/>
>
)}
+
+ {menu && (
+ setMenu(null)}
+ items={[
+ {
+ icon: ,
+ label: t("reports.keyword.addFromTransaction"),
+ onClick: () => setPending(menu.row),
+ },
+ ]}
+ />
+ )}
+
+ {pending && (
+ setPending(null)}
+ onApplied={() => setPending(null)}
+ />
+ )}
);
}