fix: white flash during page transition
Frontend CI / build (push) Successful in 3m20s

feat: account always sorted by name

feat: categories in budgets always sorted
This commit is contained in:
2026-08-01 20:09:30 -03:00
parent d8d5d92b7f
commit 71b28daa18
6 changed files with 53 additions and 8 deletions
+7
View File
@@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Budgets now survives months and years, allowing to set a budget for a category and have it applied to the next months.
- `.state-figure` now changes its size on mobile.
- Date format in the credit-bills page from 2026-08-1 to Aug 1.
- Categories in budgets page are now sorted alphabetically.
- All account dropdowns to display sorted accounts.
### Fixed
- Bug making transactions change place after editing.
- Bug making the page flash a white screen during page transition.
---
+3 -1
View File
@@ -39,7 +39,9 @@ export default function Budgets() {
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: CategoriesApi.list });
const categories = [...(categoriesQuery.data || [])].sort((a, b) => a.name.localeCompare(b.name));
const budgets = budgetsQuery.data || [];
const budgets = [...(budgetsQuery.data || [])].sort((a, b) =>
(a.category?.name || '').localeCompare(b.category?.name || '')
);
const spendByCategory = Object.fromEntries(
(spendQuery.data || []).map((s) => [s.categoryId, Number(s.spent)])
);
+10 -2
View File
@@ -92,7 +92,10 @@ export default function CardBills() {
const { sentinelRef, progress, isStuck } = useStickyHeader();
const accountsQuery = useQuery({ queryKey: ['accounts'], queryFn: AccountsApi.list });
const accounts = useMemo(() => accountsQuery.data || [], [accountsQuery.data]);
const accounts = useMemo(
() => [...(accountsQuery.data || [])].sort((a, b) => a.name.localeCompare(b.name)),
[accountsQuery.data]
);
const allCards = useMemo(() => accounts.filter((a) => a.kind === 'CREDIT_CARD'), [accounts]);
const eligibleCards = useMemo(() => allCards.filter((c) => c.dueDayOfMonth != null), [allCards]);
@@ -792,7 +795,12 @@ export default function CardBills() {
<div className="txn-meta">
<span>{catName || 'Uncategorized'}</span>
<span className="dot-sep" />
<span>{t.occurredOn}</span>
<span className="txn-date">
{parseLocalDate(t.occurredOn).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})}
</span>
</div>
</div>
<div className="txn-right">
+4 -1
View File
@@ -33,7 +33,10 @@ export default function Import() {
const fileInputRef = useRef(null);
const accountsQuery = useQuery({ queryKey: ['accounts'], queryFn: AccountsApi.list });
const accounts = useMemo(() => accountsQuery.data || [], [accountsQuery.data]);
const accounts = useMemo(
() => [...(accountsQuery.data || [])].sort((a, b) => a.name.localeCompare(b.name)),
[accountsQuery.data]
);
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: CategoriesApi.list });
const categories = useMemo(
() => [...(categoriesQuery.data || [])].sort((a, b) => a.name.localeCompare(b.name)),
+14 -3
View File
@@ -269,7 +269,10 @@ export default function Transactions() {
const accountsQuery = useQuery({ queryKey: ['accounts'], queryFn: AccountsApi.list });
// Credit cards are handled entirely on the dedicated Card Bills page.
const accounts = useMemo(
() => (accountsQuery.data || []).filter((a) => a.kind !== 'CREDIT_CARD'),
() =>
(accountsQuery.data || [])
.filter((a) => a.kind !== 'CREDIT_CARD')
.sort((a, b) => a.name.localeCompare(b.name)),
[accountsQuery.data]
);
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: CategoriesApi.list });
@@ -535,7 +538,11 @@ export default function Transactions() {
.filter((t) => !filterCategoryId || String(t.category?.id) === filterCategoryId)
.filter((t) => !start || parseLocalDate(t.occurredOn) >= start)
.filter((t) => !end || parseLocalDate(t.occurredOn) <= end)
.sort((a, b) => parseLocalDate(b.occurredOn) - parseLocalDate(a.occurredOn));
.sort((a, b) => {
const dateDiff = parseLocalDate(b.occurredOn) - parseLocalDate(a.occurredOn);
if (dateDiff !== 0) return dateDiff;
return b.id - a.id;
});
}, [txnQueries, search, filterAccountId, filterCategoryId, filterRange, filterStartDate, filterEndDate, monthOffset]);
const grouped = useMemo(() => {
@@ -557,7 +564,11 @@ export default function Transactions() {
.filter((t) => !filterAccountId || String(t.account?.id) === filterAccountId)
.filter((t) => !start || parseLocalDate(t.occurredOn) >= start)
.filter((t) => !end || parseLocalDate(t.occurredOn) <= end)
.sort((a, b) => parseLocalDate(b.occurredOn) - parseLocalDate(a.occurredOn));
.sort((a, b) => {
const dateDiff = parseLocalDate(b.occurredOn) - parseLocalDate(a.occurredOn);
if (dateDiff !== 0) return dateDiff;
return b.id - a.id;
});
}, [txnQueries, filterAccountId, filterRange, filterStartDate, filterEndDate, monthOffset]);
const monthlyCredits = monthlyFlat.reduce(
+15 -1
View File
@@ -1,13 +1,23 @@
@import './tokens';
@import 'bootstrap/scss/bootstrap';
html,
body {
background: var(--bg);
background: #0e1116;
color: var(--text);
font-size: 14px;
-webkit-font-smoothing: antialiased;
}
#root {
view-transition-name: root;
}
::view-transition-old(root),
::view-transition-new(root) {
background: #0e1116;
}
.serif {
font-family: 'Fraunces', Georgia, serif;
}
@@ -445,6 +455,10 @@ body {
align-items: center;
gap: 6px;
}
.txn-meta .txn-date {
color: var(--text);
font-size: 13px;
}
.txn-meta .dot-sep {
width: 2px;
height: 2px;