diff --git a/frontend/src/components/TransactionsTable.tsx b/frontend/src/components/TransactionsTable.tsx
index 13fb601..bf624b2 100644
--- a/frontend/src/components/TransactionsTable.tsx
+++ b/frontend/src/components/TransactionsTable.tsx
@@ -355,6 +355,25 @@ export default function TransactionsTable() {
);
}
+ // Calculate stats from current page transactions
+ const totalIncome = transactions
+ .filter((t: Transaction) => t.transaction_value > 0)
+ .reduce((sum: number, t: Transaction) => sum + t.transaction_value, 0);
+
+ const totalExpenses = Math.abs(
+ transactions
+ .filter((t: Transaction) => t.transaction_value < 0)
+ .reduce((sum: number, t: Transaction) => sum + t.transaction_value, 0)
+ );
+
+ const stats = {
+ totalCount: pagination?.total || 0,
+ pageCount: transactions.length,
+ totalIncome,
+ totalExpenses,
+ netChange: totalIncome - totalExpenses,
+ };
+
return (
{/* New FilterBar */}
@@ -366,6 +385,78 @@ export default function TransactionsTable() {
isSearchLoading={isSearchLoading}
/>
+ {/* Transaction Statistics */}
+ {transactions.length > 0 && (
+
+
+
+
+
+ Showing
+
+
+ {stats.pageCount}
+
+
+ of {stats.totalCount} total
+
+
+
+
+
+
+
+
+
+ Income
+
+
+ +{formatCurrency(stats.totalIncome, transactions[0]?.transaction_currency || "EUR")}
+
+
+
+
+
+
+
+
+
+
+ Expenses
+
+
+ -{formatCurrency(stats.totalExpenses, transactions[0]?.transaction_currency || "EUR")}
+
+
+
+
+
+
+
+
+
+
+ Net Change
+
+
= 0 ? "text-green-600" : "text-red-600"
+ }`}
+ >
+ {stats.netChange >= 0 ? "+" : ""}
+ {formatCurrency(stats.netChange, transactions[0]?.transaction_currency || "EUR")}
+
+
+ {stats.netChange >= 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+ )}
+
{/* Responsive Table/Cards */}
{/* Desktop Table View (hidden on mobile) */}
diff --git a/frontend/src/components/filters/FilterBar.tsx b/frontend/src/components/filters/FilterBar.tsx
index 8400680..2b60ab4 100644
--- a/frontend/src/components/filters/FilterBar.tsx
+++ b/frontend/src/components/filters/FilterBar.tsx
@@ -1,3 +1,4 @@
+import { useRef, useEffect } from "react";
import { Search } from "lucide-react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
@@ -30,6 +31,26 @@ export function FilterBar({
isSearchLoading = false,
className,
}: FilterBarProps) {
+ const searchInputRef = useRef(null);
+ const wasFocused = useRef(false);
+
+ // Track if search input was focused before re-render
+ useEffect(() => {
+ const currentInput = searchInputRef.current;
+ if (!currentInput) return;
+
+ // Check if the input was focused before the effect runs
+ if (document.activeElement === currentInput) {
+ wasFocused.current = true;
+ }
+
+ // If it was focused, restore focus after render
+ if (wasFocused.current && document.activeElement !== currentInput) {
+ currentInput.focus();
+ wasFocused.current = false;
+ }
+ });
+
const hasActiveFilters =
filterState.searchTerm ||
filterState.selectedAccount ||
@@ -61,6 +82,7 @@ export function FilterBar({