Compare commits

...

2 Commits

Author SHA1 Message Date
Elisiário Couto
7a81f9ff9c fix(frontend): Prevent full transactions page reload on search. 2025-12-08 16:06:29 +00:00
Elisiário Couto
362410c29b fix(frontend): Blur balances in transactions page cards. 2025-12-08 15:56:18 +00:00
2 changed files with 35 additions and 19 deletions

View File

@@ -123,6 +123,7 @@ export default function TransactionsTable() {
search: debouncedSearchTerm || undefined,
summaryOnly: false,
}),
placeholderData: (previousData) => previousData,
});
const transactions = transactionsResponse?.data || [];
@@ -413,9 +414,9 @@ export default function TransactionsTable() {
<p className="text-xs text-muted-foreground uppercase tracking-wider">
Income
</p>
<p className="text-2xl font-bold text-green-600 mt-1">
<BlurredValue className="text-2xl font-bold text-green-600 mt-1 block">
+{formatCurrency(stats.totalIncome, displayCurrency)}
</p>
</BlurredValue>
</div>
<TrendingUp className="h-8 w-8 text-green-600 opacity-50" />
</div>
@@ -427,9 +428,9 @@ export default function TransactionsTable() {
<p className="text-xs text-muted-foreground uppercase tracking-wider">
Expenses
</p>
<p className="text-2xl font-bold text-red-600 mt-1">
<BlurredValue className="text-2xl font-bold text-red-600 mt-1 block">
-{formatCurrency(stats.totalExpenses, displayCurrency)}
</p>
</BlurredValue>
</div>
<TrendingDown className="h-8 w-8 text-red-600 opacity-50" />
</div>
@@ -441,14 +442,14 @@ export default function TransactionsTable() {
<p className="text-xs text-muted-foreground uppercase tracking-wider">
Net Change
</p>
<p
className={`text-2xl font-bold mt-1 ${
<BlurredValue
className={`text-2xl font-bold mt-1 block ${
stats.netChange >= 0 ? "text-green-600" : "text-red-600"
}`}
>
{stats.netChange >= 0 ? "+" : ""}
{formatCurrency(stats.netChange, displayCurrency)}
</p>
</BlurredValue>
</div>
{stats.netChange >= 0 ? (
<TrendingUp className="h-8 w-8 text-green-600 opacity-50" />

View File

@@ -32,22 +32,19 @@ export function FilterBar({
className,
}: FilterBarProps) {
const searchInputRef = useRef<HTMLInputElement>(null);
const cursorPositionRef = useRef<number | null>(null);
// Maintain focus on search input during re-renders
// Maintain focus and cursor position on search input during re-renders
useEffect(() => {
const currentInput = searchInputRef.current;
if (!currentInput) return;
// Only restore focus if the search input had focus before
const wasFocused = document.activeElement === currentInput;
// Use requestAnimationFrame to restore focus after React finishes rendering
if (wasFocused && document.activeElement !== currentInput) {
requestAnimationFrame(() => {
currentInput.focus();
});
// Restore focus and cursor position after data fetches complete
if (cursorPositionRef.current !== null && document.activeElement !== currentInput) {
currentInput.focus();
currentInput.setSelectionRange(cursorPositionRef.current, cursorPositionRef.current);
}
}, [isSearchLoading]);
});
const hasActiveFilters =
filterState.searchTerm ||
@@ -83,7 +80,16 @@ export function FilterBar({
ref={searchInputRef}
placeholder="Search transactions..."
value={filterState.searchTerm}
onChange={(e) => onFilterChange("searchTerm", e.target.value)}
onChange={(e) => {
cursorPositionRef.current = e.target.selectionStart;
onFilterChange("searchTerm", e.target.value);
}}
onFocus={() => {
cursorPositionRef.current = searchInputRef.current?.selectionStart ?? null;
}}
onBlur={() => {
cursorPositionRef.current = null;
}}
className="pl-9 pr-8 bg-background"
/>
{isSearchLoading && (
@@ -122,7 +128,16 @@ export function FilterBar({
ref={searchInputRef}
placeholder="Search..."
value={filterState.searchTerm}
onChange={(e) => onFilterChange("searchTerm", e.target.value)}
onChange={(e) => {
cursorPositionRef.current = e.target.selectionStart;
onFilterChange("searchTerm", e.target.value);
}}
onFocus={() => {
cursorPositionRef.current = searchInputRef.current?.selectionStart ?? null;
}}
onBlur={() => {
cursorPositionRef.current = null;
}}
className="pl-9 pr-8 bg-background w-full"
/>
{isSearchLoading && (