You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(frontend/purchase-modal): full per-purchase breakdown + per-row skip checkboxes #320
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:
Verify the breakdown by configuration (instance type, term, payment, count, upfront, monthly cost, savings) before clicking through.
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):
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.
Modal lists every selected purchase with all specified columns — openPurchaseModal now builds a table with columns: Include, Account, Service / Type, Region, Count, Upfront, Monthly Cost, Eff. Savings, Eff. %, Term, Payment. All data sourced from LocalRecommendation fields; effectiveMonthlySavings/effectiveSavingsPct helpers reused (no re-derivation).
Per-row checkbox + select-all/deselect-all in header — .purchase-modal-row-include checkboxes wired per row; #purchase-modal-select-all header checkbox syncs all rows bidirectionally via change listeners.
Live totals row updates on checkbox toggle — updatePurchaseModalTotals() rebuilds the sticky <tfoot> row (count, upfront, monthly cost, effective savings, weighted effective %) on every per-row or select-all change.
Execute Purchase disabled when zero rows selected — updatePurchaseModalTotals sets execute-purchase-btn.disabled = true and adds a tooltip when checkedPurchaseIndices.size === 0; tested explicitly in the new suite.
Only checked rows sent through execute path — getPurchaseModalRecommendations() now filters currentPurchaseRecommendations by checkedPurchaseIndices; existing app.ts execute path unchanged.
Modal scrolls cleanly for long lists — .purchase-modal-commits { max-height: 55vh; overflow-y: auto } with sticky thead/tfoot added in styles/modals.css.
Existing close/clear behaviour preserved — clearPurchaseModalRecommendations() resets both currentPurchaseRecommendations and checkedPurchaseIndices/checkedPurchaseModalInitialised; no change to the close path in plans.ts.
Unit tests — 12 new tests + 1 updated test covering: all-selected default, per-row exclusion from submitted set, totals math, disabled-button state, re-enable on re-check, select-all deselect/reselect, and column headers.
Activity
feat(frontend/purchase-modal): full per-purchase breakdown + per-row …
cristim commentedon May 13, 2026
Verified resolved by PR #325 (merged 2026-05-06).
Evidence per acceptance criterion:
openPurchaseModalnow builds a table with columns: Include, Account, Service / Type, Region, Count, Upfront, Monthly Cost, Eff. Savings, Eff. %, Term, Payment. All data sourced fromLocalRecommendationfields;effectiveMonthlySavings/effectiveSavingsPcthelpers reused (no re-derivation)..purchase-modal-row-includecheckboxes wired per row;#purchase-modal-select-allheader checkbox syncs all rows bidirectionally viachangelisteners.updatePurchaseModalTotals()rebuilds the sticky<tfoot>row (count, upfront, monthly cost, effective savings, weighted effective %) on every per-row or select-all change.updatePurchaseModalTotalssetsexecute-purchase-btn.disabled = trueand adds a tooltip whencheckedPurchaseIndices.size === 0; tested explicitly in the new suite.getPurchaseModalRecommendations()now filterscurrentPurchaseRecommendationsbycheckedPurchaseIndices; existingapp.tsexecute path unchanged..purchase-modal-commits { max-height: 55vh; overflow-y: auto }with stickythead/tfootadded instyles/modals.css.clearPurchaseModalRecommendations()resets bothcurrentPurchaseRecommendationsandcheckedPurchaseIndices/checkedPurchaseModalInitialised; no change to the close path inplans.ts.Recommending closure.