feat(dashboard): add Budget Summary Card with real data (#472)#710
Conversation
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>
steilerDev
left a comment
There was a problem hiding this comment.
[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.
steilerDev
left a comment
There was a problem hiding this comment.
[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.cssused 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
.jsextensions
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 interfaceBudgetHealthIndicator: remainingVsProjectedMax, availableFunds — correct props
DashboardPage integration: Clean integration:
- New
budgetOverviewstate extracted from existingPromise.allSettledresult — 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.
steilerDev
left a comment
There was a problem hiding this comment.
[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 usevar(--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:
.remainingGreen→var(--color-success)(emerald-400 in dark mode — ~5:1 contrast on dark surfaces, passes WCAG AA).remainingYellow→var(--color-warning)(orange-300 in dark mode).remainingRed→var(--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-visiblecorrectly usesoutline: none+box-shadow: var(--shadow-focus)— matches the established pattern across the codebase- Transition:
transition: opacity var(--transition-normal)— specifies a single property (nottransition: 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.
steilerDev
left a comment
There was a problem hiding this comment.
[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, notqa-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-computedremainingVsActualCost / availableFunds * 100— the values should be equivalent, but using the derived field would be more consistent with how the overview data is structured.
fcac870 to
80c55a0
Compare
|
🎉 This PR is included in version 1.15.0-beta.4 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.15.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
BudgetSummaryCardcomponent displaying available funds, budget bar, health indicator, planned cost range, actual spend, subsidy impact, and color-coded remaining percentageDashboardPageby storingBudgetOverviewstate from parallel data fetch and conditionally rendering the cardFixes #472
Test plan
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com