Skip to content

MPDX-9943 - Fix geographic multiplier clearing to save on blur - #1987

Open
wjames111 wants to merge 11 commits into
mainfrom
MPDX-9943-pds-geographic-multiplier
Open

MPDX-9943 - Fix geographic multiplier clearing to save on blur#1987
wjames111 wants to merge 11 commits into
mainfrom
MPDX-9943-pds-geographic-multiplier

Conversation

@wjames111

@wjames111 wjames111 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

  • Fixes the PDS Goal Calculator's Geographic Multiplier autocomplete firing an extra UpdatePdsGoalCalculation mutation when the field is backspaced to empty: MUI's Autocomplete fires onChange(null, 'clear') the moment the typed input becomes empty, which saved geographicLocation: null mid-edit and reset the visible field to "None" while the user was still typing. Saves with the 'clear' reason are now deferred, and null is saved from the field's blur instead — so clearing only mutates once the user clicks out.

  • Applies the same fix to the MPD Goal Calculator's Geographic Location autocomplete (InformationCategoryPersonalForm), which had the identical latent bug.

  • Replaces the PDS helper text "If not applicable, select "None"" with the MPD calculator's explanatory copy ("Do you live within 50 miles of one of these major cities?") — the old text instructed users to do something the field already does by default, since an unset value displays as "None".

  • Makes the geographic location optional with a "None" default across all five tools that render the constants dropdown:

    • Guarantees a leading None (0 multiplier) option in goalGeographicConstantMap, so every dropdown offers it even when a curated year dataset omits the row (the 2026 data currently does — see the API PR below).
    • The MPD Goal Calculator, NS Goal Calculator, Salary Calculator, and NSO questionnaire now display "None" when no location is saved, matching PDS.
    • The NSO geographic question is no longer required (validation + step completion), since an unanswered question now displays and calculates as "None". Explicitly selecting "None" still saves it.
    • The Salary Calculator's AutosaveAutocomplete gets the same defer-clear-to-blur autosave treatment, since the "None" default would otherwise expose the same mid-typing reset bug there.

Related Jira ticket: MPDX-9943
API counterpart (restores the missing "None" constant row): https://github.com/CruGlobal/mpdx_api/pull/3547

Testing

  • Go to hrTools/pdsGoalCalculator, open a goal, and go to the Setup step
  • Set the Geographic Multiplier to a location (e.g. Orlando, FL)
  • Backspace the field to empty and keep it focused — check that no mutation fires (network tab) and the field stays empty instead of snapping back to "None"
  • Click out of the field — check that a single mutation fires with geographicLocation: null and the field then shows "None"
  • Repeat the same steps on the MPD Goal Calculator's settings (Information → Geographic Location), which now behaves the same way
  • Check that selecting a location from the dropdown still saves immediately
  • Check that "None" appears as a dropdown option in all five tools (PDS, MPD, and NS goal calculators, Salary Calculator, NSO questionnaire) and that an unset location displays as "None"
  • In the NSO questionnaire, check that the Ministry Information step completes without answering the geographic question

Checklist:

  • I have given my PR a title with the format "MPDX-(JIRA#) (summary sentence max 80 chars)"
  • I have applied the appropriate labels (Add the label "Preview" to automatically create a preview environment)
  • I have run the Claude Code /quality:agent-review command locally and fixed any relevant suggestions
  • I have requested a review from another person on the project
  • I have tested my changes in preview or in staging
  • I have cleaned up my commit history

🤖 Generated with Claude Code

wjames111 and others added 3 commits August 13, 2026 15:28
Emptying the autocomplete input fires onChange(null, 'clear') while the
user is still typing, which saved geographicLocation: null immediately
and reset the visible field to "None" mid-edit. Defer the null save to
blur so clearing the field only mutates once the user clicks out.

MPDX-9943

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"If not applicable, select \"None\"" instructed users to do what the
field already does by default. Use the generic Goal Calculator's
explanatory helper text instead.

MPDX-9943

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MUI's Autocomplete fires onChange(null, 'clear') the moment the typed
input becomes empty, so backspacing the Geographic Location field to
empty saved geographicLocation: null mid-edit and reset the field.
Skip saves when the change reason is 'clear' and save null from the
TextField's blur instead, matching the MPDX-9943 fix in the PDS goal
calculator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Bundle sizes [mpdx-react]

Compared against 8b6c94e

No significant changes found

- Guarantee a leading 'None' (0 multiplier) option in
  goalGeographicConstantMap so every geographic dropdown offers it even
  when a curated year dataset omits the row (as the 2026 data does)
- Display 'None' when no location is saved in the MPD Goal Calculator,
  NS Goal Calculator, Salary Calculator, and NSO questionnaire, matching
  the existing PDS behavior
- Make the NSO geographic question optional: drop its required
  validation and remove it from Ministry step completion, since an
  unanswered question now displays and calculates as 'None'
- Give the Salary Calculator's AutosaveAutocomplete the same
  defer-clear-to-blur autosave treatment as the PDS and MPD fields,
  since the None default would otherwise expose the mid-typing reset

MPDX-9943

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@wjames111 wjames111 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AI Review (senior) — summary

No must-fix issues. Clear/blur autosave semantics, None display defaults, and the guaranteed None option are consistent across all five tools; None/null equivalence verified in every constants-map consumer and server-side (multiplier_for! yields ×1.0 for both). Findings below are quality-level and posted as individual comments:

  1. [Reuse] Defer-clear-to-blur block now triplicated — extract a shared component
  2. [Nice-to-have] AutosaveAutocomplete's blur-null behavior is generic — document or gate
  3. [Nice-to-have] Transient MUI dev warnings while constants load (MPD/NS)
  4. [Nice-to-have] NSO's placeholder row is inert once emptyValue is set
  5. [Nice-to-have] NS persists 'None' while other tools persist null — normalize eventually
  6. [Pattern] Hook-level None seeding is correctly placed — no change requested
  7. [Suggested tests] Guard the yup .required() removal directly

@wjames111

Copy link
Copy Markdown
Contributor Author

[Reuse] This clear-guard + blur-save block is now copied in three places (here, MPD InformationCategoryPersonalForm, Salary AutosaveAutocomplete). Consider extracting a shared DeferredClearAutocomplete that takes saveField.

(at src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx:290)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Nice-to-have] Empty-input-on-blur now saves null for ANY fieldName using this component. Fine for location; could 500 for a field whose null is invalid server-side. Consider a JSDoc note or an opt-in prop.

(at src/components/HrTools/SalaryCalculator/Autosave/AutosaveAutocomplete.tsx:55)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Nice-to-have] While constants load, value 'None' has no matching option and MUI logs a dev warning. Cosmetic only; could gate on the constants loading flag like NSO does.

(at src/components/HrTools/GoalCalculator/CalculatorSettings/Categories/InformationCategory/InformationCategoryForm/InformationCategoryPersonalForm.tsx:153)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Nice-to-have] With emptyValue set, the disabled placeholder MenuItem still renders as an inert row in the dropdown. Consider hiding the placeholder when emptyValue is provided.

(at src/components/HrTools/NsoMpdQuestionnaire/Shared/SelectQuestion.tsx:39)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Nice-to-have] NS now persists the string 'None' on save while PDS/MPD/Salary persist null unless None is explicitly picked. Server treats both as x1.0, but consider normalizing to one representation eventually.

(at src/components/HrTools/NsGoalCalculator/GoalSettings/goalSettingsApiMapping.ts:73)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Pattern] Seeding 'None' here is the right single point (all five dropdowns build from this map), and a server 'None' row still wins on value. No change requested.

(at src/hooks/useGoalCalculatorConstants.ts:56)

@wjames111

Copy link
Copy Markdown
Contributor Author

[Suggested tests] Consider one test that blurring the optional city question with no answer produces no validation error, guarding the yup .required() removal directly.

(at src/components/HrTools/NsoMpdQuestionnaire/MinistryInformation/MinistryDetails.test.tsx:110)

wjames111 and others added 7 commits August 17, 2026 09:26
The Salary Calculator and NSO questionnaire helper texts still instructed
users to "select None", which reads as a requirement. The field is
optional in every tool and defaults to "None" on its own, so tell users
they can leave it as-is instead.

MPDX-9943

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add JSDoc noting that clearing the input and blurring saves null, so the field's null must be valid server-side

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate the Geographic Location Autocomplete value and disabled state on constantsLoading so the 'None' fallback never renders against empty options and MUI's value-not-in-options dev warning cannot fire

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hide the disabled placeholder MenuItem when emptyValue is provided, since the select always displays a real option in that case

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Normalize a geographicLocation of 'None' to null on submit so NS goals persist null (the canonical no-multiplier form) instead of the string 'None'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add test that blurring the optional city question empty shows no validation error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract shared DeferredClearAutocomplete for the clear-guard + blur-save autocomplete pattern and rewire all three call sites

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in ae2ab22: [Reuse] Extracted shared HrTools/Shared/DeferredClearAutocomplete (clear-reason skip + empty-input blur-save) and rewired PDS SetupStep, MPD InformationCategoryPersonalForm, and Salary AutosaveAutocomplete to use it, with a focused test file. Posted by Claude Code via /agent-fix.

