Skip to content

Conversation

@jsandoval81
Copy link
Collaborator

@jsandoval81 jsandoval81 commented Oct 15, 2025

Requirements List

  • yarn install --ignore-engines

Description List

  • Added a feature flag: ENCUMBER_MULTI_CATEGORY
  • Added a new InputSelectMultiple form input component
  • Updated LicenseCard & PrivilegeCard components to:
    • Support new NPDB multi-select behind a feature flag
    • Continue to support existing NPDB single-select while new feature is disabled
  • Updated encumbrance model / store / network modules
  • Updated CSS build config to suppress load order warnings - they aren't relevant to apps with our module / build structure
  • Updated tmp transitive dev dependency

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • Code review
  • Testing
    • The unencumber workflow now shows the Discipline Type rather than the NPDB Category
    • If the new feature flag is disabled:
      • The license & privilege encumber workflows should continue to work as they have, including the single-select NPDB category
    • If the new feature flag is enabled:
      • The license & privilege encumber workflows now allow multi-select of the NPDB categories
      • NOTE: The native multi-select input may look different between browser / OS combos. This is expected and preserves a high level of a11y by using the native input

Some notes regarding the new multi-select input and a11y:

Warning: The mechanism for selecting multiple non-contiguous items via the full a11y keyboard approach described below only works in certain browser / OS combos.

On macOS, the Ctrl + Up and Ctrl + Down shortcuts conflict with the OS default shortcuts for Mission Control / Spotlight Search / Application windows, so those will collide if not disabled.
  • Keyboard users can select multiple contiguous items by:
    • Focusing on the <select> element (e.g., using Tab).
    • Selecting an item at the top or bottom of the range they want to select using the Up and Down cursor keys to go up and down the options.
    • Holding down the Shift key and then using the Up and Down cursor keys to increase or decrease the range of items selected.
  • Keyboard users can select multiple non-contiguous items by:
    • Focusing on the <select> element (e.g., using Tab).
    • Holding down the Ctrl / Cmd key then using the Up and Down cursor keys to change the "focused" select option, i.e., the one that will be selected if you choose to do so. The "focused" select option is highlighted with an outline - in the same way as a keyboard-focused link.
    • Pressing Space to select / deselect "focused" select options.

Closes #1091

Summary by CodeRabbit

  • New Features

    • Feature-gated multi-select for NPDB categories in encumbrance forms (supports single or multiple categories); removable tokens and hover hint added; Style Guide example updated.
  • Tests

    • Unit tests added for the multi-select input and NPDB type handling.
  • Chores

    • Added locale strings for multi-select instructions and labels.
    • Build config adjusted to suppress CSS order warnings in production.

- Add the beginnings of an a11y multi-select
- @todo: Coordinate with design for the input UI
- @todo: Apply to the adverse action UI
- @todo: Consider using a feature flag
- Add npdb example to review w/ design team
- @todo: Coordinate with design for the input UI
- @todo: Apply to the adverse action UI
- @todo: Consider using a feature flag
- Added multi-select NPDB category to encumber UI
- Updated model / store / network modules
- Update unencumber UI to display encumbrance type rather than NPDB category
- Updated webpack config to suppress css load order warnings
- @todo: Make final UI updates once designs are complete
- @todo: Use a feature flag
- Added initial feature flag conditionals
- @todo: Update for any design changes once ready
- @todo: Wire up backend once ready
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 15, 2025

Walkthrough

Adds a feature-flagged multi-select NPDB category capability: new InputSelectMultiple component, UI and form model changes in LicenseCard/PrivilegeCard/ExampleForm, model and serializer updates, store and API signatures extended for npdbCategories, mock updates, locales, tests, and a small webpack CSS tweak.

Changes

Cohort / File(s) Summary
Feature gate config
webroot/src/app.config.ts
Added FeatureGates.ENCUMBER_MULTI_CATEGORY = 'encumbrance-multi-category-flag'.
New multi-select component
webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.vue, .../InputSelectMultiple.ts, .../InputSelectMultiple.less, .../InputSelectMultiple.spec.ts
New InputSelectMultiple Vue component (TS + Less) with removable tokens, accessibility attributes, helper methods (getValueDisplay, removeSelectedValue), styles, and a shallow-mount unit test.
License encumbrance UI/logic
webroot/src/components/LicenseCard/LicenseCard.ts, .../LicenseCard.vue
Exposes FeatureGates getter; conditional rendering and form fields switch between single npdbCategory and multi npdbCategories based on gate; encumbrance payload and populate/mock flows adjusted accordingly.
Privilege encumbrance UI/logic
webroot/src/components/PrivilegeCard/PrivilegeCard.ts, .../PrivilegeCard.vue
Same gate-aware conditional rendering and form/payload switching for NPDB categories as LicenseCard; exposes FeatureGates getter and preserves fallback behavior.
Example form demo
webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts, .../ExampleForm.vue
Registers InputSelectMultiple, adds demo multi-select states field and npdbCategoryOptions getter, and renders the multi-select in the template.
Locales
webroot/src/locales/en.json, webroot/src/locales/es.json
Added common.selectMultipleKeys and common.statesMultiple translation keys.
AdverseAction model & tests
webroot/src/models/AdverseAction/AdverseAction.model.ts, .../AdverseAction.model.spec.ts
Added npdbTypes array on model; serializer maps clinicalPrivilegeActionCategoriesnpdbTypes; added getNpdbTypeName(npdbType?: string); tests extended for defaults and mapping.
Public data API
webroot/src/network/data.api.ts
Updated encumberLicense and encumberPrivilege signatures to accept and forward npdbCategories: Array<string>.
License API + mocks
webroot/src/network/licenseApi/data.api.ts, webroot/src/network/mocks/mock.data.api.ts
Added npdbCategories parameter; runtime feature-gated payload construction: send clinicalPrivilegeActionCategories (array) when gate ON, otherwise clinicalPrivilegeActionCategory (single). Reads $features from Vue globalProperties in runtime code.
Store actions
webroot/src/store/users/users.actions.ts
Added npdbCategories to encumberLicenseRequest and encumberPrivilegeRequest and forward to dataApi calls.
Build config
webroot/vue.config.js
In production webpack chain, tap extract-css plugin to set ignoreOrder = true to suppress CSS order warnings.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant C as Card (License/Privilege)
  participant S as Store
  participant API as data.api
  participant LA as licenseApi
  participant SV as Server/Mock

  U->>C: Open encumber modal / select category(ies)
  note over C: Check FeatureGates.ENCUMBER_MULTI_CATEGORY
  alt Gate ON
    C->>C: Render InputSelectMultiple
    U->>C: Select multiple -> npdbCategories [...]
    C->>S: encumber...Request({ npdbCategories: [...] })
  else Gate OFF
    C->>C: Render InputSelect (single)
    U->>C: Select one -> npdbCategory "..."
    C->>S: encumber...Request({ npdbCategory: "..." })
  end

  S->>API: encumber...(…, npdbCategory, npdbCategories)
  API->>LA: encumber...(…, npdbCategory, npdbCategories)
  note over LA: Read $features at runtime to decide payload field
  alt Gate ON
    LA->>SV: POST { clinicalPrivilegeActionCategories: [...] }
  else Gate OFF
    LA->>SV: POST { clinicalPrivilegeActionCategory: "..." }
  end
  SV-->>LA: Response
  LA-->>API: Response
  API-->>S: Response
  S-->>C: Update UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Frontend/feature flags #1114 — modifies/extends FeatureGates enum and feature-flag usage; directly related to adding ENCUMBER_MULTI_CATEGORY.

Suggested reviewers

  • jlkravitz
  • isabeleliassen
  • carlsims
  • rmolinares

Poem

"A rabbit hopped into code so spry,
I planted flags beneath the sky.
Chips and options, many to pick,
One or many—flip that switch quick!
Hooray for multi-select delight 🐇✨"

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Frontend/adverse action NPDB multi select" is clear, concise, and directly related to the main changes in the pull request. It specifically identifies the key feature being implemented (NPDB multi-select for adverse actions in the frontend) and accurately represents the primary purpose of the changeset. The title is specific enough that a developer reviewing the git history would understand the significant change without being overly verbose.
Linked Issues Check ✅ Passed The code changes comprehensively address the primary objective from issue #1091 to implement frontend support for adverse-action NPDB multi-category selection. The PR adds the ENCUMBER_MULTI_CATEGORY feature gate, implements the new InputSelectMultiple component, updates LicenseCard and PrivilegeCard to conditionally support multi-select based on the gate, and modifies the encumbrance model, store, and network modules to handle both single and multi-category workflows. All acceptance criteria regarding UI implementation, backward compatibility, and data model updates are satisfied by the changes.
Description Check ✅ Passed The pull request description follows the repository template with all required sections properly filled out. The Requirements List specifies the installation command, the Description List provides detailed bullets covering the feature flag, new component, updated components, and CSS build config changes, and the Testing List includes the required yarn commands, testing scenarios based on feature flag state, and relevant accessibility notes. The description also includes a reference to the linked issue (#1091) at the end, making the connection explicit.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (12)
webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.spec.ts (1)

12-19: Remove unnecessary async on describe and assert multiple-select semantics

  • describe need not be async.
  • Add an assertion that the select has multiple enabled to verify core behavior.

Apply this diff:

-describe('InputSelectMultiple component', async () => {
+describe('InputSelectMultiple component', () => {
     it('should mount the component', async () => {
         const wrapper = await mountShallow(InputSelectMultiple);

         expect(wrapper.exists()).to.equal(true);
         expect(wrapper.findComponent(InputSelectMultiple).exists()).to.equal(true);
+        expect(wrapper.find('select[multiple]').exists()).to.equal(true);
     });
 });
webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less (1)

19-21: Select resize likely has no effect

resize on a native select is unsupported in most browsers. Consider setting a size attribute on the select or a custom control if vertical resizing is needed.

webroot/src/models/AdverseAction/AdverseAction.model.ts (1)

86-92: Deduplicate NPDB type name resolution

npdbTypeName and getNpdbTypeName duplicate lookup logic. Extract a single resolver and reuse to reduce drift.

Also applies to: 94-100

webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.vue (2)

24-34: Expose description to AT via aria-describedby

Give the helper text an id and reference it.

Apply this diff:

-        <div class="multi-select-description">{{ $t('common.selectMultipleKeys') }}</div>
+        <div
+            class="multi-select-description"
+            :id="`${formInput.id}-desc`"
+        >
+            {{ $t('common.selectMultipleKeys') }}
+        </div>
@@
-            :aria-describedby="`${formInput.id}-error`"
+            :aria-describedby="`${formInput.id}-desc ${formInput.id}-error`"

44-51: Use stable keys for options

Index keys are fragile. Use option.value.

Apply this diff:

-                :key="index"
+                :key="option.value"
webroot/src/store/users/users.actions.ts (1)

135-147: npdbCategories plumbed correctly; add safe defaults and types

Looks good. Consider defaulting to [] to avoid undefined and improve typing of the payload.

Apply this minimal change at both action payload destructures:

-        npdbCategories,
+        npdbCategories = [],

Also applies to: 233-245

webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts (1)

79-86: Option builder duplication; consider centralizing

npdbCategoryOptions mirrors logic in LicenseCard. Extract a shared helper to avoid divergence.

webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.ts (1)

32-37: Normalize display value (optional)

If templates expect strings, consider unref-ing ComputedRef names before returning.

-    getValueDisplay(value = ''): string | ComputedRef<string> {
+    getValueDisplay(value = ''): string {
         const selectedOption: SelectOption = this.formInput?.valueOptions?.find((option: SelectOption) =>
             option.value === value) || { value, name: '' };

-        return selectedOption?.name || '';
+        const name = selectedOption?.name ?? '';
+        return typeof name === 'object' && 'value' in name ? (name as ComputedRef<string>).value : String(name);
     }
webroot/src/components/LicenseCard/LicenseCard.ts (1)

458-460: Remove debug logging

Drop console.log before merge (or guard by env).

-            // @TODO
-            console.log(response);
+            // no-op
webroot/src/network/licenseApi/data.api.ts (1)

277-288: Avoid global window feature lookup in data layer (optional)

Prefer passing the gate decision from callers to keep network layer pure/testable.

-        const { $features } = (window as any).Vue?.config?.globalProperties || {};
-        ...($features?.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY) ? { ... } : { ... })
+        // Pass a boolean flag or infer from provided params:
+        const useMulti = Array.isArray(npdbCategories) && npdbCategories.length > 0;
+        ...(useMulti ? { clinicalPrivilegeActionCategories: npdbCategories } : { clinicalPrivilegeActionCategory: npdbCategory })
webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (1)

101-101: Consider testing the actual lookup behavior.

While this test validates the method signature with an optional category parameter, it appears to test a pass-through scenario (getNpdbTypeName('Other') returns 'Other'). Consider adding a test that validates the method's lookup logic when it needs to resolve a category from stored data or translations.

webroot/src/components/PrivilegeCard/PrivilegeCard.ts (1)

275-295: LGTM! Proper conditional form input initialization.

The feature-gated logic correctly initializes:

  • Multi-select mode: encumberModalNpdbCategories with array validation (Joi.array().min(1)) and empty array default
  • Single-select mode: encumberModalNpdbCategory with string validation (Joi.string().required())

The validation schemas and initial values are appropriate for each mode.

Optional refactor: Consider extracting the conditional form input creation to a helper method (e.g., createNpdbCategoryInput()) to improve testability and reduce the cognitive load of the initialization method.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 183cfbc and 03f56e9.

⛔ Files ignored due to path filters (1)
  • webroot/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (20)
  • webroot/src/app.config.ts (1 hunks)
  • webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.less (1 hunks)
  • webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.spec.ts (1 hunks)
  • webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.ts (1 hunks)
  • webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.vue (1 hunks)
  • webroot/src/components/LicenseCard/LicenseCard.ts (8 hunks)
  • webroot/src/components/LicenseCard/LicenseCard.vue (1 hunks)
  • webroot/src/components/PrivilegeCard/PrivilegeCard.ts (8 hunks)
  • webroot/src/components/PrivilegeCard/PrivilegeCard.vue (1 hunks)
  • webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts (5 hunks)
  • webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue (1 hunks)
  • webroot/src/locales/en.json (2 hunks)
  • webroot/src/locales/es.json (2 hunks)
  • webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (8 hunks)
  • webroot/src/models/AdverseAction/AdverseAction.model.ts (4 hunks)
  • webroot/src/network/data.api.ts (4 hunks)
  • webroot/src/network/licenseApi/data.api.ts (5 hunks)
  • webroot/src/network/mocks/mock.data.api.ts (4 hunks)
  • webroot/src/store/users/users.actions.ts (4 hunks)
  • webroot/vue.config.js (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-21T16:36:48.723Z
Learnt from: jsandoval81
PR: csg-org/CompactConnect#1019
File: webroot/src/components/PrivilegeCard/PrivilegeCard.vue:270-278
Timestamp: 2025-08-21T16:36:48.723Z
Learning: In PrivilegeCard component's unencumber modal (webroot/src/components/PrivilegeCard/PrivilegeCard.vue), the unencumber-select wrapper element intentionally uses space key handling to catch bubbled events from child InputCheckbox elements for custom handling actions. When adverseAction.hasEndDate() is true, items show an inactive-category div and are designed to be non-interactive (not focusable), while items without end dates contain focusable InputCheckbox child elements. This design pattern is consistent with prior implementation and represents intentional UX behavior.

Applied to files:

  • webroot/src/components/PrivilegeCard/PrivilegeCard.vue
  • webroot/src/components/LicenseCard/LicenseCard.vue
📚 Learning: 2025-06-24T00:02:39.944Z
Learnt from: jsandoval81
PR: csg-org/CompactConnect#873
File: webroot/src/components/LicenseCard/LicenseCard.ts:414-443
Timestamp: 2025-06-24T00:02:39.944Z
Learning: In LicenseCard component's clickUnencumberItem method (webroot/src/components/LicenseCard/LicenseCard.ts), complex event handling for checkbox interactions is intentionally designed to ensure consistent behavior across checkbox input, wrapper label, and outer selection parent elements for custom UI requirements. This complexity should be preserved rather than simplified.

Applied to files:

  • webroot/src/components/LicenseCard/LicenseCard.ts
🧬 Code graph analysis (5)
webroot/src/components/LicenseCard/LicenseCard.ts (1)
webroot/src/models/FormInput/FormInput.model.ts (1)
  • FormInput (58-168)
webroot/src/network/data.api.ts (1)
webroot/src/network/licenseApi/data.api.ts (1)
  • licenseDataApi (499-499)
webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts (1)
webroot/src/models/FormInput/FormInput.model.ts (1)
  • FormInput (58-168)
webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.spec.ts (1)
webroot/tests/helpers/setup.ts (1)
  • mountShallow (265-265)
webroot/src/components/PrivilegeCard/PrivilegeCard.ts (1)
webroot/src/models/FormInput/FormInput.model.ts (1)
  • FormInput (58-168)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CheckWebroot
🔇 Additional comments (26)
webroot/vue.config.js (1)

93-107: LGTM! Suppressing CSS order warnings is reasonable for code-split apps.

The ignoreOrder flag prevents noisy warnings from mini-css-extract-plugin in applications using code splitting/chunking, where CSS load order may vary but doesn't affect functionality. This is a standard practice.

webroot/src/app.config.ts (1)

292-292: LGTM! Feature gate addition follows the existing pattern.

The new ENCUMBER_MULTI_CATEGORY flag enables conditional multi-category NPDB support throughout the application.

webroot/src/network/mocks/mock.data.api.ts (1)

237-238: LGTM! Feature gate access pattern works but is unconventional.

The mock API accesses the feature gate via (window as any).Vue?.config?.globalProperties to determine payload shape at runtime. While this approach works and includes safe fallback via optional chaining, it's an unusual pattern. The conditional spread operator correctly includes either npdbCategories (when flag is enabled) or npdbCategory (when disabled).

Also applies to: 250-257

webroot/src/components/StyleGuide/ExampleForm/ExampleForm.vue (1)

21-21: LGTM! Demonstrates the new multi-select component in the style guide.

The InputSelectMultiple component is correctly integrated for demonstration purposes.

webroot/src/locales/es.json (1)

21-21: LGTM! Spanish translations for multi-select UI are appropriate.

The added keys selectMultipleKeys and statesMultiple provide proper Spanish translations for the multi-select functionality.

Also applies to: 73-73

webroot/src/locales/en.json (1)

21-21: LGTM! English translations clearly communicate multi-select usage.

The added keys provide clear instructions ("Hold Ctrl / Cmd key to select multiple") and appropriate labels for the multi-select UI.

Also applies to: 72-72

webroot/src/components/PrivilegeCard/PrivilegeCard.vue (1)

193-198: LGTM! Clean feature-gated UI switching between single and multi-select.

The conditional rendering correctly displays InputSelectMultiple for multi-category mode when the ENCUMBER_MULTI_CATEGORY feature flag is enabled, and falls back to the single InputSelect otherwise. This provides backward compatibility while enabling the new functionality.

webroot/src/components/LicenseCard/LicenseCard.vue (1)

133-137: Gate-driven switch looks good; verify validators, value types, and payloads

Confirm:

  • encumberModalNpdbCategories initializes to [] and has validators aligned to array values.
  • Hidden field’s validators don’t block submit (only the visible input should be required).
  • Store/network paths send npdbCategories vs npdbCategory per gate.
webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.vue (1)

25-31: Verify v-model array contract and validators

Ensure formInput.value is always an array ([]) for multi-select, and validators/transformers expect arrays. Also ensure tokens render correctly when value is empty.

Also applies to: 63-67

webroot/src/components/StyleGuide/ExampleForm/ExampleForm.ts (1)

136-143: Multi-select FormInput config LGTM

Validation, options, and default [] align with multi-select usage.

webroot/src/components/LicenseCard/LicenseCard.ts (4)

283-303: Gate-aware form inputs for NPDB categories: solid implementation

Good split between single vs multi with correct validations and options.

Ensure the template renders InputSelectMultiple when the gate is on and binds to encumberModalNpdbCategories.value (array).


444-451: Payload gating LGTM

Correctly selects npdbCategories vs npdbCategory based on the feature gate.


245-250: Placeholder option only in single-select mode: good

Prevents empty selection token in multi-select.


655-659: Date format in mockPopulate may not match Joi pattern

Joi validation uses MM/DD/YYYY, but mockPopulate sets YYYY-MM-DD. Align formats or relax validation.

-            this.formData.encumberModalStartDate.value = moment().format('YYYY-MM-DD');
+            this.formData.encumberModalStartDate.value = moment().format('MM/DD/YYYY');
webroot/src/network/licenseApi/data.api.ts (2)

277-288: Confirm required payload keys with backend
Model and specs map both clinicalPrivilegeActionCategory and clinicalPrivilegeActionCategories, but mocks only include the singular field. Verify via API docs that both keys are expected on the license and privilege encumbrance endpoints, and update mocks to include the plural field for multi-category scenarios. Applies to lines 277–288 and 364–375.


262-276: All encumberLicense and encumberPrivilege call sites updated
All callers now supply the new npdbCategories argument.

webroot/src/components/Forms/InputSelectMultiple/InputSelectMultiple.ts (1)

39-47: TypeScript compile error: assignment to a type assertion

Left-hand assignment uses a type assertion, which TS disallows. Also, tighten typing and mark input as edited.

Apply:

-    removeSelectedValue(value): void {
+    removeSelectedValue(value: string | number): void {
         const { formInput } = this;

         if (Array.isArray(formInput.value)) {
-            (formInput.value as Array<string>) = formInput.value.filter((selected) => selected !== value);
+            formInput.value = (formInput.value as Array<string | number>)
+                .filter((selected) => selected !== value);
         }

-        formInput.validate();
+        // Maintain input semantics (edited + validate)
+        formInput.input();
     }
⛔ Skipped due to learnings
Learnt from: jsandoval81
PR: csg-org/CompactConnect#822
File: webroot/src/components/Forms/InputEmailList/InputEmailList.ts:77-89
Timestamp: 2025-05-28T19:57:53.185Z
Learning: In TypeScript, when dealing with union types that include arrays, direct assignment after filtering can cause compilation errors. The pattern `(formInput.value as Array<string>) = ...` is valid syntax for type assertion on the left-hand side of assignments and can be used as a workaround when TypeScript struggles with type inference on complex nested properties.
webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (3)

50-50: LGTM! Good test coverage for default initialization.

The tests correctly validate that npdbTypes initializes as an empty array and getNpdbTypeName() returns an empty string when called without arguments.

Also applies to: 62-62


74-74: LGTM! Constructor properly handles npdbTypes array.

The test validates that npdbTypes is correctly populated from constructor data, ensuring data-driven initialization works as expected.

Also applies to: 89-89


156-156: LGTM! Comprehensive serializer test coverage.

The tests properly validate that:

  • Server data (clinicalPrivilegeActionCategories) is correctly deserialized to npdbTypes
  • getNpdbTypeName() works correctly with categories from deserialized data

This provides good coverage of the serialization pathway.

Also applies to: 172-172, 188-188

webroot/src/components/PrivilegeCard/PrivilegeCard.ts (6)

19-19: LGTM! Proper component setup for multi-select feature.

The imports and component registration correctly add the necessary dependencies for the feature-gated NPDB multi-select functionality.

Also applies to: 24-24, 49-49


78-80: LGTM! Feature gate exposure for template access.

The public featureGates getter appropriately exposes FeatureGates for template-based conditional rendering. This is a standard pattern for feature-gated UI components.


217-222: LGTM! Appropriate UX handling for single vs. multi-select.

The conditional placeholder logic correctly adds a "Select an option" entry only for single-select mode. Multi-select mode naturally starts with an empty selection, eliminating the need for a placeholder option.


499-506: LGTM! Proper conditional payload construction.

The feature-gated payload logic correctly includes either:

  • npdbCategories (array) when multi-category is enabled
  • npdbCategory (single value) when multi-category is disabled

This matches the expected API contract for each mode.


709-713: LGTM! Mock populate correctly handles both modes.

The mock populate logic properly sets:

  • [this.npdbCategoryOptions[1]?.value] (array) for multi-select mode
  • this.npdbCategoryOptions[1]?.value (single value) for single-select mode

This ensures the mock data matches the expected structure for each mode.


327-327: Approve change: encumbranceTypeName() is implemented and tested: method exists in AdverseAction.model.ts (line 78) and is covered by unit tests, so the update aligns with requirements.

- Added defensive case for serializer in AdverseAction model
- @todo: Update for any design changes once ready
- @todo: Wire up backend once ready
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
webroot/src/models/AdverseAction/AdverseAction.model.ts (1)

94-100: Consider refactoring to eliminate duplication.

The new getNpdbTypeName method duplicates the logic of the existing npdbTypeName() method (lines 86-92). Both methods perform identical lookups in the licensing.npdbTypes translation array.

Apply this diff to refactor npdbTypeName() to delegate to getNpdbTypeName():

 public npdbTypeName(): string {
-    const npdbTypes = this.$tm('licensing.npdbTypes') || [];
-    const npdbType = npdbTypes.find((translate) => translate.key === this.npdbType);
-    const typeName = npdbType?.name || '';
-
-    return typeName;
+    return this.getNpdbTypeName(this.npdbType || '');
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03f56e9 and c845560.

📒 Files selected for processing (2)
  • webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (8 hunks)
  • webroot/src/models/AdverseAction/AdverseAction.model.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (1)
webroot/src/models/AdverseAction/AdverseAction.model.ts (2)
  • AdverseActionSerializer (127-147)
  • AdverseAction (35-122)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CheckWebroot
🔇 Additional comments (6)
webroot/src/models/AdverseAction/AdverseAction.model.ts (2)

26-26: LGTM!

The addition of npdbTypes as an optional array field in both the interface and class supports the multi-select NPDB category feature.

Also applies to: 44-44


137-139: LGTM!

The serializer correctly coerces clinicalPrivilegeActionCategories to an array, preventing UI loops when the server returns null or undefined. This implementation matches the resolution of the past review comment.

Based on past review comment.

webroot/src/models/AdverseAction/AdverseAction.model.spec.ts (4)

50-50: LGTM!

The test coverage correctly validates the default empty array for npdbTypes and the ability to construct an AdverseAction with specific npdbTypes values.

Also applies to: 74-74, 89-89


101-101: LGTM!

The tests correctly validate getNpdbTypeName with proper arguments, demonstrating the method's ability to map NPDB type keys to display names.

Also applies to: 188-188


156-156: LGTM!

The test data and assertions correctly validate that the serializer populates npdbTypes from the server's clinicalPrivilegeActionCategories array.

Also applies to: 172-172


191-200: LGTM!

This test case provides valuable coverage for the serializer's handling of invalid data types from the server, confirming that non-array values are coerced to an empty array as expected.

- Update for UI design changes
- @todo: Wire up backend once ready
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
webroot/src/components/PrivilegeCard/PrivilegeCard.ts (1)

78-80: Same patterns as LicenseCard - see type safety concern.

The feature gate exposure, conditional options, and form initialization follow the same patterns as LicenseCard. The type safety concern regarding FormInput.value being a string while multi-select assigns an array applies here as well (see LicenseCard.ts review).

Also applies to: 217-222, 275-295

🧹 Nitpick comments (2)
webroot/src/components/LicenseCard/LicenseCard.ts (2)

94-96: Consider limiting FeatureGates exposure in templates.

While functional, exposing the entire FeatureGates object couples templates to the full gate structure. For better encapsulation, consider exposing only the specific gate state or check method you need.

Example alternative:

-get featureGates(): typeof FeatureGates {
-    return FeatureGates;
-}
+get isMultiCategoryEnabled(): boolean {
+    return this.$features.checkGate(FeatureGates.ENCUMBER_MULTI_CATEGORY);
+}

283-303: Consider extracting shared encumbrance logic into a mixin.

The feature-gated NPDB category logic is duplicated between LicenseCard.ts and PrivilegeCard.ts. Since both components share identical patterns for form initialization, payload construction, and mock population, consider extracting this logic into a shared mixin to improve maintainability.

This would reduce duplication and ensure consistent behavior across both license and privilege encumbrance workflows.

Also applies to: 444-451, 651-655

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97ed2d0 and 7c01b09.

📒 Files selected for processing (2)
  • webroot/src/components/LicenseCard/LicenseCard.ts (8 hunks)
  • webroot/src/components/PrivilegeCard/PrivilegeCard.ts (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-24T00:02:39.944Z
Learnt from: jsandoval81
PR: csg-org/CompactConnect#873
File: webroot/src/components/LicenseCard/LicenseCard.ts:414-443
Timestamp: 2025-06-24T00:02:39.944Z
Learning: In LicenseCard component's clickUnencumberItem method (webroot/src/components/LicenseCard/LicenseCard.ts), complex event handling for checkbox interactions is intentionally designed to ensure consistent behavior across checkbox input, wrapper label, and outer selection parent elements for custom UI requirements. This complexity should be preserved rather than simplified.

Applied to files:

  • webroot/src/components/LicenseCard/LicenseCard.ts
🧬 Code graph analysis (2)
webroot/src/components/LicenseCard/LicenseCard.ts (1)
webroot/src/models/FormInput/FormInput.model.ts (1)
  • FormInput (58-168)
webroot/src/components/PrivilegeCard/PrivilegeCard.ts (1)
webroot/src/models/FormInput/FormInput.model.ts (1)
  • FormInput (58-168)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CheckWebroot
🔇 Additional comments (9)
webroot/src/components/LicenseCard/LicenseCard.ts (6)

19-19: LGTM! Feature gate and multi-select component imports are correct.

The addition of FeatureGates and InputSelectMultiple properly supports the conditional multi-select NPDB category feature.

Also applies to: 23-23


245-250: LGTM! Correct conditional empty option for single-select.

The logic correctly omits the placeholder "Select option" entry for multi-select mode, as native multi-select controls don't require an empty placeholder.


335-335: LGTM! Label change aligns with PR objectives.

The switch from npdbTypeName() to encumbranceTypeName() correctly implements the requirement that the unencumber workflow displays Discipline Type rather than NPDB Category.


444-451: LGTM! Payload construction correctly branches on feature gate.

The conditional spread properly sends either npdbCategories (array) or npdbCategory (string) based on the feature gate state, maintaining backward compatibility.


651-655: LGTM! Mock populate logic correctly handles both modes.

The conditional logic properly assigns an array for multi-select and a string for single-select, matching the respective form input value types.


283-303: ****

The review comment is based on an incorrect premise. The FormInput type definition explicitly supports array values: value?: string | number | boolean | null | Array<File> | Array<string> (FormInput.model.ts, line 22). The multi-select pattern using value: [] is type-safe and compatible with the FormInput class methods (enforceLength() and validate()), which both use generic operations (.length, .slice(), and Joi validation) that work correctly with arrays. The LicenseCard code properly initializes, validates, and uses array values for the multi-category form input.

Likely an incorrect or invalid review comment.

webroot/src/components/PrivilegeCard/PrivilegeCard.ts (3)

19-19: LGTM! Required imports for feature gate functionality.

The FeatureGates and InputSelectMultiple imports properly enable the conditional multi-select NPDB category feature for privileges.

Also applies to: 24-24


327-327: LGTM! Label change aligns with PR requirements.

The switch to encumbranceTypeName() correctly displays Discipline Type in the unencumber workflow for privileges, consistent with the changes in LicenseCard.


499-506: LGTM! Payload and mock logic correctly handle both modes.

The conditional payload construction and mock populate logic properly branch on the feature gate, maintaining consistency with LicenseCard's implementation.

Also applies to: 706-710

@ChiefStief
Copy link
Contributor

ChiefStief commented Oct 23, 2025

The encumbrance event detail is no longer displaying correctly in the history. Previously, this would match to an npbd category. Now its coming back as a comma seperated list so its not translating correctly:

public noteDisplay(): string {
        const updateType = this.updateType || '';
        let noteDisplay = this.serverNote || '';

        if (updateType === 'homeJurisdictionChange') {
            noteDisplay = this.$t('licensing.homeStateChangeNote');
        } else if (updateType === 'licenseDeactivation') {
            noteDisplay = this.$t('licensing.licenseDeactivationNote');
        } else if (updateType === 'encumbrance') {
            const npdbTypes = this.$tm('licensing.npdbTypes') || [];
            const npdbType = npdbTypes.find((translate) => translate.key === this.serverNote);
            const typeName = npdbType?.name || '';

            noteDisplay = typeName;
        }

        return noteDisplay;
    }
}
Screenshot 2025-10-23 at 1 06 26 PM (2)

@ChiefStief
Copy link
Contributor

Not sure if this your problem per se but holding command and then pressing space is the default to bring up spotlight search on mac which overwrites the select action. Clicking control direction on mac appears to be a different hotkey.

I can make this work if I command arrow, release command, space etc. Just wanted to bring this up. This may be standard.

@jsandoval81
Copy link
Collaborator Author

@ChiefStief Good catch. We had a similar situation with the unencumber workflow - where it was previously showing the NPDB category as the label. In that case, I confirmed with Product that we could change that label to the Discipline Type. That seems appropriate for me to do here as well - any concerns with that?

Copy link
Contributor

@ChiefStief ChiefStief left a comment

Choose a reason for hiding this comment

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

See comments

@ChiefStief
Copy link
Contributor

ChiefStief commented Oct 23, 2025

@jsandoval81 That would be fine the problem is we aren't returning the whole adverse action there, just the nbpd categories, so we dont have that data for every history item

@jsandoval81
Copy link
Collaborator Author

jsandoval81 commented Oct 24, 2025

Paper trail for @ChiefStief comments above:

  1. Since the server doesn't return any additional encumbrance data for history, the frontend is falling back to just say "Encumbrance" in that UI, which is sufficient for now.
  2. The native multi-select gives us the best cross platform performance & a11y. Unfortunately MacOS has a shortcut collision in 1 case, as documented in the PR / ticket notes. We'll stick with this approach for now as MacOS a11y users already have to make other config updates just to enable basic keyboard nav in Safari & other browsers; and those users may be likely to have already configured their OS to be a11y focused instead of the Apple defaults.

@jsandoval81
Copy link
Collaborator Author

@jlkravitz This is ready for your review.

Copy link
Collaborator

@jlkravitz jlkravitz left a comment

Choose a reason for hiding this comment

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

Looks good to me! @isabeleliassen Good to merge.

@isabeleliassen isabeleliassen merged commit 999c901 into csg-org:development Oct 27, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

adverse action muliple select FE

5 participants