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

View File

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