feat: blocker modal conditional messages#4795
Conversation
…om/HHS/OPRE-OPS into OPS-4763/Update-save-changes-modal
…om/HHS/OPRE-OPS into OPS-4763/Update-save-changes-modal
…om/HHS/OPRE-OPS into OPS-4763/Update-save-changes-modal
There was a problem hiding this comment.
Pull request overview
This pull request implements conditional messaging in the save & exit modal to notify users when their changes require Division Director approval. The modal now displays different messages and button text based on whether budget line items have financial snapshot changes (changes to amounts in planned BLIs) that require approval.
- Added conditional modal content based on whether changes require Division Director approval
- Introduced a new
handleFinancialSnapshotChangesViaBlockerfunction to handle approval workflow when saving via the navigation blocker - Lifted
hasUnsavedChangesstate management fromServicesComponentshook to parent components for better state coordination
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
SaveChangesAndExitModal.jsx |
Added inline style adjustment (width: 101%) to modal content container |
ServicesComponents.jsx |
Added setHasUnsavedChanges and hasUnsavedChanges props to support parent state management |
ServicesComponents.hooks.js |
Removed local hasUnsavedChanges state, now managed by parent component |
CreateBLIsAndSCs.jsx |
Passed setHasUnsavedChanges and hasUnsavedChanges props to ServicesComponents, removed redundant state reset |
CreateBLIsAndSCs.hooks.js |
Added conditional modal content logic, new blocker handler function, and reorganized blocker useEffect to show appropriate messages based on financial snapshot changes |
saveChangesToEdits.cy.js |
Added comprehensive e2e test for approval workflow via blocker modal, updated test assertions to verify correct modal descriptions |
budgetChangeRequest.cy.js |
Removed unnecessary alert close action in test flow |
Comments suppressed due to low confidence (1)
frontend/src/components/BudgetLineItems/CreateBLIsAndSCs/CreateBLIsAndSCs.hooks.js:924
- When
savedViaModalis true and there are financial snapshot changes, bothhandleFinancialSnapshotChangesViaBlocker(line 913) andshowSuccessMessage(line 924) will callsetAlert, potentially causing duplicate alerts or one alert overwriting the other. The success message should only be displayed by one of these functions to avoid confusion.
if (isThereAnyBLIsFinancialSnapshotChanged && !isSuperUser && !savedViaModal) {
await handleFinancialSnapshotChanges(existingBudgetLineItemsWithIds);
} else if (isThereAnyBLIsFinancialSnapshotChanged && !isSuperUser && savedViaModal) {
await handleFinancialSnapshotChangesViaBlocker(existingBudgetLineItemsWithIds);
} else {
await handleRegularUpdates(existingBudgetLineItemsWithIds);
}
await handleDeletions();
}
suite.reset();
budgetFormSuite.reset();
datePickerSuite.reset();
resetForm();
setIsEditMode(false);
showSuccessMessage(isThereAnyBLIsFinancialSnapshotChanged, savedViaModal);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| React.useEffect(() => { | ||
| if (blocker.state === "blocked") { | ||
| setShowSaveChangesModal(true); | ||
| setModalProps({ | ||
| ...modalContent, | ||
| handleConfirm: async () => { | ||
| await handleSave(true); | ||
| setShowSaveChangesModal(false); | ||
| blocker.proceed(); | ||
| }, | ||
| handleSecondary: () => { | ||
| setHasUnsavedChanges(false); | ||
| setShowSaveChangesModal(false); | ||
| setIsEditMode(false); | ||
| blocker.proceed(); | ||
| }, | ||
| closeModal: () => { | ||
| blocker.reset(); | ||
| } | ||
| }); | ||
| } | ||
| }, [blocker, setModalProps, setIsEditMode]); |
There was a problem hiding this comment.
The useEffect dependency array is incomplete. The effect uses modalContent and handleSave but doesn't include them in the dependencies (only includes blocker, setModalProps, and setIsEditMode). This means if tempBudgetLines changes (which affects modalContent) or if handleSave is redefined, the modal will use stale values. The modal content could be incorrect, or the save handler might use outdated closures, potentially leading to incorrect behavior when the blocker is triggered.
| const hasFinancialSnapshotChanges = tempBudgetLines.some( | ||
| (tempBudgetLine) => tempBudgetLine.financialSnapshotChanged | ||
| ); | ||
|
|
||
| const modalContent = hasFinancialSnapshotChanges | ||
| ? { | ||
| heading: "Save changes before leaving?", | ||
| description: | ||
| "You have unsaved changes and some will require approval from your Division Director if you save. If you leave without saving, these changes will be lost.", | ||
| actionButtonText: "Save & Send to Approval", | ||
| secondaryButtonText: "Leave without saving" | ||
| } | ||
| : { | ||
| heading: "Save changes before leaving?", | ||
| description: "You have unsaved changes. If you leave without saving, these changes will be lost.", | ||
| actionButtonText: "Save", | ||
| secondaryButtonText: "Leave without saving" | ||
| }; |
There was a problem hiding this comment.
The modalContent object is computed on every render based on hasFinancialSnapshotChanges, which depends on tempBudgetLines. However, this computation happens outside the useEffect, meaning the modal content is evaluated immediately when the component renders, not when the blocker is triggered. If tempBudgetLines changes after the blocker is triggered but before the modal is shown, the modal content may not reflect the current state accurately.
| setAlert({ | ||
| type: "success", | ||
| heading: "Changes Sent to Approval", | ||
| message: | ||
| "Your changes have been successfully sent to your Division Director to review. Once approved, they will update on the agreement.", | ||
| redirectUrl: blocker.nextLocation?.pathname | ||
| }); |
There was a problem hiding this comment.
The handleFinancialSnapshotChangesViaBlocker function shows a generic success message without the detailed budget change messages that are shown in the regular handleFinancialSnapshotChanges flow and in showSuccessMessage. When users save via the blocker modal, they won't see the same detailed "Pending Changes" information that they would see when saving via the normal Save & Exit button, creating an inconsistent user experience.
seeing this linter warning |
|
Claude Code's review: PR Review: feat: blocker modal conditional messages Overview This PR enhances the save & exit modal to conditionally notify users when their changes to planned budget line items require Division Director approval. The implementation includes comprehensive E2E tests and updates to the approval workflow. Critical Issues
In CreateBLIsAndSCs.hooks.js around lines 907-924, there's a potential for duplicate alerts: if (isThereAnyBLIsFinancialSnapshotChanged && !isSuperUser && savedViaModal) { Both handleFinancialSnapshotChangesViaBlocker and showSuccessMessage set alerts. When savedViaModal=true with financial snapshot changes, you'll get two competing alerts. Fix: Skip showSuccessMessage when the blocker handler was used, or have showSuccessMessage handle the blocker case specially.
The blocker useEffect at line ~978 is missing dependencies: React.useEffect(() => { This will cause stale closure bugs. All referenced variables should be in the dependency array, or use useCallback to stabilize the handlers.
In SaveChangesAndExitModal.jsx:95: Why 101%? This inline style suggests a layout bug being masked. This should be investigated and fixed properly with CSS. Code Quality Issues
Lines 823-945 in CreateBLIsAndSCs.hooks.js show large indentation changes that make the diff harder to review. These appear to be unintentional reformatting. Consider reverting to minimize diff noise.
Pick one terminology and stick with it for consistency. |
…om/HHS/OPRE-OPS into OPS-4763/Update-save-changes-modal
|
🎉 This PR is included in version 1.253.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
What changed
The save & exit modal notifies the user if their changes require Division Director approval.
Issue
#4763
How to test
Screenshots
Definition of Done Checklist