@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 94d70c7: [Nice-to-have] Added JSDoc on AutosaveAutocomplete warning that clearing + blurring saves null for fieldName, so it should only wrap fields whose null is valid server-side. Posted by Claude Code via /agent-fix.

@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in acb6957: [Nice-to-have] Gated the MPD Geographic Location value on the constants loading flag so the 'None' fallback never renders against empty options (no more MUI dev warning). Posted by Claude Code via /agent-fix.

@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 647f4ee: [Nice-to-have] SelectQuestion now hides the disabled placeholder MenuItem when emptyValue is set, with a test. Posted by Claude Code via /agent-fix.

@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 7b353e6: [Nice-to-have] NS formValuesToAttributes now normalizes 'None' to null on submit so NS goals persist null as the canonical no-multiplier form, with a test. Posted by Claude Code via /agent-fix.

@wjames111

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 7efeed0: [Suggested tests] Added a MinistryDetails test that blurring the optional city question with no answer shows no validation error, guarding the .required() removal. Posted by Claude Code via /agent-fix.

@wjames111
wjames111 requested a review from canac August 18, 2026 18:19
@wjames111 wjames111 self-assigned this Aug 18, 2026
@wjames111 wjames111 added Preview Environment Add this label to create an Amplify Preview On Staging Will be merged to the staging branch by Github Actions labels Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@canac

canac commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Playing devil's advocate, if we make None an option in the list, should we remove the clearable button?

If that's an option, this seems to achieve the same thing on PDS calculator with a lot less code. I might be missing something of why your solution is better.

diff --git a/src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx b/src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx
index 0e690a201..f5fba2d73 100644
--- a/src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx
+++ b/src/components/HrTools/PdsGoalCalculator/Setup/SetupStep.tsx
@@ -281,9 +281,10 @@ export const SetupStep: React.FC = () => {
           <Grid size={12}>
             <Autocomplete
               options={locations}
+              disableClearable
               getOptionLabel={getLocationLabel}
               value={calculation?.geographicLocation ?? 'None'}
-              onChange={(_, newValue: string | null) =>
+              onChange={(_, newValue) =>
                 saveField({ geographicLocation: newValue })
               }
               disabled={!calculation}
diff --git a/src/hooks/useGoalCalculatorConstants.ts b/src/hooks/useGoalCalculatorConstants.ts
index f70c67477..995f75de6 100644
--- a/src/hooks/useGoalCalculatorConstants.ts
+++ b/src/hooks/useGoalCalculatorConstants.ts
@@ -48,6 +48,7 @@ export const formatConstants = (
   });
 
   const goalGeographicConstantMap: GoalGeographicConstantMap = new Map();
+  goalGeographicConstantMap.set('None', 0);
   constant?.mpdGoalGeographicConstants.forEach((constant) => {
     const { location, percentageMultiplier } = constant;
     goalGeographicConstantMap.set(location, percentageMultiplier);

@canac canac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks really good! I'm wondering if we could get away with a simpler approach by disabling the clear button. Did you consider that?

import React from 'react';
import { Autocomplete, TextField, TextFieldProps } from '@mui/material';

export interface DeferredClearAutocompleteProps {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we make this interface extend AutocompleteProps so that we can pass in any Autocomplete prop without having to list them here?

AutocompleteProps<string, false, false, false>,
'renderInput' | 'onChange' | 'value'
> {
export interface AutosaveAutocompleteProps {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Extending AutocompleteProps<string, false, false, false> or DeferredClearAutocomplete makes this component more flexible in the future.

),
),
// Optional: an unanswered question displays and calculates as "None"
geographicLocation: yup.string().nullable(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So we want the choice to default to None? I thought I remembered @zweatshirt advocating for forcing the user to make an explicit choice. I'm not opposed to this change, I just want to make sure we have consensus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

On Staging Will be merged to the staging branch by Github Actions Preview Environment Add this label to create an Amplify Preview

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants