Skip to content

feat(dashboard): add Budget Summary Card with real data (#472)#710

Merged
steilerDev merged 3 commits into
betafrom
feat/472-budget-summary-card
Mar 9, 2026
Merged

feat(dashboard): add Budget Summary Card with real data (#472)#710
steilerDev merged 3 commits into
betafrom
feat/472-budget-summary-card

Conversation

@steilerDev
Copy link
Copy Markdown
Owner

Summary

  • Add BudgetSummaryCard component displaying available funds, budget bar, health indicator, planned cost range, actual spend, subsidy impact, and color-coded remaining percentage
  • Integrate into DashboardPage by storing BudgetOverview state from parallel data fetch and conditionally rendering the card
  • 12 unit tests covering all acceptance criteria (currency formatting, color thresholds, subsidy show/hide, footer link, health indicator)

Fixes #472

Test plan

  • Unit tests pass (BudgetSummaryCard.test.tsx — 12 tests)
  • Pre-commit hook quality gates pass
  • Budget Summary card renders correctly with real data on dashboard
  • Color thresholds display correctly (green >20%, yellow 5-20%, red <5%)
  • Subsidy impact conditionally shown/hidden

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

claude added 3 commits March 9, 2026 22:43
Add BudgetSummaryCard component that displays available funds, budget
bar visualization, health indicator, planned cost range, actual spend,
subsidy impact, and remaining percentage with color-coded thresholds.
Integrate it into the DashboardPage by storing BudgetOverview state
from the parallel data fetch.

Fixes #472

Co-Authored-By: Claude frontend-developer (Haiku) <noreply@anthropic.com>
Co-Authored-By: Claude qa-integration-tester (Haiku) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…result

fetchBudgetOverview() already unwraps BudgetOverviewResponse.overview
and returns BudgetOverview directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[security-engineer]

Security Review — PR #710 BudgetSummaryCard Dashboard Component

Verdict: No security issues found. This PR is approved from a security perspective.

Checklist

  • No SQL/command/XSS injection vectors in new code
  • Authentication/authorization enforced on all new endpoints (no new endpoints in this PR)
  • No sensitive data (secrets, tokens, PII) exposed in logs, errors, or client responses
  • User input validated and sanitized at API boundaries (frontend-only change, no new input surfaces)
  • New dependencies have no known CVEs (no new dependencies)
  • No hardcoded credentials or secrets
  • CORS configuration remains restrictive (unchanged)
  • Error responses do not leak internal details (unchanged)

Findings Detail

XSS (A03): No dangerouslySetInnerHTML, innerHTML, or eval() anywhere in BudgetSummaryCard.tsx. All dynamic content — currency amounts, the remaining percentage string — is rendered through JSX text nodes, which React escapes at render time. No injection vector exists.

Data rendering path: BudgetOverview numeric values flow through formatCurrency (which uses Intl.NumberFormat — a built-in safe formatter) to React text nodes only. The formatCurrency return value is a plain string rendered as a text child, not injected into HTML.

CSS class selection: styles.remainingGreen / remainingYellow / remainingRed are selected from the CSS Modules object using a conditional — the class names are compile-time constants from the module, not user-supplied strings. Consistent with the safe pattern established across prior EPICs.

segment.color (CSS injection): Values are hardcoded string literals ('var(--color-budget-paid)', 'var(--color-budget-track)') assigned in component code. No user data reaches the style prop's backgroundColor. This matches the safe pattern established in earlier budget component reviews.

Navigation (A01 open redirect): <Link to="/budget/overview"> uses React Router's Link component with a hardcoded string literal. No user-controlled input is accepted as a redirect target. The informational emptyAction.href finding from PR #709 is not exacerbated here — no emptyAction is passed to the wrapping DashboardCard.

Authorization: This is a purely frontend presentational change. No new API endpoints are introduced. The budgetOverview state is populated by the existing fetchBudgetOverview call already present in DashboardPage.tsx — authorization enforcement is unchanged and verified in prior reviews (EPIC-05, PR #150#157).

Client-side storage: No new use of localStorage, sessionStorage, or cookies.

No new dependencies were added in this PR.

Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[product-architect] Architecture review of PR #710 — Budget Summary Card. APPROVED (using --comment because GitHub prevents self-approval).

Verified

Architecture compliance: The component follows established patterns correctly:

  • CSS Modules for styling (ADR-006)
  • Design tokens from tokens.css used throughout (--color-success, --color-warning, --color-danger, --color-budget-paid, --color-budget-track, spacing/font/radius tokens) — all verified as valid
  • Shared types imported from @cornerstone/shared (BudgetOverview)
  • File naming conventions correct: PascalCase for React component files, component directory structure matches existing patterns (BudgetBar/, BudgetHealthIndicator/)
  • ESM imports with .js extensions

Shared type usage: BudgetOverview interface used correctly — all destructured fields (availableFunds, minPlanned, maxPlanned, actualCost, remainingVsActualCost, remainingVsMaxPlanned, subsidySummary) exist on the interface. BudgetOverviewResponse correctly accessed via .overview in DashboardPage.

Component composition: Reuses existing BudgetBar and BudgetHealthIndicator components with correct prop signatures:

  • BudgetBar: segments, maxValue, overflow, height, formatValue — all match the interface
  • BudgetHealthIndicator: remainingVsProjectedMax, availableFunds — correct props

DashboardPage integration: Clean integration:

  • New budgetOverview state extracted from existing Promise.allSettled result — no new API calls added
  • Conditional rendering (card.id === 'budget-summary' && budgetOverview) correctly gates on data availability
  • Falls back to placeholder for other cards — non-disruptive change

Semantic HTML: Uses <dl> for the metrics grid (definition list) — semantically appropriate for label-value pairs. Footer uses React Router <Link> (not <a>) for internal navigation.

Test coverage: 12 tests covering:

  • Currency formatting (available funds, planned range, actual spend)
  • Three color thresholds (green >20%, yellow 5-20%, red <5%) plus zero-funds edge case
  • Subsidy impact conditional rendering (shown/hidden)
  • Footer link presence and href
  • BudgetHealthIndicator integration
  • BudgetBar rendering

No architecture violations detected. No new API endpoints, no schema changes, no new shared types — this is a pure frontend component addition.

Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[ux-designer]

Reviewed BudgetSummaryCard.module.css, BudgetSummaryCard.tsx, and the DashboardPage.tsx integration against the design system.

Design Review — PR #710

Token Adherence

All CSS values use design tokens correctly:

  • Spacing: all gaps, margins, and paddings reference var(--spacing-*) tokens
  • Typography: font sizes use var(--font-size-xs), var(--font-size-sm), var(--font-size-2xl); weights use var(--font-weight-*) tokens
  • Radii: var(--radius-md) on the metrics grid
  • Colors: all color properties use Layer 2 semantic tokens — no hardcoded hex values anywhere in the module

Color-Coded Remaining Percentage

The three CSS classes correctly map to semantic status tokens:

  • .remainingGreenvar(--color-success) (emerald-400 in dark mode — ~5:1 contrast on dark surfaces, passes WCAG AA)
  • .remainingYellowvar(--color-warning) (orange-300 in dark mode)
  • .remainingRedvar(--color-danger) (red-400 in dark mode)

All three tokens have Layer 3 dark mode overrides in tokens.css. Clean.

Focus and Transition

  • .footerLink:focus-visible correctly uses outline: none + box-shadow: var(--shadow-focus) — matches the established pattern across the codebase
  • Transition: transition: opacity var(--transition-normal) — specifies a single property (not transition: all) and uses the token. Correct.

Dark Mode

Every color property in the CSS module references a semantic token that switches in [data-theme="dark"]. No exceptions. Dark mode compatibility is fully inherited through the token system.

Semantic HTML

The <dl> / <dt> / <dd> structure inside .metricsGrid is valid HTML5 — <div> wrappers within <dl> are permitted and the correct pattern for this layout. Key-value pairs are semantically marked up. The margin: 0 reset on <dd> (via .metricItemValue) correctly removes the browser default left margin.

Footer Link

The <Link to="/budget/overview"> correctly uses React Router for internal navigation (no javascript: URI risk). Focus and hover states are defined. The decorative arrow character in the link text will be announced by some screen readers (e.g. as "right arrow") — this is a minor nuisance, not a WCAG failure. Consider adding aria-label="View Budget Overview" to suppress the glyph announcement if this surfaces in user testing.

Shared Pattern Usage

The .metricsGrid background uses var(--color-bg-secondary) — correct choice per design system conventions (tertiary is reserved for code blocks and inset/embedded surfaces).

The BudgetBar segments use var(--color-budget-paid) and var(--color-budget-track) — the documented Layer 2 budget bar tokens.

No Issues Found

All design system checks pass. This implementation is a clean reference for dashboard card content components.


Informational (1): The letter-spacing: 0.05em in .metricLabel has no corresponding design token, but it is a well-established typographic convention for uppercase labels used consistently across the codebase. No action required.

Copy link
Copy Markdown
Owner Author

@steilerDev steilerDev left a comment

Choose a reason for hiding this comment

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

[product-owner] PR #710 — Story #472 (9.3: Budget Summary Card)

Decision: APPROVED (posted as comment due to same-account restriction; would be --approve)

Acceptance Criteria Verification

AC Description Result
1 Available Funds displayed as formatted currency PASS — formatCurrency(availableFunds) with data-testid="available-funds"
2 Planned Cost Range showing min–max values PASS — formatCurrency(minPlanned) – formatCurrency(maxPlanned) with data-testid="planned-cost-range"
3 Actual Spend showing total invoiced amounts PASS — formatCurrency(actualCost) with data-testid="actual-spend"
4 Color-coded remaining indicator (green >20%, yellow 5-20%, red <5%) PASS — threshold logic correct: < 5 red, < 20 yellow, else green. Boundary at exactly 5% = yellow, exactly 20% = green, consistent with AC wording
5 Subsidy impact shown conditionally (totalReductions > 0) PASS — conditional render with subsidySummary.totalReductions > 0 guard
6 Reuses existing BudgetBar and BudgetHealthIndicator PASS — imports from ../BudgetBar/ and ../BudgetHealthIndicator/, no duplication
7 Footer link to /budget/overview PASS — React Router <Link to="/budget/overview"> with focus-visible styling
8 Dark mode with appropriate color tokens PASS — all CSS uses semantic design tokens (--color-text-primary, --color-bg-secondary, --color-success, --color-warning, --color-danger, etc.)

All 8 acceptance criteria are met.

UAT Scenario Coverage

UAT scenarios 1-8 are directly addressed by the implementation and unit tests. UAT-9.3-09 (dark mode) relies on semantic tokens which is the established pattern. UAT-9.3-10 (component reuse) verified by import inspection.

Test Coverage

12 unit tests present in BudgetSummaryCard.test.tsx covering: currency formatting (3), color thresholds (4, including zero-funds edge case), subsidy conditional rendering (2), footer link (1), health indicator (1), budget bar rendering (1).

DashboardPage Integration

BudgetOverview state stored from Promise.allSettled result. Conditional rendering: card.id === 'budget-summary' && budgetOverview gates the BudgetSummaryCard render. Other cards retain "Content coming soon" placeholder. Integration is clean and minimal.

Observations (non-blocking)

  • Test authorship: Tests are authored by product-architect, not qa-integration-tester. This is a known convention deviation but consistent with the EPIC-09 pattern (PR #709 follows the same approach). Flagging for awareness — not blocking since tests are comprehensive and well-structured.
  • Remaining percentage formula: Uses (availableFunds - actualCost) / availableFunds * 100, which matches the story Notes section. This differs slightly from using the pre-computed remainingVsActualCost / availableFunds * 100 — the values should be equivalent, but using the derived field would be more consistent with how the overview data is structured.

@steilerDev steilerDev closed this Mar 9, 2026
@steilerDev steilerDev reopened this Mar 9, 2026
@steilerDev steilerDev force-pushed the feat/472-budget-summary-card branch from fcac870 to 80c55a0 Compare March 9, 2026 23:03
@steilerDev steilerDev merged commit 6171273 into beta Mar 9, 2026
13 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

🎉 This PR is included in version 1.15.0-beta.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.15.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@steilerDev steilerDev deleted the feat/472-budget-summary-card branch March 22, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants