Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Intermediate package/UI version bumps are grouped under the next published GitHu

## Unreleased

- Group Dashboard Needs review items under date headings, newest first, while preserving confirmation eligibility and grouped transfers/splits.

- Monochrome now uses a dedicated neutral light/dark palette for surfaces, controls and primary balance charts instead of a page-wide filter, retaining meaningful colors for income/expenses, gains/losses, statuses, forecasts and categories. Area charts follow the selected palette, with blue accents in Original and neutral accents in Monochrome. Red Filter also replaces its page filter with native red surfaces, controls and primary charts in light/dark modes, retaining the same meaningful financial/status and category colors.

- Refreshed cash-balance, account and portfolio history charts with EasyUI-style cards, smooth area curves, animated reveals and range pills. Dated value readouts and keyboard/touch inspection preserve native currencies, history gaps and privacy.
Expand Down
21 changes: 21 additions & 0 deletions client/src/components/dashboard-module/TransactionList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,27 @@ it('retains a manual conversion when a late market-rate request completes', asyn
})

describe('dashboard transaction presentation', () => {
it('groups review items by descending date while keeping same-day rows together and future drafts unconfirmable', () => {
renderTransfers({ transactions: [], upcomingTransactions: [
{ id: 'old', account_id: 'source', amount: -10, date: '2020-01-01', description: 'Old bill', pending_kind: 'upcoming' },
{ id: 'new', account_id: 'source', amount: -20, date: '2020-01-03', description: 'New bill', pending_kind: 'upcoming' },
{ id: 'same', account_id: 'source', amount: -30, date: '2020-01-03', description: 'Same-day draft', pending_kind: 'mcp_review' },
{ id: 'future', account_id: 'source', amount: -40, date: '2099-01-01', description: 'Future draft', pending_kind: 'mcp_review' },
] })
const queue = within(screen.getByRole('region', { name: 'Needs review' }))
expect(queue.getAllByRole('heading', { level: 4 }).map(heading => heading.textContent)).toEqual([
'Thu, Jan 1, 2099', 'Fri, Jan 3, 2020', 'Wed, Jan 1, 2020',
])
const sameDay = within(queue.getByRole('region', { name: 'Friday, Jan 3, 2020' }))
expect(sameDay.getByText('New bill')).toBeInTheDocument()
expect(sameDay.getByText('Same-day draft')).toBeInTheDocument()
expect(sameDay.getAllByRole('button', { name: /^Confirm/ })).toHaveLength(2)
const future = within(queue.getByRole('region', { name: 'Thursday, Jan 1, 2099' }))
expect(future.getByText('Future draft')).toBeInTheDocument()
expect(future.getByRole('button', { name: /^Confirm/ })).toBeDisabled()
expect(queue.queryByLabelText('Daily total')).not.toBeInTheDocument()
})

it('combines due items and MCP drafts, keeps future drafts unconfirmable, and resolves both transfer legs once', async () => {
const user = userEvent.setup()
vi.mocked(apiFetch).mockReset()
Expand Down
15 changes: 13 additions & 2 deletions client/src/components/dashboard-module/TransactionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,13 @@ export function TransactionList({
const values = cashFlowTransactions(items).map(convertCash)
return values.some(value => value === null) ? null : values.reduce<number>((sum, value) => sum + value!, 0)
}
const reviewTransactions = [...readyTransactions, ...mcpReviewTransactions].sort((a, b) => a.date.localeCompare(b.date))
const reviewTransactions = [...readyTransactions, ...mcpReviewTransactions].sort((a, b) => b.date.localeCompare(a.date))
const reviewDateGroups = reviewTransactions.reduce((groups, tx) => {
const items = groups.get(tx.date) ?? []
items.push(tx)
groups.set(tx.date, items)
return groups
}, new Map<string, PendingItem[]>())

// Preview only the review items currently shown that can be confirmed now.
// A transfer contributes both native legs, even if only one matches a filter.
Expand Down Expand Up @@ -1735,7 +1741,12 @@ export function TransactionList({
: <div className="space-y-4">
{reviewTransactions.length > 0 && <section aria-labelledby="review-heading" className="overflow-hidden rounded-xl border border-border">
<div className="flex items-center gap-2 px-4 py-3"><h3 id="review-heading" className="text-sm font-semibold">Needs review</h3><span className="rounded-full bg-secondary px-2 py-0.5 text-xs">{reviewTransactions.length}</span><span className="ml-auto hidden text-xs text-muted-foreground sm:block">Balances change only after confirmation</span></div>
{reviewTransactions.map(tx => renderPendingTransaction(tx, tx.date <= today))}
{Array.from(reviewDateGroups, ([date, items]) => <section key={date} aria-label={format(parseISO(date), 'EEEE, MMM d, yyyy')}>
<div className="flex flex-wrap items-baseline gap-x-4 gap-y-1 border-t border-border bg-secondary/30 px-3 py-2 text-xs text-muted-foreground sm:px-4">
<h4>{format(parseISO(date), 'EEE, MMM d, yyyy')}</h4>
</div>
{items.map(tx => renderPendingTransaction(tx, tx.date <= today))}
</section>)}
{confirmationBalancePreviews.length > 0 && <div role="region" aria-label="Confirmation balance preview" className="border-t border-border bg-background/40 px-4 py-3 text-xs text-muted-foreground"><p className="mb-1 font-medium">If all ready transactions are confirmed</p><div className="grid gap-y-1 sm:flex sm:flex-wrap sm:gap-x-4">{confirmationBalancePreviews.map(({ account, acceptedBalance }) => <p key={account.id}>{account.name}: {previewBalance(account.balance, account.id)} → {previewBalance(acceptedBalance, account.id)}</p>)}</div></div>}
</section>}
{futureTransactions.length > 0 && <section className="overflow-hidden rounded-xl border border-border">
Expand Down
Loading