Skip to content

feat: dashboard widgets — net worth, budget status, recent transactions#19

Merged
kayodebristol merged 3 commits into
mainfrom
copilot/add-dashboard-widgets
Apr 4, 2026
Merged

feat: dashboard widgets — net worth, budget status, recent transactions#19
kayodebristol merged 3 commits into
mainfrom
copilot/add-dashboard-widgets

Conversation

Copilot AI commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Implements the three dashboard widgets declared in the financial-advisor plugin manifest but not yet created.

Widgets added

widgets/NetWorth.svelte

  • Assets (checking/savings/investment + positive credit balances) minus liabilities (negative credit balances)
  • Trend arrow (↑/↓) from current-month net income vs previous month; degrades gracefully to net worth sign or hidden when no transaction history

widgets/BudgetStatus.svelte

  • Joins fa-transactions + fa-transaction-inferences to compute debit spend per category for the current calendar month
  • Ranks all budgets by % of monthlyLimit used; renders top 3 with mini progress bars coloured via the existing budgetStatus() helper (green/amber/red)

widgets/RecentTransactions.svelte

  • Last 5 transactions sorted date-desc (tie-broken by importedAt)
  • Each row: date · truncated description · amount (±colour) · category badge · confidence badge (High/Med/Low) sourced from fa-transaction-inferences

Shared patterns

All three follow the existing Svelte 5 runes style ($state/$derived/onMount), use getPluginContext() for PluresDB access, show a pulse skeleton while loading, and show a descriptive empty-state when no data exists.

<!-- BudgetStatus top-3 derivation pattern -->
const topBudgets = $derived<BudgetRow[]>(
  (() => {
    const spend: Record<string, number> = {};
    for (const tx of transactions) {
      if (!tx.date.startsWith(currentPrefix) || tx.amount >= 0) continue;
      const cat = catMap.get(tx.id);
      if (cat) spend[cat] = (spend[cat] ?? 0) + Math.abs(tx.amount);
    }
    return budgets
      .map(b => ({ budget: b, actual: spend[b.category] ?? 0,
                   pct: spendPercent(b, spend[b.category] ?? 0),
                   status: budgetStatus(b, spend[b.category] ?? 0) }))
      .sort((a, b) => b.pct - a.pct)
      .slice(0, 3);
  })(),
);

Copilot AI changed the title [WIP] Implement dashboard widgets for net worth, budget status, and recent transactions feat: dashboard widgets — net worth, budget status, recent transactions Apr 4, 2026
Copilot AI requested a review from kayodebristol April 4, 2026 11:48
@kayodebristol kayodebristol marked this pull request as ready for review April 4, 2026 12:12
Copilot AI review requested due to automatic review settings April 4, 2026 12:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the three financial-advisor dashboard widgets referenced by the plugin’s dashboardWidgets registration, providing at-a-glance net worth, budget utilization, and recent activity.

Changes:

  • Implement NetWorth widget (accounts-based net worth + month-over-month trend indicator).
  • Implement BudgetStatus widget (current-month spend by category joined via inferences; renders top 3 budgets with progress bars).
  • Implement RecentTransactions widget (last 5 transactions with inferred category + confidence badges).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
plugins/financial-advisor/src/pages/widgets/NetWorth.svelte Loads accounts/transactions and renders a net worth summary with a trend indicator plus assets/liabilities breakdown.
plugins/financial-advisor/src/pages/widgets/BudgetStatus.svelte Loads budgets/transactions/inferences and renders the top 3 budgets by spend % with mini progress bars.
plugins/financial-advisor/src/pages/widgets/RecentTransactions.svelte Loads transactions/inferences and renders the 5 most recent transactions with category + confidence badges.

>
<div
class="progress-fill"
style:width="{row.pct}%"

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style:width="{row.pct}%" won't interpolate row.pct; it will be treated as a literal string, so the progress fill width won't reflect the percentage. Use either a normal style attribute with Svelte interpolation (e.g. style="width: {row.pct}%") or a style:width directive with an expression that includes units.

Suggested change
style:width="{row.pct}%"
style:width={`${row.pct}%`}

Copilot uses AI. Check for mistakes.
}
Promise.all([
ctx.data.collection<Transaction>(FA_TRANSACTIONS_COLLECTION).query(),
ctx.data.collection<TransactionInference>(FA_INFERENCES_COLLECTION).query(),

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inferences query loads the entire fa-transaction-inferences collection and then filters client-side. Since this widget only uses category inferences, scope the query to { field: 'category' } (as done in Reports.svelte) to reduce data transfer and improve load time on large datasets.

Suggested change
ctx.data.collection<TransactionInference>(FA_INFERENCES_COLLECTION).query(),
ctx.data
.collection<TransactionInference>(FA_INFERENCES_COLLECTION)
.query({ field: 'category' }),

Copilot uses AI. Check for mistakes.
}
Promise.all([
ctx.data.collection<Transaction>(FA_TRANSACTIONS_COLLECTION).query(),
ctx.data.collection<TransactionInference>(FA_INFERENCES_COLLECTION).query(),

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inferences query loads the entire fa-transaction-inferences collection and then filters client-side. Since this widget only uses category inferences, scope the query to { field: 'category' } to reduce data transfer and improve load time on large datasets (see Reports.svelte for the same pattern).

Suggested change
ctx.data.collection<TransactionInference>(FA_INFERENCES_COLLECTION).query(),
ctx
.data
.collection<TransactionInference>(FA_INFERENCES_COLLECTION)
.query({ field: 'category' }),

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +92
.catch(() => {
/* show empty state on error */

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch handler swallows load failures and then renders the same UI as the 'no budgets' empty state, which can mislead users and makes failures hard to debug. Consider calling ctx.notify.error(...) (or rendering a distinct error empty-state) when the Promise.all fails.

Suggested change
.catch(() => {
/* show empty state on error */
.catch((error) => {
ctx.notify?.error?.(
`Failed to load budget status data${error instanceof Error && error.message ? `: ${error.message}` : '.'}`,
);

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +57
.catch(() => {
/* show empty state on error */

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch handler swallows load failures and then renders the same UI as the 'no transactions yet' empty state, which can mislead users and makes failures hard to debug. Consider calling ctx.notify.error(...) (or rendering a distinct error empty-state) when the Promise.all fails.

Suggested change
.catch(() => {
/* show empty state on error */
.catch((error) => {
ctx.notify?.error?.('Failed to load recent transactions.');

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +85
.catch(() => {
/* show empty state on error */

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch handler swallows load failures and then renders the same UI as the 'no account data' empty state, which can mislead users and makes failures hard to debug. Consider calling ctx.notify.error(...) (or rendering a distinct error empty-state) when the Promise.all fails.

Suggested change
.catch(() => {
/* show empty state on error */
.catch((error) => {
ctx.notify?.error?.(
`Failed to load net worth data${error instanceof Error && error.message ? `: ${error.message}` : '.'}`,
);

Copilot uses AI. Check for mistakes.

@kayodebristol kayodebristol left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: CI green + code review complete.

@kayodebristol kayodebristol merged commit f7cfb1b into main Apr 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Dashboard widgets — net worth, budget status, recent transactions

3 participants