Skip to content

feat(frontend/purchase-modal): full per-purchase breakdown + per-row skip checkboxes #320

@cristim

Description

@cristim

Summary

Expand the Purchase modal (the "Execute Purchase" confirmation that appears after selecting recommendations on the Recommendations page) to show full per-purchase detail — costs, savings, configuration, count — with per-row checkboxes so the user can opt out of individual purchases without leaving the modal.

Why

Today the modal confirms the selected purchases at a fairly summarized level. When a user has selected, say, 12 RIs across 4 cells (instance type × region × payment), they typically want to:

  1. Verify the breakdown by configuration (instance type, term, payment, count, upfront, monthly cost, savings) before clicking through.
  2. Drop one or two specific purchases that on second look they're not ready to commit to — without going back to the table to re-select.

Currently the modal flow forces an all-or-nothing decision: cancel → re-select → reopen. That's slow and error-prone, and users have asked for a finer-grained gate.

Scope

The modal renders a per-purchase table with these columns (all already available on LocalRecommendation and the fan-out bucket structure in frontend/src/recommendations.ts):

Column Source
☑ Include new — per-row checkbox, all checked by default
Account account_name / account_id
Region region
Service / Type provider-specific (instance type, RDS class, OpenSearch, etc.)
Term term
Payment payment
Count scaled count from the fan-out bucket
Upfront upfront_cost * count
Monthly cost monthly_cost * count
Effective monthly savings effectiveMonthlySavings(rec) * count
Effective % effectiveSavingsPct(rec)

Plus a totals row that updates live as checkboxes toggle:

  • Total selected purchases (count of checked rows × per-row count)
  • Total upfront
  • Total monthly cost
  • Total effective monthly savings
  • Average effective % (weighted by on-demand monthly)

UX requirements:

  • "Select all / Deselect all" checkbox in the header.
  • Disabling all rows disables the Execute Purchase primary button (with a tooltip "Select at least one purchase").
  • Modal is scrollable when the list is long (current modal grows past the viewport on big batches — fix as part of this).
  • Closing the modal still clears state via clearPurchaseModalRecommendations() (recommendations.ts L77).
  • The submitted set respects the unchecked exclusions — only checked rows are sent to the existing execute-purchase code path.

Implementation pointers

  • Modal markup is hand-rolled in frontend/src/recommendations.ts (search 'purchase-modal', ~L2177 / ~L2674) and the close path is in frontend/src/plans.ts (~L981).
  • The existing recommendations array already holds all the data needed; just render an additional layer.
  • Live totals: derive from the same effectiveMonthlySavings / effectiveSavingsPct helpers (L382 / L402) that the table uses, so the modal can't drift from the table's semantics.
  • The fan-out bucket structure (getFanOutBuckets) already groups purchases by configuration and carries count — reuse it for the per-config grouping rather than re-grouping from scratch.

Out of scope

  • Inline editing of count / payment / term in the modal (that's a bigger UX change; file separately if wanted).
  • Provider-specific deep-link previews (already covered by purchases-deeplink.ts).
  • Saved selections across sessions.

Acceptance criteria

  • Modal lists every selected purchase with the columns above.
  • Per-row checkbox + select-all/deselect-all in header.
  • Live totals row updates on checkbox toggle.
  • Execute Purchase disabled when zero rows selected.
  • Only checked rows are sent through the existing execute path.
  • Modal scrolls cleanly for long lists.
  • Existing close/clear behaviour preserved.
  • Unit tests covering: per-row exclusion, totals math, disabled-button state, all selected by default.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions