Skip to content

Conversation

@nabeelmd-eGov
Copy link
Contributor

@nabeelmd-eGov nabeelmd-eGov commented Sep 29, 2025

Choose the appropriate template for your PR:

Feature/Bugfix Request

JIRA ID

Module

Description

Summary by CodeRabbit

  • New Features

    • Full two‑pane App Configuration workspace with store-backed editor, lazy-loaded entry, new routes and import workflow for campaign configs.
    • New field composer and side panel: add/reorder/hide/delete fields and sections, drag‑and‑drop, field property editor, dependent-visibility rule builder.
    • Registry-driven preview templates and widgets (buttons, cards, tags, info/panel cards, filters, search, switches) and new module browser.
  • UI/UX Improvements

    • Preview renders header fields first, supports per-field click/select with visual highlighting and dynamic footer actions.
    • Debounced localization editing with async fetch/upsert and inline label management; improved loading and error states.

@nabeelmd-eGov nabeelmd-eGov requested a review from a team as a code owner September 29, 2025 07:26
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

📝 Walkthrough

Walkthrough

Adds a comprehensive "New App Configuration" redesign: local Redux store and multiple slices (remoteConfig, fieldTypeMaster, fieldPanelMaster, localization, flowPages, pageFields), MDMS→App/Flow transformers, template-driven layout/renderers and components, draggable field editor/drawer composer, localization hooks, provider wrappers/routes, and retains a legacy AppPreview.

Changes

Cohort / File(s) Summary
Entry & Providers
.../NewAppConfiguration/AppConfigurationStore.js, .../NewAppConfiguration/AppConfigurationWrapper.js, .../NewAppConfiguration/AppConfiguration.js, .../NewAppConfiguration/IntermediateWrapper.js, .../NewAppConfiguration/FullConfigWrapper.js
Add Provider-wrapped entry points and wrappers that initialize config, fetch masters/localization, expose lifecycle hooks, and compose the full two-pane configuration UI.
Store & Slices
.../NewAppConfiguration/redux/store.js, .../redux/remoteConfigSlice.js, .../redux/fieldMasterSlice.js, .../redux/fieldPanelPropertiesSlice.js, .../redux/localizationSlice.js, .../redux/flowPagesSlice.js, .../redux/pageFieldsSlice.js
Introduce local Redux store and new slices: remoteConfig (config + field operations), fieldTypeMaster (getFieldMaster thunk + fallback), fieldPanelMaster (getFieldPanelMaster thunk + fallback), localization (fetch/upsert and actions), flowPages & pageFields (MDMS fetch/caching).
Preview & Renderers
.../components/AppPreview.js, .../components/OldAppPreview.js, .../components/ComponentToRender.js, .../NewAppConfiguration/LayoutRenderer.js, .../NewLayoutRenderer.js
Refactor preview surface: simplified AppPreview with onFieldClick/isSelected, add ComponentToRender to render FieldV1 via master data, provide two layout renderers (LayoutRenderer/NewLayoutRenderer), and add legacy OldAppPreview for backward compatibility.
Field Editing UI
.../NewAppConfiguration/NewAppFieldScreenWrapper.js, .../NewAppConfiguration/NewDraggableField.js, .../NewAppConfiguration/NewDrawerFieldComposer.js, .../NewAppConfiguration/PanelFieldDisplay.js, .../NewAppConfiguration/HeaderFieldWrapper.js, .../NewAppConfiguration/SidePanelApp.js, .../NewAppConfiguration/NewDependentFieldWrapper.js
Add draggable field components, panel controls (visibility/delete), drawer composer (tabbed property editor), header/footer localization editors (debounced), side-panel integration, and dependent-field rule editor.
Helpers, Hooks & Utils
.../NewAppConfiguration/hooks/useCustomT.js, .../NewAppConfiguration/helpers/getFieldTypeFromMasterData.js, .../helpers/getFieldValueByPath.js, .../helpers/getComponentFromMasterData.js, .../helpers/index.js, .../utils/configUtils.js
New localization hooks/useCustomTranslate, utilities to resolve field type/component/value from master data and paths, and config helper/re-exports.
Template Components
.../NewAppConfiguration/components/ButtonTemplate.js, .../CardTemplate.js, .../Filter.js, .../InfoCardTemplate.js, .../PanelCardTemplate.js, .../SearchBar.js, .../SwitchTemplate.js, .../TagTemplate.js
Add template components used by layout renderers; registered in Module.js for runtime lookup.
Transformers & Utils
.../NewAppConfiguration/transformers/mdmsToAppConfig.js, .../NewAppConfiguration/utils/configUtils.js, .../NewCampaignCreate/transformMdmsConfig.js, .../NewCampaignCreate/transformExample.js
Add MDMS→AppConfig and MDMS→Flow transformers, helper transformers, and utility functions for config transformations and examples.
Routing & Integrations
.../appConfigurationRedesign/AppConfigurationWrapper.js, .../appConfigurationScreenParent/AppConfigurationWrapper.js, .../appConfigurationRedesign/RenderConditionalField.js, .../appConfigurationRedesign/useCustomT.js, .../appConfigurationScreenParent/useCustomT.js, .../components/CampaignCard.js, .../pages/employee/index.js, .../Module.js
Swap certain wrappers to legacy OldAppPreview, silence some console warnings, add campaign menu entry "NEW APP CONFIGURATION", register template components, and add a lazy route for the redesign (FullConfigWrapper).
Compatibility / Legacy
.../components/OldAppPreview.js
Add legacy AppPreview implementation preserving earlier MDMS-driven field rendering and helpers.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Router as Router
  participant Full as FullConfigWrapper
  participant Provider as AppConfigurationStore (Provider)
  participant Wrapper as AppConfigurationWrapper
  participant FM as fieldTypeMaster
  participant PM as fieldPanelMaster
  participant L10n as localizationSlice
  participant RC as remoteConfigSlice
  participant UI as AppConfiguration
  participant Preview as NewLayoutRenderer/AppPreview
  participant Side as SidePanelApp

  Router->>Full: navigate /new-app-configuration-redesign
  Full->>Provider: render Provider with local store
  Provider->>Wrapper: mount
  Wrapper->>RC: dispatch(initializeConfig)
  Wrapper->>FM: dispatch(getFieldMaster)
  Wrapper->>PM: dispatch(getFieldPanelMaster)
  Wrapper->>L10n: dispatch(fetchLocalization, fetchAppScreenConfig)
  Wrapper-->>UI: render AppConfiguration (data + handlers)
  UI->>Preview: render preview (data, selectedField, onFieldClick)
  UI->>Side: render SidePanelApp
Loading
sequenceDiagram
  autonumber
  participant User as User
  participant Preview as PreviewRenderer
  participant Store as Redux (remoteConfigSlice)
  participant Side as SidePanelApp
  participant Drawer as NewDrawerFieldComposer

  User->>Preview: click field (onFieldClick)
  Preview->>Store: dispatch(selectField)
  Store-->>Side: selectedField updated
  Side->>Drawer: show properties for selectedField
  User->>Drawer: edit property
  Drawer->>Store: dispatch(updateSelectedField)
  Store-->>Preview: currentData updated -> preview re-renders
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas requiring extra attention:

  • remoteConfigSlice — complex mutations, path bookkeeping, reactivity triggers.
  • localizationSlice & useCustomT — locale resolution, addMissingKey and upsert concurrency.
  • fieldPanelPropertiesSlice — action export name vs reducer name mismatch (clearFieldMasterData vs clearFieldPanelMasterData).
  • Transformers (mdms→app/flow) — correctness across MDMS shapes and ordering.
  • Component registry/fallbacks (getComponentFromMasterData / Digit.ComponentRegistryService) — contract and fallback rendering.

Possibly related PRs

  • Dependent Field Changes #3269 — overlapping work on NewDependentFieldWrapper, drawer composer, redux slices, and template/component registrations (strong code-level overlap).

Poem

I’m a rabbit in a config glade,
hopping through fields the devs have made.
Drag, click, label, and template song,
MDMS and Redux hum along.
Carrots of code, and keys to guide 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description fails to provide any meaningful content beyond the empty template structure. While the template requires three key sections (JIRA ID, Module, and Description), none of these are filled in. The description only contains the template placeholders and HTML comments, with no actual information about the feature, affected modules, or implementation details. This makes it impossible for reviewers to quickly understand the business context, scope, or objectives of this significant changeset.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "New App Config wth dummy config for Form, Redux setup (Config, panel config, field config, localisation) setup initial commit" is related to the primary changes in the changeset, which introduces a comprehensive app configuration system with Redux state management, multiple configuration types, field configuration, and localization support. The title captures the main aspects of the change but uses a list-like format that could be more concise. Additionally, there is a typo ("wth" instead of "with") that should be corrected. While the title is somewhat verbose, it does clearly reference real and central parts of the implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-app-config-console

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: 78

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (1)

713-714: Remove stray JSX element in SidePanel closedFooter

[<en />] renders an unknown tag and is noise. Use an empty array.

-          closedFooter={[<en />]}
+          closedFooter={[]}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js (5)

446-451: Toast is used but not imported — will crash at runtime.

Import Toast from digit-ui-components.

-import { Footer, Button, Divider, Loader, PopUp, SidePanel } from "@egovernments/digit-ui-components";
+import { Footer, Button, Divider, Loader, PopUp, SidePanel, Toast } from "@egovernments/digit-ui-components";

428-429: Boolean prop passed as string.

equalWidthButtons expects a boolean, not "false".

-          equalWidthButtons={"false"}
+          equalWidthButtons={false}

289-306: Guard against empty localisation state.

Object.keys(locState[0]) will throw if locState is empty/undefined.

-    const locales = Object.keys(locState[0]).filter((key) => key.includes("_IN") && key !== "en_IN");
+    const locales =
+      Array.isArray(locState) && locState.length > 0
+        ? Object.keys(locState[0]).filter((key) => key.includes("_IN") && key !== "en_IN")
+        : ["en_IN"];

333-334: Remove unknown JSX element in closedFooter.

renders a custom tag; pass [] or omit the prop.

-          closedFooter={[<en />]}
+          closedFooter={[]}

245-246: Explicitly use window.location for clarity.

Avoid relying on the global location symbol.

-  const searchParams = new URLSearchParams(location.search);
+  const searchParams = new URLSearchParams(window.location.search);
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)

305-320: Replace hard-coded '/workbench-ui' with window.contextPath
In health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (lines 305–320), update each window.location.pathname check from "/workbench-ui/employee/campaign/…" to /${window.contextPath}/employee/campaign/….

health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)

133-173: Deduplicate getFieldType logic.

A local “remove this function” remains and footer still depends on it. Move field‑type resolution to a single helper (or reuse ComponentToRender’s mapping via field masters) to avoid divergence.

Do you want a patch that routes footer rendering through ComponentToRender as well?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 469aeeb and 6ee037e.

⛔ Files ignored due to path filters (3)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyFieldTypeConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/fullData.json is excluded by !**/*.json
📒 Files selected for processing (24)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (3 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js (3 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js (0 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (0 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (3 hunks)
💤 Files with no reviewable changes (2)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
🧠 Learnings (5)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
📚 Learning: 2024-06-10T19:25:42.992Z
Learnt from: siddhant-nawale-egov
PR: egovernments/DIGIT-Frontend#698
File: micro-ui/web/micro-ui-internals/packages/modules/hcm-microplanning/src/components/MicroplanPreview.js:1-1
Timestamp: 2024-06-10T19:25:42.992Z
Learning: The imports in `MicroplanPreview.js` are from different libraries: `egovernments/digit-ui-components` and `egovernments/digit-ui-react-components`.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js
📚 Learning: 2025-02-05T10:18:29.947Z
Learnt from: Tulika-eGov
PR: egovernments/DIGIT-Frontend#2188
File: micro-ui/web/micro-ui-internals/packages/modules/pgr/src/pages/employee/new-inbox.js:34-47
Timestamp: 2025-02-05T10:18:29.947Z
Learning: In the DIGIT-Frontend codebase, translations within FormComposer are handled internally by the component itself, so the translation function 't' does not need to be added to useMemo dependency arrays even when used within the memoized function that generates form configurations.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js
📚 Learning: 2025-02-05T10:06:57.846Z
Learnt from: Tulika-eGov
PR: egovernments/DIGIT-Frontend#2188
File: micro-ui/web/micro-ui-internals/packages/libraries/src/hooks/useLocalities.js:5-6
Timestamp: 2025-02-05T10:06:57.846Z
Learning: In useLocalities hook, the language parameter is used in the query key to trigger React Query cache invalidation when language changes, while actual translation is handled by the 't' function parameter.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js
📚 Learning: 2024-11-07T07:17:27.636Z
Learnt from: jagankumar-egov
PR: egovernments/DIGIT-Frontend#1763
File: health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/MyCampaign.js:65-75
Timestamp: 2024-11-07T07:17:27.636Z
Learning: In `MyCampaign.js`, when suggesting code improvements for the `onClickRow` function, keep suggestions simple and avoid unnecessary complexity.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
🧬 Code graph analysis (16)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • useCustomT (4-32)
  • useCustomT (4-32)
  • dispatch (5-5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (3)
  • fieldIndex (56-56)
  • fields (94-94)
  • cardIndex (53-53)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)
  • moveField (120-125)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • name (159-159)
  • url (155-155)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (5)
  • existing (266-266)
  • contextPath (274-274)
  • url (275-275)
  • response (277-295)
  • data (296-296)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • existing (126-126)
  • mdmsContext (62-62)
  • url (63-63)
  • response (39-43)
  • response (65-83)
  • response (96-96)
  • data (84-84)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • mdmsContext (18-18)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (3)
  • useTranslation (104-104)
  • dispatch (105-105)
  • useSelector (106-106)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (7)
  • useTranslation (22-22)
  • useTranslation (218-218)
  • useTranslation (243-243)
  • dispatch (23-23)
  • dispatch (244-244)
  • useSelector (24-24)
  • useSelector (246-246)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (174-285)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (5)
  • MdmsDropdown (12-52)
  • Digit (26-37)
  • renderField (54-131)
  • getFieldType (134-173)
  • AppPreview (174-285)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • getFieldType (7-16)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js (1)
  • getFieldType (9-18)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (5)
  • useSelector (19-19)
  • fieldType (26-26)
  • getFieldType (7-16)
  • fieldTypeMasterData (23-23)
  • matched (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (3)
  • currentLocale (14-14)
  • useCustomT (12-37)
  • useCustomT (12-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • BOUNDARY_HIERARCHY_TYPE (89-104)
  • hierarchyData (106-106)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • MODULE_CONSTANTS (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)
  • MODULE_CONSTANTS (19-19)
  • enabledModules (21-21)
  • currentLocale (22-22)
  • mdmsContext (18-18)
  • tenantId (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • locale (29-29)
  • enabledModules (20-20)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
  • response (21-39)
  • url (19-19)
  • data (40-40)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)
  • response (277-295)
  • url (275-275)
  • data (296-296)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • getFieldType (134-173)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (4)
  • getFieldType (7-16)
  • fieldTypeMasterData (23-23)
  • matched (13-13)
  • useSelector (19-19)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (5)
  • getFieldType (231-240)
  • matched (237-237)
  • fieldTypeMaster (247-247)
  • useSelector (24-24)
  • useSelector (246-246)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (136-140)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (2)
  • store (7-14)
  • store (7-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • AppConfigurationWrapper (13-247)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (3)
  • AppPreview (172-280)
  • getFieldType (132-171)
  • renderField (52-129)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
  • ComponentToRender (18-58)
  • getFieldType (7-16)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (3)
  • useCustomT (12-37)
  • useCustomT (12-37)
  • currentLocale (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (4)
  • useCustomT (3-18)
  • useCustomT (3-18)
  • entry (12-12)
  • currentLocale (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (2)
  • enabledModules (21-21)
  • currentLocale (22-22)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (2)
  • locale (8-8)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • currentLocale (22-22)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)
  • currentLocale (107-107)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (1)
  • currentLocale (347-347)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (1)
  • currentLocale (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • getFieldType (134-173)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • tenantId (161-161)
  • location (290-290)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • enabledModules (20-20)
  • useFieldDataLabel (35-38)
  • useFieldDataLabel (35-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (1)
  • currentLocale (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (2)
  • getFieldMaster (5-50)
  • getFieldMaster (5-50)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)
  • getFieldPanelMaster (262-306)
  • getFieldPanelMaster (262-306)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (5)
  • existing (10-10)
  • contextPath (18-18)
  • url (19-19)
  • response (21-39)
  • data (40-40)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 223-223: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js

[error] 24-30: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 24-30: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 41-57: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 41-57: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 202-210: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 202-210: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 105-105: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js

[error] 201-212: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 201-212: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🔇 Additional comments (16)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (1)

12-30: Ignore signature concern for addMissingKey: in the redesign context addMissingKey is defined as a single-arg (code) function that closes over enabledModules and dispatches { code, enabledModules }, matching its use in useCustomT. No change needed.

Likely an incorrect or invalid review comment.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (1)

10-10: No issues with OldAppPreview prop compatibility. The OldAppPreview module’s default export (AppPreview) destructures exactly data, selectedField, and t, so its usage here will not crash.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)

136-141: Lazy import looks fine.

Chunk name and fallback are consistent with the pattern used elsewhere.


336-339: No functional change; route remains equivalent.

Multiline formatting only.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js (1)

10-11: Switch to OldAppPreview acknowledged.

If this is to preserve backward-compatible rendering, LGTM. Confirm parity of props with the previous AppPreview to avoid regressions.

health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js (1)

30-32: Session tab index reset on mount — OK.

Small UX hygiene; no concerns.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (1)

7-18: Store wiring looks correct.

Reducers registered as expected; default export is convenient for the Provider. If you need multiple isolated instances later, consider exporting a factory, but current approach is fine.

health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)

53-53: Unable to find any FieldV1 usages with type="button" or type="select" in the campaign-manager module. Please manually verify that FieldV1 now supports rendering buttons and selects natively; if it does not, reintroduce the custom populators.component for those types.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1)

8-30: Do not remove index
index is used in the useDrag({ item: { index } }) hook and in the drop hover logic to identify the dragged item; it’s not redundant.

Likely an incorrect or invalid review comment.

health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)

194-199: Visibility filter changed; confirm semantics.

New filter drops deleteFlag handling. Ensure this doesn’t regress flows that depended on showing soft‑deleted fields.

If deleteFlag should still surface fields, adjust predicate accordingly: field.active && (!field.hidden || field.deleteFlag === true).


180-181: Nested Card inside Card; verify UI intent.

Rendering a Card, then mapping cards to nested Card components may add unintended extra chrome/padding.

If unintended, remove the outer Card wrapper.

Also applies to: 190-230

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js (1)

20-22: Verify required check path

config?.["toArray.required"] assumes a flattened dotted key. If the shape is nested (config.toArray.required) this will never hide the toggle. Consider a safe getter.

Example:

const isRequired = config?.required || config?.toArray?.required || config?.["toArray.required"];
if (config?.deleteFlag || isRequired) return null;
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)

20-31: Duplicate id “helpText” across tabs—confirm uniqueness expectations

id: "helpText" appears in both content and advanced tabs. If id is used as a unique key, this will collide.

Also applies to: 137-148

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (2)

6-37: Parallel localization fetch logic looks good

Using Promise.all and building a per-code multi-locale map is sound and efficient for enabled locales.


96-101: Confirm useUpsertLocalisationParallel is not a React Hook
I couldn’t locate its definition; verify that Digit.Hooks.campaign.useUpsertLocalisationParallel (invoked in health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js lines 96–101) is a plain function factory and doesn’t rely on React hooks.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)

193-209: Dispatch payload shapes align with reducer signatures; no changes needed.

Comment on lines +67 to +87
const handleFieldChange = (value) => {
const bindTo = panelItem.bindTo;

// Update the field with the code (or value if no localization)
if (bindTo.includes(".")) {
// Handle nested properties
const keys = bindTo.split(".");
const newField = { ...selectedField };
let current = newField;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
onFieldChange(newField);
} else {
onFieldChange({ ...selectedField, [bindTo]: value });
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard bindTo before using includes

If a panelItem lacks bindTo, .includes will throw. Add a simple guard.

-    const bindTo = panelItem.bindTo;
+    const bindTo = panelItem.bindTo;
+    if (!bindTo) return;

Apply similarly in handleFieldChangeWithLoc.

Also applies to: 89-140

Comment on lines 223 to 225
<button key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>
{t(`TAB_${tab.toUpperCase()}`)}
</button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit button type for accessibility/semantics

Without type, buttons default to submit. Add type="button". (Biome: lint/a11y/useButtonType)

-        <button key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>
{t(`TAB_${tab.toUpperCase()}`)}
</button>
<button
type="button"
key={tab}
className={`drawer-tab ${activeTab === tab ? "active" : ""}`}
onClick={() => onTabChange(tab)}
>
{t(`TAB_${tab.toUpperCase()}`)}
</button>
🧰 Tools
🪛 Biome (2.1.2)

[error] 223-223: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
around lines 223 to 225, the <button> lacks an explicit type attribute so it
defaults to "submit"; update the element to include type="button" to prevent
unintended form submissions and satisfy accessibility/lint rules
(useButtonType).

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.

Review continued from previous batch...

Comment on lines 201 to 212
<div
className={`app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
}`}
key={fieldIndex}
onClick={() => onFieldClick && onFieldClick(field, data, card)}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix a11y: clickable div must be keyboard‑accessible.

Add role, tabIndex, and key handler for Enter/Space. Also satisfies lint a11y errors.

-                      <div
+                      <div
                         key={fieldIndex}
-                        onClick={() => onFieldClick && onFieldClick(field, data, card)}
+                        role="button"
+                        tabIndex={0}
+                        aria-label={t(field?.label) || "Select field"}
+                        onClick={() => onFieldClick && onFieldClick(field, data, card)}
+                        onKeyDown={(e) => {
+                          if (e.key === "Enter" || e.key === " ") {
+                            e.preventDefault();
+                            onFieldClick && onFieldClick(field, data, card);
+                          }
+                        }}
                         style={{
                           cursor: "pointer",
                           border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
                           borderRadius: "4px",
                           padding: "8px",
                           margin: "4px 0",
                           backgroundColor: isSelected ? "#f0f8ff" : "transparent",
                         }}
                       >

I can run a quick pass to add roles/keys where needed elsewhere too.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
className={`app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
}`}
key={fieldIndex}
onClick={() => onFieldClick && onFieldClick(field, data, card)}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
<div
key={fieldIndex}
role="button"
tabIndex={0}
aria-label={t(field?.label) || "Select field"}
onClick={() => onFieldClick && onFieldClick(field, data, card)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onFieldClick && onFieldClick(field, data, card);
}
}}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 201-212: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 201-212: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
around lines 201-212, the clickable div is not keyboard-accessible; add
role="button", tabIndex={0}, and an onKeyDown handler that triggers the same
action as onClick when Enter or Space is pressed (handle Space with
preventDefault to avoid page scroll), keeping the existing onClick and styles;
ensure the onKeyDown calls onFieldClick(field, data, card) only if onFieldClick
is defined.

Comment on lines 232 to 280
{data?.footer?.length > 0 &&
data?.footer?.map((footer_item, footer_index) => {
return (
<FieldV1
config={{ step: "" }}
description={null}
error={null}
infoMessage={null}
label={
getFieldType(footer_item) === "checkbox" || getFieldType(footer_item) === "button" || getFieldType(footer_item) === "custom"
? null
: footer_item?.isMdms
? t(footer_item?.label)
: footer_item?.label
}
onChange={function noRefCheck() {}}
placeholder={t(footer_item?.innerLabel) || ""}
populators={{
t: footer_item?.isMdms ? null : t,
title: footer_item?.label,
fieldPairClassName: `app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === footer_item?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === footer_item?.id
? `app-preview-selected`
: ``
}`,
mdmsConfig: footer_item?.isMdms
? {
moduleName: footer_item?.schemaCode?.split(".")[0],
masterName: footer_item?.schemaCode?.split(".")[1],
}
: null,
options: footer_item?.isMdms ? null : footer_item?.dropDownOptions,
optionsKey: footer_item?.isMdms ? "code" : "name",
component:
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" || getFieldType(footer_item) === "custom"
? renderField(footer_item, t)
: null,
}}
required={getFieldType(footer_item) === "custom" ? null : footer_item?.["toArray.required"]}
type={
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" ? "custom" : getFieldType(footer_item) || "text"
}
return (
<FieldV1
charCount={field?.charCount}
config={{
step: "",
}}
description={field?.isMdms ? t(field?.helpText) : field?.helpText || null}
error={field?.isMdms ? t(field?.errorMessage) : field?.errorMessage || null}
infoMessage={field?.isMdms ? t(field?.tooltip) : field?.tooltip || null}
label={
getFieldType(field) === "checkbox" || getFieldType(field) === "button" || getFieldType(field) === "custom"
? null
: field?.isMdms
? t(field?.label)
: field?.label
}
onChange={function noRefCheck() {}}
placeholder={t(field?.innerLabel) || ""}
populators={{
t: field?.isMdms ? null : t,
prefix: field?.prefixText,
suffix: field?.suffixText,
title: field?.label,
fieldPairClassName: `app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
} ${field?.["toArray.required"] && getFieldType(field) !== "custom" ? `required` : ``}`,
mdmsConfig: field?.isMdms
? {
moduleName: field?.schemaCode?.split(".")[0],
masterName: field?.schemaCode?.split(".")[1],
}
: null,
options: field?.isMdms ? null : field?.dropDownOptions,
optionsKey: field?.isMdms ? "code" : "name",
component:
getFieldType(field) === "button" || getFieldType(field) === "select" || getFieldType(field) === "custom"
? renderField(field, t)
: null,
}}
type={getFieldType(field) === "button" || getFieldType(field) === "select" ? "custom" : getFieldType(field) || "text"}
value={field?.value === true ? "" : field?.value || ""}
disabled={field?.readOnly || false}
/>
);
})}
{data.type !== "template" && (
<Button
className="app-preview-action-button"
variation="primary"
label={t(data?.actionLabel)}
title={t(data?.actionLabel)}
onClick={() => {}}
/>
)}
{data.type === "template" && (
<GenericTemplateScreen components={card.fields} selectedField={selectedField} t={t} templateName={data.name} />
)}
</Card>
))}
value={footer_item?.value === true ? "" : footer_item?.value || ""}
disabled={footer_item?.readOnly || false}
/>
);
})}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add keys to footer list items.

Each mapped element needs a stable key to avoid React warnings.

-          {data?.footer?.length > 0 &&
-            data?.footer?.map((footer_item, footer_index) => {
-              return (
-                <FieldV1
+          {data?.footer?.length > 0 &&
+            data?.footer?.map((footer_item, footer_index) => {
+              const footerKey = footer_item?.id ?? footer_item?.jsonPath ?? `${footer_item?.type}-${footer_index}`;
+              return (
+                <FieldV1
+                  key={footerKey}
                   config={{ step: "" }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{data?.footer?.length > 0 &&
data?.footer?.map((footer_item, footer_index) => {
return (
<FieldV1
config={{ step: "" }}
description={null}
error={null}
infoMessage={null}
label={
getFieldType(footer_item) === "checkbox" || getFieldType(footer_item) === "button" || getFieldType(footer_item) === "custom"
? null
: footer_item?.isMdms
? t(footer_item?.label)
: footer_item?.label
}
onChange={function noRefCheck() {}}
placeholder={t(footer_item?.innerLabel) || ""}
populators={{
t: footer_item?.isMdms ? null : t,
title: footer_item?.label,
fieldPairClassName: `app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === footer_item?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === footer_item?.id
? `app-preview-selected`
: ``
}`,
mdmsConfig: footer_item?.isMdms
? {
moduleName: footer_item?.schemaCode?.split(".")[0],
masterName: footer_item?.schemaCode?.split(".")[1],
}
: null,
options: footer_item?.isMdms ? null : footer_item?.dropDownOptions,
optionsKey: footer_item?.isMdms ? "code" : "name",
component:
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" || getFieldType(footer_item) === "custom"
? renderField(footer_item, t)
: null,
}}
required={getFieldType(footer_item) === "custom" ? null : footer_item?.["toArray.required"]}
type={
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" ? "custom" : getFieldType(footer_item) || "text"
}
return (
<FieldV1
charCount={field?.charCount}
config={{
step: "",
}}
description={field?.isMdms ? t(field?.helpText) : field?.helpText || null}
error={field?.isMdms ? t(field?.errorMessage) : field?.errorMessage || null}
infoMessage={field?.isMdms ? t(field?.tooltip) : field?.tooltip || null}
label={
getFieldType(field) === "checkbox" || getFieldType(field) === "button" || getFieldType(field) === "custom"
? null
: field?.isMdms
? t(field?.label)
: field?.label
}
onChange={function noRefCheck() {}}
placeholder={t(field?.innerLabel) || ""}
populators={{
t: field?.isMdms ? null : t,
prefix: field?.prefixText,
suffix: field?.suffixText,
title: field?.label,
fieldPairClassName: `app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
} ${field?.["toArray.required"] && getFieldType(field) !== "custom" ? `required` : ``}`,
mdmsConfig: field?.isMdms
? {
moduleName: field?.schemaCode?.split(".")[0],
masterName: field?.schemaCode?.split(".")[1],
}
: null,
options: field?.isMdms ? null : field?.dropDownOptions,
optionsKey: field?.isMdms ? "code" : "name",
component:
getFieldType(field) === "button" || getFieldType(field) === "select" || getFieldType(field) === "custom"
? renderField(field, t)
: null,
}}
type={getFieldType(field) === "button" || getFieldType(field) === "select" ? "custom" : getFieldType(field) || "text"}
value={field?.value === true ? "" : field?.value || ""}
disabled={field?.readOnly || false}
/>
);
})}
{data.type !== "template" && (
<Button
className="app-preview-action-button"
variation="primary"
label={t(data?.actionLabel)}
title={t(data?.actionLabel)}
onClick={() => {}}
/>
)}
{data.type === "template" && (
<GenericTemplateScreen components={card.fields} selectedField={selectedField} t={t} templateName={data.name} />
)}
</Card>
))}
value={footer_item?.value === true ? "" : footer_item?.value || ""}
disabled={footer_item?.readOnly || false}
/>
);
})}
{data?.footer?.length > 0 &&
data?.footer?.map((footer_item, footer_index) => {
const footerKey = footer_item?.id
?? footer_item?.jsonPath
?? `${footer_item?.type}-${footer_index}`;
return (
<FieldV1
key={footerKey}
config={{ step: "" }}
description={null}
error={null}
infoMessage={null}
label={
getFieldType(footer_item) === "checkbox" || getFieldType(footer_item) === "button" || getFieldType(footer_item) === "custom"
? null
: footer_item?.isMdms
? t(footer_item?.label)
: footer_item?.label
}
onChange={function noRefCheck() {}}
placeholder={t(footer_item?.innerLabel) || ""}
populators={{
t: footer_item?.isMdms ? null : t,
title: footer_item?.label,
fieldPairClassName: `app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === footer_item?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === footer_item?.id
? `app-preview-selected`
: ``
}`,
mdmsConfig: footer_item?.isMdms
? {
moduleName: footer_item?.schemaCode?.split(".")[0],
masterName: footer_item?.schemaCode?.split(".")[1],
}
: null,
options: footer_item?.isMdms ? null : footer_item?.dropDownOptions,
optionsKey: footer_item?.isMdms ? "code" : "name",
component:
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" || getFieldType(footer_item) === "custom"
? renderField(footer_item, t)
: null,
}}
required={getFieldType(footer_item) === "custom" ? null : footer_item?.["toArray.required"]}
type={
getFieldType(footer_item) === "button" || getFieldType(footer_item) === "select" ? "custom" : getFieldType(footer_item) || "text"
}
value={footer_item?.value === true ? "" : footer_item?.value || ""}
disabled={footer_item?.readOnly || false}
/>
);
})}
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
around lines 232 to 280, the mapped footer items lack a React key causing
warnings; add a stable key prop to the top-level FieldV1 component such as
key={footer_item?.id || footer_item?.jsonPath || footer_index} (use id first,
fallback to jsonPath, then index) so each item has a unique, stable key.

const searchParams = new URLSearchParams(location.search);
const fieldMasterName = searchParams.get("fieldType");
const { t } = useTranslation();
const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use MDMS v1 context for v1 searches.

You read MDMS_V2_CONTEXT_PATH and pass it to thunks that hit /v1/_search. Prefer V1 context to avoid mismatched routing.

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
...
-          mdmsContext: mdmsContext,
+          mdmsContext: mdmsV1Context,
...
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,

Also consider always fetching “FieldTypeMappingConfig” if not provided via query to populate the dropdown.

Also applies to: 71-82, 84-93

Comment on lines 132 to 161
addSection(state) {
if (state.currentData && state.currentData.cards) {
const newCard = {
fields: [],
header: "Header",
description: "Desc",
headerFields: [
{
type: "text",
label: "SCREEN_HEADING",
active: true,
jsonPath: "ScreenHeading",
metaData: {},
required: true,
},
{
type: "text",
label: "SCREEN_DESCRIPTION",
active: true,
jsonPath: "Description",
metaData: {},
required: true,
},
],
};
state.currentData.cards.push(newCard);
// Ensure reactivity by creating new reference
state.currentData = { ...state.currentData };
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

addSection: consider IDs and defaults for consistency.

Cards currently lack IDs; adding one simplifies selection and updates. Also initialize fields to an empty array safely.

 const newCard = {
-  fields: [],
+  id: nanoid(),
+  fields: [],
   header: "Header",
   description: "Desc",

If the UI relies on array indices for card identity, defer this change; otherwise adopt IDs and update selectors.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
around lines 132 to 161, the addSection reducer creates new cards without a
stable identifier and relies on implicit defaults; add a unique id property to
newCard (e.g., using a UUID/nanoid or timestamp) and ensure all expected default
fields are explicitly initialized (fields: [], header: "Header" etc.), then push
the card and keep the existing reactivity step; if the UI currently uses array
indices for identity, do not change selectors now—otherwise update any
selectors/actions to use card.id for selection and updates.

Comment on lines 171 to 174
state.currentData.cards[cardIndex].headerFields[fieldIndex].value = value;
// Ensure reactivity by creating new reference
state.currentData = { ...state.currentData };
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Unnecessary “reactivity” spreads; Immer already handles immutable updates.

These extra copies add noise without benefit.

- // Ensure reactivity by creating new reference
- state.currentData = { ...state.currentData };

Apply this removal here and in other reducers where present (deleteField, hideField, reorderFields, addField, addSection, updateHeaderField, updateActionLabel).

Also applies to: 179-182

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
around lines 171-174 (and similarly 179-182), you’re performing manual shallow
copies like "state.currentData = { ...state.currentData }" to force reactivity,
which is unnecessary because Immer (used by Redux Toolkit) already tracks and
produces immutable updates; remove those redundant spread assignments from this
reducer and the other listed reducers (deleteField, hideField, reorderFields,
addField, addSection, updateHeaderField, updateActionLabel) so each reducer only
mutates the draft state directly and returns nothing (let Immer produce the new
state).

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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ee037e and a0dcfb4.

⛔ Files ignored due to path filters (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.json is excluded by !**/*.json
📒 Files selected for processing (3)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
🧠 Learnings (3)
📚 Learning: 2025-01-02T04:41:15.957Z
Learnt from: jagankumar-egov
PR: egovernments/DIGIT-Frontend#2089
File: micro-ui/web/micro-ui-internals/packages/modules/workbench/src/pages/employee/MDMSAddV2.js:111-111
Timestamp: 2025-01-02T04:41:15.957Z
Learning: In micro-ui/web/micro-ui-internals/packages/modules/workbench/src/pages/employee/MDMSAddV2.js, always use "mdms[0].uniqueIdentifier" for the jsonPath instead of differentiating between "SOR" and other masters.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
📚 Learning: 2025-06-26T09:43:00.597Z
Learnt from: Hari-egov
PR: egovernments/DIGIT-Frontend#2644
File: micro-ui/web/micro-ui-internals/packages/modules/core/src/components/LoginSignupSelector.js:24-24
Timestamp: 2025-06-26T09:43:00.597Z
Learning: In LoginSignupSelector component, the URL checking using window.location.href.includes("sandbox-ui/user/sign-up") is intentional for URL validation purposes, not navigation. The team prefers this approach over React Router's useLocation hook for specific URL checking needs.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
🧬 Code graph analysis (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (1)
  • getFieldType (132-171)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • getFieldType (134-173)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.js (2)
  • getFieldType (9-18)
  • matched (15-15)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
  • getFieldType (231-240)
  • matched (237-237)
  • fieldType (260-265)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • enabledModules (20-20)
  • useFieldDataLabel (35-38)
  • useFieldDataLabel (35-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (2)
  • getFieldMaster (5-50)
  • getFieldMaster (5-50)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)
  • getFieldPanelMaster (262-306)
  • getFieldPanelMaster (262-306)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • useCustomT (4-32)
  • useCustomT (4-32)
  • dispatch (5-5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (3)
  • cardIndex (53-53)
  • fieldIndex (56-56)
  • fields (94-94)
⏰ Context from checks skipped due to timeout of 10000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (3)

18-19: Use the v1 MDMS context for v1 searches.

getFieldMaster/getFieldPanelMaster hit /v1/_search, but you pass MDMS_V2_CONTEXT_PATH, which routes to the wrong service and leaves the dropdown empty. Fall back to MDMS_V1_CONTEXT_PATH (or egov-mdms-service) when constructing these requests.

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
...
-          mdmsContext: mdmsContext,
+          mdmsContext: mdmsV1Context,
...
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,

Also applies to: 71-90


69-79: Always load the field type mapping used by the dropdown.

When the fieldType query param is absent (default flow), you never fetch FieldTypeMappingConfig, so the ADD FIELD dropdown receives undefined options. Trigger a fetch for the mapping as the fallback to keep the popup usable.

     if (fieldMasterName) {
       dispatch(/* existing fetch */);
     }
+    if (!fieldMasterName) {
+      dispatch(
+        getFieldMaster({
+          tenantId,
+          moduleName: MODULE_CONSTANTS,
+          name: "FieldTypeMappingConfig",
+          mdmsContext: mdmsV1Context,
+          limit: 10000,
+        })
+      );
+    }

102-112: Defer setLocalizationData until the fetch resolves.

setLocalizationData runs immediately after fetchLocalization, but localizationData still holds stale state, so you populate the localisation store with an empty array. Await/unwrap the thunk or watch localizationStatus === "succeeded" before dispatching setLocalizationData.

-      dispatch(fetchAppScreenConfig({ tenantId }));
-
-      dispatch(setLocalizationData({ localisationData: localizationData, ... }));
+      dispatch(fetchAppScreenConfig({ tenantId }));
     }
   }, [..., localizationData]);
+
+  useEffect(() => {
+    if (localeModule && localizationStatus === "succeeded" && Array.isArray(localizationData)) {
+      dispatch(setLocalizationData({ localisationData: localizationData, currentLocale, enabledModules: [currentLocale], localeModule }));
+    }
+  }, [dispatch, localeModule, localizationStatus, localizationData, currentLocale]);
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)

21-29: Remove leftover debug logs.

These console.log calls leak noisy/sensitive data in production bundles. Please drop them (or gate them behind a proper debug logger).

-  console.log("responsePanelComponent", field, byName);
...
-  console.log("fieldType", fieldType);

36-41: Restore i18n-aware labels.

Passing field?.label directly shows raw localisation keys and surfaces labels on checkbox/custom buttons. Re-instate the translation behaviour so MDMS keys are localised and suppress labels where the design expects none.

-      label={field?.label}
+      label={
+        ["checkbox", "button", "custom"].includes(fieldType)
+          ? null
+          : field?.isMdms
+            ? t(field?.label)
+            : field?.label ?? null
+      }

52-55: Use the resolved field type and preserve boolean values.

Calling getFieldType(field) without the master data always returns "textInput", so custom fields never get the required=null relaxation, and coercing booleans to "" breaks checkbox rendering. Reuse the fieldType you already calculated and nullish-coalesce the value.

-      required={getFieldType(field) === "custom" ? null : field?.["toArray.required"]}
-      type={fieldType}
-      value={field?.value === true ? "" : field?.value || ""}
+      required={fieldType === "custom" ? null : field?.["toArray.required"]}
+      type={fieldType}
+      value={field?.value ?? ""}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (5)

18-22: Pass projectType into LocalizedHeaderField and sanitise the fallback key.

projectType is currently an undeclared variable inside this memo, so any header lacking a pre-existing localisation code throws a ReferenceError. Accept projectType via props and normalise it to a safe uppercase key when building the fallback.

-const LocalizedHeaderField = React.memo(({ type, label, active, value, formId, currentCard, onChange }) => {
-  const localizedValue = useCustomT(value ? value : `${projectType}_${currentCard.parent}_${currentCard.name}_${label}`);
+const LocalizedHeaderField = React.memo(({ type, label, active, value, formId, currentCard, onChange, projectType }) => {
+  const headerKey =
+    value ||
+    [projectType, currentCard?.parent, currentCard?.name, label]
+      .filter(Boolean)
+      .map((part) => String(part).trim().replace(/[^A-Za-z0-9]+/g, "_").toUpperCase())
+      .join("_");
+  const localizedValue = useCustomT(headerKey);

Remember to include projectType={projectType} when rendering.


101-104: Prefer useLocation over the global location.

Directly touching the global location breaks in SSR/tests. Use const { search } = useLocation(); const searchParams = new URLSearchParams(search || ""); to stay router-safe.


137-139: Add dispatch to the useCallback dependency list.

Even though Redux stabilises dispatch, omitting it trips lint and risks future regressions. Include [dispatch] for consistency.


198-205: Stop hardcoding localisation codes (SKJSKSJSKJSKJ).

These placeholders leak into Redux and localisation stores, and the header/action updates never persist the generated key back into state. Derive a deterministic code from projectType/currentCard (as in the header fallback above), dispatch updateLocalizationEntry with that code, and when the card had no code, also call handleUpdateHeaderField/handleUpdateActionLabel with the newly generated code.

Also applies to: 302-309


260-264: Fix the button variation typo.

"teritiary" doesn’t match any supported variation, so the ADD FIELD button renders unstyled. Change it to "tertiary".

-              variation={"teritiary"}
+              variation={"tertiary"}

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: 7

♻️ Duplicate comments (22)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)

35-46: Fix a11y: clickable div must be keyboard-accessible.

The clickable div lacks keyboard accessibility. Add role, tabIndex, and a keyboard event handler for Enter/Space keys.

 <div
   key={fieldIndex}
+  role="button"
+  tabIndex={0}
+  aria-label={`Select ${field?.label || 'field'}`}
   onClick={() => onFieldClick && onFieldClick(field, data, card)}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      onFieldClick && onFieldClick(field, data, card);
+    }
+  }}
   style={{
     cursor: "pointer",
     border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
     borderRadius: "4px",
     padding: "8px",
     margin: "4px 0",
     backgroundColor: isSelected ? "#f0f8ff" : "transparent",
   }}
 >

55-66: Add keys to footer mapped elements.

The mapped footer items lack React keys, which will cause warnings and potential rendering issues.

 {data?.footer?.length > 0 &&
   data?.footer?.map((footer_item, footer_index) => {
+    const footerKey = footer_item?.id ?? footer_item?.jsonPath ?? `footer-${footer_index}`;
     return (
       <Button
+        key={footerKey}
         className="app-preview-action-button"
         variation="primary"
         label={t(footer_item?.label)}
         title={t(footer_item?.label)}
         onClick={() => {}}
       />
     );
   })}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (3)

1-1: Remove unused Fragment import.

The Fragment import is unused in this file.

-import React, { Fragment } from "react";
+import React from "react";

9-9: Remove unused constant.

MODULE_CONSTANTS is declared but never used.

-const MODULE_CONSTANTS = "HCM-ADMIN-CONSOLE";

22-22: CRITICAL: Don't pass hook as prop (violates Rules of Hooks).

Passing useCustomT (a hook identifier) as the t prop violates the Rules of Hooks. When AppPreview calls t(someKey), it's actually invoking useCustomT(someKey), which calls hooks (useSelector, useDispatch) conditionally or in a non-component context.

Solution: Create a wrapper function at this component level that calls useCustomT and pass that function instead.

 function AppConfiguration({ screenConfig, localeModule, pageTag }) {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentData, selectedField, currentScreen, currentCard, isFieldSelected } = useSelector((state) => state.remoteConfig);

+  // Create a wrapper function that properly calls the useCustomT hook
+  const customTranslate = (code) => useCustomT(code);
+
   const handleFieldClick = (field, screen, card) => {
     dispatch(selectField({ field, screen, card }));
   };

   return (
     <div>
-      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={useCustomT} />
+      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={customTranslate} />
       <SidePanelApp showPanelProperties={isFieldSelected && selectedField} />
     </div>
   );
 }

Note: This still has architectural concerns (calling a hook in a callback), but it's better than passing the hook identifier. Ideally, useCustomT should be refactored to return a memoized translate function at the component level, not be called per-key.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (4)

4-4: Remove unused import.

HTML5Backend isn't used in this component; the DnD backend is configured at the Provider level. Remove to avoid dead code.

Apply this diff:

-import { HTML5Backend } from "react-dnd-html5-backend";

27-41: Fix DnD reorder: incorrect drag item payload and missing cardIndex guard.

The drag item lacks cardIndex, which can cause cross-card reordering bugs. Additionally, the hover handler should guard against dragging items from different cards.

Apply this diff:

   const [, drop] = useDrop({
     accept: FIELD_TYPE,
     hover: (draggedItem) => {
+      if (draggedItem.cardIndex !== cardIndex) return;
       if (draggedItem.index !== fieldIndex) {
         moveField(draggedItem.index, fieldIndex, cardIndex);
         draggedItem.index = fieldIndex;
       }
     },
   });

   const [{ isDragging }, drag] = useDrag({
     type: FIELD_TYPE,
-    item: { index: fieldIndex },
+    item: { index: fieldIndex, cardIndex },
     collect: (monitor) => ({ isDragging: monitor.isDragging() }),
   });

47-56: Add basic a11y to drag handle.

Provide semantic attributes for keyboard navigation and assistive technology: role, tabIndex, and aria-grabbed.

Apply this diff:

-      <div className="drag-handle">
+      <div className="drag-handle" role="button" tabIndex={0} aria-grabbed={isDragging} aria-label="Drag to reorder">

64-66: Avoid recreating trivial handlers.

Passing new arrow functions every render can cause needless child re-renders. Pass the handlers directly.

Apply this diff:

-        onDelete={() => onDelete()}
-        onSelectField={() => onSelectField()}
-        onHide={() => onHide()}
+        onDelete={onDelete}
+        onSelectField={onSelectField}
+        onHide={onHide}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (5)

37-39: Use react-router's useLocation for SSR/test safety.

Accessing the global location can break in SSR or test environments. Use useLocation from react-router-dom for robustness.

Apply this diff:

+import { useLocation } from "react-router-dom";
 ...
-  const searchParams = new URLSearchParams(location.search);
+  const { search } = useLocation();
+  const searchParams = new URLSearchParams(search || "");
   const projectType = searchParams.get("prefix");
   const formId = searchParams.get("formId");

73-75: Add missing dependency to useCallback.

While dispatch is stable, it should be listed in the dependency array to avoid lint noise and future regressions.

Apply this diff:

-  const handleAddField = useCallback((currentCard, card) => {
-    dispatch(handleShowAddFieldPopup({ currentCard, card }));
-  }, []);
+  const handleAddField = useCallback(
+    (currentCard, card) => {
+      dispatch(handleShowAddFieldPopup({ currentCard, card }));
+    },
+    [dispatch]
+  );

102-102: Drop debug logging.

console.log will spam consoles and expose config data. Remove it before merging.

Apply this diff:

-  console.log("currentCard", currentCard);

118-137: Sanitize header field localization key generation.

Building keys from raw parts risks including spaces, special characters, or undefined values. Sanitize and uppercase for consistency.

Apply this diff:

             onChange={(event) => {
+              const headerKey = value
+                ? value
+                : [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
+                    .filter(Boolean)
+                    .map(s => s.trim())
+                    .join("_")
+                    .toUpperCase()
+                    .replace(/[^A-Z0-9_]/g, "_");
               if (value) {
                 dispatch(
                   updateLocalizationEntry({
                     code: value,
                     locale: currentLocale || "en_IN",
                     message: event.target.value,
                   })
                 );
               } else {
                 dispatch(
                   updateLocalizationEntry({
-                    code: `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`,
+                    code: headerKey,
                     locale: currentLocale || "en_IN",
                     message: event.target.value,
                   })
                 );
               }
               handleUpdateHeaderField(label, index);
             }}

178-178: Fix prop typo: variation should be "tertiary".

Current value "teritiary" will not map to a valid button style.

Apply this diff:

-              variation={"teritiary"}
+              variation={"tertiary"}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (8)

10-20: Preserve falsy values in getValueByPath.

Using || loses valid falsy values like false or 0. Use nullish coalescing (??) instead.

Apply this diff:

-  if (!path.includes(".")) return source?.[path] || defaultValue;
+  if (!path.includes(".")) return (source?.[path] ?? defaultValue);
   const keys = path.split(".");
   let value = source;
   for (const key of keys) {
     value = value?.[key];
     if (value === undefined || value === null) return defaultValue;
   }
-  return value;
+  return value ?? defaultValue;

154-165: Don't call hooks inside render branches; hoist translator.

useCustomT (a hook) is called inside the "text" case of a render switch, violating React's Rules of Hooks. Move the hook call to the top of the component.

Apply this diff:

 const RenderField = ({ panelItem, selectedField, onFieldChange, fieldType }) => {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentLocale } = useSelector((state) => state.localization);
+  const translate = useCustomT;

   ...

       case "text":
-        console.log("RENDERING TEXT", panelItem, getFieldValue(), selectedField);
         return (
           <FieldV1
             type="text"
             label={t(`FIELD_DRAWER_LABEL_${panelItem.label}`)}
-            value={useCustomT(getFieldValue())}
+            value={translate(getFieldValue())}
             onChange={(event) => handleFieldChangeWithLoc(getFieldValue(), event.target.value, panelItem)}
             placeholder={t(panelItem.innerLabel) || ""}
             populators={{ fieldPairClassName: "drawer-field" }}
           />
         );

67-87: Guard bindTo before using includes.

If panelItem lacks bindTo, .includes will throw. Add a guard at the beginning.

Apply this diff:

   const handleFieldChange = (value) => {
     const bindTo = panelItem.bindTo;
+    if (!bindTo) return;

     // Update the field with the code (or value if no localization)
     if (bindTo.includes(".")) {

Also apply the same guard in handleFieldChangeWithLoc at line 92.


90-90: Remove debug logs.

Strip console logs before merging.

Apply this diff:

-    console.log("HANDLE CHANGE", code, value, pI, selectedField);

Also remove the console.log at line 155 and line 283.


173-173: Specify radix for parseInt.

Avoid environment-dependent parsing by specifying base 10.

Apply this diff:

-            onChange={(event) => handleFieldChange(parseInt(event.target.value) || 0)}
+            onChange={(event) => handleFieldChange(parseInt(event.target.value, 10) || 0)}

184-205: Support dotted paths for conditional field updates.

Current code reads/writes only shallow keys. Use the same by-path logic as main fields to support nested properties.

Apply this diff:

       case "text":
         return (
           <FieldV1
             key={cField.bindTo}
             type="text"
             label={t(`FIELD_DRAWER_LABEL_${cField.label}`)}
-            value={selectedField[cField.bindTo]}
+            value={getValueByPath(selectedField, cField.bindTo, "")}
             onChange={(event) => {
+              const keys = cField.bindTo.split(".");
               const newField = { ...selectedField };
-              newField[cField.bindTo] = event.target.value;
+              let current = newField;
+              for (let i = 0; i < keys.length - 1; i++) {
+                if (!current[keys[i]]) current[keys[i]] = {};
+                current = current[keys[i]];
+              }
+              current[keys[keys.length - 1]] = event.target.value;
               onFieldChange(newField);
             }}
             placeholder={t(cField.innerLabel) || ""}
             populators={{ fieldPairClassName: "drawer-field" }}
           />
         );

227-227: Add explicit button type for accessibility/semantics.

Without type, buttons default to submit. Add type="button".

Apply this diff:

-        <button key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}>

235-244: Consolidate getFieldType utility.

This implementation diverges from the one in PanelFieldDisplay.js (default "text" vs "textInput"). Extract to a shared util and import to keep behavior consistent.

Consider extracting this logic into a shared utility file (e.g., utils/fieldTypeHelpers.js) and importing it here to ensure consistent behavior across the UI.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0dcfb4 and 5b4ad14.

⛔ Files ignored due to path filters (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.json is excluded by !**/*.json
📒 Files selected for processing (8)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
🧠 Learnings (1)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (8)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (1)
  • AppPreview (172-280)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • ComponentToRender (18-58)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)
  • moveField (45-50)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (1)
  • PanelFieldDisplay (64-102)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (3)
  • getFieldType (8-17)
  • matched (14-14)
  • fieldTypeMaster (66-66)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (3)
  • useTranslation (12-12)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (3)
  • localizedValue (8-8)
  • useTranslation (7-7)
  • HeaderFieldWrapper (6-24)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • useCustomT (4-32)
  • useCustomT (4-32)
  • dispatch (5-5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (3)
  • cardIndex (53-53)
  • fieldIndex (56-56)
  • fields (94-94)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1)
  • localizedLabel (26-26)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
  • localizedValue (23-23)
  • currentCard (41-43)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
  • useTranslation (29-29)
  • dispatch (30-30)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (5)
  • useTranslation (22-22)
  • useTranslation (222-222)
  • useTranslation (247-247)
  • dispatch (23-23)
  • dispatch (248-248)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (5)
  • getFieldType (235-244)
  • matched (241-241)
  • fieldTypeMaster (251-251)
  • useSelector (24-24)
  • useSelector (250-250)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (23-23)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js

[error] 35-46: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 35-46: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 227-227: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js

[error] 23-29: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 23-29: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 40-56: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 40-56: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 70-78: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 70-78: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🔇 Additional comments (9)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (4)

1-4: LGTM: Clean import refactoring.

The imports correctly reflect the refactoring to use ComponentToRender for field rendering.


6-9: LGTM: onFieldClick prop enables field interaction.

The new onFieldClick callback properly supports the field selection feature required for the app configuration workflow.


10-20: LGTM: Header rendering is correct.

The header rendering logic correctly uses data.headerFields and includes proper keys for mapped elements.


27-27: Verify filter logic change is intentional.

The filter logic changed from field.hidden == false || field.deleteFlag == true (old) to !field.hidden (new). This means fields marked for deletion (deleteFlag == true) will no longer be shown if they are also hidden. Confirm this aligns with the desired behavior for the new app configuration workflow.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)

1-16: LGTM: Imports and back handler are correct.

The component properly imports dependencies and implements the back button handler to deselect fields.


40-57: LGTM: Conditional rendering logic is correct.

The component correctly switches between field composer (with back button) and field screen wrapper based on the showPanelProperties prop. The DndProvider is properly placed to enable drag-and-drop functionality.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

16-18: LGTM: Field click handler is correct.

The handler properly dispatches the selectField action with all necessary context.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (2)

10-23: LGTM: Render logic is correct.

The component correctly handles conditional rendering of TextArea vs TextInput based on the type prop, with proper value binding and change handling.


25-25: LGTM: Proper memoization.

Using React.memo is appropriate for this presentational component to prevent unnecessary re-renders.

Comment on lines 6 to 9
const HeaderFieldWrapper = ({ label, type, value, onChange, currentCard }) => {
const { t } = useTranslation();
const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`);

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM: Value localization logic is correct.

The component properly uses useCustomT for localization with a fallback key pattern. The fallback key construction follows the expected i18n naming convention.

Optionally, for improved readability, consider extracting the fallback key:

const fallbackKey = `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`;
const localizedValue = useCustomT(value || fallbackKey);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
around lines 6 to 9, extract the fallback localization key into a named variable
for readability and reuse: compute fallbackKey from currentCard and label (e.g.
`${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`) and
then call useCustomT with value || fallbackKey so the component behavior is
unchanged but the code is clearer.

Comment on lines +23 to +29
<div
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
className="appConfigLabelField-toggleVisibility"
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix a11y: toggle control must be keyboard-accessible.

The visibility toggle div lacks keyboard accessibility.

 <div
+  role="button"
+  tabIndex={0}
+  aria-label="Toggle field visibility"
   onClick={(e) => {
     e.stopPropagation();
     onToggle();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onToggle();
+    }
+  }}
   className="appConfigLabelField-toggleVisibility"
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
className="appConfigLabelField-toggleVisibility"
>
<div
role="button"
tabIndex={0}
aria-label="Toggle field visibility"
onClick={(e) => {
e.stopPropagation();
onToggle();
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
onToggle();
}
}}
className="appConfigLabelField-toggleVisibility"
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 23-29: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 23-29: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

Comment on lines +40 to +56
<div
onClick={(e) => {
e.stopPropagation();
onDelete();
}}
style={{
cursor: "pointer",
fontWeight: "600",
marginLeft: "1rem",
fontSize: "1rem",
color: PRIMARY_COLOR,
display: "flex",
gap: "0.5rem",
alignItems: "center",
marginTop: "1rem",
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix a11y: delete control must be keyboard-accessible.

The delete button div lacks keyboard accessibility.

 <div
+  role="button"
+  tabIndex={0}
+  aria-label="Delete field"
   onClick={(e) => {
     e.stopPropagation();
     onDelete();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onDelete();
+    }
+  }}
   style={{
     cursor: "pointer",
     fontWeight: "600",
     marginLeft: "1rem",
     fontSize: "1rem",
     color: PRIMARY_COLOR,
     display: "flex",
     gap: "0.5rem",
     alignItems: "center",
     marginTop: "1rem",
   }}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={(e) => {
e.stopPropagation();
onDelete();
}}
style={{
cursor: "pointer",
fontWeight: "600",
marginLeft: "1rem",
fontSize: "1rem",
color: PRIMARY_COLOR,
display: "flex",
gap: "0.5rem",
alignItems: "center",
marginTop: "1rem",
}}
>
<div
role="button"
tabIndex={0}
aria-label="Delete field"
onClick={(e) => {
e.stopPropagation();
onDelete();
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
onDelete();
}
}}
style={{
cursor: "pointer",
fontWeight: "600",
marginLeft: "1rem",
fontSize: "1rem",
color: PRIMARY_COLOR,
display: "flex",
gap: "0.5rem",
alignItems: "center",
marginTop: "1rem",
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 40-56: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 40-56: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
around lines 40 to 56, the delete control is a plain div that is not
keyboard-accessible; replace or augment it to be keyboard operable by using a
semantic <button> (preferable) or at minimum add role="button", tabIndex={0}, an
onKeyDown handler that triggers the same delete action for Enter and Space, and
an appropriate aria-label; ensure styles and focus outline are preserved so
keyboard users can see focus.

Comment on lines +70 to +78
<div
ref={componentRef}
onClick={(e) => {
e.stopPropagation();
onSelectField();
}}
className="app-config-field-wrapper"
style={{}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix a11y: field selection wrapper must be keyboard-accessible.

The field selection wrapper div lacks keyboard accessibility.

 <div
   ref={componentRef}
+  role="button"
+  tabIndex={0}
+  aria-label={`Select field: ${label}`}
   onClick={(e) => {
     e.stopPropagation();
     onSelectField();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onSelectField();
+    }
+  }}
   className="app-config-field-wrapper"
   style={{}}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
ref={componentRef}
onClick={(e) => {
e.stopPropagation();
onSelectField();
}}
className="app-config-field-wrapper"
style={{}}
>
<div
ref={componentRef}
role="button"
tabIndex={0}
aria-label={`Select field: ${label}`}
onClick={(e) => {
e.stopPropagation();
onSelectField();
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
onSelectField();
}
}}
className="app-config-field-wrapper"
style={{}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 70-78: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 70-78: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
around lines 70 to 78, the field selection wrapper div is not
keyboard-accessible; make it act like an interactive control by adding
tabIndex={0}, role="button", and an onKeyDown handler that listens for Enter and
Space keys (invoke e.preventDefault(), e.stopPropagation(), and call
onSelectField()) so keyboard users can activate it; keep the existing onClick
and ref, and ensure any visual focus styles remain (or add a focus-visible
class) for accessibility.

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: 2

♻️ Duplicate comments (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (3)

1-1: Remove unused Fragment import.

The Fragment import is not used in this component.

Apply this diff:

-import React, { Fragment } from "react";
+import React from "react";

9-10: Remove unused constant.

MODULE_CONSTANTS is declared but never used in this component.

Apply this diff:

-const MODULE_CONSTANTS = "HCM-ADMIN-CONSOLE";
-

22-22: Critical: Don't pass a hook as a prop (violates Rules of Hooks).

useCustomT is a React hook (it calls useDispatch and useSelector internally), but you're passing it directly as the t prop to AppPreview. This violates the Rules of Hooks because hooks must be called at the top level of a component, not conditionally or as a prop reference.

Looking at useCustomT, it's designed to be called with a code parameter and return a translated string. To use it as a translation function, you need to create a wrapper:

+  const customT = React.useCallback((code) => {
+    return useCustomT(code);
+  }, []);
+
-      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={useCustomT} />
+      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={customT} />

However, this approach still won't work because you cannot call useCustomT inside a callback—it's a hook and must be called at the component's top level.

The correct solution is to refactor useCustomT to return a stable translation function instead of requiring the hook to be called per translation. Modify useCustomT in hooks/useCustomT.js:

export const useCustomT = () => {
  const dispatch = useDispatch();
  const { data: locState, currentLocale } = useSelector((state) => state.localization);
  
  return React.useCallback((code) => {
    if (!code || !Array.isArray(locState)) {
      return "";
    }
    
    const entry = locState?.find((item) => item.code === code);
    
    if (!entry) {
      const enabledModules = Digit?.SessionStorage.get("initData")?.languages || [];
      dispatch(addMissingKey({ code, enabledModules }));
      return "";
    }
    
    const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
    return entry[locale] || "";
  }, [locState, currentLocale, dispatch]);
};

Then call it properly in AppConfiguration:

+  const customT = useCustomT();
+
-      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={useCustomT} />
+      <AppPreview data={currentData} onFieldClick={handleFieldClick} selectedField={selectedField} t={customT} />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b4ad14 and 8066433.

📒 Files selected for processing (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧠 Learnings (1)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (3)
  • useTranslation (12-12)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)

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

♻️ Duplicate comments (12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (3)

14-20: Keyboard accessibility missing for visibility toggle.

The toggle control is not keyboard-accessible, preventing keyboard-only users from toggling field visibility.

Add keyboard support:

 <div
+  role="button"
+  tabIndex={0}
+  aria-label="Toggle field visibility"
   onClick={(e) => {
     e.stopPropagation();
     onToggle();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onToggle();
+    }
+  }}
   className="appConfigLabelField-toggleVisibility"
 >

31-47: Keyboard accessibility missing for delete control.

The delete button is not keyboard-accessible, preventing keyboard-only users from deleting fields.

Add keyboard support:

 <div
+  role="button"
+  tabIndex={0}
+  aria-label="Delete field"
   onClick={(e) => {
     e.stopPropagation();
     onDelete();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onDelete();
+    }
+  }}
   style={{

61-69: Keyboard accessibility missing for field selection.

The field selection wrapper is not keyboard-accessible, preventing keyboard-only users from selecting fields.

Add keyboard support:

 <div
   ref={componentRef}
+  role="button"
+  tabIndex={0}
+  aria-label={`Select field: ${label}`}
   onClick={(e) => {
     e.stopPropagation();
     onSelectField();
   }}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      e.stopPropagation();
+      onSelectField();
+    }
+  }}
   className="app-config-field-wrapper"
   style={{}}
 >
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (9)

79-79: Remove debug console.log statement.

Debug logging should be stripped before production.

-    console.log("HANDLE CHANGE", code, value, pI, selectedField);

144-144: Remove debug console.log statement.

Debug logging should be stripped before production.

-        console.log("RENDERING TEXT", panelItem, getFieldValue(), selectedField);

143-154: Critical: Hook called conditionally inside render.

useCustomT at line 149 is a React hook called inside a switch case, violating the Rules of Hooks. Hooks must be called at the top level of the component.

Hoist the hook to the top of RenderField:

 const RenderField = ({ panelItem, selectedField, onFieldChange, fieldType }) => {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentLocale } = useSelector((state) => state.localization);
+  const translate = useCustomT();

Then call the returned function in the value prop:

           <FieldV1
             type="text"
             label={t(`FIELD_DRAWER_LABEL_${panelItem.label}`)}
-            value={useCustomT(getFieldValue())}
+            value={translate(getFieldValue())}
             onChange={(event) => handleFieldChangeWithLoc(getFieldValue(), event.target.value, panelItem)}
             placeholder={t(panelItem.innerLabel) || ""}
             populators={{ fieldPairClassName: "drawer-field" }}
           />

Note: Based on the useCustomT implementation in hooks/useCustomT.js (lines 3-31 from relevant snippets), the hook expects a code parameter but also uses hooks internally. You need to refactor useCustomT to return a translator function instead of directly taking a code parameter, or use a different pattern that doesn't violate hook rules.


56-76: Guard bindTo before using .includes().

If panelItem.bindTo is undefined or null, calling .includes() at line 60 will throw a TypeError.

 const handleFieldChange = (value) => {
   const bindTo = panelItem.bindTo;
+  if (!bindTo) return;

78-129: Guard bindTo before using .includes().

If panelItem.bindTo is undefined or null, calling .includes() at line 113 will throw a TypeError.

 const handleFieldChangeWithLoc = (code, value, pI) => {
   console.log("HANDLE CHANGE", code, value, pI, selectedField);

   const bindTo = panelItem.bindTo;
+  if (!bindTo) return;
   let finalValueToSave;

156-166: Specify radix parameter for parseInt.

Missing radix can lead to unexpected parsing behavior (octal interpretation for strings starting with "0").

-            onChange={(event) => handleFieldChange(parseInt(event.target.value) || 0)}
+            onChange={(event) => handleFieldChange(parseInt(event.target.value, 10) || 0)}

173-194: Support nested paths in conditional field updates.

Conditional fields use shallow property access (selectedField[cField.bindTo]) at lines 181 and 184, but should support dotted paths like the main field logic does (lines 60-75, 113-128).

Use the same by-path logic:

       case "text":
         return (
           <FieldV1
             key={cField.bindTo}
             type="text"
             label={t(`FIELD_DRAWER_LABEL_${cField.label}`)}
-            value={selectedField[cField.bindTo]}
+            value={getFieldValueByPath(selectedField, cField.bindTo, "")}
             onChange={(event) => {
+              const bindTo = cField.bindTo;
+              if (!bindTo) return;
               const newField = { ...selectedField };
-              newField[cField.bindTo] = event.target.value;
+              if (bindTo.includes(".")) {
+                const keys = bindTo.split(".");
+                let current = newField;
+                for (let i = 0; i < keys.length - 1; i++) {
+                  if (!current[keys[i]]) current[keys[i]] = {};
+                  current = current[keys[i]];
+                }
+                current[keys[keys.length - 1]] = event.target.value;
+              } else {
+                newField[bindTo] = event.target.value;
+              }
               onFieldChange(newField);
             }}
             placeholder={t(cField.innerLabel) || ""}
             populators={{ fieldPairClassName: "drawer-field" }}
           />
         );

209-222: Add explicit type="button" to button element.

Without an explicit type, the button defaults to "submit", which can cause unintended form submissions.

       <button 
+        type="button"
         key={tab} 
         className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} 
         onClick={() => onTabChange(tab)}
       >
         {t(`TAB_${tab.toUpperCase()}`)}
       </button>

261-261: Remove debug console.log statement.

Debug logging should be stripped before production.

-  console.log("NABEEL", panelProperties, panelConfig, tabs, currentTabProperties, fieldType, visibleTabProperties);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8066433 and 287f097.

📒 Files selected for processing (6)
  • health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/fieldTypeHelpers.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/objectHelpers.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/objectHelpers.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/fieldTypeHelpers.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
🧬 Code graph analysis (4)
health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/fieldTypeHelpers.js (3)
  • getFieldTypeFromMasterData (7-16)
  • getFieldTypeFromMasterData (7-16)
  • matched (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (23-23)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/fieldTypeHelpers.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (23-23)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
  • fieldTypeMaster (229-229)
  • useSelector (13-13)
  • useSelector (228-228)
health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js (2)
  • getFieldTypeFromMasterData (7-16)
  • getFieldTypeFromMasterData (7-16)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
  • dispatch (5-5)
  • useCustomT (4-32)
  • useCustomT (4-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/objectHelpers.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js (2)
  • getFieldTypeFromMasterData (7-16)
  • getFieldTypeFromMasterData (7-16)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js

[error] 14-20: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 14-20: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 31-47: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 31-47: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 61-69: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 61-69: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 216-216: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js (1)

1-1: Update import path if duplication is resolved.

If getFieldTypeFromMasterData is deduplicated (by removing the local copy and importing from src/helpers/fieldTypeHelpers.js), this re-export will need to be updated to reflect the new import path.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/objectHelpers.js (1)

8-18: Verify falsy value handling is intentional.

Line 10 uses source?.[path] || defaultValue, which will return defaultValue for any falsy value (0, false, "", null, undefined). If the source object legitimately contains falsy values that should not trigger the default, this logic will incorrectly replace them.

If falsy values should be preserved, update the guard:

-  if (!path.includes(".")) return source?.[path] || defaultValue;
+  if (!path.includes(".")) {
+    const value = source?.[path];
+    return value !== undefined && value !== null ? value : defaultValue;
+  }

Similarly, consider whether line 15's check should distinguish between undefined/null and other falsy values depending on your use case.

health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js (1)

7-16: LGTM - canonical version of the helper.

This is the original location for the helper. The duplicate in NewAppConfiguration/helpers/fieldTypeHelpers.js should be removed and imports should reference this canonical version.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (1)

7-7: Update import path after duplication fix.

Once getFieldTypeFromMasterData duplication is resolved, this import should reference the canonical version from ../../../helpers/fieldTypeHelpers instead of the local helpers directory.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (2)

1-9: LGTM - Clean imports and setup.

The imports are well-organized and the helper functions are appropriately imported from shared modules.


224-321: Component structure looks good.

The main component properly uses Redux state, implements tab navigation, and handles field visibility filtering. The memoization of computed values (fieldType, visibleTabProperties) is appropriate for performance.

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

♻️ Duplicate comments (24)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (4)

61-61: Guard against missing bindTo before using includes.

If panelItem.bindTo is undefined or null, calling .includes will throw a TypeError. Add a guard at the start of both handleFieldChange and handleFieldChangeWithLoc.

Apply this diff to add guards:

 const handleFieldChange = (value) => {
   const bindTo = panelItem.bindTo;
+  if (!bindTo) return;

   // Update the field with the code (or value if no localization)
   if (bindTo.includes(".")) {

And similarly for handleFieldChangeWithLoc:

 const handleFieldChangeWithLoc = (code, value) => {
   const bindTo = panelItem.bindTo;
+  if (!bindTo) return;
   let finalValueToSave;

Based on past review comments.

Also applies to: 83-83


163-163: Specify radix parameter for parseInt.

Without an explicit radix, parseInt can produce unexpected results (e.g., treating strings starting with "0" as octal in older environments).

Apply this diff:

-            onChange={(event) => handleFieldChange(parseInt(event.target.value) || 0)}
+            onChange={(event) => handleFieldChange(parseInt(event.target.value, 10) || 0)}

Based on past review comments.


182-186: Support dotted paths in conditional field updates.

The conditional field change handler uses shallow property access (selectedField[cField.bindTo]), but bindTo may contain dotted paths like "metadata.label". Use the same nested path logic as the main fields.

Apply this diff to support nested paths:

-            value={selectedField[cField.bindTo]}
+            value={getFieldValueByPath(selectedField, cField.bindTo, "")}
             onChange={(event) => {
+              const bindTo = cField.bindTo;
+              if (!bindTo) return;
               const newField = { ...selectedField };
-              newField[cField.bindTo] = event.target.value;
+              if (bindTo.includes(".")) {
+                const keys = bindTo.split(".");
+                let current = newField;
+                for (let i = 0; i < keys.length - 1; i++) {
+                  if (!current[keys[i]]) current[keys[i]] = {};
+                  current = current[keys[i]];
+                }
+                current[keys[keys.length - 1]] = event.target.value;
+              } else {
+                newField[bindTo] = event.target.value;
+              }
               onFieldChange(newField);
             }}

Based on past review comments.


217-217: Add explicit type="button" to tab buttons.

Without an explicit type attribute, buttons default to type="submit", which can cause unintended form submissions. This is both an accessibility and semantics issue.

Apply this diff:

-        <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>

Based on past review comments and static analysis.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (1)

1-10: Code duplication: consolidate to canonical helper location.

This function is identical to getFieldTypeFromMasterData already present in health/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.js. Maintaining duplicate logic violates DRY principles and creates unnecessary maintenance overhead.

Remove this duplicate and re-export from the canonical location:

-export const getFieldTypeFromMasterData = (field, fieldTypeMasterData) => {
-  if (!fieldTypeMasterData || !Array.isArray(fieldTypeMasterData)) {
-    return "text";
-  }
-
-  // Find matching field type based on type and format
-  const matched = fieldTypeMasterData.find((item) => item?.metadata?.type === field.type && item?.metadata?.format === field.format);
-
-  return matched?.fieldType || "text";
-};
+export { getFieldTypeFromMasterData } from "../../../helpers/fieldTypeHelpers";
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)

13-15: Gate dummy fallback to non-production environments.

The unconditional return of dummyFieldTypeConfig for FieldTypeMappingConfig will ship dummy data to production, masking real MDMS failures.

-      if (name === "FieldTypeMappingConfig") {
+      if (name === "FieldTypeMappingConfig" && process.env.NODE_ENV !== "production") {
         return dummyFieldTypeConfig;
       }

Apply the same guard at lines 43-46 in the catch block.


39-40: Normalize response shape to array.

Downstream consumers expect an array but MDMS may return a singleton object or null, risking runtime errors.

-      const data = response?.MdmsRes?.[moduleName]?.[name] || [];
-      return data;
+      const raw = response?.MdmsRes?.[moduleName]?.[name];
+      const data = Array.isArray(raw) ? raw : raw ? [raw] : [];
+      return data;

52-80: Consider per-master status tracking.

The current implementation uses a single global status and error that flip with every request, causing last-write-wins behavior. This can display incorrect loading/error states when multiple masters are fetched concurrently.

For more granular UI control, consider tracking statusByName and errorByName keyed by master name.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)

5-37: useCustomT still accepts code parameter, risking misuse in render loops.

While useMemo improves this implementation, the hook signature useCustomT(code) encourages calling it directly in JSX or loops (e.g., value={useCustomT(someCode)}), which violates React's rules of hooks when code changes conditionally.

The codebase already provides useCustomTranslate() (lines 40-73) that returns a stable translator function—the recommended pattern.

Option 1 (Recommended): Refactor useCustomT to match the pattern in useCustomTranslate:

  • Return a translator function instead of accepting code param
  • Update useFieldDataLabel to call the returned function
  • Migrate call sites to use const t = useCustomT(); t(code)

Option 2: Deprecate useCustomT(code) in favor of useCustomTranslate() and update all consumers to use the function-returning hook.

Based on learnings: Translation functions in FormComposer are handled internally, so they don't require inclusion in dependency arrays.


76-79: useFieldDataLabel calls hook with dynamic parameter.

This helper calls useCustomT(label) with a dynamic value, which means the hook is invoked with different arguments on each call. This is safe here since it's at the top level, but the pattern is fragile.

Refactor to use the function-returning pattern:

 export const useFieldDataLabel = (label) => {
-  const customT = useCustomT(label || "");
-  return label ? customT : "";
+  const t = useCustomTranslate();
+  return label ? t(label) : "";
 };
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (9)

7-17: Missing currentData in initialState.

currentData is assigned in initializeConfig (line 21) and used throughout the slice but is not declared in initialState. This will cause undefined reference errors until initializeConfig is dispatched.

 initialState: {
   remoteData: null,
+  currentData: null,
   parentData: [],

19-22: Object aliasing creates mutation hazard.

Both remoteData and currentData point to the same dummyConfig object. Mutations to currentData will corrupt remoteData, breaking undo/reset functionality.

 initializeConfig(state) {
-  state.remoteData = dummyConfig;
-  state.currentData = dummyConfig || {};
+  state.remoteData = structuredClone(dummyConfig);
+  state.currentData = structuredClone(dummyConfig);
 },

26-28: Wrong fallback type for currentData.

Fallback to [] conflicts with the expected object shape (code accesses .cards elsewhere). This will cause runtime errors.

 setCurrentData(state, action) {
-  state.currentData = action.payload || [];
+  state.currentData = action.payload ?? null;
 },

49-67: Prototype pollution risk and redundant double-write.

Using for..in can traverse prototype properties, enabling prototype pollution attacks. Additionally, updating both selectedField and the same object in currentData.cards is redundant since they reference the same object under Immer.

 updateSelectedField(state, action) {
   if (!state.selectedField || !action?.payload) return;
-  const updates = action.payload;
   const { cardIndex, fieldIndex } = state.selectedFieldPath;

-  // Update selected field
-  for (const key in updates) {
-    state.selectedField[key] = updates[key];
-  }
-
-  // Also update the field in currentData using stored path (O(1) instead of O(n*m))
-  if (state.currentData?.cards && cardIndex !== null && cardIndex !== -1 && fieldIndex !== null && fieldIndex !== -1) {
-    for (const key in updates) {
-      state.currentData.cards[cardIndex].fields[fieldIndex][key] = updates[key];
-    }
-    // Ensure reactivity by creating new reference
-    state.currentData = { ...state.currentData };
-  }
+  // Update selected field (Immer tracks the mutation)
+  for (const [key, value] of Object.entries(action.payload)) {
+    if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
+    state.selectedField[key] = value;
+  }
 },

116-129: Non-deterministic ID generation breaks reducer purity.

crypto.randomUUID() inside a reducer makes it non-deterministic, breaking Redux DevTools time-travel and reproducibility.

Use RTK's prepare callback with nanoid:

+import { createSlice, nanoid } from "@reduxjs/toolkit";

-    addField(state, action) {
+    addField: {
+      reducer(state, action) {
-      const { cardIndex, fieldData } = action.payload;
-      if (state.currentData && state.currentData.cards && state.currentData.cards[cardIndex]) {
-        const newField = {
-          ...fieldData,
-          id: crypto.randomUUID(),
-          deleteFlag: true,
-          active: true,
-        };
-        state.currentData.cards[cardIndex].fields.push(newField);
-        // Ensure reactivity by creating new reference
-        state.currentData = { ...state.currentData };
-      }
+        const { cardIndex, fieldData } = action.payload;
+        const cards = state.currentData?.cards;
+        if (!cards || !cards[cardIndex]) return;
+        const newField = { ...fieldData, deleteFlag: true, active: true };
+        cards[cardIndex].fields.push(newField);
+      },
+      prepare({ cardIndex, fieldData }) {
+        return { payload: { cardIndex, fieldData: { ...fieldData, id: nanoid() } } };
+      },
     },

69-76: Missing bounds checks and selection state cleanup.

deleteField splices without validating indices, risking removal of unintended elements. It also fails to clear selection state if the deleted field was selected.

 deleteField(state, action) {
-  const { fieldIndex, cardIndex } = action.payload;
-  if (state.currentData && state.currentData.cards && state.currentData.cards[cardIndex]) {
-    state.currentData.cards[cardIndex].fields.splice(fieldIndex, 1);
-    // Ensure reactivity by creating new reference
-    state.currentData = { ...state.currentData };
-  }
+  const { fieldIndex, cardIndex } = action.payload || {};
+  const cards = state.currentData?.cards;
+  if (!cards || !Number.isInteger(cardIndex) || !cards[cardIndex]) return;
+  const fields = cards[cardIndex].fields || [];
+  if (!Number.isInteger(fieldIndex) || fieldIndex < 0 || fieldIndex >= fields.length) return;
+  const toDelete = fields[fieldIndex];
+  fields.splice(fieldIndex, 1);
+  if (state.selectedField === toDelete) {
+    state.selectedField = null;
+    state.selectedFieldPath = { cardIndex: null, fieldIndex: null };
+    state.currentScreen = null;
+    state.currentCard = null;
+    state.isFieldSelected = false;
+  }
 },

90-107: reorderFields: incorrect bounds and index adjustment.

The current bounds check rejects moving to the end (toIndex === fields.length) and fails to adjust the target index when moving forward.

 reorderFields(state, action) {
-  const { cardIndex, fromIndex, toIndex } = action.payload;
-  if (state.currentData && state.currentData.cards && state.currentData.cards[cardIndex]) {
-    const fields = state.currentData.cards[cardIndex].fields;
-    if (fromIndex >= 0 && toIndex >= 0 && fromIndex < fields.length && toIndex < fields.length) {
-      const [movedField] = fields.splice(fromIndex, 1);
-      fields.splice(toIndex, 0, movedField);
-
-      // Update order property
-      fields.forEach((field, index) => {
-        field.order = index + 1;
-      });
-
-      // Ensure reactivity by creating new reference
-      state.currentData = { ...state.currentData };
-    }
-  }
+  const { cardIndex, fromIndex, toIndex } = action.payload || {};
+  const cards = state.currentData?.cards;
+  if (!cards || !Number.isInteger(cardIndex) || !cards[cardIndex]) return;
+  const fields = cards[cardIndex].fields || [];
+  const len = fields.length;
+  if (!Number.isInteger(fromIndex) || !Number.isInteger(toIndex)) return;
+  if (fromIndex < 0 || fromIndex >= len || toIndex < 0 || toIndex > len || fromIndex === toIndex) return;
+  const [moved] = fields.splice(fromIndex, 1);
+  const insertAt = toIndex > fromIndex ? toIndex - 1 : toIndex;
+  fields.splice(insertAt, 0, moved);
+  fields.forEach((f, i) => (f.order = i + 1));
 },

65-65: Remove unnecessary reactivity spreads.

Immer (used by Redux Toolkit) already produces immutable updates. Manual shallow copies like state.currentData = { ...state.currentData } are redundant and add noise.

Remove this pattern from all reducers (lines 65, 74, 87, 104, 127, 157, 171, 179). Let Immer handle immutability automatically.


185-189: Remove placeholder comments.

These TODO-style comments for unimplemented slices can confuse maintainers. The related slices (fieldMasterSlice, fieldPanelPropertiesSlice, localizationSlice) already exist as separate files in this PR.

-// slice to store field type
-
-// slice to store field panel properties
-
-// slice to store localisation data
-
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)

38-38: Use the already-resolved fieldType variable.

Line 38 calls getFieldTypeFromMasterData(field) again without the fieldTypeMasterData parameter, causing it to return the default "text" instead of using the already-computed fieldType from line 14. This can misclassify custom fields.

Apply this diff:

-      required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required}
+      required={fieldType === "custom" ? null : field?.required}

40-40: Preserve boolean and falsy values correctly.

The current code field?.value === true ? "" : field?.value || "" coerces true to an empty string and converts 0 or false to "", losing semantic information. For checkbox fields, boolean values should be preserved.

Apply this diff:

-      value={field?.value === true ? "" : field?.value || ""}
+      value={field?.value ?? ""}

Using nullish coalescing (??) preserves 0, false, and other falsy values while only defaulting null or undefined to "".


25-25: Apply internationalization to labels for MDMS fields.

The label prop currently passes raw field?.label without translation. When field?.isMdms is true, the label should be translated like the other text properties (description, error, infoMessage).

Apply this diff:

-      label={field?.label}
+      label={field?.isMdms ? t(field?.label) : field?.label}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)

54-56: Use react-router's useLocation for SSR-safe routing.

Accessing the global location object directly can break in SSR and test environments. Import and use useLocation from react-router-dom instead.

Apply this diff:

+import { useLocation } from "react-router-dom";
...
-  const searchParams = new URLSearchParams(location.search);
+  const { search } = useLocation();
+  const searchParams = new URLSearchParams(search);
   const projectType = searchParams.get("prefix");
   const formId = searchParams.get("formId");

192-192: Fix typo in button variation prop.

The value "teritiary" is misspelled and won't map to a valid button style.

Apply this diff:

-              variation={"teritiary"}
+              variation={"tertiary"}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 287f097 and b7dbb5b.

⛔ Files ignored due to path filters (3)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullData.json is excluded by !**/*.json
📒 Files selected for processing (11)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
🧠 Learnings (2)
📚 Learning: 2025-02-05T10:18:29.947Z
Learnt from: Tulika-eGov
PR: egovernments/DIGIT-Frontend#2188
File: micro-ui/web/micro-ui-internals/packages/modules/pgr/src/pages/employee/new-inbox.js:34-47
Timestamp: 2025-02-05T10:18:29.947Z
Learning: In the DIGIT-Frontend codebase, translations within FormComposer are handled internally by the component itself, so the translation function 't' does not need to be added to useMemo dependency arrays even when used within the memoized function that generates form configurations.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (136-140)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (2)
  • store (7-14)
  • store (7-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • AppConfigurationWrapper (13-249)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomT (5-37)
  • useCustomT (5-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (3)
  • useCustomT (12-37)
  • useCustomT (12-37)
  • currentLocale (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (4)
  • useCustomT (3-18)
  • useCustomT (3-18)
  • entry (12-12)
  • currentLocale (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (2)
  • locale (8-8)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (52-52)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (5)
  • existing (266-266)
  • contextPath (274-274)
  • url (275-275)
  • response (277-295)
  • data (296-296)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • existing (126-126)
  • mdmsContext (62-62)
  • url (63-63)
  • response (39-43)
  • response (65-83)
  • response (96-96)
  • data (84-84)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • mdmsContext (18-18)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1)
  • customT (77-77)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (2)
  • useSelector (10-10)
  • t (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-10)
  • getFieldTypeFromMasterData (1-10)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 217-217: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)

6-12: LGTM!

Clean implementation of a Redux store provider. The component correctly wraps AppConfigurationWrapper with the Redux Provider, establishing the store context for the entire New App Configuration feature.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js (1)

1-2: LGTM! Clean barrel export pattern.

The helper index cleanly re-exports the utility functions for centralized access.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

1-29: LGTM! All previous concerns addressed.

The component correctly:

  • Uses useCustomTranslate() hook at component level and passes the stable t function
  • Memoizes handleFieldClick with proper dependencies
  • Connects to Redux state cleanly
  • Is properly memoized for performance
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)

88-90: handleAddField dependencies look correct.

The dispatch dependency is properly included in the useCallback array. Good work addressing the previous feedback.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (1)

13-16: Fix falsy value handling in traversal.

The current null/undefined check at line 15 will incorrectly return defaultValue when intermediate values are 0, false, or "" (empty string), which are valid object keys or intermediate values in some scenarios.

Apply this diff to fix the traversal:

   for (const key of keys) {
     value = value?.[key];
-    if (value === undefined || value === null) return defaultValue;
+    if (value == null) return defaultValue;
   }

The loose equality == null checks both null and undefined while preserving 0, false, and "" as valid intermediate values.

Likely an incorrect or invalid review comment.

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: 12

♻️ Duplicate comments (14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)

262-266: Breadcrumb should include new-app-configuration-redesign.

Update crumb visibility to reflect the new route.

       show: pathVar === "app-modules" || pathVar === "app-configuration-redesign" ? true : false,
+      // include new route
+      show: pathVar === "app-modules" || pathVar === "app-configuration-redesign" || pathVar === "new-app-configuration-redesign" ? true : false,
@@
-      show: pathVar === "app-configuration-redesign" ? true : false,
+      show: pathVar === "app-configuration-redesign" || pathVar === "new-app-configuration-redesign" ? true : false,

Also applies to: 273-276

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (6)

20-21: Use MDMS V1 context for v1/_search calls.

Pass V1 context, not V2, to thunks hitting /v1/_search.

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
@@
-        getFieldMaster({
+        getFieldMaster({
           tenantId,
           moduleName: MODULE_CONSTANTS,
           name: fieldMasterName,
-          mdmsContext: mdmsContext,
+          mdmsContext: mdmsV1Context,
           limit: 10000,
         })
@@
-      getFieldPanelMaster({
+      getFieldPanelMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
         name: "FieldPropertiesPanelConfig",
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })

Also applies to: 75-85, 89-96


98-119: Dispatch setLocalizationData only after fetch succeeds.

Current call likely uses empty/stale data.

     if (localeModule) {
       dispatch(
         fetchLocalization({
           tenantId,
           localeModule,
           enabledModules: [currentLocale],
         })
       );
-
-      dispatch(fetchAppScreenConfig({ tenantId }));
-
-      // Set localization context data
-      dispatch(
-        setLocalizationData({
-          localisationData: localizationData,
-          currentLocale,
-          enabledModules: [currentLocale],
-          localeModule,
-        })
-      );
+      dispatch(fetchAppScreenConfig({ tenantId }));
     }
   }, [dispatch, flow, pageName, fieldMasterName, localeModule, tenantId, mdmsContext, currentLocale]);
+
+  useEffect(() => {
+    if (localeModule && localizationStatus === "succeeded" && Array.isArray(localizationData)) {
+      dispatch(
+        setLocalizationData({
+          localisationData: localizationData,
+          currentLocale,
+          enabledModules: [currentLocale],
+          localeModule,
+        })
+      );
+    }
+  }, [dispatch, localeModule, localizationStatus, localizationData, currentLocale]);

25-26: Fix null spread for newFieldType.

Initialize as object and guard spreads.

-  const [newFieldType, setNewFieldType] = useState(null);
+  const [newFieldType, setNewFieldType] = useState({});
@@
-    setNewFieldType((prev) => ({
-      ...prev,
+    setNewFieldType((prev) => ({
+      ...(prev || {}),
       label: locVal,
     }));
@@
-                  const updatedData = { ...newFieldType, field: value };
+                  const updatedData = { ...(newFieldType || {}), field: value };
                   setNewFieldType(updatedData);

Also applies to: 164-167, 273-276


41-53: Guard metadata spread to avoid spreading undefined.

Default to empty object.

-      ...selectedFieldType?.metadata, // Include any metadata from field type
+      ...(selectedFieldType?.metadata || {}), // Include any metadata from field type

75-86: Ensure field type mapping is available when query param is absent.

Add fallback fetch for “FieldTypeMappingConfig”.

     if (fieldMasterName) {
       dispatch(
         getFieldMaster({
           tenantId,
           moduleName: MODULE_CONSTANTS,
           name: fieldMasterName,
-          mdmsContext: mdmsContext,
+          mdmsContext: mdmsV1Context,
           limit: 10000,
         })
       );
     }
+    if (!fieldMasterName) {
+      dispatch(
+        getFieldMaster({
+          tenantId,
+          moduleName: MODULE_CONSTANTS,
+          name: "FieldTypeMappingConfig",
+          mdmsContext: mdmsV1Context,
+          limit: 10000,
+        })
+      );
+    }

34-34: Remove debug logs.

Clean production console noise.

-  console.log("currentDatacurrentData", currentData, actualState)
@@
-      console.log("Navigating to previous page:", currentData.previousRoute);
@@
-    console.log("Submitting current data:", { ...currentData, roles: addedRoles.length > 0 ? addedRoles : currentData.roles });
@@
-      console.log("Navigating to next page:", currentData.nextRoute);

Also applies to: 130-135, 143-145, 147-149

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (7)

7-18: Add missing currentData to initialState.

The currentData property is referenced throughout the reducers but is not initialized in initialState. This will cause issues when selectors or components try to access it before initializeConfig runs.

Apply this diff:

 initialState: {
   remoteData: null,
+  currentData: null,
   parentData: [],
   pageType: "object", // "object" or "template"

20-31: Avoid aliasing remoteData and currentData.

Lines 23-24 and 27-28 assign the same object reference to both state.remoteData and state.currentData. This means mutations to currentData will affect remoteData, breaking the separation between pristine and working copies.

Apply this diff to create independent copies:

 initializeConfig(state, action) {
   const pageConfig = action.payload;
   if (pageConfig) {
-    state.remoteData = pageConfig;
-    state.currentData = pageConfig;
+    state.remoteData = JSON.parse(JSON.stringify(pageConfig));
+    state.currentData = JSON.parse(JSON.stringify(pageConfig));
     state.pageType = pageConfig.type || "object"; // Extract pageType from config
   } else {
-    state.remoteData = dummyConfig;
-    state.currentData = dummyConfig || {};
+    state.remoteData = JSON.parse(JSON.stringify(dummyConfig));
+    state.currentData = JSON.parse(JSON.stringify(dummyConfig || {}));
     state.pageType = dummyConfig?.type || "object";
   }
 },

78-85: Add bounds checks and clear selection in deleteField.

The reducer splices without validating indices, which can remove unintended elements. It also doesn't clear selection state if the deleted field was currently selected.

Apply this diff:

 deleteField(state, action) {
-  const { fieldIndex, cardIndex } = action.payload;
-  if (state.currentData && state.currentData.cards && state.currentData.cards[cardIndex]) {
+  const { fieldIndex, cardIndex } = action.payload || {};
+  const cards = state.currentData?.cards;
+  if (!cards || !Number.isInteger(cardIndex) || !cards[cardIndex]) return;
+  const fields = cards[cardIndex].fields || [];
+  if (!Number.isInteger(fieldIndex) || fieldIndex < 0 || fieldIndex >= fields.length) return;
+  
+  const toDelete = fields[fieldIndex];
-    state.currentData.cards[cardIndex].fields.splice(fieldIndex, 1);
-    // Ensure reactivity by creating new reference
-    state.currentData = { ...state.currentData };
+  fields.splice(fieldIndex, 1);
+  
+  // Clear selection if deleted field was selected
+  if (state.selectedField === toDelete) {
+    state.selectedField = null;
+    state.selectedFieldPath = { cardIndex: null, fieldIndex: null };
+    state.currentScreen = null;
+    state.currentCard = null;
+    state.isFieldSelected = false;
   }
 },

125-138: Replace non-deterministic ID generation with RTK prepare callback.

Using crypto.randomUUID() inside a reducer breaks determinism and makes time-travel debugging impossible. Use Redux Toolkit's prepare callback pattern with nanoid() instead.

Apply these diffs:

First, add the import at the top:

-import { createSlice } from "@reduxjs/toolkit";
+import { createSlice, nanoid } from "@reduxjs/toolkit";

Then refactor the reducer:

-    addField(state, action) {
-      const { cardIndex, fieldData } = action.payload;
-      if (state.currentData && state.currentData.cards && state.currentData.cards[cardIndex]) {
-        const newField = {
-          ...fieldData,
-          id: crypto.randomUUID(),
-          deleteFlag: true,
-          active: true,
-        };
-        state.currentData.cards[cardIndex].fields.push(newField);
-        // Ensure reactivity by creating new reference
-        state.currentData = { ...state.currentData };
-      }
+    addField: {
+      reducer(state, action) {
+        const { cardIndex, fieldData } = action.payload;
+        const cards = state.currentData?.cards;
+        if (!cards || !cards[cardIndex]) return;
+        const fields = cards[cardIndex].fields || (cards[cardIndex].fields = []);
+        const newField = {
+          ...fieldData,
+          deleteFlag: true,
+          active: true,
+          order: fields.length + 1,
+        };
+        fields.push(newField);
+      },
+      prepare({ cardIndex, fieldData }) {
+        return { payload: { cardIndex, fieldData: { ...fieldData, id: nanoid() } } };
+      },
     },

35-37: Fix incorrect fallback type.

Line 36 falls back to [] (array), but currentData is used as an object throughout the code (e.g., state.currentData.cards). This type mismatch will cause runtime errors.

Apply this diff:

 setCurrentData(state, action) {
-  state.currentData = action.payload || [];
+  state.currentData = action.payload ?? null;
 },

202-206: Remove placeholder comments.

The placeholder comments for other slices (field type, field panel properties, localization) should be removed or replaced with actual implementations if those slices exist elsewhere.

Apply this diff:

 });

-// slice to store field type
-
-// slice to store field panel properties
-
-// slice to store localisation data
-
 export const {

58-76: Fix prototype pollution vulnerability in updateSelectedField.

Lines 64 and 70 use for...in loops without checking hasOwnProperty, allowing potentially dangerous keys like __proto__, constructor, or prototype to pollute the object prototype chain.

Apply this diff:

 updateSelectedField(state, action) {
   if (!state.selectedField || !action?.payload) return;
   const updates = action.payload;
   const { cardIndex, fieldIndex } = state.selectedFieldPath;

   // Update selected field
-  for (const key in updates) {
+  for (const key of Object.keys(updates)) {
+    if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
     state.selectedField[key] = updates[key];
   }

   // Also update the field in currentData using stored path (O(1) instead of O(n*m))
   if (state.currentData?.cards && cardIndex !== null && cardIndex !== -1 && fieldIndex !== null && fieldIndex !== -1) {
-    for (const key in updates) {
+    for (const key of Object.keys(updates)) {
+      if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
       state.currentData.cards[cardIndex].fields[fieldIndex][key] = updates[key];
     }
-    // Ensure reactivity by creating new reference
-    state.currentData = { ...state.currentData };
   }
 },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7dbb5b and 6812f30.

⛔ Files ignored due to path filters (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfig.json is excluded by !**/*.json
📒 Files selected for processing (8)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
🧠 Learnings (2)
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (3)
micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/CreateChecklist.js (1)
  • flow (29-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (137-141)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js (1)
micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/CreateChecklist.js (1)
  • flow (29-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (76-79)
  • useFieldDataLabel (76-79)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js (2)
  • getPageFromConfig (8-31)
  • getPageFromConfig (8-31)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (2)
  • getFieldMaster (5-49)
  • getFieldMaster (5-49)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)
  • getFieldPanelMaster (262-306)
  • getFieldPanelMaster (262-306)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (137-141)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (2)
  • store (7-14)
  • store (7-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • AppConfigurationWrapper (15-306)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (61-61)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (3)
  • t (209-209)
  • LayoutRenderer (206-503)
  • LayoutRenderer (206-503)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • BOUNDARY_HIERARCHY_TYPE (89-104)
  • hierarchyData (106-106)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)
  • FullConfigWrapper (5-193)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 126-135: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 126-135: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 144-152: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 144-152: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 167-176: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 167-176: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js

[error] 62-62: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 284-301: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 284-301: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 317-337: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 317-337: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

⏰ Context from checks skipped due to timeout of 10000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

1-36: LGTM! All past review issues have been addressed.

The component is now clean and follows React best practices:

  • Proper hook usage with useCustomTranslate() called at component level
  • Memoized callback with useCallback for handleFieldClick
  • Clean conditional rendering based on pageType
  • No unused imports, props, or debug statements

Comment on lines +137 to +141
const AppConfigurationStore = lazyWithFallback(
() => import(/* webpackChunkName: "app-configuration-store" */ "./NewAppConfiguration/AppConfigurationStore"),
() => require("./NewAppConfiguration/AppConfigurationStore").default,
{ loaderText: "Loading App Configuration..." }
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove unused lazy component or wire it.

AppConfigurationStore is declared but not used in routes. Either use it or drop the lazy import to avoid bundle bloat.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
around lines 137-141, AppConfigurationStore is lazily imported but never used;
either remove this unused lazy import to prevent bundle bloat, or wire it into
the component routing where the other lazy components are registered (add an
entry in the routes/components array for the Employee page that references
AppConfigurationStore with the same loaderText and chunkName), and remove any
dead imports/exports that become unused as a result.

Comment on lines 126 to 135
<div
key={index}
style={{
...styles.flowItem,
backgroundColor: selectedFlow === flow.id ? "#e3f2fd" : "transparent",
color: selectedFlow === flow.id ? "#1976d2" : "#333",
fontWeight: selectedFlow === flow.id ? "600" : "500",
}}
onClick={() => handleFlowClick(flow)}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make flow items accessible (role + keyboard).

Clickable div needs role and key handling.

-            <div
+            <div
               key={index}
               style={{
                 ...styles.flowItem,
                 backgroundColor: selectedFlow === flow.id ? "#e3f2fd" : "transparent",
                 color: selectedFlow === flow.id ? "#1976d2" : "#333",
                 fontWeight: selectedFlow === flow.id ? "600" : "500",
               }}
-              onClick={() => handleFlowClick(flow)}
+              onClick={() => handleFlowClick(flow)}
+              role="button"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handleFlowClick(flow);
+                }
+              }}
             >

[Based on static analysis hints]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
key={index}
style={{
...styles.flowItem,
backgroundColor: selectedFlow === flow.id ? "#e3f2fd" : "transparent",
color: selectedFlow === flow.id ? "#1976d2" : "#333",
fontWeight: selectedFlow === flow.id ? "600" : "500",
}}
onClick={() => handleFlowClick(flow)}
>
<div
key={index}
style={{
...styles.flowItem,
backgroundColor: selectedFlow === flow.id ? "#e3f2fd" : "transparent",
color: selectedFlow === flow.id ? "#1976d2" : "#333",
fontWeight: selectedFlow === flow.id ? "600" : "500",
}}
onClick={() => handleFlowClick(flow)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleFlowClick(flow);
}
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 126-135: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 126-135: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 126 to 135, the clickable div lacks accessibility semantics and
keyboard support; add role="button" and tabIndex={0}, and implement an onKeyDown
handler that triggers handleFlowClick(flow) when Enter or Space is pressed (also
prevent default for Space), and include an appropriate aria-pressed or
aria-selected attribute tied to selectedFlow to convey state to assistive
technologies.

Comment on lines 144 to 160
<div
key={index}
style={{
...styles.roleItem,
backgroundColor: addedRoles.includes(role) ? "#e8f5e9" : "#f5f5f5",
color: addedRoles.includes(role) ? "#2e7d32" : "#555",
}}
onClick={() => handleRoleToggle(role)}
>
<input
type="checkbox"
style={styles.checkbox}
checked={addedRoles.includes(role)}
onChange={() => handleRoleToggle(role)}
/>
<span>{role}</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix double-toggle bug and add a11y for roles.

Row onClick plus checkbox onChange toggles twice; also missing role/keyboard.

-            <div
+            <div
               key={index}
               style={{
                 ...styles.roleItem,
                 backgroundColor: addedRoles.includes(role) ? "#e8f5e9" : "#f5f5f5",
                 color: addedRoles.includes(role) ? "#2e7d32" : "#555",
               }}
-              onClick={() => handleRoleToggle(role)}
+              onClick={() => handleRoleToggle(role)}
+              role="checkbox"
+              aria-checked={addedRoles.includes(role)}
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handleRoleToggle(role);
+                }
+              }}
             >
               <input
                 type="checkbox"
                 style={styles.checkbox}
                 checked={addedRoles.includes(role)}
-                onChange={() => handleRoleToggle(role)}
+                onClick={(e) => e.stopPropagation()}
+                onChange={(e) => e.stopPropagation()}
               />
               <span>{role}</span>
             </div>

[Based on static analysis hints]

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 144-152: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 144-152: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 144 to 160, the row div and the checkbox both call handleRoleToggle
which causes double toggles and the role item lacks keyboard/accessibility
support; fix by removing the duplicate toggle (have only one control trigger
state change) — either remove the div onClick and use the checkbox onChange, or
keep the div onClick and have the checkbox onChange stop propagation and no-op;
add proper a11y attributes to the clickable row (role="checkbox", aria-checked
based on addedRoles.includes(role), tabIndex={0}) and handle key presses (toggle
on Enter/Space in onKeyDown) so keyboard users can toggle roles.

Comment on lines 284 to 301
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
style={{
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: depth > 0 ? "4px" : "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
cursor: "pointer",
transition: "all 0.2s ease",
width: "100%",
boxSizing: "border-box",
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make interactive container accessible (role + keyboard).

Clickable div should expose role and key handlers.

-      <div
+      <div
         key={`${field.componentName}-${index}-${depth}`}
-        onClick={(e) => {
+        onClick={(e) => {
           e.stopPropagation();
           onFieldClick?.(field);
         }}
+        role="button"
+        tabIndex={0}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            e.stopPropagation();
+            onFieldClick?.(field);
+          }
+        }}
         style={{

[Based on static analysis hints]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
style={{
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: depth > 0 ? "4px" : "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
cursor: "pointer",
transition: "all 0.2s ease",
width: "100%",
boxSizing: "border-box",
}}
>
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
onFieldClick?.(field);
}
}}
style={{
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: depth > 0 ? "4px" : "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
cursor: "pointer",
transition: "all 0.2s ease",
width: "100%",
boxSizing: "border-box",
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 284-301: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 284-301: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

Comment on lines 317 to 337
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
style={{
display: "flex",
flexDirection: isRow ? "row" : "column",
gap: "8px",
border: isSelected ? "2px solid #0B4B66" : "1px dashed #d0d0d0",
borderRadius: "4px",
padding: "12px",
margin: "8px 0",
backgroundColor: isSelected ? "#f0f8ff" : "rgba(250, 250, 250, 0.5)",
minHeight: "60px",
width: "100%",
position: "relative",
boxSizing: "border-box",
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add role and keyboard support for layout container selection.

Same a11y issue for Row/Column containers.

-      <div
+      <div
         key={`${field.componentName}-${index}-${depth}`}
-        onClick={(e) => {
+        onClick={(e) => {
           e.stopPropagation();
           onFieldClick?.(field);
         }}
+        role="button"
+        tabIndex={0}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            e.stopPropagation();
+            onFieldClick?.(field);
+          }
+        }}
         style={{

[Based on static analysis hints]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
style={{
display: "flex",
flexDirection: isRow ? "row" : "column",
gap: "8px",
border: isSelected ? "2px solid #0B4B66" : "1px dashed #d0d0d0",
borderRadius: "4px",
padding: "12px",
margin: "8px 0",
backgroundColor: isSelected ? "#f0f8ff" : "rgba(250, 250, 250, 0.5)",
minHeight: "60px",
width: "100%",
position: "relative",
boxSizing: "border-box",
}}
>
<div
key={`${field.componentName}-${index}-${depth}`}
onClick={(e) => {
e.stopPropagation();
onFieldClick?.(field);
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
e.stopPropagation();
onFieldClick?.(field);
}
}}
style={{
display: "flex",
flexDirection: isRow ? "row" : "column",
gap: "8px",
border: isSelected ? "2px solid #0B4B66" : "1px dashed #d0d0d0",
borderRadius: "4px",
padding: "12px",
margin: "8px 0",
backgroundColor: isSelected ? "#f0f8ff" : "rgba(250, 250, 250, 0.5)",
minHeight: "60px",
width: "100%",
position: "relative",
boxSizing: "border-box",
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 317-337: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 317-337: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

state.currentData.cards[cardIndex].fields[fieldIndex][key] = updates[key];
}
// Ensure reactivity by creating new reference
state.currentData = { ...state.currentData };
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary reactivity spreads.

Immer (used by Redux Toolkit) automatically handles immutable updates. The manual shallow copies like state.currentData = { ...state.currentData } are redundant and add noise without benefit.

Remove these lines from the following reducers:

  • updateSelectedField (line 74)
  • deleteField (line 83)
  • hideField (line 96)
  • reorderFields (line 113)
  • addField (line 136)
  • addSection (line 166)
  • updateHeaderField (line 180)
  • updateActionLabel (line 188)
  • updatePageRoles (line 196)

Example for updateHeaderField:

 updateHeaderField(state, action) {
   const { cardIndex, fieldIndex, value } = action.payload;
   if (
     state.currentData &&
     state.currentData.cards &&
     state.currentData.cards[cardIndex] &&
     state.currentData.cards[cardIndex].headerFields &&
     state.currentData.cards[cardIndex].headerFields[fieldIndex]
   ) {
     state.currentData.cards[cardIndex].headerFields[fieldIndex].value = value;
-    // Ensure reactivity by creating new reference
-    state.currentData = { ...state.currentData };
   }
 },

Also applies to: 83-83, 96-96, 113-113, 136-136, 166-166, 180-180, 188-188, 196-196

Comment on lines +9 to +11
if (!fullParentConfig || !fullParentConfig.flows || !flow || !pageName) {
console.warn("Invalid parameters for getPageFromConfig:", { fullParentConfig, flow, pageName });
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid logging full config objects; gate warnings for production.

Logging fullParentConfig can expose sensitive data and flood consoles. Keep messages concise and guard with NODE_ENV.

Apply:

-  if (!fullParentConfig || !fullParentConfig.flows || !flow || !pageName) {
-    console.warn("Invalid parameters for getPageFromConfig:", { fullParentConfig, flow, pageName });
+  if (!fullParentConfig || !fullParentConfig.flows || !flow || !pageName) {
+    if (process.env.NODE_ENV !== "production") {
+      console.warn("Invalid parameters for getPageFromConfig");
+    }
     return null;
   }
@@
-  if (!targetFlow) {
-    console.warn(`Flow with id "${flow}" not found in fullParentConfig`);
+  if (!targetFlow) {
+    if (process.env.NODE_ENV !== "production") {
+      console.warn(`Flow with id "${flow}" not found`);
+    }
     return null;
   }
@@
-  if (!targetPage) {
-    console.warn(`Page with name "${pageName}" not found in flow "${flow}"`);
+  if (!targetPage) {
+    if (process.env.NODE_ENV !== "production") {
+      console.warn(`Page "${pageName}" not found in flow "${flow}"`);
+    }
     return null;
   }

Also applies to: 18-19, 25-27

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js
around lines 9-11 (and similarly at 18-19 and 25-27), the code logs the entire
fullParentConfig which may expose sensitive data and should be suppressed in
production; change the warnings to only include minimal identifiers (e.g., flow
and pageName or a simple message) and wrap them with a NODE_ENV check (only emit
console warnings when process.env.NODE_ENV !== 'production'), or use the
project’s logger at warn level without dumping full objects.

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: 3

♻️ Duplicate comments (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)

97-99: Use react-router's useLocation for SSR/test safety.

Accessing the global location object can break in SSR or test environments. Use react-router's useLocation hook instead.

Apply this diff:

+import { useLocation } from "react-router-dom";
@@
-  const searchParams = new URLSearchParams(location.search);
+  const { search } = useLocation();
+  const searchParams = new URLSearchParams(search || "");
   const projectType = searchParams.get("prefix");
   const formId = searchParams.get("formId");

205-205: Fix button variation typo.

The value "teritiary" is misspelled and won't map to a valid button style. Should be "tertiary".

Apply this diff:

-              variation={"teritiary"}
+              variation={"tertiary"}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)

251-251: Specify radix 10 for parseInt.

Missing radix can lead to environment-dependent parsing (e.g., octal for strings starting with "0").

Apply this diff:

-            const value = parseInt(event.target.value) || 0;
+            const value = parseInt(event.target.value, 10) || 0;

340-340: Add explicit type="button" attribute.

Without an explicit type, buttons default to type="submit", which can trigger unintended form submissions.

Apply this diff:

-        <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>

267-296: Support dotted paths in conditional field updates.

The conditional field change handlers use shallow property access (selectedField[cField.bindTo]), but main fields support dotted paths like "metadata.format". For consistency, conditional fields should use the same path-based logic.

After extracting ConditionalField as a separate component (per the critical issue above), update it to support dotted paths:

 const ConditionalField = React.memo(({ cField, selectedField, onFieldChange }) => {
   const { t } = useTranslation();
-  const [conditionalLocalValue, setConditionalLocalValue] = useState(selectedField[cField.bindTo] || "");
+  const [conditionalLocalValue, setConditionalLocalValue] = useState(
+    getFieldValueByPath(selectedField, cField.bindTo, "")
+  );
   const conditionalDebounceRef = useRef(null);

   useEffect(() => {
-    setConditionalLocalValue(selectedField[cField.bindTo] || "");
+    setConditionalLocalValue(getFieldValueByPath(selectedField, cField.bindTo, ""));
   }, [selectedField, cField.bindTo]);

   const handleConditionalChange = useCallback(
     (value) => {
       if (conditionalDebounceRef.current) {
         clearTimeout(conditionalDebounceRef.current);
       }
       conditionalDebounceRef.current = setTimeout(() => {
-        const newField = { ...selectedField };
-        newField[cField.bindTo] = value;
-        onFieldChange(newField);
+        const bindTo = cField.bindTo;
+        if (bindTo.includes(".")) {
+          const keys = bindTo.split(".");
+          const newField = { ...selectedField };
+          let current = newField;
+          for (let i = 0; i < keys.length - 1; i++) {
+            if (!current[keys[i]]) current[keys[i]] = {};
+            current = current[keys[i]];
+          }
+          current[keys[keys.length - 1]] = value;
+          onFieldChange(newField);
+        } else {
+          onFieldChange({ ...selectedField, [bindTo]: value });
+        }
       }, 800);
     },
     [selectedField, cField.bindTo, onFieldChange]
   );

   const handleConditionalBlur = useCallback(() => {
     if (conditionalDebounceRef.current) {
       clearTimeout(conditionalDebounceRef.current);
-      const newField = { ...selectedField };
-      newField[cField.bindTo] = conditionalLocalValue;
-      onFieldChange(newField);
+      const bindTo = cField.bindTo;
+      if (bindTo.includes(".")) {
+        const keys = bindTo.split(".");
+        const newField = { ...selectedField };
+        let current = newField;
+        for (let i = 0; i < keys.length - 1; i++) {
+          if (!current[keys[i]]) current[keys[i]] = {};
+          current = current[keys[i]];
+        }
+        current[keys[keys.length - 1]] = conditionalLocalValue;
+        onFieldChange(newField);
+      } else {
+        onFieldChange({ ...selectedField, [bindTo]: conditionalLocalValue });
+      }
     }
   }, [selectedField, cField.bindTo, conditionalLocalValue, onFieldChange]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6812f30 and 7530a16.

📒 Files selected for processing (3)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
🧬 Code graph analysis (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomT (5-37)
  • useCustomT (5-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • useCustomT (5-37)
  • useCustomT (5-37)
  • dispatch (6-6)
  • dispatch (41-41)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (8)
  • localValue (14-14)
  • debounceTimerRef (15-15)
  • handleChange (54-67)
  • dispatch (11-11)
  • currentLocale (12-12)
  • handleBlur (69-76)
  • useTranslation (10-10)
  • HeaderFieldWrapper (9-105)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (8)
  • useTranslation (93-93)
  • dispatch (94-94)
  • currentLocale (96-96)
  • useSelector (95-95)
  • localValue (22-22)
  • debounceTimerRef (23-23)
  • handleChange (39-59)
  • handleBlur (61-75)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (10)
  • useTranslation (11-11)
  • useTranslation (335-335)
  • useTranslation (349-349)
  • dispatch (12-12)
  • dispatch (350-350)
  • useSelector (13-13)
  • useSelector (352-352)
  • localValue (16-16)
  • debounceTimerRef (17-17)
  • handleBlur (202-214)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • cardIndex (42-42)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 40-40: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 43-43: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 52-52: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 105-105: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 169-169: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 202-202: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 267-267: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 268-268: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 270-270: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 274-274: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 289-289: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 340-340: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)

19-90: LGTM: FooterLabelField component is well-structured.

The component correctly implements debounced localization updates with proper cleanup and blur-immediate dispatch. The memoization prevents unnecessary re-renders.


103-137: LGTM: Event handlers properly memoized with correct dependencies.

All callbacks correctly include dispatch in their dependency arrays and are properly memoized.

const { t } = useTranslation();
const dispatch = useDispatch();
const currentLocale = useSelector((state) => state.localization.currentLocale);
const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize localization key generation to prevent malformed keys.

Building the localization key directly from currentCard properties can produce invalid keys like "undefined_undefined_undefined_ScreenHeading" when properties are missing.

Apply this diff to sanitize the key:

-  const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`);
+  const fallbackKey = [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
+    .filter(Boolean)
+    .map(part => String(part).trim())
+    .join("_")
+    .toUpperCase()
+    .replace(/[^A-Z0-9_]/g, "_");
+  const localizedValue = useCustomT(value || fallbackKey);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`);
const fallbackKey = [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
.filter(Boolean)
.map(part => String(part).trim())
.join("_")
.toUpperCase()
.replace(/[^A-Z0-9_]/g, "_");
const localizedValue = useCustomT(value || fallbackKey);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
around line 13, the localization key is built directly from currentCard
properties which can produce malformed keys like
"undefined_undefined_undefined_ScreenHeading"; instead, construct the key by
collecting the parts [currentCard.flow, currentCard.parent, currentCard.name],
filter out missing or falsy values, sanitize each remaining part (trim,
remove/replace whitespace and unsafe characters), then join with underscores and
append the label as a guaranteed suffix; use the resulting safeKey (or a
fallback like label alone) when calling useCustomT to ensure no "undefined"
fragments or illegal characters appear in the localization key.

Comment on lines 31 to 52
const dispatchUpdates = useCallback((newValue) => {
// Update localization
if (value) {
dispatch(
updateLocalizationEntry({
code: value,
locale: currentLocale || "en_IN",
message: newValue,
})
);
} else {
dispatch(
updateLocalizationEntry({
code: `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`,
locale: currentLocale || "en_IN",
message: newValue,
})
);
}
// Update header field in Redux
dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: label }));
}, [value, currentCard, label, currentLocale, cardIndex, index, dispatch]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect value passed to updateHeaderField and sanitize localization key.

Two issues:

  1. Line 44: Unsanitized key generation creates malformed keys when currentCard properties are undefined.
  2. Line 51: Passing value: label updates the header field with the field label (e.g., "ScreenHeading") instead of the localization code, breaking localization lookups.

Apply this diff:

  const dispatchUpdates = useCallback((newValue) => {
+   const fallbackCode = [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
+     .filter(Boolean)
+     .map(part => String(part).trim())
+     .join("_")
+     .toUpperCase()
+     .replace(/[^A-Z0-9_]/g, "_");
+   const code = value || fallbackCode;
+
    // Update localization
-   if (value) {
-     dispatch(
-       updateLocalizationEntry({
-         code: value,
-         locale: currentLocale || "en_IN",
-         message: newValue,
-       })
-     );
-   } else {
-     dispatch(
-       updateLocalizationEntry({
-         code: `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`,
-         locale: currentLocale || "en_IN",
-         message: newValue,
-       })
-     );
-   }
+   dispatch(
+     updateLocalizationEntry({
+       code,
+       locale: currentLocale || "en_IN",
+       message: newValue,
+     })
+   );
    // Update header field in Redux
-   dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: label }));
+   dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: code }));
  }, [value, currentCard, label, currentLocale, cardIndex, index, dispatch]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dispatchUpdates = useCallback((newValue) => {
// Update localization
if (value) {
dispatch(
updateLocalizationEntry({
code: value,
locale: currentLocale || "en_IN",
message: newValue,
})
);
} else {
dispatch(
updateLocalizationEntry({
code: `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`,
locale: currentLocale || "en_IN",
message: newValue,
})
);
}
// Update header field in Redux
dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: label }));
}, [value, currentCard, label, currentLocale, cardIndex, index, dispatch]);
const dispatchUpdates = useCallback((newValue) => {
const fallbackCode = [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
.filter(Boolean)
.map(part => String(part).trim())
.join("_")
.toUpperCase()
.replace(/[^A-Z0-9_]/g, "_");
const code = value || fallbackCode;
// Update localization
dispatch(
updateLocalizationEntry({
code,
locale: currentLocale || "en_IN",
message: newValue,
})
);
// Update header field in Redux
dispatch(
updateHeaderField({
cardIndex,
fieldIndex: index,
value: code,
})
);
}, [value, currentCard, label, currentLocale, cardIndex, index, dispatch]);
🤖 Prompt for AI Agents
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
lines 31-52: the dispatch creates an unsanitized localization key when
currentCard props are undefined and then incorrectly passes label to
updateHeaderField; compute a sanitized localization key once (e.g., build from
[currentCard?.flow, currentCard?.parent, currentCard?.name, label], filter out
falsy parts, join with '_' and strip/replace unsafe characters like spaces or
non-word chars) and use that key for both the fallback code in
updateLocalizationEntry and as the value sent to updateHeaderField (i.e., set
value to value || sanitizedKey) so the header field stores the localization
code, not the label.

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

♻️ Duplicate comments (11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)

305-312: Normalize FieldPropertiesPanelConfig response shape.

MDMS returns an array but the slice stores dummy data as an object. When the real fetch succeeds we end up with an array in state, breaking consumers that expect the object shape your fallback provided (panelProperties.FieldPropertiesPanelConfig.content, etc.). Normalize the payload by picking the first element (or {}) for this master, and leave other masters as arrays to keep the state consistent.


312-313: Fix the error copy to reference the correct slice.

This slice manages the field panel master, yet the fallback message still says “field master,” which is misleading while debugging.


349-350: Export the right action name to avoid runtime undefined.

The reducer registers clearFieldPanelMasterData, but you export clearFieldMasterData. Any importer will receive undefined and crash when invoked. Export the matching action name.

-export const { clearFieldMasterData } = fieldPanelMasterSlice.actions;
+export const { clearFieldPanelMasterData } = fieldPanelMasterSlice.actions;
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)

247-255: Specify the radix when parsing numbers.

parseInt(event.target.value) can misinterpret strings in some environments; pass a radix of 10 (or use Number(...)) so numeric fields behave predictably.

-              const value = parseInt(event.target.value) || 0;
+              const value = parseInt(event.target.value, 10) || 0;

333-385: Handle dotted bindTo paths in conditional fields.

Conditional field configs such as "required.message" or "pattern.message" never display or persist because this component only reads/writes shallow keys (selectedField[cField.bindTo]). Use the same path-aware helpers you use elsewhere (getFieldValueByPath and a matching setter) so nested properties resolve correctly.


403-407: Add an explicit button type for accessibility.

Without type="button" the tab element defaults to submit, which is incorrect in this context and violates the lint rule (a11y/useButtonType).

-        <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button
+          type="button"
+          key={tab}
+          className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`}
+          onClick={() => onTabChange(tab)}
+        >
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)

22-43: Restore localized labels and preserve falsy field values.

This component now always renders field?.label and forces false/0 to "", so checkboxes lose their checked state and numeric fields lose 0. Please reinstate the previous behaviour: translate MDMS-driven labels, suppress the label for custom/checkbox/button field types, reuse the computed fieldType, and use nullish coalescing so false/0 survive.

-      label={field?.label}
+      label={
+        ["checkbox", "button", "custom"].includes(fieldType)
+          ? null
+          : field?.isMdms
+          ? t(field?.label)
+          : field?.label ?? null
+      }
@@
-      required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required}
-      type={fieldType}
-      value={field?.value === true ? "" : field?.value || ""}
+      required={fieldType === "custom" ? null : field?.required}
+      type={fieldType}
+      value={fieldType === "checkbox" ? Boolean(field?.value) : field?.value ?? ""}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)

200-208: Correct the variation spelling.

"teritiary" isn’t a supported variant, so the button falls back to the default style. Use "tertiary" to get the intended styling.

-              variation={"teritiary"}
+              variation={"tertiary"}
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)

55-65: Add stable keys when rendering footer actions.

The footer loop emits buttons without a key, causing React warnings and unstable diffing. Provide a deterministic key (id/jsonPath/label + index).

-            data?.footer?.map((footer_item) => {
-              return (
-                <Button
+            data?.footer?.map((footer_item, footerIndex) => {
+              const footerKey = footer_item?.id ?? footer_item?.jsonPath ?? `${footer_item?.type ?? "action"}-${footerIndex}`;
+              return (
+                <Button
+                  key={footerKey}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (2)

133-145: Generate IDs outside the reducer to keep it pure.

Calling crypto.randomUUID() inside addField makes the reducer impure and non-deterministic. Use a prepare callback (with nanoid or crypto there) so the ID is generated before the reducer runs.


20-37: Avoid aliasing config state and ensure the fallback shape is correct.

initializeConfig assigns the same object to both remoteData and currentData, so any edit to the working copy mutates the supposed “pristine” copy. On top of that, setCurrentData falls back to [], which breaks later .cards reads. Clone the payload (or dummyConfig) for each state branch, and default to null (or a minimal object) instead of an array.

    initializeConfig(state, action) {
-      const pageConfig = action.payload;
-      if (pageConfig) {
-        state.remoteData = pageConfig;
-        state.currentData = pageConfig;
+      const src = action.payload ?? dummyConfig;
+      if (src) {
+        const clone = structuredClone ? structuredClone(src) : JSON.parse(JSON.stringify(src));
+        state.remoteData = structuredClone ? structuredClone(src) : JSON.parse(JSON.stringify(src));
+        state.currentData = clone;
         state.pageType = pageConfig.type || "object"; // Extract pageType from config
       } else {
-        state.remoteData = dummyConfig;
-        state.currentData = dummyConfig || {};
-        state.pageType = dummyConfig?.type || "object";
+        state.remoteData = null;
+        state.currentData = null;
+        state.pageType = "object";
       }
     },
@@
-      state.currentData = action.payload || [];
+      state.currentData = action.payload ?? null;

(Adjust cloning helper to your preference.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7530a16 and 8096660.

📒 Files selected for processing (8)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js (9 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js
🧠 Learnings (1)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (71-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (3)
  • t (209-209)
  • LayoutRenderer (206-503)
  • LayoutRenderer (206-503)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • useCustomT (5-37)
  • useCustomT (5-37)
  • dispatch (6-6)
  • dispatch (41-41)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (8)
  • localValue (14-14)
  • debounceTimerRef (15-15)
  • handleChange (54-67)
  • dispatch (11-11)
  • currentLocale (12-12)
  • handleBlur (69-76)
  • useTranslation (10-10)
  • HeaderFieldWrapper (9-105)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (5)
  • existing (9-9)
  • contextPath (17-17)
  • url (18-18)
  • response (20-38)
  • data (39-39)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomT (5-37)
  • useCustomT (5-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (1)
  • AppPreview (172-280)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1)
  • MobileBezelFrame (17-42)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • ComponentToRender (7-46)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1)
  • customT (77-77)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (2)
  • useSelector (11-11)
  • t (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-10)
  • getFieldTypeFromMasterData (1-10)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (2)
  • searchParams (97-97)
  • projectType (98-98)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js (2)
  • searchParams (244-244)
  • state (240-240)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (3)
  • searchParams (354-354)
  • useT (341-341)
  • state (344-344)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • location (291-291)
  • projectType (159-159)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 284-289: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 41-41: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 44-44: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 53-53: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 106-106: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 170-170: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 203-203: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 263-263: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 264-264: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 404-404: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js

[error] 35-46: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 35-46: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 58-64: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js

[error] 205-205: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js (2)

90-94: LGTM: Improved error handling consistency.

The changes to standardize pattern validation error messaging using translation keys and remove the console.warn in favor of direct error return improve code consistency and production readiness.


201-201: LGTM: Improved null safety for label prop.

Explicitly providing null as a fallback when the label is falsy improves predictability and null safety.

Comment on lines 35 to 49
<div
className={`app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
}`}
key={fieldIndex}
onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
<CheckBox
mainClassName={"app-config-checkbox-main"}
labelClassName={`app-config-checkbox-label ${field?.["toArray.required"] ? "required" : ""}`}
onChange={(e) => {}}
value={""}
label={t(field?.label)}
isLabelFirst={false}
disabled={field?.readOnly || false}
/>
<ComponentToRender field={field} t={t} selectedField={selectedField} />
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the field wrapper keyboard-accessible.

This div is clickable but lacks role, tabIndex, and keyboard handlers, triggering the a11y lints. Add role="button", tabIndex={0}, and handle Enter/Space so keyboard users can select fields.

-                      <div
-                        key={fieldIndex}
-                        onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
+                      <div
+                        key={fieldIndex}
+                        role="button"
+                        tabIndex={0}
+                        aria-label={t(field?.label) || "Select field"}
+                        onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
+                        onKeyDown={(event) => {
+                          if (event.key === "Enter" || event.key === " ") {
+                            event.preventDefault();
+                            onFieldClick && onFieldClick(field, data, card, index, fieldIndex);
+                          }
+                        }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
className={`app-preview-field-pair ${
selectedField?.jsonPath && selectedField?.jsonPath === field?.jsonPath
? `app-preview-selected`
: selectedField?.id && selectedField?.id === field?.id
? `app-preview-selected`
: ``
}`}
key={fieldIndex}
onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
<CheckBox
mainClassName={"app-config-checkbox-main"}
labelClassName={`app-config-checkbox-label ${field?.["toArray.required"] ? "required" : ""}`}
onChange={(e) => {}}
value={""}
label={t(field?.label)}
isLabelFirst={false}
disabled={field?.readOnly || false}
/>
<ComponentToRender field={field} t={t} selectedField={selectedField} />
</div>
);
<div
key={fieldIndex}
role="button"
tabIndex={0}
aria-label={t(field?.label) || "Select field"}
onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onFieldClick && onFieldClick(field, data, card, index, fieldIndex);
}
}}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
}}
>
<ComponentToRender field={field} t={t} selectedField={selectedField} />
</div>
🧰 Tools
🪛 Biome (2.1.2)

[error] 35-46: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 35-46: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
around lines 35 to 49, the div wrapper for each field is clickable but not
keyboard-accessible; add role="button" and tabIndex={0}, and implement an
onKeyDown handler that listens for Enter (key === "Enter") and Space (key === "
" or key === "Spacebar") to call the same onFieldClick handler (and
preventDefault for Space to avoid page scroll); ensure the handler checks
onFieldClick exists and reuses the same parameters used by onClick so keyboard
users can activate the field.

}) => {
const { t } = useTranslation();
const { state: appState, setFieldError, clearFieldError } = useAppConfigContext();
const useT = useCustomT();
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

Address static analysis false positive.

The static analysis tool flags line 205 as a conditional hook call, but this is a false positive. useT is a translation function returned by the useCustomT() hook (called at the top level on line 156), not a hook itself. The analyzer is confused by the "use" prefix.

Consider renaming useT to something like translateCustom or tCustom to avoid confusion with hook naming conventions.

Also applies to: 205-205


🏁 Script executed:

#!/bin/bash
# Verify useCustomT returns a function, not a hook
rg -nP --type=js -C5 'export.*useCustomT|const useCustomT' -g '!node_modules/**'

Length of output: 148


🏁 Script executed:

#!/usr/bin/env bash
rg -n -C3 "useCustomT" -g '!node_modules/**'

Length of output: 148


🏁 Script executed:

#!/usr/bin/env bash
rg -n "useCustomT" -C3

Length of output: 76814


Rename local translator variable to avoid false positive
The static analyzer misidentifies useT(...) as a hook call; since useT is the function returned by useCustomT() (a real hook), rename it (e.g. tCustom or translate) to align with hook naming conventions and silence the warning.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js
around line 156, the local variable is named useT which the static analyzer
mistakes for a hook; rename the returned translator variable to a
non-hook-prefixed name like tCustom or translate and update all its usages in
this file accordingly so it no longer begins with "use", then run lint/CI to
confirm the false positive is resolved.

Comment on lines +203 to +209
value={
isLocalisable
? useT(drawerState?.[cField?.bindTo])
: drawerState?.[cField?.bindTo] === true
? ""
: drawerState?.[cField?.bindTo]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting value calculation logic.

The multi-line ternary is explicit but still complex. Consider extracting this to a helper function for improved readability.

+const getFieldValue = (field, drawerState, isLocalisable, useT) => {
+  const boundValue = drawerState?.[field?.bindTo];
+  if (isLocalisable) {
+    return useT(boundValue);
+  }
+  if (boundValue === true) {
+    return "";
+  }
+  return boundValue;
+};
+
 <FieldV1
   type={cField?.type}
   label={cField?.label || null}
   withoutLabel={Boolean(!cField?.label)}
-  value={
-    isLocalisable
-      ? useT(drawerState?.[cField?.bindTo])
-      : drawerState?.[cField?.bindTo] === true
-      ? ""
-      : drawerState?.[cField?.bindTo]
-  }
+  value={getFieldValue(cField, drawerState, isLocalisable, useT)}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 205-205: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js
around lines 203-209, extract the multi-line ternary that computes the input
value into a small helper function (e.g., getFieldValue(drawerState, cField,
isLocalisable, useT)) placed above the component or in a nearby utils area;
implement it to return useT(drawerState?.[cField?.bindTo]) when isLocalisable is
true, return "" when drawerState?.[cField?.bindTo] === true, otherwise return
drawerState?.[cField?.bindTo]; then replace the inline ternary with
value={getFieldValue(drawerState, cField, isLocalisable, useT)} to improve
readability.

Comment on lines +294 to +296
item?.name
? item?.name
: `${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_${item?.code}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting localization key generation.

The localization key generation pattern (${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_...) appears multiple times. Consider extracting this to a helper function to reduce duplication.

+const generateLocalizationKey = (projectType, state, ...parts) => {
+  return `${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_${parts.join('_')}`;
+};

 updateLocalization(
-  item?.name
-    ? item?.name
-    : `${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_${item?.code}`,
+  item?.name || generateLocalizationKey(projectType, state, item?.code),
   Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage,
   event.target.value
 );
 return {
   ...i,
-  name: item?.name
-    ? item?.name
-    : `${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_${item?.code}`,
+  name: item?.name || generateLocalizationKey(projectType, state, item?.code),
 };

Also applies to: 302-304

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/RenderConditionalField.js
around lines 294-296 (and similarly 302-304), the repeated localization key
construction
`${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_...`
should be extracted into a reusable helper; create a small function (e.g.,
buildLocalizationKey(projectType, state, suffix)) that composes projectType,
state.currentScreen.parent and state.currentScreen.name with the given suffix,
replace inline template strings at the noted lines to call this helper, and
ensure it handles undefined/null parts consistently (preserve existing optional
chaining behavior).

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

♻️ Duplicate comments (19)
health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js (1)

58-63: Use snake_case for the i18n key.

Good progress on addressing the past review: the hard-coded query params have been removed from the link. However, the i18n key still contains spaces. Use snake_case to align with existing i18n key conventions.

Apply this diff to fix the i18n key:

     {
-      label: t("NEW APP CONFIGURATION"),
+      label: t("NEW_APP_CONFIGURATION"),
       link: `/${window?.contextPath}/employee/campaign/new-app-configuration-redesign`,
       roles: ROLES.CAMPAIGN_MANAGER,
       // count: isLoading?"-":data
     },

Add NEW_APP_CONFIGURATION to the i18n resource files. Based on learnings.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)

37-41: Gate dummy fallback to development only.

Returning dummyFieldTypeConfig on every error masks real failures in production and can ship dummy data to users.

Apply this diff to restrict the fallback:

     } catch (err) {
       // Fallback to dummy data on error
       console.error("Failed to fetch from MDMS, using fallback:", err);
-      return dummyFieldTypeConfig;
+      if (process.env.NODE_ENV !== "production") {
+        return dummyFieldTypeConfig;
+      }
+      return rejectWithValue(err?.message || "Failed to fetch field master");
     }

13-14: Clarify MDMS context version.

This thunk builds a v1 MDMS request. Rename the parameter to mdmsContextV1 or add a comment to make the version requirement explicit.


35-36: Normalize response to array.

If MDMS returns a single object instead of an array, downstream code expecting an array will break.

Apply this diff:

-      const data = response?.MdmsRes?.[moduleName]?.[name] || [];
-      return data;
+      const raw = response?.MdmsRes?.[moduleName]?.[name];
+      const data = Array.isArray(raw) ? raw : raw ? [raw] : [];
+      return data;
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)

22-26: Localize labels and suppress for custom/checkbox.

Current code passes raw labels without i18n and shows labels for all field types.

Apply this diff:

       description={field?.isMdms ? t(field?.helpText) : field?.helpText || null}
       error={field?.isMdms ? t(field?.errorMessage) : field?.errorMessage || null}
       infoMessage={field?.isMdms ? t(field?.tooltip) : field?.tooltip || null}
-      label={field?.label}
+      label={
+        ["checkbox", "button", "custom"].includes(fieldType)
+          ? null
+          : (field?.isMdms ? t(field?.label) : field?.label)
+      }

40-43: Use resolved fieldType and preserve falsy values.

Line 40 recomputes the field type instead of using the already-resolved fieldType variable. Line 42 coerces booleans to empty strings and uses || which discards 0/false.

Apply this diff:

-      required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required}
+      required={fieldType === "custom" ? null : field?.required}
       type={fieldType}
-      value={field?.value === true ? "" : field?.value || ""}
+      value={fieldType === "checkbox" ? Boolean(field?.value) : (field?.value ?? "")}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (3)

14-23: Make toggle control keyboard-accessible.

The visibility toggle div lacks keyboard navigation support.

Apply this diff:

     <div
+      role="button"
+      tabIndex={0}
+      aria-label="Toggle field visibility"
       onClick={(e) => {
         e.stopPropagation();
         onToggle();
       }}
+      onKeyDown={(e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          e.stopPropagation();
+          onToggle();
+        }
+      }}
       className="appConfigLabelField-toggleVisibility"
     >

31-50: Make delete control keyboard-accessible.

The delete button div lacks keyboard navigation support.

Apply this diff:

     <div
+      role="button"
+      tabIndex={0}
+      aria-label="Delete field"
       onClick={(e) => {
         e.stopPropagation();
         onDelete();
       }}
+      onKeyDown={(e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          e.stopPropagation();
+          onDelete();
+        }
+      }}
       style={{

61-69: Make field selection wrapper keyboard-accessible.

The field selection wrapper div lacks keyboard navigation support.

Apply this diff:

     <div
       ref={componentRef}
+      role="button"
+      tabIndex={0}
+      aria-label={`Select field: ${label}`}
       onClick={(e) => {
         e.stopPropagation();
         onSelectField();
       }}
+      onKeyDown={(e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          e.stopPropagation();
+          onSelectField();
+        }
+      }}
       className="app-config-field-wrapper"
       style={{}}
     >
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1)

200-200: Fix button variation typo.

The value "teritiary" is misspelled and won't map to a valid button style.

Apply this diff:

-              variation={"teritiary"}
+              variation={"tertiary"}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (4)

24-24: Use MDMS v1 context for v1 searches.

You read MDMS_V2_CONTEXT_PATH but pass it to thunks that hit /v1/_search. This will cause routing errors.

Apply this diff:

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";

Also update lines 82 and 93 to use the corrected context.


28-28: Initialize newFieldType to empty object.

Starting with null causes TypeError: Cannot convert undefined or null to object when spreading at lines 164-167 and 276.

Apply this diff:

-  const [newFieldType, setNewFieldType] = useState(null);
+  const [newFieldType, setNewFieldType] = useState({});

69-74: Guard initializeConfig when pageConfig is null.

If getPageFromConfig returns null, dispatching it will cause errors.

Apply this diff:

     // Initialize config
-    dispatch(initializeConfig(pageConfig));
+    if (pageConfig) {
+      dispatch(initializeConfig(pageConfig));
+    }

98-119: Wait for localization fetch before dispatching data.

setLocalizationData is dispatched immediately after fetchLocalization starts, so it receives stale/empty data.

Await the fetch result:

-      dispatch(
-        fetchLocalization({
+      const localizationResult = await dispatch(
+        fetchLocalization({
           tenantId,
           localeModule,
           enabledModules: [currentLocale],
         })
-      );
+      ).unwrap();

       dispatch(fetchAppScreenConfig({ tenantId }));

       // Set localization context data
       dispatch(
         setLocalizationData({
-          localisationData: localizationData,
+          localisationData: localizationResult,
           currentLocale,
           enabledModules: [currentLocale],
           localeModule,
         })
       );

Also mark the useEffect callback as async.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (2)

12-335: CRITICAL: Hoist all hooks above the early return.

The component violates React's Rules of Hooks by calling hooks after the conditional early return on line 32. Hooks (useCustomT, useEffect, useCallback) at lines 43, 46, 55, 105, 169, and 202 will not execute on every render, causing state corruption. Additionally, lines 262-263 declare hooks inside a switch case, which is also conditional.

Hoist all hooks before line 32:

 const RenderField = React.memo(({ panelItem, selectedField, onFieldChange, fieldType }) => {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentLocale } = useSelector((state) => state.localization);
   const { byName: fieldTypeMaster } = useSelector((state) => state.fieldTypeMaster);

   // Local state for immediate UI feedback
   const [localValue, setLocalValue] = useState("");
   const debounceTimerRef = useRef(null);
+  
+  // Hooks for fieldTypeDropdown case (always called, used conditionally)
+  const switchRef = useRef(null);
+  const [showTooltip, setShowTooltip] = useState(false);
+  
+  // Get field value and localized value BEFORE any early returns
+  const getFieldValue = () => {
+    const bindTo = panelItem.bindTo;
+    return getFieldValueByPath(selectedField, bindTo, panelItem.defaultValue || "");
+  };
+  const fieldValue = getFieldValue();
+  const localizedFieldValue = useCustomT(fieldValue);
+  
+  // All useEffect and useCallback hooks must be here
+  useEffect(() => {
+    if (panelItem.fieldType === "text") {
+      setLocalValue(localizedFieldValue || "");
+    } else if (panelItem.fieldType === "number") {
+      setLocalValue(getFieldValue() || 0);
+    }
+  }, [selectedField, panelItem.bindTo, panelItem.fieldType]);
+
+  useEffect(() => {
+    return () => {
+      if (debounceTimerRef.current) {
+        clearTimeout(debounceTimerRef.current);
+      }
+    };
+  }, []);
+  
+  const handleFieldChange = useCallback((value) => {
+    // ... implementation
+  }, [panelItem.bindTo, selectedField, onFieldChange]);
+  
+  const handleFieldChangeWithLoc = useCallback((code, value) => {
+    // ... implementation  
+  }, [panelItem.bindTo, dispatch, currentLocale, selectedField, onFieldChange]);
+  
+  const handleNumberChange = useCallback((value) => {
+    // ... implementation
+  }, [panelItem.bindTo, selectedField, onFieldChange]);
+  
+  const handleBlur = useCallback(() => {
+    // ... implementation
+  }, [panelItem.fieldType, fieldValue, localValue, handleFieldChangeWithLoc, handleNumberChange]);

   // Check if field should be visible based on field type
   const isFieldVisible = () => {
     // ... existing logic
   };

   if (!isFieldVisible()) {
     return null;
   }

-  const getFieldValue = () => {
-    // ... (moved above)
-  };
-  
-  const fieldValue = getFieldValue();
-  const localizedFieldValue = useCustomT(fieldValue);
-  
-  useEffect(() => {
-    // ... (moved above)
-  }, [selectedField, panelItem.bindTo, panelItem.fieldType]);
-  
-  // ... (other hooks moved above)

For the fieldTypeDropdown case, replace the conditional hook declarations with conditional usage:

       case "fieldTypeDropdown": {
-        const switchRef = useRef(null);
-        const [showTooltip, setShowTooltip] = useState(false);
-
         // Get field type options from Redux - using fixed key 'fieldTypeMappingConfig'

481-481: Add explicit button type.

Without type, buttons default to submit, which can trigger unintended form submissions.

Apply this diff:

-        <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)

361-371: Normalize payload shape.

The dummy config returns an object { content, validation } but the MDMS fetch can return an array. Consumers expect a consistent object shape.

Apply this diff:

       const data = response?.MdmsRes?.[moduleName]?.[name] || [];

       // Extract the actual config from the MDMS response
       // MDMS returns an array with an object containing id and the actual content/validation
       // We only want content and validation, not the id (which would show up as a tab)
       if (Array.isArray(data) && data.length > 0 && data[0]?.content && data[0]?.validation) {
         const { content, validation } = data[0];
         return { content, validation };
       }

-      return data;
+      return Array.isArray(data) && data.length === 0 ? { content: [], validation: [] } : data;

372-376: Correct error message.

The message says "field master" but this slice is "field panel master".

Apply this diff:

     } catch (err) {
       // Fallback to dummy data on error
-      console.error("Failed to fetch from MDMS, using fallback:", err);
+      console.error("Failed to fetch field panel master from MDMS, using fallback:", err);
       return dummyPanelConfig;
     }

412-412: Fix exported action name mismatch.

The reducer action is clearFieldPanelMasterData but you export clearFieldMasterData, which will be undefined at import sites.

Apply this diff:

-export const { clearFieldMasterData } = fieldPanelMasterSlice.actions;
+export const { clearFieldPanelMasterData } = fieldPanelMasterSlice.actions;

Update any importers to use the corrected name.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8096660 and 1dc2c85.

📒 Files selected for processing (8)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js (3 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
🧠 Learnings (1)
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧬 Code graph analysis (8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (6)
  • existing (336-336)
  • contextPath (339-339)
  • url (340-340)
  • response (342-360)
  • data (361-361)
  • data (367-367)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • existing (126-126)
  • mdmsContext (62-62)
  • url (63-63)
  • response (39-43)
  • response (65-83)
  • response (96-96)
  • data (84-84)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • mdmsContext (24-24)
health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • useCustomT (5-37)
  • useCustomT (5-37)
  • dispatch (6-6)
  • dispatch (41-41)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (8)
  • localValue (14-14)
  • debounceTimerRef (15-15)
  • handleChange (54-67)
  • dispatch (11-11)
  • currentLocale (12-12)
  • handleBlur (69-76)
  • useTranslation (10-10)
  • HeaderFieldWrapper (9-105)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
  • useSelector (15-15)
  • useSelector (493-493)
  • fieldType (507-512)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-10)
  • getFieldTypeFromMasterData (1-10)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (76-79)
  • useFieldDataLabel (76-79)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.js (2)
  • getPageFromConfig (8-31)
  • getPageFromConfig (8-31)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (2)
  • getFieldMaster (5-43)
  • getFieldMaster (5-43)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)
  • getFieldPanelMaster (331-378)
  • getFieldPanelMaster (331-378)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/deliveryRule/AddDeliverycontext.js (1)
  • DustbinIcon (21-28)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • useSelector (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • fieldTypeMaster (32-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (5)
  • existing (10-10)
  • contextPath (13-13)
  • url (14-14)
  • response (16-34)
  • data (35-35)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (3)
  • dispatch (26-26)
  • handleFieldChange (153-168)
  • currentLocale (27-27)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomT (5-37)
  • useCustomT (5-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/deliveryRule/AddDeliverycontext.js (1)
  • DustbinIcon (21-28)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js

[error] 30-30: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js

[error] 14-20: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 14-20: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 31-47: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 31-47: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 61-69: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 61-69: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 283-288: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 415-428: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 415-428: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 451-451: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 452-452: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 43-43: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 46-46: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 55-55: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 105-105: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 169-169: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 202-202: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 262-262: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 263-263: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 481-481: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

⏰ Context from checks skipped due to timeout of 10000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +30 to +32
useEffect(() => {
sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX");
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Move useEffect above the early return.

The useEffect hook is called after a conditional early return (line 24), violating React's Rules of Hooks. Hooks must be called unconditionally and in the same order on every render. When the employee lacks required roles, the component returns null before reaching the useEffect, breaking the hook calling order.

Apply this diff to fix the hook placement:

 const CampaignCard = () => {
+  useEffect(() => {
+    sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX");
+  }, []);
+
   if (!Digit.Utils.didEmployeeHasAtleastOneRole(Object.values(ROLES).flatMap((e) => e))) {
     return null;
   }
 
   const { t } = useTranslation();
   const microplanStatus = "RESOURCE_ESTIMATIONS_APPROVED";
 
-  useEffect(() => {
-    sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX");
-  }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX");
}, []);
// health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
const CampaignCard = () => {
// Move this hook above the early return so it always runs in the same order
useEffect(() => {
sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX");
}, []);
if (!Digit.Utils.didEmployeeHasAtleastOneRole(Object.values(ROLES).flatMap((e) => e))) {
return null;
}
const { t } = useTranslation();
const microplanStatus = "RESOURCE_ESTIMATIONS_APPROVED";
// ...rest of the component...
};
🧰 Tools
🪛 Biome (2.1.2)

[error] 30-30: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
around lines 30 to 32, the useEffect that calls
sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX") is placed after an early
return (line 24), violating React's Rules of Hooks; move the useEffect so it
sits above the conditional return so it executes (and hooks are called)
unconditionally on every render, keep the empty dependency array to run it once,
and ensure no other hooks are reordered when making this change.

Comment on lines +283 to +288
<div
ref={switchRef}
className="drawer-container-tooltip"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make tooltip wrapper keyboard-accessible.

The div with hover behavior lacks keyboard support.

Apply this diff:

           <div
             ref={switchRef}
+            role="button"
+            tabIndex={0}
+            aria-label="Field type dropdown with tooltip"
             className="drawer-container-tooltip"
             onMouseEnter={() => setShowTooltip(true)}
             onMouseLeave={() => setShowTooltip(false)}
+            onFocus={() => setShowTooltip(true)}
+            onBlur={() => setShowTooltip(false)}
           >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
ref={switchRef}
className="drawer-container-tooltip"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
<div
ref={switchRef}
role="button"
tabIndex={0}
aria-label="Field type dropdown with tooltip"
className="drawer-container-tooltip"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onFocus={() => setShowTooltip(true)}
onBlur={() => setShowTooltip(false)}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 283-288: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
around lines 283 to 288, the tooltip wrapper div is only hoverable and not
keyboard-accessible; update it to be focusable and respond to keyboard
focus/activation by adding tabIndex={0}, onFocus={() => setShowTooltip(true)}
and onBlur={() => setShowTooltip(false)}, and handle onKeyDown to toggle the
tooltip on Enter/Space (e.g., treat Enter/Space like onMouseEnter/onMouseLeave).
Also add an appropriate ARIA attribute (role="button" or aria-describedby
pointing to the tooltip id) to make screen-reader intent clear; preserve the
existing ref (switchRef) and className.

Comment on lines +415 to +428
<div
onClick={() => {
const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
}}
style={{
cursor: "pointer",
color: "#c84c0e",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "0.5rem",
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make delete icon keyboard-accessible.

The div with click handler lacks keyboard navigation.

Apply this diff:

               <div
+                role="button"
+                tabIndex={0}
+                aria-label="Delete option"
                 onClick={() => {
                   const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
                   onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
                 }}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
+                    onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
+                  }
+                }}
                 style={{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={() => {
const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
}}
style={{
cursor: "pointer",
color: "#c84c0e",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "0.5rem",
}}
>
<div
role="button"
tabIndex={0}
aria-label="Delete option"
onClick={() => {
const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
}
}}
style={{
cursor: "pointer",
color: "#c84c0e",
fontWeight: 600,
display: "flex",
alignItems: "center",
gap: "0.5rem",
}}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 415-428: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 415-428: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
around lines 415 to 428, the delete icon is rendered as a div with only an
onClick handler and is not keyboard-accessible; replace the div with a semantic
focusable element (preferred: <button type="button">) or add tabindex="0",
role="button", and an onKeyDown that triggers the same removal logic on Enter
and Space, include an appropriate aria-label (e.g., "Remove field") and ensure
existing styles are preserved and pointer/cursor behavior remains unchanged;
also prevent default/stop propagation as needed in the key handler to match
click behavior.

Comment on lines +450 to +468
case "dropdown":
const dropdownOptionKey = cField.optionKey || "schemaCode";
const bindValue = selectedField[cField.bindTo];
return (
<div style={{ marginTop: "8px" }}>
<FieldV1
type="dropdown"
label={cField.label ? t(Digit.Utils.locale.getTransformedLocale(`${cField.label}`)) : null}
value={cField.options?.find((i) => i[dropdownOptionKey] === bindValue) || null}
onChange={(value) => onFieldChange({ ...selectedField, [cField.bindTo]: value?.[dropdownOptionKey] })}
placeholder={cField.innerLabel ? t(cField.innerLabel) : null}
populators={{
options: cField.options || [],
optionsKey: dropdownOptionKey,
fieldPairClassName: "drawer-field",
}}
/>
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Wrap switch declarations in block.

Variables dropdownOptionKey and bindValue at lines 451-452 leak into other switch cases.

Apply this diff:

-    case "dropdown":
+    case "dropdown": {
       const dropdownOptionKey = cField.optionKey || "schemaCode";
       const bindValue = selectedField[cField.bindTo];
       return (
         // ... existing JSX
       );
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "dropdown":
const dropdownOptionKey = cField.optionKey || "schemaCode";
const bindValue = selectedField[cField.bindTo];
return (
<div style={{ marginTop: "8px" }}>
<FieldV1
type="dropdown"
label={cField.label ? t(Digit.Utils.locale.getTransformedLocale(`${cField.label}`)) : null}
value={cField.options?.find((i) => i[dropdownOptionKey] === bindValue) || null}
onChange={(value) => onFieldChange({ ...selectedField, [cField.bindTo]: value?.[dropdownOptionKey] })}
placeholder={cField.innerLabel ? t(cField.innerLabel) : null}
populators={{
options: cField.options || [],
optionsKey: dropdownOptionKey,
fieldPairClassName: "drawer-field",
}}
/>
</div>
);
case "dropdown": {
const dropdownOptionKey = cField.optionKey || "schemaCode";
const bindValue = selectedField[cField.bindTo];
return (
<div style={{ marginTop: "8px" }}>
<FieldV1
type="dropdown"
label={cField.label ? t(Digit.Utils.locale.getTransformedLocale(`${cField.label}`)) : null}
value={cField.options?.find((i) => i[dropdownOptionKey] === bindValue) || null}
onChange={(value) =>
onFieldChange({ ...selectedField, [cField.bindTo]: value?.[dropdownOptionKey] })
}
placeholder={cField.innerLabel ? t(cField.innerLabel) : null}
populators={{
options: cField.options || [],
optionsKey: dropdownOptionKey,
fieldPairClassName: "drawer-field",
}}
/>
</div>
);
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 451-451: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 452-452: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
around lines 450 to 468, the case "dropdown" declares dropdownOptionKey and
bindValue as consts at function scope which leak into other switch cases; wrap
the case body in a block by adding an opening brace after case "dropdown": and a
closing brace before the break, move the return inside that block, and ensure
you include a break; after the block so the const declarations are block-scoped
and do not leak to other cases.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dc2c85 and 01d410d.

⛔ Files ignored due to path filters (2)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfig.json is excluded by !**/*.json
📒 Files selected for processing (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧠 Learnings (1)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
🧬 Code graph analysis (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (3)
  • t (209-209)
  • LayoutRenderer (206-503)
  • LayoutRenderer (206-503)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-71)
🔇 Additional comments (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

10-37: Clean implementation with proper Redux integration and memoization.

The component structure is well-organized:

  • Redux state properly accessed via useSelector
  • Translation function obtained from useCustomTranslate hook
  • handleFieldClick correctly memoized with useCallback
  • Clear conditional rendering based on pageType
  • Appropriate use of React.memo for performance optimization

All previously flagged issues from past reviews have been successfully addressed.

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: 14

♻️ Duplicate comments (15)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)

28-42: Make the field wrapper keyboard-accessible.

This clickable div lacks role, tabIndex, and keyboard handlers, preventing keyboard-only users from selecting fields.

Apply this diff:

                       <div
-                        key={fieldIndex}
+                        key={fieldIndex}
+                        role="button"
+                        tabIndex={0}
+                        aria-label={t(field?.label) || "Select field"}
                         onClick={() => onFieldClick && onFieldClick(field, data, card, index, fieldIndex)}
+                        onKeyDown={(e) => {
+                          if (e.key === "Enter" || e.key === " ") {
+                            e.preventDefault();
+                            onFieldClick && onFieldClick(field, data, card, index, fieldIndex);
+                          }
+                        }}
                         style={{

48-59: Add keys to footer items.

Each mapped footer item needs a stable key to avoid React warnings.

Apply this diff:

           {data?.footer?.length > 0 &&
             data?.footer?.map((footer_item, footer_index) => {
               return (
                 <Button
+                  key={footer_item?.id || footer_item?.jsonPath || `footer-${footer_index}`}
                   className="app-preview-action-button"
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (6)

36-218: CRITICAL: Hoist all hooks above the early return.

Hooks (useCustomT at line 47, useEffect at lines 50 and 59, useCallback at lines 109, 173, and 206) are called after the conditional early return at line 36, violating React's Rules of Hooks. All hooks must be called unconditionally at the top of the component.

Move all hook calls to the top of RenderField, before the isFieldVisible() check:

 const RenderField = React.memo(({ panelItem, selectedField, onFieldChange, fieldType, isGroupChild = false }) => {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentLocale } = useSelector((state) => state.localization);
   const { byName: fieldTypeMaster } = useSelector((state) => state.fieldTypeMaster);

   // Local state for immediate UI feedback
   const [localValue, setLocalValue] = useState("");
   const debounceTimerRef = useRef(null);

+  // Get field value and localized value first (before any early returns)
+  const getFieldValue = () => {
+    const bindTo = panelItem.bindTo;
+    return getFieldValueByPath(selectedField, bindTo, panelItem.defaultValue || "");
+  };
+  const fieldValue = getFieldValue();
+  const localizedFieldValue = useCustomT(fieldValue);
+
+  // All useEffect and useCallback hooks must be here, before any conditional returns
+  useEffect(() => {
+    if (panelItem.fieldType === "text") {
+      setLocalValue(localizedFieldValue || "");
+    } else if (panelItem.fieldType === "number") {
+      setLocalValue(getFieldValue() || 0);
+    }
+  }, [selectedField, panelItem.bindTo, panelItem.fieldType]);
+
+  useEffect(() => {
+    return () => {
+      if (debounceTimerRef.current) {
+        clearTimeout(debounceTimerRef.current);
+      }
+    };
+  }, []);
+
+  const handleFieldChange = useCallback((value) => {
+    // ... implementation
+  }, [panelItem.bindTo, selectedField, onFieldChange]);
+
+  const handleFieldChangeWithLoc = useCallback((code, value) => {
+    // ... implementation
+  }, [panelItem.bindTo, dispatch, currentLocale, selectedField, onFieldChange]);
+
+  const handleNumberChange = useCallback((value) => {
+    // ... implementation
+  }, [panelItem.bindTo, selectedField, onFieldChange]);
+
+  const handleBlur = useCallback(() => {
+    // ... implementation
+  }, [panelItem.fieldType, fieldValue, localValue, handleFieldChangeWithLoc, handleNumberChange]);

   // Check if field should be visible based on field type
   const isFieldVisible = () => {
     // ... existing logic
   };

   if (!isFieldVisible()) {
     return null;
   }
-
-  const getFieldValue = () => {
-    // ... moved above
-  };
-
-  // Get localized field value for text fields
-  const fieldValue = getFieldValue();
-  const localizedFieldValue = useCustomT(fieldValue);
-
-  // Initialize local value when field changes
-  useEffect(() => {
-    // ... moved above
-  }, [selectedField, panelItem.bindTo, panelItem.fieldType]);
-
-  // Cleanup debounce timer on unmount
-  useEffect(() => {
-    // ... moved above
-  }, []);

337-396: Hoist fieldTypeDropdown hooks to the top level.

The useRef and useState hooks at lines 338-339 are declared inside a switch case, which is conditional. React requires all hooks to be called unconditionally.

Move these hooks to the top of RenderField along with the other hooks, and guard their usage conditionally inside renderMainField:

 const RenderField = React.memo(({ panelItem, selectedField, onFieldChange, fieldType, isGroupChild = false }) => {
   const { t } = useTranslation();
   const dispatch = useDispatch();
   const { currentLocale } = useSelector((state) => state.localization);
   const { byName: fieldTypeMaster } = useSelector((state) => state.fieldTypeMaster);

   // Local state for immediate UI feedback
   const [localValue, setLocalValue] = useState("");
   const debounceTimerRef = useRef(null);
+  const switchRef = useRef(null);
+  const [showTooltip, setShowTooltip] = useState(false);

   // ... rest of hooks

Then in the switch case, simply use them:

       case "fieldTypeDropdown": {
-        const switchRef = useRef(null);
-        const [showTooltip, setShowTooltip] = useState(false);
-
         // Get field type options from Redux - using fixed key 'fieldTypeMappingConfig'

557-560: Add explicit button type.

Without an explicit type, buttons default to "submit", which can cause unintended form submissions.

Apply this diff:

         <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>

526-544: Wrap switch declarations in a block.

The const declarations at lines 527-528 leak into other switch cases. Wrap the case body in a block to prevent this.

Apply this diff:

-    case "dropdown":
+    case "dropdown": {
       const dropdownOptionKey = cField.optionKey || "schemaCode";
       const bindValue = selectedField[cField.bindTo];
       return (
         // ... existing JSX
       );
+    }

359-365: Make tooltip wrapper keyboard-accessible.

The div with hover behavior lacks keyboard support, preventing keyboard-only users from accessing the tooltip.

Apply this diff:

           <div
             ref={switchRef}
+            role="button"
+            tabIndex={0}
+            aria-label="Field type dropdown with tooltip"
             className="drawer-container-tooltip"
             onMouseEnter={() => setShowTooltip(true)}
             onMouseLeave={() => setShowTooltip(false)}
+            onFocus={() => setShowTooltip(true)}
+            onBlur={() => setShowTooltip(false)}
           >

491-507: Make delete icon keyboard-accessible.

The delete icon div is clickable but not keyboard-accessible, blocking keyboard-only users from removing options.

Apply this diff:

               <div
+                role="button"
+                tabIndex={0}
+                aria-label="Delete option"
                 onClick={() => {
                   const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
                   onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
                 }}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
+                    onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
+                  }
+                }}
                 style={{
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (1)

13-57: Fix fallback localization key and header field updates.

We’re still generating fallback codes like undefined_undefined_undefined_ScreenHeading, and when fieldKey is absent we dispatch value: label, overwriting legacy configs with the literal label instead of a translation code. Editing any legacy header now breaks lookups.

Compute a sanitized fallback code once, reuse it for localization updates, and pass that same code (or the provided value) into both updateHeaderProperty and updateHeaderField.

-  const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`);
+  const fallbackCode = [currentCard?.flow, currentCard?.parent, currentCard?.name, label]
+    .filter((part) => part !== undefined && part !== null && String(part).trim().length > 0)
+    .map((part) =>
+      String(part)
+        .trim()
+        .replace(/\s+/g, "_")
+        .replace(/[^A-Za-z0-9_]/g, "_")
+        .toUpperCase()
+    )
+    .join("_");
+  const localizationCode = value || fallbackCode;
+  const localizedValue = useCustomT(localizationCode);
@@
-    if (value) {
-      dispatch(
-        updateLocalizationEntry({
-          code: value,
-          locale: currentLocale || "en_IN",
-          message: newValue,
-        })
-      );
-    } else {
-      dispatch(
-        updateLocalizationEntry({
-          code: `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`,
-          locale: currentLocale || "en_IN",
-          message: newValue,
-        })
-      );
-    }
+    dispatch(
+      updateLocalizationEntry({
+        code: localizationCode,
+        locale: currentLocale || "en_IN",
+        message: newValue,
+      })
+    );
@@
-    if (fieldKey) {
-      dispatch(updateHeaderProperty({ fieldKey, value: value || `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}` }));
-    } else {
-      // Fallback to old method for backward compatibility
-      dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: label }));
-    }
+    if (fieldKey) {
+      dispatch(updateHeaderProperty({ fieldKey, value: localizationCode }));
+    } else {
+      dispatch(updateHeaderField({ cardIndex, fieldIndex: index, value: localizationCode }));
+    }

This keeps legacy flows functional and prevents malformed localization keys.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)

137-141: Remove or use the lazy AppConfigurationStore import.

We’re still pulling this chunk without using it anywhere in this module. Either wire it into a route or drop the lazy import to avoid unnecessary bundle weight.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (3)

248-257: Add button semantics to flow selector.

The flow row div is clickable but lacks role, focus, and keyboard handling, blocking keyboard users. Add role="button", tabIndex={0}, and trigger handleFlowClick on Enter/Space (prevent default on Space). Include aria-selected={selectedFlow === flow.id} to expose state.
Based on static analysis hints


269-276: Make page tabs keyboard accessible.

div tabs fire onClick only. Add role="tab", tabIndex={0}, aria-selected, and handle Enter/Space so keyboard users can switch pages.
Based on static analysis hints


285-329: Fix navigation arrow accessibility.

Both arrow containers are clickable divs without semantics. Convert to <button> (or add role="button" + tabIndex={0} + keyboard handlers) and supply an accessible name (e.g., aria-label="Go to previous page"/"...next page"). Also give the SVGs a <title> or aria-hidden="true" to satisfy the a11y requirement.
Based on static analysis hints

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)

1-9: Import the store as a named export.

redux/store.js exports store via export const store = ..., so the default import here resolves to undefined, breaking the Provider. Switch to a named import.

-import store from "./redux/store";
+import { store } from "./redux/store";
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1)

30-37: Add accessible labelling for the filter SVG

This inline SVG is exposed without any alternative text. Screen-reader users get silence, Biome already flags this. Add a <title> with a unique id and reference it via aria-labelledby, or mark the icon decorative with aria-hidden="true" if that’s the intent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01d410d and 24dff66.

⛔ Files ignored due to path filters (7)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfigSchema.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fieldPanelPropertiesSchema.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfigSchema.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfigSkull.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/newFullParentConfig.json is excluded by !**/*.json
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/newFullParentConfigSchema.json is excluded by !**/*.json
📒 Files selected for processing (25)
  • health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (3 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SearchBar.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/Module.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SearchBar.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
🧠 Learnings (2)
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧬 Code graph analysis (21)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.js (1)
  • AppPreview (172-280)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • ComponentToRender (7-51)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (6)
  • infoConfig (118-118)
  • fieldTypeConfig (157-157)
  • infoTypes (119-119)
  • infoType (120-120)
  • variantMap (121-126)
  • variantMap (143-148)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeConfig (19-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js (1)
  • variantMap (7-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (4)
  • panelConfig (84-84)
  • fieldTypeConfig (157-157)
  • panelTypes (85-85)
  • panelType (86-86)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1)
  • panelConfig (575-575)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeConfig (19-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (7)
  • dispatch (90-90)
  • currentLocale (92-92)
  • useSelector (91-91)
  • localValue (15-15)
  • debounceTimerRef (16-16)
  • handleChange (32-55)
  • handleBlur (57-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (7)
  • dispatch (14-14)
  • dispatch (567-567)
  • useSelector (15-15)
  • useSelector (569-569)
  • localValue (19-19)
  • debounceTimerRef (20-20)
  • handleBlur (206-218)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (71-71)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (137-141)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
  • response (378-396)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1)
  • FilterIcon (30-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (3)
  • useSelector (190-190)
  • NewLayoutRenderer (187-220)
  • isFieldSelected (32-47)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (71-71)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-64)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (10)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • response (39-43)
  • response (65-83)
  • response (96-96)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (76-79)
  • useFieldDataLabel (76-79)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (2)
  • state (71-71)
  • pageConfig (21-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)
  • response (378-396)
  • getFieldPanelMaster (364-416)
  • getFieldPanelMaster (364-416)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
  • response (16-34)
  • getFieldMaster (5-43)
  • getFieldMaster (5-43)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (2)
  • iconName (65-65)
  • IconComponent (66-66)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeMasterData (191-191)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1)
  • localizedLabel (26-26)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • useCustomT (5-37)
  • useCustomT (5-37)
  • dispatch (6-6)
  • dispatch (41-41)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.js (8)
  • localValue (14-14)
  • debounceTimerRef (15-15)
  • handleChange (60-73)
  • dispatch (11-11)
  • currentLocale (12-12)
  • handleBlur (75-82)
  • useTranslation (10-10)
  • HeaderFieldWrapper (9-111)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • BOUNDARY_HIERARCHY_TYPE (293-293)
  • hierarchyData (294-294)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1)
  • SwitchTemplate (3-5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • BOUNDARY_HIERARCHY_TYPE (97-112)
  • hierarchyData (114-114)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)
  • FullConfigWrapper (6-357)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (4)
  • cardConfig (99-99)
  • fieldTypeConfig (157-157)
  • cardTypes (100-100)
  • cardType (101-101)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeConfig (19-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • AppConfigurationStore (137-141)
  • campaignNumber (157-157)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (2)
  • store (7-14)
  • store (7-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • AppConfigurationWrapper (15-275)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (6)
  • tagConfig (140-140)
  • fieldTypeConfig (157-157)
  • tagTypes (141-141)
  • tagType (142-142)
  • variantMap (121-126)
  • variantMap (143-148)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeConfig (19-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (1)
  • variantMap (7-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (5)
  • existing (10-10)
  • contextPath (13-13)
  • url (14-14)
  • response (16-34)
  • data (35-35)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-10)
  • getFieldTypeFromMasterData (1-10)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js (2)
  • getComponentFromMasterData (7-14)
  • getComponentFromMasterData (7-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SearchBar.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (9)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (2)
  • t (13-13)
  • useSelector (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1)
  • FilterIcon (4-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (2)
  • iconName (6-6)
  • IconComponent (7-7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js (3)
  • panelConfig (4-4)
  • panelTypes (5-5)
  • panelType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (8)
  • fieldTypeConfig (19-21)
  • useSelector (190-190)
  • isFieldSelected (32-47)
  • renderTemplateComponent (61-133)
  • isSelected (64-64)
  • Component (71-71)
  • uniqueKey (65-65)
  • renderSection (146-181)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js (3)
  • cardConfig (4-4)
  • cardTypes (5-5)
  • cardType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (4)
  • infoConfig (4-4)
  • infoTypes (5-5)
  • infoType (6-6)
  • variantMap (7-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js (4)
  • variantMap (7-12)
  • tagConfig (4-4)
  • tagTypes (5-5)
  • tagType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
  • useSelector (8-8)
  • component (17-17)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js

[error] 28-39: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 28-39: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 51-57: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 359-365: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 491-505: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 491-505: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 527-527: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 528-528: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 47-47: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 50-50: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 109-109: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 173-173: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 206-206: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 338-338: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 339-339: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 557-558: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 248-257: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 248-257: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 269-276: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 269-276: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 285-296: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 285-296: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 297-297: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 314-325: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 314-325: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 326-326: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js

[error] 5-5: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js

[error] 81-96: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 81-96: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js

[error] 31-31: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 240-255: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 240-255: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 271-291: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 271-291: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🔇 Additional comments (6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js (1)

1-19: LGTM!

The component correctly derives the panel type from configuration with appropriate fallbacks and uses safe optional chaining throughout.

health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (1)

67-74: LGTM!

The new template components are correctly imported and registered.

Also applies to: 206-215

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SearchBar.js (1)

1-15: LGTM!

The component correctly uses FieldV1 with appropriate props and translation support.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (1)

1-26: LGTM!

The component correctly handles optional icon rendering and passes appropriate props to Button.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js (1)

4-19: LGTM on card rendering.

Metadata-driven card type lookup and fallback handling look sound.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (1)

4-22: Looks good—variants resolve correctly.

Fetching card type options with sensible defaults and mapping to component variants is aligned with the renderer contract.

Comment on lines +45 to +48
required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required}
type={fieldType}
value={field?.value === true ? "" : field?.value || ""}
disabled={field?.readOnly || false}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reuse the resolved fieldType instead of recalculating

Calling getFieldTypeFromMasterData(field) without the master data param forces the helper to return the default "text", so required is wrong for custom/component types. Just use the fieldType computed above (and drop the extra call).

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
around lines 45 to 48, the required prop calls getFieldTypeFromMasterData(field)
again (without master data) which forces a default "text" and miscomputes
required for custom/component types; replace that call by using the already
computed fieldType variable for the required check (i.e., use fieldType ===
"custom" ? null : field?.required) and remove the extra helper invocation so
required reflects the resolved type.

Comment on lines 149 to 170
// Fetch localization data if locale module is provided
if (localeModule) {
dispatch(
fetchLocalization({
tenantId,
localeModule,
enabledModules: [currentLocale],
})
);

dispatch(fetchAppScreenConfig({ tenantId }));

// Set localization context data
dispatch(
setLocalizationData({
localisationData: localizationData,
currentLocale,
enabledModules: [currentLocale],
localeModule,
})
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t hydrate localization store with stale data

You dispatch fetchLocalization and then immediately call setLocalizationData using localizationData from the selector, which is still the pre-fetch value (usually empty). Move setLocalizationData into a useEffect that waits for the thunk to resolve (localizationStatus === "succeeded" and fresh data) or unwrap the thunk result before dispatching.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 149 to 170, the code dispatches fetchLocalization and immediately
dispatches setLocalizationData using localizationData from the selector (stale
pre-fetch value). Fix by either (A) moving the setLocalizationData dispatch into
a useEffect that watches localizationStatus (e.g., when localizationStatus ===
"succeeded") and then reads the updated selector to dispatch
setLocalizationData, or (B) unwrap the fetchLocalization thunk (await
dispatch(fetchLocalization(...)).unwrap()) and dispatch setLocalizationData
using the resolved payload returned by the thunk, ensuring you only set
localization data after the fetch completes.

Comment on lines 186 to 200
const handleFieldChange = (value) => {
const locVal = newFieldType?.label
? newFieldType?.label
: `${showAddFieldPopup?.currentCard?.flow}_${showAddFieldPopup?.currentCard?.name}_newField_${Date.now()}`;
dispatch(
updateLocalizationEntry({
code: locVal,
locale: currentLocale || "en_IN",
message: value,
})
);
setNewFieldType((prev) => ({
...prev,
label: locVal,
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard newFieldType before spreading

setNewFieldType((prev) => ({ ...prev, label: locVal })) still executes while the state is null, so the first change throws a null-spread TypeError (same for the dropdown handler). Initialise with {} or use ...(prev || {}).

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 186-200, setNewFieldType spreads prev which can be null causing a
TypeError; fix by guarding the spread or initializing state: either ensure
newFieldType is initialized as {} where declared or change
setNewFieldType((prev) => ({ ...(prev || {}), label: locVal })) and apply same
(prev || {}) guard in the dropdown handler to avoid null-spread errors.

Comment on lines +1 to +14
/**
* Fetches the component from fieldTypeMasterData based on field type and format
* @param {Object} field - The field object containing type and format
* @param {Array} fieldTypeMasterData - The master data array containing field type configurations
* @returns {string|null} - The component name or null if not found
*/
export const getComponentFromMasterData = (field, fieldTypeMasterData = []) => {
if (!fieldTypeMasterData || !Array.isArray(fieldTypeMasterData)) {
return null;
}
const matched = fieldTypeMasterData.find((item) => item?.metadata?.type === field.type && item?.metadata?.format === field.format);

return matched?.component || null;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect property path.

Line 13 returns matched?.component, but based on the pattern established in line 11 where you match against item?.metadata?.type and item?.metadata?.format, the component is also nested under metadata.

Apply this diff:

-  return matched?.component || null;
+  return matched?.metadata?.component || null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Fetches the component from fieldTypeMasterData based on field type and format
* @param {Object} field - The field object containing type and format
* @param {Array} fieldTypeMasterData - The master data array containing field type configurations
* @returns {string|null} - The component name or null if not found
*/
export const getComponentFromMasterData = (field, fieldTypeMasterData = []) => {
if (!fieldTypeMasterData || !Array.isArray(fieldTypeMasterData)) {
return null;
}
const matched = fieldTypeMasterData.find((item) => item?.metadata?.type === field.type && item?.metadata?.format === field.format);
return matched?.component || null;
};
/**
* Fetches the component from fieldTypeMasterData based on field type and format
* @param {Object} field - The field object containing type and format
* @param {Array} fieldTypeMasterData - The master data array containing field type configurations
* @returns {string|null} - The component name or null if not found
*/
export const getComponentFromMasterData = (field, fieldTypeMasterData = []) => {
if (!fieldTypeMasterData || !Array.isArray(fieldTypeMasterData)) {
return null;
}
const matched = fieldTypeMasterData.find(
(item) =>
item?.metadata?.type === field.type &&
item?.metadata?.format === field.format
);
return matched?.metadata?.component || null;
};
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js
around lines 1 to 14, the returned property uses matched?.component but the
component lives under metadata (you matched item?.metadata?.type and
item?.metadata?.format), so update the return to use
matched?.metadata?.component (keep the fallback to null) so the function
correctly returns the nested component value.

Comment on lines +205 to +212
className={"app-config-drawer-button"}
type={"button"}
size={"medium"}
icon={"AddIcon"}
variation={"teritiary"}
label={t("ADD_FIELD")}
onClick={() => handleAddField(currentCard, card[index])}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Correct button variation spelling

variation="teritiary" is invalid, so the button falls back to default styling. Change it to "tertiary" (or another supported variant).

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
around lines 205 to 212, the Button uses an invalid variation value "teritiary"
causing it to fallback to default styling; change the prop to the correct
supported variant (e.g., "tertiary") or another valid variant name provided by
the design system, and run the UI locally to verify the button now renders with
the intended tertiary styling.

Comment on lines 81 to 109
<div
key={uniqueKey}
onClick={(e) => {
e.stopPropagation();
onFieldClick && onFieldClick(field, data, null, index, null);
}}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
transition: "all 0.2s ease",
}}
>
<FieldV1
type={fieldType}
label={field?.label}
value={field?.value || ""}
placeholder={t(field?.innerLabel) || ""}
onChange={() => {}}
disabled={field?.readOnly || false}
populators={{
fieldPairClassName: "app-preview-field-pair",
}}
/>
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keyboard-enable the selectable wrapper (fallback path)

This wrapper handles clicks but exposes no role or keyboard handler. Keyboard users can’t focus/activate it. Please add role="button", tabIndex={0}, and handle Enter/Space to mirror the click.

🧰 Tools
🪛 Biome (2.1.2)

[error] 81-96: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 81-96: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around lines 81 to 109, the clickable wrapper is not keyboard-accessible; add
role="button" and tabIndex={0} to make it focusable and implement an onKeyDown
handler that listens for Enter (key === "Enter") and Space (key === " " or key
=== "Spacebar") to call the same logic as the onClick (stopPropagation and
invoke onFieldClick with the same args), and preventDefault for Space to avoid
page scrolling; keep existing onClick behavior unchanged so both mouse and
keyboard activation mirror each other.

Comment on lines +114 to +132
<div
key={uniqueKey}
onClick={(e) => {
e.stopPropagation();
onFieldClick && onFieldClick(field, data, null, index, null);
}}
style={{
cursor: "pointer",
border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
borderRadius: "4px",
padding: "8px",
margin: "4px 0",
backgroundColor: isSelected ? "#f0f8ff" : "transparent",
transition: "all 0.2s ease",
}}
>
<Component field={field} t={t} />
</div>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Apply the same keyboard fix to the custom-component wrapper

The custom component path has identical a11y gap: clickable div with no keyboard support. Add the same role/tabIndex/Enter+Space handling here too.

🧰 Tools
🪛 Biome (2.1.2)

[error] 114-129: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around lines 114 to 132, the custom-component wrapper is a clickable div but
lacks keyboard accessibility; add role="button", tabIndex={0}, and an onKeyDown
handler that listens for Enter and Space to call the same onFieldClick (with
e.stopPropagation() and the same args as the onClick) so keyboard users can
activate the component; keep the existing onClick and styling intact and ensure
you prevent default for Space to avoid page scroll.

Comment on lines +450 to +451
export const { clearFieldMasterData } = fieldPanelMasterSlice.actions;
export default fieldPanelMasterSlice.reducer;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Export the actual reducer action

The slice defines clearFieldPanelMasterData, but you export clearFieldMasterData. Consumers importing this get undefined and dispatch fails. Export the correctly named action:
export const { clearFieldPanelMasterData } = fieldPanelMasterSlice.actions;

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
around lines 450-451, the exported action name is incorrect: the slice defines
clearFieldPanelMasterData but the file exports clearFieldMasterData, causing
consumers to receive undefined; change the exported named action to match the
slice definition by exporting clearFieldPanelMasterData from
fieldPanelMasterSlice.actions so imports/dispatches reference the correct action
name.

Comment on lines +35 to +37
setCurrentData(state, action) {
state.currentData = action.payload || [];
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep currentData shape consistent

setCurrentData defaults to [], but every reducer assumes an object with body, footer, etc. Passing undefined will set currentData to an array and the next access (e.g. currentData.body) throws. Default to null or the expected object shape instead of [].

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
around lines 35-37, setCurrentData currently defaults action.payload to [] which
breaks code that expects an object with keys like body and footer; change the
fallback to either null or the expected object shape (e.g. { body: {}, footer:
{}, ... }) so currentData maintains a consistent type, and update any callers if
they rely on an array fallback.

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: 17

♻️ Duplicate comments (28)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)

45-48: Fix required computation and preserve falsy values.

Two issues:

  1. Line 45: Calling getFieldTypeFromMasterData(field) without the fieldTypeMasterData parameter forces the default return value "text", causing required to be incorrectly computed for custom/component types. Use the already-resolved fieldType variable.

  2. Line 47: Converting true to "" and using || coerces false/0 to "", losing valid falsy values. Use nullish coalescing to preserve them.

Apply this diff:

-  required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required}
+  required={fieldType === "custom" ? null : field?.required}
   type={fieldType}
-  value={field?.value === true ? "" : field?.value || ""}
+  value={field?.value ?? ""}
   disabled={field?.readOnly || false}

Based on past review comments.


27-30: Apply i18n to label and suppress for certain field types.

The label currently bypasses the isMdms translation logic and is shown for all field types. Align with previous feedback: translate MDMS labels via t() and suppress labels for custom/checkbox/button types.

Apply this diff:

   description={field?.isMdms ? t(field?.helpText) : customT(field?.helpText) || null}
   error={field?.isMdms ? t(field?.errorMessage) : customT(field?.errorMessage) || null}
   infoMessage={field?.isMdms ? t(field?.tooltip) : customT(field?.tooltip) || null}
-  label={customT(field?.label)}
+  label={
+    ["checkbox", "button", "custom"].includes(fieldType)
+      ? null
+      : (field?.isMdms ? t(field?.label) : customT(field?.label))
+  }

Based on past review comments.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)

5-37: Previous review suggestion not fully addressed – refactor to match the unified pattern.

The past review recommended refactoring useCustomT to return a stable translator function (following the pattern in appConfigurationRedesign/useCustomT.js). Instead of creating a separate useCustomTranslate hook, consolidate both by accepting an optional maybeCode parameter:

  • If called with a string → return translated string (backward compatibility)
  • If called with no args → return a stable translator function

This eliminates code duplication and aligns with the established pattern.

Apply this diff to match the appConfigurationRedesign pattern:

+import { useSelector, useDispatch } from "react-redux";
+import { useCallback, useMemo } from "react";
+import { addMissingKey } from "../redux/localizationSlice";
+
-export const useCustomT = (code) => {
+export const useCustomT = (maybeCode) => {
   const dispatch = useDispatch();
   const { data: locState, currentLocale } = useSelector((state) => state.localization);
 
-  const translatedValue = useMemo(() => {
-    if (!code) {
-      return "";
-    }
-
-    if (!Array.isArray(locState)) {
-      return ""; // Return empty string if locState not ready
-    }
-
-    const entry = locState.find((item) => item.code === code);
-
-    if (!entry) {
-      // Get enabled modules from session storage
-      const enabledModules = Digit?.SessionStorage.get("initData")?.languages || [];
-
-      // Add the missing key to Redux store with empty message
-      dispatch(addMissingKey({ code, enabledModules }));
-
-      return ""; // Return empty string when entry not found
-    }
-
-    // Get current locale from Redux state or session storage as fallback
-    const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
-
-    return entry[locale] || ""; // Return the message or empty string
-  }, [code, locState, currentLocale, dispatch]);
-
-  return translatedValue;
+  const translate = useMemo(() => {
+    const list = Array.isArray(locState) ? locState : [];
+    return (code) => {
+      if (!code) return "";
+      
+      const entry = list.find((item) => item?.code === code);
+      
+      if (!entry) {
+        const enabledModules = Digit?.SessionStorage.get("initData")?.languages || [];
+        dispatch(addMissingKey({ code, enabledModules }));
+        return "";
+      }
+      
+      const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
+      return entry?.[locale] || "";
+    };
+  }, [locState, currentLocale, dispatch]);
+
+  // Back-compat: allow direct call useCustomT("KEY")
+  if (typeof maybeCode === "string") return translate(maybeCode);
+  
+  // Preferred: return a stable translator function
+  return translate;
 };

Based on learnings


76-79: Fix useFieldDataLabel to use the refactored translator function.

After refactoring useCustomT to return a translator function, update this helper to call the hook once at the top level and then invoke the returned function.

Apply this diff:

 // Helper hook to get localization label for field data
 export const useFieldDataLabel = (label) => {
-  const customT = useCustomT(label || "");
-  return label ? customT : "";
+  const t = useCustomT();
+  return label ? t(label) : "";
 };
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (12)

7-19: State shape: add currentData, stabilize popup state, and consider dropping redundant flags.

  • currentData is missing in initialState but used throughout. Add it to avoid undefined shape issues.
  • Keep showAddFieldPopup type stable. Prefer boolean flag + separate context object.
  • isFieldSelected is derivable from !!selectedField; keeping both risks divergence.

Minimal fix (safe):

 initialState: {
   remoteData: null,
   parentData: [],
+  currentData: null,
   pageType: "object", // "object" or "template"
   responseData: null, // Store full MDMS response for updates
   // Drawer state for field selection and editing
   selectedField: null,
   selectedFieldPath: { cardIndex: null, fieldIndex: null }, // Store field path for O(1) updates
   currentScreen: null,
   currentCard: null,
-  isFieldSelected: false,
-  showAddFieldPopup: false,
+  // derived: !!selectedField
+  showAddFieldPopup: false,
+  addFieldContext: null,
 }

If you adopt the addFieldContext change, update handleShowAddFieldPopup accordingly (see separate comment). Do not forget to replace UI uses of isFieldSelected && selectedField with !!selectedField.
Run to check external usage:

#!/bin/bash
rg -n --glob '!**/dist/**' '\bisFieldSelected\b|showAddFieldPopup\.(currentCard|card)'

21-34: Avoid aliasing remoteData and currentData; deep‑clone on init.

Both are set to the same object, so edits to currentData mutate remoteData. Clone once and create a separate working copy.

- initializeConfig(state, action) {
-   const { pageConfig, responseData } = action.payload;
-   if (pageConfig) {
-     state.remoteData = pageConfig;
-     state.currentData = pageConfig;
-     state.pageType = pageConfig.type || "object"; // Extract pageType from config
-     state.responseData = responseData || null; // Store full MDMS response
-   } else {
-     state.remoteData = dummyConfig;
-     state.currentData = dummyConfig || {};
-     state.pageType = dummyConfig?.type || "object";
-     state.responseData = null;
-   }
- },
+ initializeConfig(state, action) {
+   const { pageConfig, responseData } = action?.payload || {};
+   const base = pageConfig ?? dummyConfig;
+   const pristine = (typeof structuredClone === "function")
+     ? structuredClone(base)
+     : JSON.parse(JSON.stringify(base));
+   state.remoteData = pristine;
+   // separate working copy
+   state.currentData = (typeof structuredClone === "function")
+     ? structuredClone(pristine)
+     : JSON.parse(JSON.stringify(pristine));
+   state.pageType = base?.type || "object";
+   state.responseData = responseData ?? null;
+ },

38-40: Type safety: setCurrentData should not default to an array.

Downstream reducers expect an object with body, etc. Default to null (or a well‑formed shape) to avoid runtime errors.

- setCurrentData(state, action) {
-   state.currentData = action.payload || [];
- },
+ setCurrentData(state, action) {
+   state.currentData = action?.payload ?? null;
+ },

71-87: Harden updates: avoid proto‑pollution and single‑source the write.

  • Replace for..in with Object.entries and skip dangerous keys.
  • Update via the stored path; then rebind selectedField to the updated target to keep references aligned.
- updateSelectedField(state, action) {
-   if (!state.selectedField || !action?.payload) return;
-   const updates = action.payload;
-   const { cardIndex, fieldIndex } = state.selectedFieldPath;
-   // Update selected field
-   for (const key in updates) {
-     state.selectedField[key] = updates[key];
-   }
-   // Also update the field in currentData using stored path (O(1) instead of O(n*m))
-   if (state.currentData?.body && cardIndex !== null && cardIndex !== -1 && fieldIndex !== null && fieldIndex !== -1) {
-     for (const key in updates) {
-       state.currentData.body[cardIndex].fields[fieldIndex][key] = updates[key];
-     }
-   }
- },
+ updateSelectedField(state, action) {
+   const updates = action?.payload;
+   const { cardIndex, fieldIndex } = state.selectedFieldPath || {};
+   const target = state.currentData?.body?.[cardIndex]?.fields?.[fieldIndex];
+   if (!updates || !target) return;
+   for (const [key, value] of Object.entries(updates)) {
+     if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
+     target[key] = value;
+   }
+   state.selectedField = target;
+ },

89-96: Bounds checks required: splice(-1,1) will delete the last item; also clear selection when deleting the selected field.

Add guards for indices and keep selection state coherent. Drop redundant spread.

- deleteField(state, action) {
-   const { fieldIndex, cardIndex } = action.payload;
-   if (state.currentData && state.currentData.body && state.currentData.body[cardIndex]) {
-     state.currentData.body[cardIndex].fields.splice(fieldIndex, 1);
-     // Ensure reactivity by creating new reference
-     state.currentData = { ...state.currentData };
-   }
- },
+ deleteField(state, action) {
+   const { fieldIndex, cardIndex } = action.payload || {};
+   const body = state.currentData?.body;
+   if (!body || !Number.isInteger(cardIndex) || !body[cardIndex]) return;
+   const fields = body[cardIndex].fields || [];
+   if (!Number.isInteger(fieldIndex) || fieldIndex < 0 || fieldIndex >= fields.length) return;
+   const removed = fields[fieldIndex];
+   fields.splice(fieldIndex, 1);
+   if (state.selectedField === removed) {
+     state.selectedField = null;
+     state.selectedFieldPath = { cardIndex: null, fieldIndex: null };
+     state.currentCard = null;
+     state.currentScreen = null;
+   }
+ },

97-109: Toggle with boolean coercion; drop reactivity spread.

Ensure consistent boolean type for hidden.

- const { fieldIndex, cardIndex } = action.payload;
+ const { fieldIndex, cardIndex } = action.payload || {};
  if (
    state.currentData &&
    state.currentData.body &&
    state.currentData.body[cardIndex] &&
    state.currentData.body[cardIndex].fields[fieldIndex]
  ) {
-   state.currentData.body[cardIndex].fields[fieldIndex].hidden = !state.currentData.body[cardIndex].fields[fieldIndex].hidden;
-   // Ensure reactivity by creating new reference
-   state.currentData = { ...state.currentData };
+   const fld = state.currentData.body[cardIndex].fields[fieldIndex];
+   fld.hidden = !Boolean(fld.hidden);
  }

110-127: Reorder: allow moving to end; correct index math; add bounds.

Currently disallows toIndex === len and misplaces when moving forward.

- const { cardIndex, fromIndex, toIndex } = action.payload;
+ const { cardIndex, fromIndex, toIndex } = action.payload || {};
  if (state.currentData && state.currentData.body && state.currentData.body[cardIndex]) {
    const fields = state.currentData.body[cardIndex].fields;
-   if (fromIndex >= 0 && toIndex >= 0 && fromIndex < fields.length && toIndex < fields.length) {
-     const [movedField] = fields.splice(fromIndex, 1);
-     fields.splice(toIndex, 0, movedField);
+   const len = fields.length;
+   if (!Number.isInteger(fromIndex) || !Number.isInteger(toIndex)) return;
+   if (fromIndex < 0 || fromIndex >= len || toIndex < 0 || toIndex > len || fromIndex === toIndex) return;
+   const [moved] = fields.splice(fromIndex, 1);
+   const insertAt = toIndex > fromIndex ? toIndex - 1 : toIndex;
+   fields.splice(insertAt, 0, moved);
    // Update order property
-   fields.forEach((field, index) => {
-     field.order = index + 1;
-   });
-
-   // Ensure reactivity by creating new reference
-   state.currentData = { ...state.currentData };
+   fields.forEach((f, i) => { f.order = i + 1; });
-   }
+   }
  }

128-135: Keep popup state type‑stable; store payload separately.

showAddFieldPopup flips between boolean and object. Use a boolean flag and move payload to addFieldContext.

- handleShowAddFieldPopup(state, action) {
-   if (!action.payload) {
-     state.showAddFieldPopup = false;
-   } else {
-     const { currentCard, card } = action.payload;
-     state.showAddFieldPopup = { currentCard, card };
-   }
- },
+ handleShowAddFieldPopup(state, action) {
+   if (!action?.payload) {
+     state.showAddFieldPopup = false;
+     state.addFieldContext = null;
+     return;
+   }
+   const { currentCard, card } = action.payload;
+   state.showAddFieldPopup = true;
+   state.addFieldContext = { currentCard, card };
+ },

Verify and update consumers:

#!/bin/bash
rg -n --glob '!**/dist/**' 'showAddFieldPopup(\b|[^\w])|addFieldContext' -C2

136-149: Reducer purity: move ID generation to RTK prepare with nanoid.

Random IDs inside reducers hinder determinism/time‑travel. Use a prepare callback and also set order.

Add import:

- import { createSlice } from "@reduxjs/toolkit";
+ import { createSlice, nanoid } from "@reduxjs/toolkit";

Replace reducer:

- addField(state, action) {
-   const { cardIndex, fieldData } = action.payload;
-   if (state.currentData && state.currentData.body && state.currentData.body[cardIndex]) {
-     const newField = {
-       ...fieldData,
-       id: crypto.randomUUID(),
-       deleteFlag: true,
-       active: true,
-     };
-     state.currentData.body[cardIndex].fields.push(newField);
-     // Ensure reactivity by creating new reference
-     state.currentData = { ...state.currentData };
-   }
- },
+ addField: {
+   reducer(state, action) {
+     const { cardIndex, fieldData } = action.payload;
+     const body = state.currentData?.body;
+     if (!body || !body[cardIndex]) return;
+     const fields = body[cardIndex].fields || (body[cardIndex].fields = []);
+     fields.push({ ...fieldData, deleteFlag: true, active: true, order: fields.length + 1 });
+   },
+   prepare({ cardIndex, fieldData }) {
+     return { payload: { cardIndex, fieldData: { id: nanoid(), ...fieldData } } };
+   },
+ },

150-179: Minor: drop redundant “reactivity” spread; consider card IDs (optional).

  • The spread on Lines 176‑177 is unnecessary with Immer and adds noise. Remove it.
  • Optional: add stable id on cards to simplify selection/update flows (can be done later with a prepare).
- state.currentData.body.push(newCard);
- // Ensure reactivity by creating new reference
- state.currentData = { ...state.currentData };
+ state.currentData.body.push(newCard);

180-193: Remove unnecessary spreads after mutations (Immer handles immutability).

Delete the reassignments in these reducers; they are redundant and make diffs noisy.

Examples:

- state.currentData.body[cardIndex].headerFields[fieldIndex].value = value;
- // Ensure reactivity by creating new reference
- state.currentData = { ...state.currentData };
+ state.currentData.body[cardIndex].headerFields[fieldIndex].value = value;
- state.currentData.actionLabel = value;
- state.currentData = { ...state.currentData };
+ state.currentData.actionLabel = value;
- state.currentData[fieldKey] = value;
- state.currentData = { ...state.currentData };
+ state.currentData[fieldKey] = value;

Also applies to: 194-201, 202-209


9-9: Question: is parentData still used? If not, remove to reduce state surface.

If unused, delete it from initialState and clean up selectors/usages.

Quick check:

#!/bin/bash
rg -n --glob '!**/dist/**' '\bparentData\b' -C2
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (2)

297-306: Make flow items keyboard-accessible (role + keyboard) and announce selection.

Add role, tabIndex, keyboard handler, and aria-selected.

Apply this diff:

-            <div
+            <div
               key={index}
               style={{
                 ...styles.flowItem,
                 backgroundColor: selectedFlow === flow.id ? "#F47738" : "transparent",
                 color: selectedFlow === flow.id ? "#FFFFFF" : "#505A5F",
                 fontWeight: selectedFlow === flow.id ? "700" : "400",
               }}
-              onClick={() => handleFlowClick(flow)}
+              onClick={() => handleFlowClick(flow)}
+              role="button"
+              tabIndex={0}
+              aria-selected={selectedFlow === flow.id}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handleFlowClick(flow);
+                }
+              }}
             >

Based on static analysis hints.


318-325: Enable keyboard for page tabs and expose state.

Add role/tabIndex/keys and aria-selected.

-            <div
+            <div
               key={index}
               style={{
                 ...styles.pageTab,
                 ...(selectedPageName === page.name ? styles.pageTabActive : {}),
               }}
-              onClick={() => handlePageClick(page)}
+              onClick={() => handlePageClick(page)}
+              role="button"
+              tabIndex={0}
+              aria-selected={selectedPageName === page.name}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handlePageClick(page);
+                }
+              }}
             >

Based on static analysis hints.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

34-34: Remove debug log.

Stray console.log in production code.

-  console.log("pageTypepageType", pageType);
+  // dev: console.debug("pageType", pageType);
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (2)

81-96: Keyboard-enable the clickable fallback wrapper.

Add role, tabIndex, and Enter/Space handling.

       <div
         key={uniqueKey}
-        onClick={(e) => {
+        onClick={(e) => {
           e.stopPropagation();
           onFieldClick && onFieldClick(field, data, null, index, null);
         }}
+        role="button"
+        tabIndex={0}
+        aria-pressed={isSelected}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            e.stopPropagation();
+            onFieldClick && onFieldClick(field, data, null, index, null);
+          }
+        }}
         style={{

Based on static analysis hints.


114-129: Do the same for the custom-component wrapper.

Mirror click behavior for keyboard users.

     <div
       key={uniqueKey}
-      onClick={(e) => {
+      onClick={(e) => {
         e.stopPropagation();
         onFieldClick && onFieldClick(field, data, null, index, null);
       }}
+      role="button"
+      tabIndex={0}
+      aria-pressed={isSelected}
+      onKeyDown={(e) => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          e.stopPropagation();
+          onFieldClick && onFieldClick(field, data, null, index, null);
+        }
+      }}
       style={{

Based on static analysis hints.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js (2)

1-1: Remove unused React named imports.

Fragment, useEffect, useState aren’t used.

-import React, { Fragment, useEffect, useState } from "react";
+import React from "react";

4-6: Forward wrapper props; don’t drop them.

Current wrapper discards most inputs; this breaks callers.

-function IntermediateWrapper({ variant, screenConfig, submit, back, showBack, parentDispatch, localeModule, pageTag, onNext, isUpdating, ...props }) {
-  return <AppConfiguration onNext={onNext} isUpdating={isUpdating} />;
+function IntermediateWrapper({
+  variant,
+  screenConfig,
+  submit,
+  back,
+  showBack,
+  parentDispatch,
+  localeModule,
+  pageTag,
+  onNext,
+  isUpdating,
+  ...props
+}) {
+  return (
+    <AppConfiguration
+      variant={variant}
+      screenConfig={screenConfig}
+      submit={submit}
+      back={back}
+      showBack={showBack}
+      parentDispatch={parentDispatch}
+      localeModule={localeModule}
+      pageTag={pageTag}
+      onNext={onNext}
+      isUpdating={isUpdating}
+      {...props}
+    />
+  );
 }
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)

22-28: Remove debug console logs; they leak payloads and clutter prod logs.

Strip console.log calls printing props/MDMS payloads/responses/currentData.

Apply:

-  console.log("AppConfigurationWrapper Props:", {
-    flow,
-    flowName,
-    pageName,
-    campaignNumber,
-    localeModule,
-  });
...
-  console.log("currentDatacurrentData", currentData);
...
-      console.log("MDMS Update Payload:", updatePayload);
...
-      console.log("MDMS Update Response:", response);
...
-        console.log("MDMS NewFormConfig Response:", response);
-          console.log("Page Config from MDMS:", pageConfig, responseData);
-          console.log("Response Data from MDMS:", responseData);

If needed, replace with a project logger behind a debug flag.

Also applies to: 46-46, 73-73, 81-81, 150-156


31-31: Use MDMS v1 context for thunks that call v1/_search and correct master names.

Your thunks use v1/_search; pass MDMS_V1 context. Also fetch the expected master names for type mapping and panel config.

Apply:

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
@@
       getFieldMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
-        name: "NewFieldType",
-        mdmsContext: mdmsContext,
+        name: "FieldTypeMappingConfig",
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })
@@
       getFieldPanelMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
-        name: "NewDrawerPanelConfig",
-        mdmsContext: mdmsContext,
+        name: "AppPanelMasters",
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })
@@
-  }, [dispatch, flow, pageName, campaignNumber, localeModule, tenantId, mdmsContext, currentLocale]);
+  }, [dispatch, flow, pageName, campaignNumber, localeModule, tenantId, mdmsV1Context, currentLocale]);
#!/bin/bash
# Verify the thunks indeed call v1/_search and expected names.
rg -nP -C2 'createAsyncThunk\\("fieldTypeMaster/fetch' health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js
rg -nP -C2 'createAsyncThunk\\("fieldPanelMaster/fetch' health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
# Check for MDMS_V2_CONTEXT_PATH usage inside fetchAppScreenConfig (should align with v1/_search).
rg -nP -C2 'fetchAppScreenConfig|MDMS_V2_CONTEXT_PATH|v1/_search' health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js

Based on relevant code snippets.

Also applies to: 177-185, 189-196, 220-220


35-35: Fix null-spread crashes in add-field flow. Initialize state to {} and guard spreads.

newFieldType starts as null; spreading it (and prev) throws TypeError on first input/select.

Apply:

-  const [newFieldType, setNewFieldType] = useState(null);
+  const [newFieldType, setNewFieldType] = useState({});
@@
-    setNewFieldType(null);
+    setNewFieldType({});
@@
-    setNewFieldType((prev) => ({
-      ...prev,
-      label: locVal,
-    }));
+    setNewFieldType((prev) => ({
+      ...(prev || {}),
+      label: locVal,
+    }));
@@
-                  const updatedData = { ...newFieldType, field: value };
+                  const updatedData = { ...(newFieldType || {}), field: value };
@@
-            setNewFieldType(null);
+            setNewFieldType({});
@@
-                  setNewFieldType(null);
+                  setNewFieldType({});

Also applies to: 125-125, 256-259, 300-304, 272-272, 317-317


104-113: Guard metadata spread to avoid TypeError when selected type not found.

Spreading undefined throws; default to empty object.

Apply:

-      ...selectedFieldType?.metadata, // Include any metadata from field type
+      ...(selectedFieldType?.metadata || {}), // Include any metadata from field type

198-219: Avoid hydrating localization store with stale data; wait for fetch to complete.

You dispatch setLocalizationData immediately after fetchLocalization; selector still has pre-fetch data.

Apply:

       dispatch(fetchAppScreenConfig({ tenantId }));
 
-      // Set localization context data
-      dispatch(
-        setLocalizationData({
-          localisationData: localizationData,
-          currentLocale,
-          enabledModules: [currentLocale],
-          localeModule,
-        })
-      );

Then add a separate effect:

+  // Hydrate localization after fetch completes
+  useEffect(() => {
+    if (localeModule && localizationStatus === "succeeded" && localizationData) {
+      dispatch(
+        setLocalizationData({
+          localisationData: localizationData,
+          currentLocale,
+          enabledModules: [currentLocale],
+          localeModule,
+        })
+      );
+    }
+  }, [localeModule, localizationStatus, localizationData, currentLocale, dispatch]);

Also applies to: 222-229

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24dff66 and 0e26590.

📒 Files selected for processing (9)
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
🧠 Learnings (3)
📚 Learning: 2025-02-05T10:18:29.947Z
Learnt from: Tulika-eGov
PR: egovernments/DIGIT-Frontend#2188
File: micro-ui/web/micro-ui-internals/packages/modules/pgr/src/pages/employee/new-inbox.js:34-47
Timestamp: 2025-02-05T10:18:29.947Z
Learning: In the DIGIT-Frontend codebase, translations within FormComposer are handled internally by the component itself, so the translation function 't' does not need to be added to useMemo dependency arrays even when used within the memoized function that generates form configurations.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧬 Code graph analysis (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (4)
  • useCustomT (3-18)
  • useCustomT (3-18)
  • entry (12-12)
  • currentLocale (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (3)
  • useCustomT (12-37)
  • useCustomT (12-37)
  • currentLocale (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.js (1)
  • state (240-240)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (3)
  • state (344-344)
  • enabledModules (362-362)
  • currentLocale (347-347)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (2)
  • locale (8-8)
  • currentLocale (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (3)
  • dispatch (33-33)
  • useSelector (42-42)
  • isUpdating (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomTranslate (40-73)
  • useCustomTranslate (40-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (3)
  • useSelector (190-190)
  • NewLayoutRenderer (187-220)
  • isFieldSelected (32-47)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-64)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)
  • fieldTypeMasterData (11-11)
  • fieldType (14-14)
  • useSelector (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (5)
  • fieldTypeConfig (157-157)
  • isFieldSelected (206-221)
  • renderTemplateComponent (223-259)
  • renderSection (353-370)
  • useSelector (155-155)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
  • fieldType (583-588)
  • useSelector (15-15)
  • useSelector (569-569)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • AppConfigurationStore (137-141)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (2)
  • transformMdmsToAppConfig (47-127)
  • transformMdmsToAppConfig (47-127)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (76-79)
  • useFieldDataLabel (76-79)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)
  • getFieldPanelMaster (364-416)
  • getFieldPanelMaster (364-416)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (2)
  • getFieldMaster (5-43)
  • getFieldMaster (5-43)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • isUpdating (38-38)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-10)
  • getFieldTypeFromMasterData (1-10)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.js (2)
  • getComponentFromMasterData (7-14)
  • getComponentFromMasterData (7-14)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js

[error] 81-96: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 81-96: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 297-306: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 297-306: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 318-325: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 318-325: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 334-345: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 334-345: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 346-346: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 363-379: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 363-379: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 380-380: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

⏰ Context from checks skipped due to timeout of 10000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)

1-11: LGTM: Clean imports and selector setup.

The Redux selector and optional chaining for fieldTypeMappingConfig extraction are correctly implemented.


13-17: LGTM: Correct field type and component resolution.

The helpers are invoked with the proper parameters, and the conditional component resolution logic is sound.

// Get component from fieldTypeMasterData, fallback to null
const component = fieldType === "component" ? getComponentFromMasterData(field, fieldTypeMasterData) : null;

console.log("component", fieldType, component);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove leftover console log.

This debugging statement still exists despite being marked as addressed in previous reviews. It pollutes logs and can leak sensitive data in production.

Apply this diff:

-  console.log("component", fieldType, component);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("component", fieldType, component);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
around line 19, remove the leftover console.log("component", fieldType,
component) statement; delete that line and, if runtime debugging info is needed,
replace it with a call to the project's logger (e.g., logger.debug) guarded by
environment or debug flag, and run a quick search in this file for other
console.* calls to remove or replace them similarly.

Comment on lines +15 to +25
// Expose onNext and isUpdating to parent via window object for FullConfigWrapper to access
React.useEffect(() => {
if (onNext) {
window.__appConfig_onNext = onNext;
window.__appConfig_isUpdating = isUpdating;
}
return () => {
delete window.__appConfig_onNext;
delete window.__appConfig_isUpdating;
};
}, [onNext, isUpdating]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid global window plumbing for callbacks.

Leaking handlers on window is fragile; prefer prop drilling via store/context or a tiny event bus scoped to the provider.
If unavoidable, at least namespace uniquely and guard multiple mounts.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
around lines 15 to 25, the component attaches callbacks directly to the global
window (window.__appConfig_onNext / window.__appConfig_isUpdating), which leaks
handlers and is fragile; replace this by exposing the callbacks via React
context or a scoped event bus provided by the parent (FullConfigWrapper) so the
parent consumes the functions through props/context instead of window; if you
must keep a global, namespace the keys with a unique prefix, detect and
warn/avoid overwriting when multiple mounts exist (e.g., check existing key
before assigning), and ensure cleanup only removes keys you set (e.g., confirm
identity) to prevent stomping other instances.

Comment on lines +245 to +248
const locVal = newFieldType?.label
? newFieldType?.label
: `${campaignNumber}_${flow}_${pageName}_${crypto.randomUUID()}`.toUpperCase();

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Provide a UUID fallback for older browsers.

crypto.randomUUID may be unavailable; add a safe fallback.

Apply:

-    const locVal = newFieldType?.label
-      ? newFieldType?.label
-      : `${campaignNumber}_${flow}_${pageName}_${crypto.randomUUID()}`.toUpperCase();
+    const uid = window.crypto?.randomUUID
+      ? window.crypto.randomUUID()
+      : `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
+    const locVal = newFieldType?.label
+      ? newFieldType?.label
+      : `${campaignNumber}_${flow}_${pageName}_${uid}`.toUpperCase();
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 245-248, crypto.randomUUID() is used without a fallback which will
break in older browsers; replace the direct call with a short safe helper: check
if typeof crypto?.randomUUID === 'function' and use it, otherwise generate a
RFC4122 v4 UUID via crypto.getRandomValues (or as a last resort
Math.random-based fallback), then use that UUID in the template string and keep
the final .toUpperCase(); implement the helper in the same file (or import from
a utils module) and call it here so the code works across environments.

Comment on lines +269 to +273
onOverlayClick={() => {}}
onClose={() => {
dispatch(handleShowAddFieldPopup(null));
setNewFieldType(null);
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Close popup on overlay click for expected UX parity.

Overlay is a no-op. Mirror onClose to overlay click.

Apply:

-          onOverlayClick={() => {}}
+          onOverlayClick={() => {
+            dispatch(handleShowAddFieldPopup(null));
+            setNewFieldType({});
+          }}
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 269 to 273, the onOverlayClick handler is a no-op while onClose
clears the popup state; change onOverlayClick to perform the same actions as
onClose by dispatching handleShowAddFieldPopup(null) and calling
setNewFieldType(null) (or extract a shared close function and call it from both
handlers) so clicking the overlay closes the popup.

Comment on lines +284 to +289
<TextInput
name="fieldLabel"
value={fieldDataLabel}
placeholder={t("ENTER_FIELD_LABEL")}
onChange={(event) => handleFieldChange(event.target.value)}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Typing UX: avoid dispatching localization updates on every keystroke.

Consider local input state and dispatch onBlur/submit to reduce churn and improve perceived latency.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 284 to 289, the TextInput currently dispatches localization/state
updates on every keystroke which hurts typing UX; change it to use a local piece
of state for the input value (initialize from fieldDataLabel), update that local
state on onChange, and only call handleFieldChange/dispatch the localization
update on onBlur or on form submit; also add an effect to resync the local state
when the incoming prop fieldDataLabel changes so external updates are reflected.

Comment on lines 40 to 73
export const useCustomTranslate = () => {
const dispatch = useDispatch();
const { data: locState, currentLocale } = useSelector((state) => state.localization);

return useCallback(
(code) => {
if (!code) {
return "";
}

if (!Array.isArray(locState)) {
return ""; // Return empty string if locState not ready
}

const entry = locState?.find((item) => item.code === code);

if (!entry) {
// Get enabled modules from session storage
const enabledModules = Digit?.SessionStorage.get("initData")?.languages || [];

// Add the missing key to Redux store with empty message
dispatch(addMissingKey({ code, enabledModules }));

return ""; // Return empty string when entry not found
}

// Get current locale from Redux state or session storage as fallback
const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;

return entry[locale] || ""; // Return the message or empty string
},
[dispatch, locState, currentLocale]
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove redundant hook after consolidating useCustomT.

Once useCustomT is refactored to return a translator function (as suggested above), this separate useCustomTranslate hook becomes unnecessary. The refactored useCustomT() will serve the same purpose.

Remove this hook entirely or keep it as a simple alias:

-// Hook that returns a stable translate function (can be passed as prop)
-export const useCustomTranslate = () => {
-  const dispatch = useDispatch();
-  const { data: locState, currentLocale } = useSelector((state) => state.localization);
-
-  return useCallback(
-    (code) => {
-      if (!code) {
-        return "";
-      }
-
-      if (!Array.isArray(locState)) {
-        return ""; // Return empty string if locState not ready
-      }
-
-      const entry = locState?.find((item) => item.code === code);
-
-      if (!entry) {
-        // Get enabled modules from session storage
-        const enabledModules = Digit?.SessionStorage.get("initData")?.languages || [];
-
-        // Add the missing key to Redux store with empty message
-        dispatch(addMissingKey({ code, enabledModules }));
-
-        return ""; // Return empty string when entry not found
-      }
-
-      // Get current locale from Redux state or session storage as fallback
-      const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
-
-      return entry[locale] || ""; // Return the message or empty string
-    },
-    [dispatch, locState, currentLocale]
-  );
-};
+// Alias for backward compatibility (if needed)
+export const useCustomTranslate = useCustomT;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
around lines 40 to 73, the useCustomTranslate hook is redundant after
consolidating useCustomT; remove the entire useCustomTranslate implementation
(or replace it with a one-line alias export const useCustomTranslate =
useCustomT) and update any imports/usages across the codebase to either use
useCustomT directly or keep the alias to preserve compatibility. Ensure you run
a quick grep/TS check to fix any broken imports after removal.

Comment on lines +42 to +63
selectField(state, action) {
const { field, screen, card, cardIndex, fieldIndex } = action.payload;
// Use passed indices if available, otherwise try to find them
let finalCardIndex = cardIndex;
let finalFieldIndex = fieldIndex;

if (finalCardIndex === undefined || finalCardIndex === null) {
// Fallback: try to find by ID or reference
finalCardIndex = state.currentData?.body?.findIndex((c) => c.id === card?.id || c === card) ?? -1;
}

if (finalFieldIndex === undefined || finalFieldIndex === null) {
// Fallback: try to find by ID or reference
finalFieldIndex = card?.fields?.findIndex((f) => f.id === field?.id || f === field) ?? -1;
}

state.selectedField = field;
state.selectedFieldPath = { cardIndex: finalCardIndex, fieldIndex: finalFieldIndex };
state.currentScreen = screen;
state.currentCard = card;
state.isFieldSelected = true;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Bind selectedField to canonical state; avoid stale payload refs.

Current code stores the payload object in selectedField, which may not be the same reference as the one inside state. Resolve indices, then bind to the field in state.currentData to keep updates consistent.

- state.selectedField = field;
- state.selectedFieldPath = { cardIndex: finalCardIndex, fieldIndex: finalFieldIndex };
+ state.selectedFieldPath = { cardIndex: finalCardIndex, fieldIndex: finalFieldIndex };
+ const canonical =
+   state.currentData?.body?.[finalCardIndex]?.fields?.[finalFieldIndex] ?? field ?? null;
+ state.selectedField = canonical;
- state.currentScreen = screen;
- state.currentCard = card;
- state.isFieldSelected = true;
+ state.currentScreen = screen ?? null;
+ state.currentCard = state.currentData?.body?.[finalCardIndex] ?? card ?? null;
+ // derived: !!selectedField
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
around lines 42 to 63, the reducer stores the payload field object directly into
state.selectedField which can be a stale/out-of-tree reference; after resolving
finalCardIndex and finalFieldIndex, look up the canonical field from
state.currentData.body[finalCardIndex].fields[finalFieldIndex] (with safe
null/bounds checks) and assign that to state.selectedField, falling back to the
incoming payload only if the canonical reference is not available; keep
state.selectedFieldPath, currentScreen/currentCard, and isFieldSelected updates
as-is.

Comment on lines 6 to 40
const transformSingleScreen = (screenData) => {
if (screenData.type === "template") {
return {
screenType: "TEMPLATE",
name: screenData.page,
heading: screenData.heading,
description: screenData.description,
body: transformTemplateBody(screenData.body),
footer: screenData.footer || [],
...(screenData.navigateTo && { navigateTo: screenData.navigateTo })
};
} else if (screenData.type === "object") {
return {
screenType: "FORM",
name: screenData.flow,
project: screenData.project,
version: screenData.version,
disabled: false,
isSelected: true,
pages: [{
page: screenData.page,
type: screenData.type,
label: screenData.heading,
order: screenData.order,
description: screenData.description,
properties: transformFormProperties(screenData.body),
...(screenData.navigateTo && { navigateTo: screenData.navigateTo }),
...(screenData.footer && screenData.footer.length > 0 && { actionLabel: screenData.footer[0].label })
}],
...(screenData.footer && screenData.footer.length > 0 && {
onAction: extractOnActions(screenData.footer)
})
};
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return null for unsupported screenData.type.

Avoid returning undefined to keep the structure predictable.

   } else if (screenData.type === "object") {
     return {
       ...
     };
   }
-};
+  return null;
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transformSingleScreen = (screenData) => {
if (screenData.type === "template") {
return {
screenType: "TEMPLATE",
name: screenData.page,
heading: screenData.heading,
description: screenData.description,
body: transformTemplateBody(screenData.body),
footer: screenData.footer || [],
...(screenData.navigateTo && { navigateTo: screenData.navigateTo })
};
} else if (screenData.type === "object") {
return {
screenType: "FORM",
name: screenData.flow,
project: screenData.project,
version: screenData.version,
disabled: false,
isSelected: true,
pages: [{
page: screenData.page,
type: screenData.type,
label: screenData.heading,
order: screenData.order,
description: screenData.description,
properties: transformFormProperties(screenData.body),
...(screenData.navigateTo && { navigateTo: screenData.navigateTo }),
...(screenData.footer && screenData.footer.length > 0 && { actionLabel: screenData.footer[0].label })
}],
...(screenData.footer && screenData.footer.length > 0 && {
onAction: extractOnActions(screenData.footer)
})
};
}
};
const transformSingleScreen = (screenData) => {
if (screenData.type === "template") {
return {
screenType: "TEMPLATE",
name: screenData.page,
heading: screenData.heading,
description: screenData.description,
body: transformTemplateBody(screenData.body),
footer: screenData.footer || [],
...(screenData.navigateTo && { navigateTo: screenData.navigateTo })
};
} else if (screenData.type === "object") {
return {
screenType: "FORM",
name: screenData.flow,
project: screenData.project,
version: screenData.version,
disabled: false,
isSelected: true,
pages: [{
page: screenData.page,
type: screenData.type,
label: screenData.heading,
order: screenData.order,
description: screenData.description,
properties: transformFormProperties(screenData.body),
...(screenData.navigateTo && { navigateTo: screenData.navigateTo }),
...(screenData.footer && screenData.footer.length > 0 && { actionLabel: screenData.footer[0].label })
}],
...(screenData.footer && screenData.footer.length > 0 && {
onAction: extractOnActions(screenData.footer)
})
};
}
return null;
};
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
around lines 6-40, transformSingleScreen currently returns objects for types
"template" and "object" but falls through to undefined for any other
screenData.type; change the function to explicitly return null for unsupported
types (e.g., add a final return null or an else branch) so callers get a
predictable null value instead of undefined.

Comment on lines +47 to +50
export const transformMdmsToAppConfig = (fullData) => {
if (!fullData || !Array.isArray(fullData) || fullData.length === 0) {
throw new Error("Invalid fullData: Expected non-empty array");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize fullData: accept arrays or single objects.

Current code throws when fullData is an array of arrays (common when MDMS returns data arrays).

-export const transformMdmsToAppConfig = (fullData) => {
-  if (!fullData || !Array.isArray(fullData) || fullData.length === 0) {
+export const transformMdmsToAppConfig = (fullData) => {
+  if (!fullData || !Array.isArray(fullData) || fullData.length === 0) {
     throw new Error("Invalid fullData: Expected non-empty array");
   }
+  // Flatten one level if MDMS returned an array of arrays
+  const items = fullData.flatMap((it) => (Array.isArray(it) ? it : [it])).filter(Boolean);

And replace later loops to iterate items instead of fullData:

-  fullData.forEach(item => {
+  items.forEach((item) => {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
around lines 47-50, change the strict array-only guard to normalize fullData
into an items array that the rest of the function will iterate: if fullData is
null/undefined throw, if fullData is an array and contains nested arrays (MDMS
data arrays) flatten one level into items, if fullData is a plain object wrap it
in an array as items, otherwise use the array as-is; then update later loops to
iterate over items instead of fullData.

* Dependent Field Chnages

* Removed console logs and updated hardcoded params

---------

Co-authored-by: Nabeel Ayubee <[email protected]>
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: 19

♻️ Duplicate comments (8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1)

4-11: Add title to SVG for accessibility.

The inline SVG lacks alternative text, which screen readers need to convey its purpose.

Apply this diff:

   const FilterIcon = () => (
     <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <title>Filter icon</title>
       <path
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (1)

1-21: Address the code duplication flagged in previous review.

A past review identified this function as a duplicate of getFieldTypeFromMasterData in the root helpers directory (src/helpers/fieldTypeHelpers.js). The template-specific logic added in lines 8-15 should be added to the canonical version, and this file should import from there instead.

Based on past review comments.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (2)

81-109: Add keyboard accessibility to the selectable wrapper.

The clickable div lacks keyboard support, preventing keyboard-only users from focusing or activating it.

Apply this diff to make it keyboard-accessible:

       <div
         key={uniqueKey}
+        role="button"
+        tabIndex={0}
         onClick={(e) => {
           e.stopPropagation();
           onFieldClick && onFieldClick(field, data, null, index, null);
         }}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            e.stopPropagation();
+            onFieldClick && onFieldClick(field, data, null, index, null);
+          }
+        }}
         style={{

114-132: Apply the same keyboard accessibility fix to the custom component wrapper.

This wrapper has the same accessibility gap as the fallback path.

Apply this diff:

       <div
         key={uniqueKey}
+        role="button"
+        tabIndex={0}
         onClick={(e) => {
           e.stopPropagation();
           onFieldClick && onFieldClick(field, data, null, index, null);
         }}
+        onKeyDown={(e) => {
+          if (e.key === "Enter" || e.key === " ") {
+            e.preventDefault();
+            e.stopPropagation();
+            onFieldClick && onFieldClick(field, data, null, index, null);
+          }
+        }}
         style={{
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (4)

398-404: Make the tooltip wrapper keyboard-accessible.

The div with hover behavior lacks keyboard support, preventing keyboard users from seeing the tooltip.

Apply this diff:

           <div
             ref={switchRef}
+            role="button"
+            tabIndex={0}
+            aria-label="Field type dropdown with tooltip"
             className="drawer-container-tooltip"
             onMouseEnter={() => setShowTooltip(true)}
             onMouseLeave={() => setShowTooltip(false)}
+            onFocus={() => setShowTooltip(true)}
+            onBlur={() => setShowTooltip(false)}
           >

525-540: Make the delete icon keyboard-accessible.

The delete div lacks keyboard navigation support.

Apply this diff:

               <div
+                role="button"
+                tabIndex={0}
+                aria-label="Delete option"
                 onClick={() => {
                   const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
                   onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
                 }}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
+                    onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
+                  }
+                }}
                 style={{

559-577: Wrap switch case declarations in a block.

The const declarations at lines 560-561 leak into other switch cases.

Apply this diff:

-    case "dropdown":
+    case "dropdown": {
       const dropdownOptionKey = cField.optionKey || "schemaCode";
       const bindValue = selectedField[cField.bindTo];
       return (
         // ... existing JSX
       );
+    }

598-599: Add explicit button type.

Without an explicit type, the button defaults to "submit", which may cause unintended form submissions.

Apply this diff:

-        <button key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
+        <button type="button" key={tab} className={`configure-app-tab-head ${activeTab === tab ? "active" : ""} hover`} onClick={() => onTabChange(tab)}>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e26590 and 6b725cf.

📒 Files selected for processing (10)
  • health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (3 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/Module.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js
🧬 Code graph analysis (10)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (2)
  • BOUNDARY_HIERARCHY_TYPE (293-293)
  • hierarchyData (294-294)
health/micro-ui/web/builds/workbench-ui/index.js (2)
  • stateCode (45-45)
  • userType (17-17)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1)
  • SwitchTemplate (3-5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (1)
  • ButtonTemplate (4-24)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1)
  • FilterIcon (30-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)
  • t (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • tenantId (95-95)
  • moduleName (96-96)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (2)
  • fetchFlowPages (4-41)
  • fetchFlowPages (4-41)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (2)
  • fetchPageFields (4-44)
  • fetchPageFields (4-44)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (1)
  • store (123-128)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (1)
  • response (14-29)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • tenantId (95-95)
  • moduleName (96-96)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (6)
  • tenantId (27-27)
  • tenantId (71-71)
  • moduleName (82-82)
  • masterName (83-83)
  • campaignNumber (86-86)
  • flowId (85-85)
micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/CreateChecklist.js (1)
  • flow (29-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1)
  • fieldTypeMasterData (190-190)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (1)
  • fieldTypeMasterData (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)
  • fieldTypeMasterData (11-11)
  • fieldType (14-14)
  • useSelector (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (5)
  • fieldTypeConfig (157-157)
  • isFieldSelected (206-221)
  • renderTemplateComponent (223-259)
  • renderSection (353-370)
  • useSelector (155-155)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-21)
  • getFieldTypeFromMasterData (1-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (2)
  • dispatch (70-70)
  • selectedField (74-74)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (41-41)
  • useCustomT (5-37)
  • useCustomT (5-37)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (2)
  • currentLocale (38-38)
  • existing (126-126)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-21)
  • getFieldTypeFromMasterData (1-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (1)
  • response (8-20)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • tenantId (95-95)
  • moduleName (96-96)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (4)
  • tenantId (27-27)
  • tenantId (71-71)
  • moduleName (82-82)
  • campaignNumber (86-86)
micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/CreateChecklist.js (1)
  • flow (29-29)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js

[error] 5-5: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js

[error] 634-634: The elements with this role can be changed to the following elements:

For examples and more information, see WAI-ARIA Roles

(lint/a11y/useSemanticElements)


[error] 633-639: The HTML element with the interactive role "button" is not focusable.

A non-interactive HTML element that is not focusable may not be reachable for users that rely on keyboard navigation, even with an added role like "button".
Add a tabIndex attribute to make this element focusable.

(lint/a11y/useFocusableInteractive)


[error] 633-639: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 648-648: The elements with this role can be changed to the following elements:

For examples and more information, see WAI-ARIA Roles

(lint/a11y/useSemanticElements)


[error] 647-653: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 647-653: The HTML element with the interactive role "button" is not focusable.

A non-interactive HTML element that is not focusable may not be reachable for users that rely on keyboard navigation, even with an added role like "button".
Add a tabIndex attribute to make this element focusable.

(lint/a11y/useFocusableInteractive)


[error] 1039-1045: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 1039-1045: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 471-471: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 493-493: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 494-494: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 495-495: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 496-496: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 497-497: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 501-501: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 712-712: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js

[error] 81-96: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 81-96: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 114-129: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 398-404: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 525-539: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 525-539: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 560-561: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 561-562: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 56-56: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 118-118: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 182-182: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 215-215: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 377-377: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 378-378: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 598-599: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1)

12-32: LGTM!

The Filter component logic is sound. The conditional rendering of RoundedLabel when field.value is a non-empty array is correct, and the use of optional chaining prevents potential errors.

health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)

137-142: LGTM!

The formatting change improves readability by expanding the EmployeeApp JSX across multiple lines without altering functionality.


206-208: Review comment is resolved. The mapping is intentional and actively used.

All three component names map to SwitchTemplate by design:

  • searchByProximity and searchByID are active field types used extensively in MDMS configs (FormConfigTemplate, FieldTypeMappingConfig, AppModuleSchema)
  • They represent distinct semantic purposes but share the same toggle/switch UI representation
  • This is a valid pattern in configurable component systems where different logical types can share identical visual implementations
  • Both are properly registered in RegistrationComponents.js and active in deployment configs

No action needed.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (1)

46-88: LGTM!

The slice structure follows Redux Toolkit best practices. The caching strategy in fetchPageFields prevents redundant API calls, and the clearPageFields reducer provides flexibility for cache management.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (1)

43-72: LGTM!

The slice structure follows Redux Toolkit best practices with proper state management for loading, success, and error states. The clearFlowPages reducer provides a clean way to reset state when needed.

Comment on lines +4 to +11
const FilterIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.250666 1.61C2.27067 4.2 6.00067 9 6.00067 9V15C6.00067 15.55 6.45067 16 7.00067 16H9.00067C9.55067 16 10.0007 15.55 10.0007 15V9C10.0007 9 13.7207 4.2 15.7407 1.61C16.2507 0.95 15.7807 0 14.9507 0H1.04067C0.210666 0 -0.259334 0.95 0.250666 1.61Z"
fill="#C84C0E"
/>
</svg>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract FilterIcon to eliminate duplication.

The same FilterIcon SVG appears in LayoutRenderer.js (lines 29-36). Extract it to a shared location or utility file to maintain consistency and reduce duplication.

For example, create a shared icons/FilterIcon.js:

const FilterIcon = () => (
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <title>Filter icon</title>
    <path
      d="M0.250666 1.61C2.27067 4.2 6.00067 9 6.00067 9V15C6.00067 15.55 6.45067 16 7.00067 16H9.00067C9.55067 16 10.0007 15.55 10.0007 15V9C10.0007 9 13.7207 4.2 15.7407 1.61C16.2507 0.95 15.7807 0 14.9507 0H1.04067C0.210666 0 -0.259334 0.95 0.250666 1.61Z"
      fill="#C84C0E"
    />
  </svg>
);
export default FilterIcon;

Then import it in both files:

import FilterIcon from "../../icons/FilterIcon";
🧰 Tools
🪛 Biome (2.1.2)

[error] 5-5: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js
around lines 4-11, the FilterIcon SVG is duplicated (also present in
LayoutRenderer.js lines ~29-36); extract the SVG into a shared component file
(e.g.,
health/micro-ui/web/packages/modules/campaign-manager/src/icons/FilterIcon.js)
that returns the same SVG (add a <title> for accessibility) and exports it as
default, then replace the inline FilterIcon definitions in both Filter.js and
LayoutRenderer.js with a single import from that shared file and remove the
local definitions so both files reuse the same component.

Comment on lines +3 to +5
const SwitchTemplate = ({ field, t }) => (
<Switch label={t(field?.label)} onToggle={() => {}} isCheckedInitially={field?.value || false} shapeOnOff />
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Implement the onToggle handler for functional switch interaction.

The onToggle={() => {}} no-op handler renders the switch non-functional—users can click it but nothing happens. While this may be acceptable as a placeholder in this initial dummy configuration setup, ensure a proper handler is implemented before production use. The handler should update the field value and trigger any necessary side effects (e.g., dispatching Redux actions, calling parent callbacks).

Do you want me to generate a proper implementation that integrates with the field state management system, or open an issue to track this task?

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.js
around lines 3 to 5, the Switch currently uses a no-op onToggle which makes it
non-functional; implement onToggle to flip the field value and propagate the
change by calling the field's change handler or parent callback (e.g.,
field.onChange or a provided onChange prop), update any local/state value if
needed, and trigger side effects such as dispatching Redux actions or calling a
passed callback; ensure the new handler reads the current value (field?.value ||
false), computes the toggled value, calls the appropriate updater
(field.onChange(toggledValue) or onChange(toggledValue)), and preserves
shapeOnOff and existing props.

return ReactDOM.createPortal(children, document.body);
}

function MdmsValueDropdown({ schemaCode, value, onChange, t }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded tenant fallback value.

The fallback tenant ID "mz" is hardcoded. If this is a real tenant identifier, consider using a more generic fallback or a configuration constant.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js
around line 26, the MdmsValueDropdown component uses a hardcoded tenant fallback
"mz"; replace this hardcoded value by reading the tenant ID from a single source
of truth (prop, context, or a config constant) instead — e.g., accept tenantId
as a prop or import a TENANT_ID default from a config file or use an existing
tenant context/getTenant helper; update callers to pass the tenant when needed
and remove the literal "mz" so the component uses the configurable/default
tenant value.

Comment on lines +68 to +283
function NewDependentFieldWrapper({ t }) {
const useT = useCustomT();
const dispatch = useDispatch();
const tenantId = Digit?.ULBService?.getCurrentTenantId?.() || "mz";

// Get data from Redux
const selectedField = useSelector((state) => state.remoteConfig.selectedField);
const currentData = useSelector((state) => state.remoteConfig.currentData);

// FIXED: Access pages directly from state.flowPages.pages
const flowPages = useSelector((state) => state.flowPages.pages);
const flowPagesStatus = useSelector((state) => state.flowPages.status);
const pageConfigs = useSelector((state) => state.pageFields.byPage);
const pageFieldsLoading = useSelector((state) => state.pageFields.loadingPages);
const moduleName = "HCM-ADMIN-CONSOLE";
const masterName = "AppFlowConfig";

const flowId = currentData?.flow || "REGISTRATION";
const campaignNumber = currentData?.project || "";
const currentPageName = currentData?.page;

// Fetch flows on mount or when campaign changes
useEffect(() => {
if (flowPagesStatus === 'idle' && campaignNumber) {
dispatch(fetchFlowPages({
tenantId,
campaignNumber,
flowId,
moduleName,
masterName,
}));
}
}, [dispatch, flowPagesStatus, tenantId, campaignNumber]);

// Fetch current page fields if not already loaded
useEffect(() => {
if (currentPageName && flowId && campaignNumber &&
!pageConfigs[currentPageName] && !pageFieldsLoading[currentPageName]) {
dispatch(fetchPageFields({
tenantId,
flow: flowId,
campaignNumber,
pageName: currentPageName,
}));
}
}, [dispatch, currentPageName, flowId, campaignNumber, pageConfigs, pageFieldsLoading, tenantId]);

// Pre-fetch all pages in the flow for better UX
useEffect(() => {
if (flowId && campaignNumber && flowPages.length > 0) {
flowPages.forEach(page => {
if (!pageConfigs[page.name] && !pageFieldsLoading[page.name]) {
dispatch(fetchPageFields({
tenantId,
flow: flowId,
campaignNumber,
pageName: page.name,
}));
}
});
}
}, [dispatch, flowId, campaignNumber, flowPages, pageConfigs, pageFieldsLoading, tenantId]);

// ---------- labels ----------
const displayLogicLabel = t("DISPLAY_LOGIC") || "Display Logic";
const noLogicAddedLabel = t("NO_LOGIC_ADDED") || "No logic added yet.";
const addDisplayLogicLabel = t("ADD_DISPLAY_LOGIC") || "Add Display Logic";
const editLabel = t("EDIT") || "Edit";
const deleteRuleLabel = t("HCM_REMOVE_RULE") || "Delete Rule";
const joinWithLabel = t("HCM_JOIN_WITH") || "Join with";
const selectPageLabel = t("HCM_SELECT_PAGE") || "Select Page";
const selectFieldLabel = t("HCM_SELECT_FIELD") || "Select Field";
const comparisonTypeLabel = t("HCM_COMPARISION_TYPE") || "Comparison";
const selectValueLabel = t("HCM_SELECT_VALUE") || "Select Value";
const enterValueLabel = t("ENTER_VALUE") || "Enter value";
const closeLabel = t("CLOSE") || "Cancel";
const submitLabel = t("SUBMIT") || "Submit";
const andText = t("AND") || "And";
const orText = t("OR") || "Or";
const completeAllMsg =
t("PLEASE_COMPLETE_ALL_CONDITIONS") ||
"Please complete all condition fields before confirming.";
const logicLabel = t("HCM_LOGIC") || "Logic";

// ---------- constants & helpers ----------
const LOGICALS = [
{ code: "&&", name: t("AND") || "AND" },
{ code: "||", name: t("OR") || "OR" },
];
const ALL_OPERATOR_OPTIONS = [
{ code: "==", name: "EQUALS_TO"},
{ code: "!=", name: "NOT_EQUALS_TO" },
{ code: ">=", name: "GREATER_THAN_OR_EQUALS_TO" },
{ code: "<=", name: "LESS_THAN_OR_EQUALS_TO"},
{ code: ">", name: "GREATER_THAN"},
{ code: "<", name: "LESS_THAN" },
];
const PARSE_OPERATORS = useMemo(
() => ["!=", ">=", "<=", "==", ">", "<"].sort((a, b) => b.length - a.length),
[]
);

// Safe extraction of expression
const getExpressionString = () => {
const expr = selectedField?.visibilityCondition?.expression;
if (typeof expr === 'string') {
return expr.trim();
}
if (expr && typeof expr === 'object' && expr.expression) {
return String(expr.expression).trim();
}
return "";
};

// Build page options from flowPages directly
const pageOptions = useMemo(() => {
// Get current page index
const currentPageIndex = flowPages.findIndex((p) => p?.name === currentPageName);

// Only show pages up to and including current page
const availablePages = currentPageIndex >= 0
? flowPages.slice(0, currentPageIndex + 1)
: flowPages;

return availablePages.map((p) => ({
code: p.name,
name: p.name,
order: p.order
}));
}, [flowPages, currentPageName]);

// Updated getPageObj to use fetched page configs
const getPageObj = (pageCode) => {
if (pageCode === currentPageName) {
// Use currentData for current page (it's already loaded)
return currentData?.body?.[0];
}

// Check if we have fetched the page configuration
const pageConfig = pageConfigs[pageCode];
if (pageConfig) {
return pageConfig.body?.[0] || { fields: [] };
}

// Return empty object if no data yet
return { fields: [] };
};

// Get field options for a page
const getFieldOptions = (pageCode) => {
const pageObj = getPageObj(pageCode);
if (!pageObj?.fields) return [];

return pageObj.fields
.filter((f) => {
// Skip template, dynamic, and custom fields
const fieldType = String(f?.type || "").toLowerCase();
if (["template", "dynamic", "custom"].includes(fieldType)) return false;

// Skip hidden fields unless explicitly included in form
const isHidden = f?.hidden === true;
const includeInForm = f?.includeInForm;
if (isHidden && !includeInForm) return false;

return true;
})
.filter((f) => {
// For current page, only show fields before the selected field
if (pageCode === currentPageName && selectedField) {
return f?.order < (selectedField?.order || Number.MAX_VALUE);
}
return true;
})
.map((f) => ({
code: f.fieldName || f.jsonPath || f.id,
name: f.fieldName || f.jsonPath || f.id,
label: f.label || f.fieldName,
format: f.format,
type: f.type || f.datatype || f.format || "string",
schemaCode: f.schemaCode,
enums: f.enums || f.dropDownOptions || f.options || [],
order: f.order,
}));
};

// Fetch fields when a page is selected
const handlePageSelection = useCallback((pageCode) => {
if (!flowId || !campaignNumber) {
console.warn("Missing flow or campaign information");
return;
}

// Fetch fields for the selected page if not already cached
if (!pageConfigs[pageCode] && !pageFieldsLoading[pageCode]) {
dispatch(fetchPageFields({
tenantId,
flow: flowId,
campaignNumber,
pageName: pageCode,
}));
}
}, [dispatch, pageConfigs, pageFieldsLoading, tenantId, flowId, campaignNumber]);

// Don't render until we have the necessary data
if (!campaignNumber || !flowId) {
return (
<Card type="secondary">
<div style={{ padding: "1rem" }}>
<p style={{ opacity: 0.7, margin: 0 }}>
{t("LOADING_CONFIGURATION") || "Loading configuration..."}
</p>
</div>
</Card>
);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Hooks called after conditional early return.

The early return at lines 272-282 violates React's Rules of Hooks. All hooks (including useMemo at line 471 and useState at lines 493-497) must be called before any conditional returns to maintain consistent hook order across renders.

Move the early return to the top of the component, before any hooks:

 function NewDependentFieldWrapper({ t }) {
     const useT = useCustomT();
     const dispatch = useDispatch();
     const tenantId = Digit?.ULBService?.getCurrentTenantId?.() || "mz";
 
+    // Early return before hooks
+    const selectedField = useSelector((state) => state.remoteConfig.selectedField);
+    const currentData = useSelector((state) => state.remoteConfig.currentData);
+    const campaignNumber = currentData?.project || "";
+    const flowId = currentData?.flow || "REGISTRATION";
+
+    if (!campaignNumber || !flowId) {
+        return (
+            <Card type="secondary">
+                <div style={{ padding: "1rem" }}>
+                    <p style={{ opacity: 0.7, margin: 0 }}>
+                        {t("LOADING_CONFIGURATION") || "Loading configuration..."}
+                    </p>
+                </div>
+            </Card>
+        );
+    }
+
     // Get data from Redux
-    const selectedField = useSelector((state) => state.remoteConfig.selectedField);
-    const currentData = useSelector((state) => state.remoteConfig.currentData);
     
     // ... rest of hooks
-    
-    // Don't render until we have the necessary data
-    if (!campaignNumber || !flowId) {
-        return (
-            <Card type="secondary">
-                <div style={{ padding: "1rem" }}>
-                    <p style={{ opacity: 0.7, margin: 0 }}>
-                        {t("LOADING_CONFIGURATION") || "Loading configuration..."}
-                    </p>
-                </div>
-            </Card>
-        );
-    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +82 to +83
const moduleName = "HCM-ADMIN-CONSOLE";
const masterName = "AppFlowConfig";
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extract hardcoded configuration values to constants.

The moduleName and masterName are hardcoded and duplicated across multiple files in this PR.

Consider extracting to a shared constants file as suggested in the pageFieldsSlice review.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js
around lines 82-83, the moduleName ("HCM-ADMIN-CONSOLE") and masterName
("AppFlowConfig") are hardcoded; extract these into a shared constants file
(e.g., src/constants/appConfigConstants.js or the existing pageFieldsSlice
constants file), replace the hardcoded values with named imports, update all
other files in this PR that duplicate these literals to import the same
constants, and ensure the constants file exports clear names (MODULE_NAME,
MASTER_NAME) and preserves current values for backward compatibility.

const flows = response.mdms?.[0]?.data?.flows || [];

// Find the REGISTRATION flow and return its pages
const registrationFlow = flows.find((flow) => flow?.id === flowId);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Misleading variable name.

The variable is named registrationFlow but it actually finds a flow by the dynamic flowId parameter, which may not always be "REGISTRATION".

-        // Find the REGISTRATION flow and return its pages
-        const registrationFlow = flows.find((flow) => flow?.id === flowId);
-        const pages = registrationFlow?.pages || [];
+        // Find the target flow and return its pages
+        const targetFlow = flows.find((flow) => flow?.id === flowId);
+        const pages = targetFlow?.pages || [];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js
around line 28, the variable named `registrationFlow` is misleading because it
finds a flow by the dynamic `flowId` and not specifically the registration flow;
rename it to a neutral, descriptive name such as `selectedFlow` or `flowById`
and update all subsequent references in the function/scope to use the new name
to accurately reflect its purpose.

// Async thunk to fetch page configuration (fields and metadata)
export const fetchPageFields = createAsyncThunk(
"pageFields/fetch",
async ({ tenantId, flow, campaignNumber, pageName, moduleName = "HCM-ADMIN-CONSOLE" }, { getState, rejectWithValue }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting the hardcoded module name to a constant.

The default value "HCM-ADMIN-CONSOLE" is hardcoded here and in several other files. Consider extracting it to a shared constant or configuration file to improve maintainability.

Example refactor in a shared constants file:

// constants.js
export const DEFAULT_MODULE_NAME = "HCM-ADMIN-CONSOLE";
export const DEFAULT_MASTER_NAME = "AppFlowConfig";

Then import and use:

+import { DEFAULT_MODULE_NAME } from "../constants";

-async ({ tenantId, flow, campaignNumber, pageName, moduleName = "HCM-ADMIN-CONSOLE" }, { getState, rejectWithValue }) => {
+async ({ tenantId, flow, campaignNumber, pageName, moduleName = DEFAULT_MODULE_NAME }, { getState, rejectWithValue }) => {
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js
around line 6, the default moduleName value "HCM-ADMIN-CONSOLE" is hardcoded;
extract this literal into a shared constant (e.g., DEFAULT_MODULE_NAME) in a
central constants/config file, export it, then replace the inline default with
an imported DEFAULT_MODULE_NAME; also update other files that use the same
literal to import and use the new constant to keep behavior identical and
improve maintainability.

import { configureStore } from "@reduxjs/toolkit";
import remoteConfigReducer from "./remoteConfigSlice";
import fieldMasterReducer from "./fieldMasterSlice";
import fieldPropertiesReducer from "./fieldPanelPropertiesSlice";
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Naming inconsistency: Consider renaming import for clarity.

The import is named fieldPropertiesReducer but comes from fieldPanelPropertiesSlice. For consistency and clarity, consider renaming the import to match the file's purpose.

-import fieldPropertiesReducer from "./fieldPanelPropertiesSlice";
+import fieldPanelPropertiesReducer from "./fieldPanelPropertiesSlice";

Then update the store configuration:

   reducer: {
     remoteConfig: remoteConfigReducer,
     fieldTypeMaster: fieldMasterReducer,
-    fieldPanelMaster: fieldPropertiesReducer,
+    fieldPanelMaster: fieldPanelPropertiesReducer,
     localization: localizationReducer,
     flowPages: flowPagesReducer,
     pageFields: pageFieldsReducer,
   },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js
around line 4 the imported identifier fieldPropertiesReducer does not match the
source filename fieldPanelPropertiesSlice; rename the import to match the file
purpose (e.g., fieldPanelPropertiesReducer) and update any references in this
store file (and nearby exports/usage) to use the new name so the import
identifier clearly reflects the slice module.

},
});

export const AppDispatch = store.dispatch;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix incorrect AppDispatch export.

AppDispatch is exported as a constant value (store.dispatch) rather than a type. This will cause runtime errors when used as a TypeScript type. If TypeScript is in use, this should be a type export. If not using TypeScript, this export is unnecessary.

If using TypeScript, apply this fix:

-export const AppDispatch = store.dispatch;
+export type AppDispatch = typeof store.dispatch;

If not using TypeScript, remove this line as it serves no purpose and may confuse developers:

-export const AppDispatch = store.dispatch;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const AppDispatch = store.dispatch;
// Remove the line entirely for a JavaScript file
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.js
around line 20, the file exports AppDispatch as a runtime constant (export const
AppDispatch = store.dispatch;) which is incorrect for TypeScript and unnecessary
for plain JS; if this is a TypeScript project change the export to a type export
(export type AppDispatch = typeof store.dispatch) so consumers use it as a type,
otherwise remove the line entirely to avoid exporting a useless runtime value.

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: 23

♻️ Duplicate comments (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)

139-143: Remove unused lazy import or wire it into routes.

AppConfigurationStore is declared but unused, adding to bundle for no gain. Remove or add a route that uses it.

-const AppConfigurationStore = lazyWithFallback(
-  () => import(/* webpackChunkName: "app-configuration-store" */ "./NewAppConfiguration/AppConfigurationStore"),
-  () => require("./NewAppConfiguration/AppConfigurationStore").default,
-  { loaderText: "Loading App Configuration..." }
-);
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (4)

300-312: A11y + keys: add roles/keyboard and stable keys.

  • Replace index keys with stable ids.
  • Add role/tabIndex/onKeyDown for keyboard access.
-          {flowConfig.flows?.map((flow, index) => (
+          {flowConfig.flows?.map((flow) => (
             <div
-              key={index}
+              key={flow.id ?? flow.name}
               style={{
                 ...styles.flowItem,
                 backgroundColor: selectedFlow === flow.id ? "#F47738" : "transparent",
                 color: selectedFlow === flow.id ? "#FFFFFF" : "#505A5F",
                 fontWeight: selectedFlow === flow.id ? "700" : "400",
               }}
-              onClick={() => handleFlowClick(flow)}
+              role="button"
+              tabIndex={0}
+              aria-pressed={selectedFlow === flow.id}
+              onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && (e.preventDefault(), handleFlowClick(flow))}
+              onClick={() => handleFlowClick(flow)}
           >
@@
-          {activeFlow?.pages?.map((page, index) => (
+          {activeFlow?.pages?.map((page) => (
             <div
-              key={index}
+              key={page.name}
               style={{
                 ...styles.pageTab,
                 ...(selectedPageName === page.name ? styles.pageTabActive : {}),
               }}
-              onClick={() => handlePageClick(page)}
+              role="button"
+              tabIndex={0}
+              aria-selected={selectedPageName === page.name}
+              onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && (e.preventDefault(), handlePageClick(page))}
+              onClick={() => handlePageClick(page)}
           >

[Based on static analysis hints]

Also applies to: 320-331


337-352: Make navigation arrows accessible (button semantics + labels).

-          <div
+          <div
             style={{
               ...styles.navArrow,
               opacity: !previousRoute ? 0.3 : 1,
               cursor: !previousRoute ? "not-allowed" : "pointer",
             }}
-            onClick={() => {
+            role="button"
+            tabIndex={previousRoute ? 0 : -1}
+            aria-disabled={!previousRoute}
+            aria-label="Previous page"
+            onKeyDown={(e) => {
+              if (!previousRoute) return;
+              if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelectedPageName(previousRoute); }
+            }}
+            onClick={() => {
               if (previousRoute) {
                 setSelectedPageName(previousRoute);
               }
             }}
           >
-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" aria-labelledby="prevTitle" role="img" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <title id="prevTitle">Previous page</title>
               <path d="M15 18L9 12L15 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>
           </div>
@@
-          <div
+          <div
             style={{
               ...styles.navArrow,
               opacity: !nextRoute ? 0.3 : 1,
               cursor: !nextRoute ? "not-allowed" : "pointer",
             }}
-            onClick={async () => {
+            role="button"
+            tabIndex={nextRoute ? 0 : -1}
+            aria-disabled={!nextRoute}
+            aria-label="Next page"
+            onKeyDown={async (e) => {
+              if (!nextRoute) return;
+              if (e.key === "Enter" || e.key === " ") {
+                e.preventDefault();
+                if (window.__appConfig_onNext && typeof window.__appConfig_onNext === "function") await window.__appConfig_onNext();
+                setSelectedPageName(nextRoute);
+              }
+            }}
+            onClick={async () => {
               if (nextRoute) {
                 // Call MDMS update if available
                 if (window.__appConfig_onNext && typeof window.__appConfig_onNext === "function") {
                   await window.__appConfig_onNext();
                 }
                 // Navigate to next page
                 setSelectedPageName(nextRoute);
               }
             }}
           >
-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" aria-labelledby="nextTitle" role="img" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <title id="nextTitle">Next page</title>
               <path d="M9 18L15 12L9 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>
           </div>

[Based on static analysis hints]

Also applies to: 366-385


355-363: Props passed to AppConfigurationStore are ignored.

onPageChange, nextRoute, previousRoute aren’t accepted/forwarded by AppConfigurationStore.

Option A (remove here):

           <AppConfigurationStore
             flow={selectedFlow}
             flowName={activeFlow?.name}
             pageName={selectedPageName}
             campaignNumber={flowConfig?.project}
-            onPageChange={setSelectedPageName}
-            nextRoute={nextRoute}
-            previousRoute={previousRoute}
           />

Option B: update AppConfigurationStore API to accept and forward these props to AppConfigurationWrapper.


160-278: Avoid recreating large styles object each render.

Move to module scope or memoize.

-  const styles = {
+  const styles = React.useMemo(() => ({
     /* unchanged */
-  };
+  }), []);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b725cf and 5748f60.

📒 Files selected for processing (7)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformExample.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformExample.js
🧬 Code graph analysis (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (6)
  • useTranslation (9-9)
  • navigate (10-10)
  • searchParams (11-11)
  • campaignNumber (12-12)
  • tenantId (13-13)
  • isLoading (19-19)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js (7)
  • useTranslation (137-137)
  • navigate (138-138)
  • searchParams (141-141)
  • location (139-139)
  • campaignNumber (142-142)
  • tenantId (147-147)
  • moduleName (149-149)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (7)
  • useTranslation (9-9)
  • navigate (10-10)
  • searchParams (11-11)
  • campaignNumber (12-12)
  • tenantId (14-14)
  • schemaCode (16-16)
  • mdmsData (17-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js (1)
  • moduleName (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js (1)
  • CampaignDetails (136-618)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)
  • FullConfigWrapper (8-414)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (1)
  • NewAppModule (7-70)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (1)
  • DummyLoader (8-127)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (2)
  • transformMdmsToAppConfig (47-127)
  • transformMdmsToAppConfig (47-127)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (2)
  • mdmsData (17-32)
  • flow (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (1)
  • mdmsData (15-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformExample.js (2)
  • appConfig (26-26)
  • appConfig (32-32)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (5)
  • navigate (9-9)
  • campaignNumber (11-11)
  • tenantId (12-12)
  • schemaCode (14-14)
  • mdmsData (15-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js (5)
  • appConfig (12-12)
  • transformMdmsToAppConfig (6-88)
  • transformMdmsToAppConfig (6-88)
  • transformMdmsToFlowConfig (293-387)
  • transformMdmsToFlowConfig (293-387)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)
  • tenantId (13-13)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js (1)
  • tenantId (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/NewAppModule.js (1)
  • tenantId (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js (1)
  • tenantId (164-164)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformExample.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js (5)
  • transformMdmsData (392-401)
  • transformMdmsData (392-401)
  • transformMdmsToAppConfig (6-88)
  • transformMdmsToAppConfig (6-88)
  • appConfig (12-12)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 300-309: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 300-309: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 321-328: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 321-328: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 337-348: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 337-348: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 349-349: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 366-382: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 366-382: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 383-383: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.js

[error] 72-72: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

🔇 Additional comments (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)

6-6: The import statement is valid—no fix required.

The module exports transformMdmsToAppConfig both as a named export (line 47) and as a default export (line 274). The current default import correctly uses the default export. While changing to a named import would also work, it is unnecessary.

Likely an incorrect or invalid review comment.

Comment on lines 328 to 331
{window?.location?.pathname === "/workbench-ui/employee/campaign/add-product" ||
window?.location?.pathname === "/workbench-ui/employee/campaign/response" ? null : (
window?.location?.pathname === "/workbench-ui/employee/campaign/response" ||
window?.location?.pathname === "/workbench-ui/employee/campaign/new-app-configuration-redesign" ? null : (
<CampaignBreadCrumb location={location} defaultPath={path} />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Breadcrumbs don’t cover new routes; header hides crumbs for redesign path.

  • Either stop suppressing crumbs for “new-app-configuration-redesign” or add it to crumbs’ show conditions (and include “new-app-modules”). Also avoid hardcoding “/workbench-ui”.
-        {window?.location?.pathname === "/workbench-ui/employee/campaign/add-product" ||
-        window?.location?.pathname === "/workbench-ui/employee/campaign/response" ||
-        window?.location?.pathname === "/workbench-ui/employee/campaign/new-app-configuration-redesign" ? null : (
+        {[
+          `/${window?.contextPath}/employee/campaign/add-product`,
+          `/${window?.contextPath}/employee/campaign/response`
+        ].includes(window?.location?.pathname) ? null : (
           <CampaignBreadCrumb location={location} defaultPath={path} />
         )}

And update crumbs’ show logic (representative changes):

@@
-        pathVar.match("app-configuration-redesign") ||
+        pathVar.match("app-configuration-redesign") ||
+        pathVar === "new-app-configuration-redesign" ||
-        pathVar.match("app-modules") ||
+        pathVar.match("app-modules") || pathVar === "new-app-modules" ||
@@
-      show: pathVar === "app-modules" || pathVar === "app-configuration-redesign" ? true : false,
+      show: ["app-modules","new-app-modules","app-configuration-redesign","new-app-configuration-redesign"].includes(pathVar),
@@
-      show: pathVar === "app-configuration-redesign" ? true : false,
+      show: ["app-configuration-redesign","new-app-configuration-redesign"].includes(pathVar),

Also applies to: 201-216, 264-268, 275-278

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
around lines 328-331 (and also update similar logic at 201-216, 264-268,
275-278): the conditional currently suppresses breadcrumbs for the
"new-app-configuration-redesign" route and hardcodes "/workbench-ui", which
hides the header for redesign paths and misses other new routes; change the
logic to stop suppressing breadcrumbs for "new-app-configuration-redesign" (or
explicitly include it) and also include "new-app-modules" where appropriate, and
replace hardcoded "/workbench-ui" checks with a route-prefix-agnostic check
using the location pathname segments or a configurable basePath so the
breadcrumb visibility is determined by route names rather than a fixed prefix.

Comment on lines 105 to 118
const fullData = response?.mdms && response?.mdms?.map((item) => item.data);
const transformedData = transformMdmsToAppConfig(fullData);
console.log("Transformed App Config:", fullData, transformedData);

// Create single MDMS record for the transformed config
const payload = {
Mdms: {
tenantId: "mz",
schemaCode: "HCM-ADMIN-CONSOLE.NewAppConfig",
uniqueIdentifier: `${transformedData.project}.${transformedData.name}`,
data: transformedData,
isActive: true
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Correct transform usage: flatten input and create deterministic records (array).

transformMdmsToAppConfig returns an array; current code treats it as a single object and constructs an invalid uniqueIdentifier.

-      const fullData = response?.mdms && response?.mdms?.map((item) => item.data);
-      const transformedData = transformMdmsToAppConfig(fullData);
-      console.log("Transformed App Config:", fullData, transformedData);
-
-      // Create single MDMS record for the transformed config
-      const payload = {
-        Mdms: {
-          tenantId: "mz",
-          schemaCode: "HCM-ADMIN-CONSOLE.NewAppConfig",
-          uniqueIdentifier: `${transformedData.project}.${transformedData.name}`,
-          data: transformedData,
-          isActive: true
-        }
-      };
-
-      const createResponse = await Digit.CustomService.getResponse({
-        url: "/mdms-v2/v2/_create/HCM-ADMIN-CONSOLE.NewAppConfig",
-        body: payload,
-      });
-      console.log(`Created app config for ${transformedData.name}:`, createResponse);
+      const fullData = (response?.mdms || []).flatMap((item) =>
+        Array.isArray(item?.data) ? item.data : [item?.data].filter(Boolean)
+      );
+      const transformedConfigs = transformMdmsToAppConfig(fullData); // array
+
+      const createCalls = transformedConfigs.map((cfg) =>
+        Digit.CustomService.getResponse({
+          url: "/mdms-v2/v2/_create/HCM-ADMIN-CONSOLE.NewAppConfig",
+          body: {
+            Mdms: {
+              tenantId,
+              schemaCode: "HCM-ADMIN-CONSOLE.NewAppConfig",
+              uniqueIdentifier: `${tenantId}.${campaignNumber}.${cfg.name}`,
+              data: cfg,
+              isActive: true
+            }
+          }
+        })
+      );
+      await Promise.all(createCalls);

Also applies to: 120-133

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 105-118 (and similarly 120-133): transformMdmsToAppConfig returns
an array but the code treats it as a single object; first flatten fullData into
a single array before calling transformMdmsToAppConfig, then treat the result as
an array and map each transformed item to its own MDMS record ensuring a
deterministic uniqueIdentifier (e.g., `${item.project}.${item.name}`), setting
each record's data to the individual item and isActive true — replace the single
payload with an array of payloads generated from the transformed array.

Comment on lines +286 to +295
{currentPageRoles.length > 0 ? (
currentPageRoles.map((role, index) => (
<div key={index} style={styles.roleItem}>
{role}
</div>
))
) : (
<div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>No roles assigned</div>
)}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Localize user-visible string.

“No roles assigned” should use i18n.

-            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>No roles assigned</div>
+            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>{t("NO_ROLES_ASSIGNED")}</div>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 286 to 295: the user-visible string "No roles assigned" is
hardcoded and must be localized; replace it with the app's i18n call (e.g., use
the existing translation hook or translate function in the project such as
t('...') or i18n.t('...')), import the hook/function at the top of the file if
not already imported, use a descriptive translation key like
'employee.noRolesAssigned' or similar, and ensure the component renders the
localized string as the fallback when currentPageRoles is empty.

// type: modulesData?.length > 0 ? "secondary" : "primary",
type: hasVersionGreaterThanOne ? "secondary" : "primary",
navLink: `app-modules?projectType=${campaignData?.projectType}&campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
navLink: `new-app-modules?projectType=${campaignData?.projectType}&campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

Route update looks good; verify the new route is properly configured.

The navigation target change from app-modules to new-app-modules correctly preserves all query parameters and aligns with the PR's objective to introduce new app configuration flows.

Ensure the new-app-modules route is registered in your routing configuration and that the corresponding component (likely NewAppModule.js) correctly handles the projectType, campaignNumber, and tenantId query parameters.

Run the following script to verify the route setup:


🏁 Script executed:

#!/bin/bash
# Description: Verify that the new-app-modules route is properly configured

# Search for route definitions that include "new-app-modules"
echo "=== Searching for new-app-modules route definitions ==="
rg -n -A5 -B2 'new-app-modules' --type=js

# Find the NewAppModule component file and check its parameter handling
echo -e "\n=== Checking NewAppModule component for query parameter usage ==="
fd -e js -e jsx NewAppModule | head -5 | xargs -I {} sh -c 'echo "File: {}" && rg -n "searchParams|useLocation|projectType|campaignNumber|tenantId" {} | head -20'

Length of output: 4833


I found that the route is properly configured, but I need to verify if the projectType parameter passed in CampaignDetails.js is actually used by NewAppModule. Let me check:



🏁 Script executed:

#!/bin/bash
# Check if projectType is used in NewAppModule.js
echo "=== Checking full NewAppModule.js for projectType usage ==="
fd -name "NewAppModule.js" --type f | xargs cat

Length of output: 290



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if projectType is used in NewAppModule.js
echo "=== Full NewAppModule.js file content ==="
fd "NewAppModule.js" | xargs cat

Length of output: 2420


Route update is properly configured; however, the projectType parameter is unused.

The new-app-modules route is correctly registered and the navigation will function as expected. However, NewAppModule.js does not extract or use the projectType query parameter—it only uses campaignNumber and tenantId for fetching MDMS data. FullConfigWrapper.js (line 129) also navigates to this route without including projectType, suggesting it may not be needed.

Consider removing the unused projectType parameter from line 363 in CampaignDetails.js to keep the navigation link clean:

- navLink: `new-app-modules?projectType=${campaignData?.projectType}&campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
+ navLink: `new-app-modules?campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
navLink: `new-app-modules?projectType=${campaignData?.projectType}&campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
navLink: `new-app-modules?campaignNumber=${campaignData?.campaignNumber}&tenantId=${tenantId}`,
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.js
around line 363, the navLink includes a projectType query param that is not
consumed by the target page (NewAppModule.js) nor by other navigations
(FullConfigWrapper.js); remove the unused
projectType=${campaignData?.projectType} fragment from the navLink so it only
passes campaignNumber and tenantId, and update any tests or references if they
assert the presence of projectType in this URL.

Comment on lines +71 to +83
// Add onAction and wrapperConfig metadata to a separate config object if needed
if (flow.onAction || flow.wrapperConfig) {
const flowMetadata = {
module: moduleName,
flow: flowName,
onAction: flow.onAction,
wrapperConfig: flow.wrapperConfig,
project: project,
version: version,
screenType: flow.screenType,
};
// You can store this metadata separately or merge it with pages as needed
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused local flowMetadata block or wire it to outputs.

Currently created and discarded; it confuses readers and adds dead code.

-      // Add onAction and wrapperConfig metadata to a separate config object if needed
-      if (flow.onAction || flow.wrapperConfig) {
-        const flowMetadata = {
-          module: moduleName,
-          flow: flowName,
-          onAction: flow.onAction,
-          wrapperConfig: flow.wrapperConfig,
-          project: project,
-          version: version,
-          screenType: flow.screenType,
-        };
-        // You can store this metadata separately or merge it with pages as needed
-      }
+      // Metadata is exposed via extractFlowMetadata(); avoid duplicating here.
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js
around lines 71 to 83, the local flowMetadata object is created but never used;
either remove the entire unused flowMetadata block to eliminate dead code, or
wire it into the function output by adding the metadata into the existing
pages/outputs (for example push/merge it into the pages array or include it on
the returned config object) so it is consumed downstream; ensure whichever
approach you choose keeps variable names consistent and updates any return
structure or comments accordingly.

Comment on lines +141 to +215
const transformPropertiesToFields = (properties) => {
if (!properties || !Array.isArray(properties)) return [];

return properties.map((prop) => {
const field = {
type: prop.type,
label: prop.label,
order: prop.order,
value: prop.value || "",
format: prop.format,
hidden: prop.hidden || false,
tooltip: prop.tooltip || "",
helpText: prop.helpText || "",
infoText: prop.infoText || "",
readOnly: prop.readOnly || false,
fieldName: prop.fieldName,
deleteFlag: prop.deleteFlag || false,
innerLabel: prop.innerLabel || "",
systemDate: prop.systemDate || false,
errorMessage: prop.errorMessage || "",
};

// Add enums if present
if (prop.enums) {
field.enums = prop.enums;
}

// Add schemaCode if present
if (prop.schemaCode) {
field.schemaCode = prop.schemaCode;
}

// Transform validations to required format
if (prop.validations && Array.isArray(prop.validations)) {
prop.validations.forEach((validation) => {
if (validation.type === "required" && validation.value === true) {
field.required = true;
field["required.message"] = validation.message || "";
} else if (validation.type === "minLength") {
field["minLength"] = validation.value;
field["minLength.message"] = validation.message || "";
} else if (validation.type === "maxLength") {
field["maxLength"] = validation.value;
field["maxLength.message"] = validation.message || "";
} else if (validation.type === "min") {
field["min"] = validation.value;
field["min.message"] = validation.message || "";
} else if (validation.type === "max") {
field["max"] = validation.value;
field["max.message"] = validation.message || "";
}
});
}

// Add visibility condition if present
if (prop.visibilityCondition) {
field.visibilityCondition = prop.visibilityCondition;
}

// Add includeInForm and includeInSummary if present
if (prop.includeInForm !== undefined) {
field.includeInForm = prop.includeInForm;
}
if (prop.includeInSummary !== undefined) {
field.includeInSummary = prop.includeInSummary;
}

// Add isMultiSelect if present
if (prop.isMultiSelect !== undefined) {
field.isMultiSelect = prop.isMultiSelect;
}

return field;
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix falsy defaulting (0/false) and ensure deterministic field order.

Using || drops valid 0/false. Sort by order to match authored sequence.

-const transformPropertiesToFields = (properties) => {
-  if (!properties || !Array.isArray(properties)) return [];
-
-  return properties.map((prop) => {
-    const field = {
-      type: prop.type,
-      label: prop.label,
-      order: prop.order,
-      value: prop.value || "",
-      format: prop.format,
-      hidden: prop.hidden || false,
-      tooltip: prop.tooltip || "",
-      helpText: prop.helpText || "",
-      infoText: prop.infoText || "",
-      readOnly: prop.readOnly || false,
-      fieldName: prop.fieldName,
-      deleteFlag: prop.deleteFlag || false,
-      innerLabel: prop.innerLabel || "",
-      systemDate: prop.systemDate || false,
-      errorMessage: prop.errorMessage || "",
-    };
+const transformPropertiesToFields = (properties) => {
+  if (!properties || !Array.isArray(properties)) return [];
+
+  const fields = properties.map((prop, idx) => {
+    const field = {
+      type: prop.type,
+      label: prop.label,
+      order: prop.order ?? idx + 1,
+      value: prop.value ?? "",
+      format: prop.format,
+      hidden: prop.hidden ?? false,
+      tooltip: prop.tooltip ?? "",
+      helpText: prop.helpText ?? "",
+      infoText: prop.infoText ?? "",
+      readOnly: prop.readOnly ?? false,
+      fieldName: prop.fieldName,
+      deleteFlag: prop.deleteFlag ?? false,
+      innerLabel: prop.innerLabel ?? "",
+      systemDate: prop.systemDate ?? false,
+      errorMessage: prop.errorMessage ?? "",
+    };
@@
-    // Add enums if present
-    if (prop.enums) {
-      field.enums = prop.enums;
-    }
+    if (prop.enums !== undefined) field.enums = prop.enums;
@@
-    // Add schemaCode if present
-    if (prop.schemaCode) {
-      field.schemaCode = prop.schemaCode;
-    }
+    if (prop.schemaCode !== undefined) field.schemaCode = prop.schemaCode;
@@
-    if (prop.validations && Array.isArray(prop.validations)) {
+    if (prop.validations && Array.isArray(prop.validations)) {
       prop.validations.forEach((validation) => {
         if (validation.type === "required" && validation.value === true) {
           field.required = true;
           field["required.message"] = validation.message || "";
         } else if (validation.type === "minLength") {
-          field["minLength"] = validation.value;
+          field["minLength"] = validation.value;
           field["minLength.message"] = validation.message || "";
         } else if (validation.type === "maxLength") {
-          field["maxLength"] = validation.value;
+          field["maxLength"] = validation.value;
           field["maxLength.message"] = validation.message || "";
         } else if (validation.type === "min") {
-          field["min"] = validation.value;
+          field["min"] = validation.value;
           field["min.message"] = validation.message || "";
         } else if (validation.type === "max") {
-          field["max"] = validation.value;
+          field["max"] = validation.value;
           field["max.message"] = validation.message || "";
         }
       });
     }
@@
-    // Add visibility condition if present
-    if (prop.visibilityCondition) {
-      field.visibilityCondition = prop.visibilityCondition;
-    }
+    if (prop.visibilityCondition !== undefined) field.visibilityCondition = prop.visibilityCondition;
@@
-    // Add includeInForm and includeInSummary if present
-    if (prop.includeInForm !== undefined) {
-      field.includeInForm = prop.includeInForm;
-    }
-    if (prop.includeInSummary !== undefined) {
-      field.includeInSummary = prop.includeInSummary;
-    }
+    if (prop.includeInForm !== undefined) field.includeInForm = prop.includeInForm;
+    if (prop.includeInSummary !== undefined) field.includeInSummary = prop.includeInSummary;
@@
-    // Add isMultiSelect if present
-    if (prop.isMultiSelect !== undefined) {
-      field.isMultiSelect = prop.isMultiSelect;
-    }
-
-    return field;
-  });
+    if (prop.isMultiSelect !== undefined) field.isMultiSelect = prop.isMultiSelect;
+    return field;
+  });
+
+  // Deterministic order: by 'order', then original index for stability
+  fields.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
+  return fields;
 };
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js
around lines 141 to 215, the current use of || for defaulting drops valid falsy
values like 0 and false and the output order is not deterministic; update all
default assignments to use explicit nullish checks (e.g., use ?? or typeof
checks) so 0/false are preserved (for example value: prop.value ?? "", hidden:
prop.hidden ?? false, readOnly: prop.readOnly ?? false, deleteFlag:
prop.deleteFlag ?? false, systemDate: prop.systemDate ?? false, and similarly
for includeInForm, includeInSummary, isMultiSelect), and ensure fields are
emitted in a deterministic sequence by sorting the properties (or the resulting
fields) by their order property before mapping/returning.

Comment on lines +264 to +286
export const extractFlowMetadata = (mdmsData) => {
if (!mdmsData?.data?.flows) {
return {};
}

const metadata = {};

mdmsData.data.flows.forEach((flow) => {
if (flow.onAction || flow.wrapperConfig) {
metadata[flow.name] = {
onAction: flow.onAction,
wrapperConfig: flow.wrapperConfig,
screenType: flow.screenType,
project: flow.project || mdmsData.data.project,
version: flow.version || mdmsData.data.version || 1,
disabled: flow.disabled,
isSelected: flow.isSelected,
};
}
});

return metadata;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

extractFlowMetadata: include all flows and normalize input shape.

Currently skips flows without onAction/wrapper, dropping disabled/isSelected and version/project.

-export const extractFlowMetadata = (mdmsData) => {
-  if (!mdmsData?.data?.flows) {
-    return {};
-  }
-
-  const metadata = {};
-
-  mdmsData.data.flows.forEach((flow) => {
-    if (flow.onAction || flow.wrapperConfig) {
-      metadata[flow.name] = {
-        onAction: flow.onAction,
-        wrapperConfig: flow.wrapperConfig,
-        screenType: flow.screenType,
-        project: flow.project || mdmsData.data.project,
-        version: flow.version || mdmsData.data.version || 1,
-        disabled: flow.disabled,
-        isSelected: flow.isSelected,
-      };
-    }
-  });
-
-  return metadata;
-};
+export const extractFlowMetadata = (mdmsData) => {
+  const data = mdmsData?.data || mdmsData;
+  if (!data?.flows) return {};
+  const metadata = {};
+  data.flows.forEach((flow) => {
+    metadata[flow.name] = {
+      onAction: flow.onAction,
+      wrapperConfig: flow.wrapperConfig,
+      screenType: flow.screenType,
+      project: flow.project ?? data.project,
+      version: flow.version ?? data.version ?? 1,
+      disabled: flow.disabled ?? false,
+      isSelected: flow.isSelected ?? false,
+      roles: flow.roles ?? [],
+    };
+  });
+  return metadata;
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const extractFlowMetadata = (mdmsData) => {
if (!mdmsData?.data?.flows) {
return {};
}
const metadata = {};
mdmsData.data.flows.forEach((flow) => {
if (flow.onAction || flow.wrapperConfig) {
metadata[flow.name] = {
onAction: flow.onAction,
wrapperConfig: flow.wrapperConfig,
screenType: flow.screenType,
project: flow.project || mdmsData.data.project,
version: flow.version || mdmsData.data.version || 1,
disabled: flow.disabled,
isSelected: flow.isSelected,
};
}
});
return metadata;
};
export const extractFlowMetadata = (mdmsData) => {
const data = mdmsData?.data || mdmsData;
if (!data?.flows) return {};
const metadata = {};
data.flows.forEach((flow) => {
metadata[flow.name] = {
onAction: flow.onAction,
wrapperConfig: flow.wrapperConfig,
screenType: flow.screenType,
project: flow.project ?? data.project,
version: flow.version ?? data.version ?? 1,
disabled: flow.disabled ?? false,
isSelected: flow.isSelected ?? false,
roles: flow.roles ?? [],
};
});
return metadata;
};
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js
around lines 264 to 286, the current extractFlowMetadata skips flows that lack
onAction or wrapperConfig and thus drops fields like disabled, isSelected,
version and project; change the logic to iterate all mdmsData.data.flows and
always create metadata[flow.name] with a normalized shape: include onAction (or
null), wrapperConfig (or null), screenType (or null), project (flow.project ||
mdmsData.data.project || null), version (flow.version || mdmsData.data.version
|| 1), disabled (Boolean or false if undefined), and isSelected (Boolean or
false if undefined), so no flow is omitted and missing fields have sensible
defaults.

Comment on lines +293 to +387
export const transformMdmsToFlowConfig = (mdmsData) => {
// Handle both mdmsData.data and direct mdmsData structure
const data = mdmsData?.data || mdmsData;

if (!data?.flows) {
console.error("Invalid MDMS data structure");
return [];
}

const flows = data.flows;
const project = data.project;

// First, collect all pages with their global order for calculating next/previous routes
const allPagesWithFlowInfo = [];

flows.forEach((flow) => {
// Handle TEMPLATE screenType - single page flows
if (flow.screenType === "TEMPLATE") {
allPagesWithFlowInfo.push({
flowId: flow.name,
flowName: flow.name,
pageName: flow.name,
order: flow.order || 1,
roles: flow.roles || [],
});
}

// Handle FORM screenType - multi-page flows
if (flow.screenType === "FORM" && flow.pages) {
flow.pages.forEach((page) => {
allPagesWithFlowInfo.push({
flowId: flow.name,
flowName: flow.name,
pageName: `${flow.name}.${page.page}`,
order: page.order || 1,
roles: flow.roles || [],
});
});
}
});

// Sort all pages by order to establish global sequence
allPagesWithFlowInfo.sort((a, b) => a.order - b.order);

// Create a map for quick next/previous lookup
const pageRouteMap = {};
allPagesWithFlowInfo.forEach((pageInfo, index) => {
const nextPage = allPagesWithFlowInfo[index + 1];
const previousPage = allPagesWithFlowInfo[index - 1];

pageRouteMap[pageInfo.pageName] = {
order: index + 1,
nextRoute: nextPage ? nextPage.pageName : null,
previousRoute: previousPage ? previousPage.pageName : null,
};
});

// Group pages by flow to create flow configs
const flowConfigs = [];
const flowGroups = {};

allPagesWithFlowInfo.forEach((pageInfo) => {
if (!flowGroups[pageInfo.flowId]) {
flowGroups[pageInfo.flowId] = {
id: pageInfo.flowId,
name: pageInfo.flowName,
pages: [],
roles: pageInfo.roles,
project: project,
};
}

flowGroups[pageInfo.flowId].pages.push({
name: pageInfo.pageName,
order: pageRouteMap[pageInfo.pageName].order,
nextRoute: pageRouteMap[pageInfo.pageName].nextRoute,
previousRoute: pageRouteMap[pageInfo.pageName].previousRoute,
});
});

// Convert flowGroups to array and set indexRoute
Object.values(flowGroups).forEach((flowGroup) => {
flowConfigs.push({
id: flowGroup.id,
name: flowGroup.name,
order: 1,
pages: flowGroup.pages,
roles: flowGroup.roles,
project: flowGroup.project,
indexRoute: flowGroup.pages[0]?.name || null,
});
});

return flowConfigs;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Critical: next/previous computed globally across flows; must be per-flow.

Global sort causes cross‑flow navigation and unstable ordering when orders collide. Compute routes within each flow.

 export const transformMdmsToFlowConfig = (mdmsData) => {
-  // Handle both mdmsData.data and direct mdmsData structure
-  const data = mdmsData?.data || mdmsData;
-
-  if (!data?.flows) {
-    console.error("Invalid MDMS data structure");
-    return [];
-  }
-
-  const flows = data.flows;
-  const project = data.project;
-
-  // First, collect all pages with their global order for calculating next/previous routes
-  const allPagesWithFlowInfo = [];
-
-  flows.forEach((flow) => {
-    // Handle TEMPLATE screenType - single page flows
-    if (flow.screenType === "TEMPLATE") {
-      allPagesWithFlowInfo.push({
-        flowId: flow.name,
-        flowName: flow.name,
-        pageName: flow.name,
-        order: flow.order || 1,
-        roles: flow.roles || [],
-      });
-    }
-
-    // Handle FORM screenType - multi-page flows
-    if (flow.screenType === "FORM" && flow.pages) {
-      flow.pages.forEach((page) => {
-        allPagesWithFlowInfo.push({
-          flowId: flow.name,
-          flowName: flow.name,
-          pageName: `${flow.name}.${page.page}`,
-          order: page.order || 1,
-          roles: flow.roles || [],
-        });
-      });
-    }
-  });
-
-  // Sort all pages by order to establish global sequence
-  allPagesWithFlowInfo.sort((a, b) => a.order - b.order);
-
-  // Create a map for quick next/previous lookup
-  const pageRouteMap = {};
-  allPagesWithFlowInfo.forEach((pageInfo, index) => {
-    const nextPage = allPagesWithFlowInfo[index + 1];
-    const previousPage = allPagesWithFlowInfo[index - 1];
-
-    pageRouteMap[pageInfo.pageName] = {
-      order: index + 1,
-      nextRoute: nextPage ? nextPage.pageName : null,
-      previousRoute: previousPage ? previousPage.pageName : null,
-    };
-  });
-
-  // Group pages by flow to create flow configs
-  const flowConfigs = [];
-  const flowGroups = {};
-
-  allPagesWithFlowInfo.forEach((pageInfo) => {
-    if (!flowGroups[pageInfo.flowId]) {
-      flowGroups[pageInfo.flowId] = {
-        id: pageInfo.flowId,
-        name: pageInfo.flowName,
-        pages: [],
-        roles: pageInfo.roles,
-        project: project,
-      };
-    }
-
-    flowGroups[pageInfo.flowId].pages.push({
-      name: pageInfo.pageName,
-      order: pageRouteMap[pageInfo.pageName].order,
-      nextRoute: pageRouteMap[pageInfo.pageName].nextRoute,
-      previousRoute: pageRouteMap[pageInfo.pageName].previousRoute,
-    });
-  });
-
-  // Convert flowGroups to array and set indexRoute
-  Object.values(flowGroups).forEach((flowGroup) => {
-    flowConfigs.push({
-      id: flowGroup.id,
-      name: flowGroup.name,
-      order: 1,
-      pages: flowGroup.pages,
-      roles: flowGroup.roles,
-      project: flowGroup.project,
-      indexRoute: flowGroup.pages[0]?.name || null,
-    });
-  });
-
-  return flowConfigs;
+  const data = mdmsData?.data || mdmsData;
+  if (!data?.flows) {
+    if (typeof console !== "undefined" && console.error) console.error("Invalid MDMS data structure");
+    return [];
+  }
+
+  const flows = data.flows;
+  const project = data.project;
+  const flowConfigs = [];
+
+  flows.forEach((flow) => {
+    const base = {
+      id: flow.name,
+      name: flow.name,
+      order: flow.order ?? 1,
+      roles: flow.roles ?? [],
+      project,
+    };
+
+    // TEMPLATE → single page
+    if (flow.screenType === "TEMPLATE") {
+      const pageName = flow.name;
+      const pages = [
+        { name: pageName, order: flow.order ?? 1, nextRoute: null, previousRoute: null },
+      ];
+      flowConfigs.push({ ...base, pages, indexRoute: pageName });
+      return;
+    }
+
+    // FORM → multi-page within the same flow
+    if (flow.screenType === "FORM" && Array.isArray(flow.pages) && flow.pages.length) {
+      const pagesWithIdx = flow.pages.map((page, idx) => ({
+        name: `${flow.name}.${page.page}`,
+        order: page.order ?? idx + 1,
+        _i: idx,
+      }));
+      // Stable sort: by order, then original index
+      pagesWithIdx.sort((a, b) => (a.order - b.order) || (a._i - b._i));
+      // Wire next/previous within this flow only
+      const pages = pagesWithIdx.map((p, i, arr) => ({
+        name: p.name,
+        order: i + 1,
+        previousRoute: i > 0 ? arr[i - 1].name : null,
+        nextRoute: i < arr.length - 1 ? arr[i + 1].name : null,
+      }));
+      flowConfigs.push({ ...base, pages, indexRoute: pages[0]?.name ?? null });
+    }
+  });
+
+  return flowConfigs;

Please validate with two flows whose page orders interleave (e.g., A:1,2 and B:1,2). Current code will point A.2 → B.1; the refactor isolates routing per flow.


#!/usr/bin/env python3
# Test the bug: trace through global sort with interleaving orders
# Simulate: Flow A with pages order 1,2 and Flow B with pages order 1,2

# Current (buggy) logic
def test_current_logic():
    flows = [
        {
            "name": "FlowA",
            "screenType": "FORM",
            "order": 1,
            "roles": [],
            "pages": [
                {"page": "page1", "order": 1},
                {"page": "page2", "order": 2}
            ]
        },
        {
            "name": "FlowB",
            "screenType": "FORM",
            "order": 2,
            "roles": [],
            "pages": [
                {"page": "pageA", "order": 1},
                {"page": "pageB", "order": 2}
            ]
        }
    ]
    
    # Step 1: Collect all pages globally
    allPagesWithFlowInfo = []
    for flow in flows:
        if flow["screenType"] == "FORM" and flow.get("pages"):
            for page in flow["pages"]:
                allPagesWithFlowInfo.append({
                    "flowId": flow["name"],
                    "flowName": flow["name"],
                    "pageName": f"{flow['name']}.{page['page']}",
                    "order": page["order"],
                    "roles": flow.get("roles", [])
                })
    
    print("=== CURRENT (BUGGY) LOGIC ===")
    print("1. Collected pages (before global sort):")
    for p in allPagesWithFlowInfo:
        print(f"   {p['pageName']} (order={p['order']})")
    
    # Step 2: Global sort
    allPagesWithFlowInfo.sort(key=lambda x: x["order"])
    
    print("\n2. After global sort:")
    for i, p in enumerate(allPagesWithFlowInfo):
        print(f"   [{i}] {p['pageName']} (order={p['order']})")
    
    # Step 3: Build pageRouteMap with global indices
    pageRouteMap = {}
    for index, pageInfo in enumerate(allPagesWithFlowInfo):
        nextPage = allPagesWithFlowInfo[index + 1] if index + 1 < len(allPagesWithFlowInfo) else None
        previousPage = allPagesWithFlowInfo[index - 1] if index > 0 else None
        pageRouteMap[pageInfo["pageName"]] = {
            "order": index + 1,
            "nextRoute": nextPage["pageName"] if nextPage else None,
            "previousRoute": previousPage["pageName"] if previousPage else None
        }
    
    print("\n3. Page route map (SHOWS CROSS-FLOW ROUTING):")
    for pageName in sorted(pageRouteMap.keys()):
        route = pageRouteMap[pageName]
        print(f"   {pageName}: order={route['order']}, next={route['nextRoute']}, prev={route['previousRoute']}")
    
    # Check for cross-flow violations
    print("\n4. Cross-flow violations:")
    cross_flow_issues = []
    for pageName, route in pageRouteMap.items():
        flow1 = pageName.split(".")[0]
        if route["nextRoute"]:
            flow2 = route["nextRoute"].split(".")[0]
            if flow1 != flow2:
                cross_flow_issues.append(f"   {pageName}{route['nextRoute']} (crosses from {flow1} to {flow2})")
        if route["previousRoute"]:
            flow2 = route["previousRoute"].split(".")[0]
            if flow1 != flow2:
                cross_flow_issues.append(f"   {pageName}{route['previousRoute']} (crosses from {flow1} to {flow2})")
    
    if cross_flow_issues:
        print("   BUG DETECTED - Cross-flow navigation:")
        for issue in cross_flow_issues:
            print(issue)
    else:
        print("   No cross-flow issues found")
    
    return pageRouteMap

# Run test
test_current_logic()
#!/usr/bin/env python3
# Test the proposed fix: per-flow routing

def test_proposed_fix():
    flows = [
        {
            "name": "FlowA",
            "screenType": "FORM",
            "order": 1,
            "roles": [],
            "pages": [
                {"page": "page1", "order": 1},
                {"page": "page2", "order": 2}
            ]
        },
        {
            "name": "FlowB",
            "screenType": "FORM",
            "order": 2,
            "roles": [],
            "pages": [
                {"page": "pageA", "order": 1},
                {"page": "pageB", "order": 2}
            ]
        }
    ]
    
    print("\n=== PROPOSED (FIXED) LOGIC ===")
    flowConfigs = []
    
    for flow in flows:
        print(f"\nProcessing flow: {flow['name']}")
        
        if flow["screenType"] == "FORM" and flow.get("pages"):
            # Collect pages for THIS FLOW ONLY
            pages_in_flow = []
            for idx, page in enumerate(flow["pages"]):
                pages_in_flow.append({
                    "name": f"{flow['name']}.{page['page']}",
                    "order": page.get("order", idx + 1),
                    "_i": idx
                })
            
            print(f"  Pages before sort: {[p['name'] for p in pages_in_flow]}")
            
            # Sort within this flow only
            pages_in_flow.sort(key=lambda x: (x["order"], x["_i"]))
            
            print(f"  Pages after sort: {[p['name'] for p in pages_in_flow]}")
            
            # Compute next/previous WITHIN THIS FLOW
            pages = []
            for i, p in enumerate(pages_in_flow):
                pages.append({
                    "name": p["name"],
                    "order": i + 1,
                    "previousRoute": pages_in_flow[i - 1]["name"] if i > 0 else None,
                    "nextRoute": pages_in_flow[i + 1]["name"] if i < len(pages_in_flow) - 1 else None
                })
            
            print(f"  Routes within flow:")
            for page in pages:
                print(f"    {page['name']}: order={page['order']}, next={page['nextRoute']}, prev={page['previousRoute']}")
            
            flowConfigs.append({
                "id": flow["name"],
                "name": flow["name"],
                "pages": pages
            })
    
    print("\n=== VERIFICATION ===")
    print("All pages have routes ONLY within their own flow:")
    for config in flowConfigs:
        for page in config["pages"]:
            if page["nextRoute"]:
                flow1 = page["name"].split(".")[0]
                flow2 = page["nextRoute"].split(".")[0]
                status = "✓ SAME FLOW" if flow1 == flow2 else "✗ CROSS-FLOW"
                print(f"  {page['name']}{page['nextRoute']}: {status}")
            if page["previousRoute"]:
                flow1 = page["name"].split(".")[0]
                flow2 = page["previousRoute"].split(".")[0]
                status = "✓ SAME FLOW" if flow1 == flow2 else "✗ CROSS-FLOW"
                print(f"  {page['name']}{page['previousRoute']}: {status}")

test_proposed_fix()
#!/usr/bin/env python3
# Test the bug: simulate current vs proposed logic with interleaving orders
# Scenario: Flow A with pages order 1,2 and Flow B with pages order 1,2

print("=" * 70)
print("TESTING: Current (Buggy) Logic - Global Sort")
print("=" * 70)

flows = [
    {
        "name": "FlowA",
        "screenType": "FORM",
        "order": 1,
        "roles": [],
        "pages": [
            {"page": "pageA1", "order": 1},
            {"page": "pageA2", "order": 2}
        ]
    },
    {
        "name": "FlowB",
        "screenType": "FORM",
        "order": 2,
        "roles": [],
        "pages": [
            {"page": "pageB1", "order": 1},
            {"page": "pageB2", "order": 2}
        ]
    }
]

# Current logic: collect ALL pages globally
allPages = []
for flow in flows:
    if flow["screenType"] == "FORM" and flow.get("pages"):
        for page in flow["pages"]:
            allPages.append({
                "flowId": flow["name"],
                "pageName": f"{flow['name']}.{page['page']}",
                "order": page["order"]
            })

print("\n1. Collected pages (before global sort):")
for p in allPages:
    print(f"   {p['pageName']} (order={p['order']}, flow={p['flowId']})")

# Global sort
allPages.sort(key=lambda x: x["order"])

print("\n2. After global sort (THIS IS THE BUG):")
for i, p in enumerate(allPages):
    print(f"   [{i}] {p['pageName']} (order={p['order']}, flow={p['flowId']})")

# Build route map using global indices
pageRouteMap = {}
for index, pageInfo in enumerate(allPages):
    nextPage = allPages[index + 1] if index + 1 < len(allPages) else None
    previousPage = allPages[index - 1] if index > 0 else None
    pageRouteMap[pageInfo["pageName"]] = {
        "nextRoute": nextPage["pageName"] if nextPage else None,
        "previousRoute": previousPage["pageName"] if previousPage else None
    }

print("\n3. Routing (CROSS-FLOW VIOLATIONS):")
for pageName in sorted(pageRouteMap.keys()):
    route = pageRouteMap[pageName]
    next_flow = route["nextRoute"].split(".")[0] if route["nextRoute"] else None
    curr_flow = pageName.split(".")[0]
    prev_flow = route["previousRoute"].split(".")[0] if route["previousRoute"] else None
    
    next_marker = f" [CROSS-FLOW ERROR: {curr_flow}{next_flow}]" if route["nextRoute"] and next_flow != curr_flow else ""
    prev_marker = f" [CROSS-FLOW ERROR: {curr_flow}{prev_flow}]" if route["previousRoute"] and prev_flow != curr_flow else ""
    
    print(f"   {pageName}: next={route['nextRoute']}{next_marker}, prev={route['previousRoute']}{prev_marker}")

print("\n" + "=" * 70)
print("TESTING: Proposed (Fixed) Logic - Per-Flow Routing")
print("=" * 70)

for flow in flows:
    if flow["screenType"] == "FORM" and flow.get("pages"):
        print(f"\nProcessing {flow['name']}:")
        
        # Collect pages for THIS FLOW ONLY
        flowPages = []
        for idx, page in enumerate(flow["pages"]):
            flowPages.append({
                "name": f"{flow['name']}.{page['page']}",
                "order": page.get("order", idx + 1),
                "_i": idx
            })
        
        # Sort within this flow only
        flowPages.sort(key=lambda x: (x["order"], x["_i"]))
        
        print(f"  Pages after sort: {[p['name'] for p in flowPages]}")
        
        # Compute next/previous WITHIN THIS FLOW
        print(f"  Routes within flow:")
        for i, p in enumerate(flowPages):
            prev_page = flowPages[i - 1]["name"] if i > 0 else None
            next_page = flowPages[i + 1]["name"] if i < len(flowPages) - 1 else None
            print(f"    {p['name']}: next={next_page}, prev={prev_page} [SAME FLOW ONLY ✓]")

print("\n" + "=" * 70)
print("CONCLUSION")
print("=" * 70)
print("Current logic: FlowA.pageA2 → FlowB.pageB2 (BUG: crosses flows)")
print("Fixed logic:  FlowA.pageA2 → null (CORRECT: stays within flow)")

🏁 Script executed:

#!/bin/bash
# Find the transformMdmsConfig.js file and examine the current implementation
find . -name "transformMdmsConfig.js" -type f 2>/dev/null | head -20

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Locate and cat the specific file mentioned in the review
fd -t f "transformMdmsConfig.js" | xargs cat

Length of output: 11298


🏁 Script executed:

#!/bin/bash
# Find the transformMdmsConfig.js file
fd -t f "transformMdmsConfig.js"

Length of output: 113


Critical: next/previous computed globally across flows; must be per-flow.

The implementation sorts all pages globally, then uses global array indices to compute nextRoute and previousRoute. When pages from different flows have identical orders (e.g., Flow A pages with order 1,2 and Flow B pages with order 1,2), the global sort interleaves them—causing A.2 to point to B.2 as next and B.1 to point to A.2. Routes must stay within flow boundaries.

Compute routes within each flow:

 export const transformMdmsToFlowConfig = (mdmsData) => {
-  // Handle both mdmsData.data and direct mdmsData structure
-  const data = mdmsData?.data || mdmsData;
-
-  if (!data?.flows) {
-    console.error("Invalid MDMS data structure");
-    return [];
-  }
-
-  const flows = data.flows;
-  const project = data.project;
-
-  // First, collect all pages with their global order for calculating next/previous routes
-  const allPagesWithFlowInfo = [];
-
-  flows.forEach((flow) => {
-    // Handle TEMPLATE screenType - single page flows
-    if (flow.screenType === "TEMPLATE") {
-      allPagesWithFlowInfo.push({
-        flowId: flow.name,
-        flowName: flow.name,
-        pageName: flow.name,
-        order: flow.order || 1,
-        roles: flow.roles || [],
-      });
-    }
-
-    // Handle FORM screenType - multi-page flows
-    if (flow.screenType === "FORM" && flow.pages) {
-      flow.pages.forEach((page) => {
-        allPagesWithFlowInfo.push({
-          flowId: flow.name,
-          flowName: flow.name,
-          pageName: `${flow.name}.${page.page}`,
-          order: page.order || 1,
-          roles: flow.roles || [],
-        });
-      });
-    }
-  });
-
-  // Sort all pages by order to establish global sequence
-  allPagesWithFlowInfo.sort((a, b) => a.order - b.order);
-
-  // Create a map for quick next/previous lookup
-  const pageRouteMap = {};
-  allPagesWithFlowInfo.forEach((pageInfo, index) => {
-    const nextPage = allPagesWithFlowInfo[index + 1];
-    const previousPage = allPagesWithFlowInfo[index - 1];
-
-    pageRouteMap[pageInfo.pageName] = {
-      order: index + 1,
-      nextRoute: nextPage ? nextPage.pageName : null,
-      previousRoute: previousPage ? previousPage.pageName : null,
-    };
-  });
-
-  // Group pages by flow to create flow configs
-  const flowConfigs = [];
-  const flowGroups = {};
-
-  allPagesWithFlowInfo.forEach((pageInfo) => {
-    if (!flowGroups[pageInfo.flowId]) {
-      flowGroups[pageInfo.flowId] = {
-        id: pageInfo.flowId,
-        name: pageInfo.flowName,
-        pages: [],
-        roles: pageInfo.roles,
-        project: project,
-      };
-    }
-
-    flowGroups[pageInfo.flowId].pages.push({
-      name: pageInfo.pageName,
-      order: pageRouteMap[pageInfo.pageName].order,
-      nextRoute: pageRouteMap[pageInfo.pageName].nextRoute,
-      previousRoute: pageRouteMap[pageInfo.pageName].previousRoute,
-    });
-  });
-
-  // Convert flowGroups to array and set indexRoute
-  Object.values(flowGroups).forEach((flowGroup) => {
-    flowConfigs.push({
-      id: flowGroup.id,
-      name: flowGroup.name,
-      order: 1,
-      pages: flowGroup.pages,
-      roles: flowGroup.roles,
-      project: flowGroup.project,
-      indexRoute: flowGroup.pages[0]?.name || null,
-    });
-  });
-
-  return flowConfigs;
+  const data = mdmsData?.data || mdmsData;
+  if (!data?.flows) {
+    console.error("Invalid MDMS data structure");
+    return [];
+  }
+
+  const flows = data.flows;
+  const project = data.project;
+  const flowConfigs = [];
+
+  flows.forEach((flow) => {
+    const base = {
+      id: flow.name,
+      name: flow.name,
+      order: flow.order ?? 1,
+      roles: flow.roles ?? [],
+      project,
+    };
+
+    // TEMPLATE → single page
+    if (flow.screenType === "TEMPLATE") {
+      const pageName = flow.name;
+      const pages = [
+        { name: pageName, order: flow.order ?? 1, nextRoute: null, previousRoute: null },
+      ];
+      flowConfigs.push({ ...base, pages, indexRoute: pageName });
+      return;
+    }
+
+    // FORM → multi-page within the same flow
+    if (flow.screenType === "FORM" && Array.isArray(flow.pages) && flow.pages.length) {
+      const pagesWithIdx = flow.pages.map((page, idx) => ({
+        name: `${flow.name}.${page.page}`,
+        order: page.order ?? idx + 1,
+        _i: idx,
+      }));
+      // Stable sort: by order, then original index
+      pagesWithIdx.sort((a, b) => (a.order - b.order) || (a._i - b._i));
+      // Wire next/previous within this flow only
+      const pages = pagesWithIdx.map((p, i, arr) => ({
+        name: p.name,
+        order: i + 1,
+        previousRoute: i > 0 ? arr[i - 1].name : null,
+        nextRoute: i < arr.length - 1 ? arr[i + 1].name : null,
+      }));
+      flowConfigs.push({ ...base, pages, indexRoute: pages[0]?.name ?? null });
+    }
+  });
+
+  return flowConfigs;
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.js
around lines 293 to 387, the code currently computes nextRoute/previousRoute by
sorting all pages globally which mixes pages from different flows; update the
logic to compute next/previous per flow: first group pages by flowId, then for
each flow sort that flow's pages by their order, compute nextRoute/previousRoute
using the flow-local sorted list (and set the page order to the flow-local index
or keep the original order if intended), populate flowGroups/pages with these
per-flow next/previous values, and finally build flowConfigs from those
flow-local groups (ensuring indexRoute is the first page of that flow); do not
use a single global sort or global pageRouteMap for next/previous lookup.

Comment on lines +392 to +401
export const transformMdmsData = (mdmsData) => {
return {
appConfig: transformMdmsToAppConfig(mdmsData),
flowMetadata: extractFlowMetadata(mdmsData),
initialPage: mdmsData?.data?.initialPage,
project: mdmsData?.data?.project,
version: mdmsData?.data?.version,
name: mdmsData?.data?.name,
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Normalize input in transformMdmsData for consistent metadata.

Avoids undefined initialPage/project when callers pass direct objects.

 export const transformMdmsData = (mdmsData) => {
-  return {
-    appConfig: transformMdmsToAppConfig(mdmsData),
-    flowMetadata: extractFlowMetadata(mdmsData),
-    initialPage: mdmsData?.data?.initialPage,
-    project: mdmsData?.data?.project,
-    version: mdmsData?.data?.version,
-    name: mdmsData?.data?.name,
-  };
+  const data = mdmsData?.data || mdmsData;
+  return {
+    appConfig: transformMdmsToAppConfig(mdmsData),
+    flowMetadata: extractFlowMetadata(mdmsData),
+    initialPage: data?.initialPage,
+    project: data?.project,
+    version: data?.version,
+    name: data?.name,
+  };
 };

Ensure DummyLoader/NewAppModule callers sometimes pass mdmsData.data directly; this keeps outputs stable across both shapes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const transformMdmsData = (mdmsData) => {
return {
appConfig: transformMdmsToAppConfig(mdmsData),
flowMetadata: extractFlowMetadata(mdmsData),
initialPage: mdmsData?.data?.initialPage,
project: mdmsData?.data?.project,
version: mdmsData?.data?.version,
name: mdmsData?.data?.name,
};
};
export const transformMdmsData = (mdmsData) => {
const data = mdmsData?.data || mdmsData;
return {
appConfig: transformMdmsToAppConfig(mdmsData),
flowMetadata: extractFlowMetadata(mdmsData),
initialPage: data?.initialPage,
project: data?.project,
version: data?.version,
name: data?.name,
};
};

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

♻️ Duplicate comments (8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (8)

22-28: Drop console logs; avoid noisy prod output.

Remove or guard all console.log calls shown below. Use a proper logger behind an env flag if needed.

-  console.log("AppConfigurationWrapper Props:", {
-    flow,
-    flowName,
-    pageName,
-    campaignNumber,
-    localeModule,
-  });
...
-  console.log("currentDatacurrentData", currentData);
...
-      console.log("MDMS Update Payload:", updatePayload);
...
-      console.log("MDMS Update Response:", response);
...
-        console.log("MDMS NewFormConfig Response:", response);
-        console.log("Page Config from MDMS:", pageConfig, responseData);
-        console.log("Response Data from MDMS:", responseData);

Also applies to: 46-46, 73-73, 81-81, 156-156, 161-162


31-33: Use MDMS v1 context for v1 thunks (field master/panel); V2 ctx breaks /v1/_search.

Define mdmsV1Context and pass it to getFieldMaster/getFieldPanelMaster. Current code injects V2 context into a v1 endpoint path.

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  // Use V1 context for thunks that call /v1/_search
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
...
-      getFieldMaster({
+      getFieldMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
         name: "NewFieldType",
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })
...
-      getFieldPanelMaster({
+      getFieldPanelMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
         name: "NewDrawerPanelConfig",
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })

Also applies to: 184-191, 195-201


35-35: Fix null-spread on newFieldType; initialize to {} and guard spreads.

Spreading null throws TypeError on first input/select. Initialize with {} and guard prev/newFieldType spreads.

-  const [newFieldType, setNewFieldType] = useState(null);
+  const [newFieldType, setNewFieldType] = useState({});
...
-    setNewFieldType((prev) => ({
-      ...prev,
+    setNewFieldType((prev) => ({
+      ...(prev || {}),
       label: locVal,
     }));
...
-                  const updatedData = { ...newFieldType, field: value };
+                  const updatedData = { ...(newFieldType || {}), field: value };
                   setNewFieldType(updatedData);
...
-    dispatch(handleShowAddFieldPopup(null));
-    setNewFieldType(null);
+    dispatch(handleShowAddFieldPopup(null));
+    setNewFieldType({});
...
-          onClose={() => {
-            dispatch(handleShowAddFieldPopup(null));
-            setNewFieldType(null);
-          }}
+          onClose={() => {
+            dispatch(handleShowAddFieldPopup(null));
+            setNewFieldType({});
+          }}
...
-                  dispatch(handleShowAddFieldPopup(null));
-                  setNewFieldType(null);
+                  dispatch(handleShowAddFieldPopup(null));
+                  setNewFieldType({});

Also applies to: 262-265, 308-309, 125-126, 278-279, 323-324


204-225: Hydrate localization only after fetch resolves; current code uses stale selector.

Move setLocalizationData into an effect that waits for localizationStatus === "succeeded" (or unwrap the thunk). Remove the immediate dispatch.

-      dispatch(
-        setLocalizationData({
-          localisationData: localizationData,
-          currentLocale,
-          enabledModules: [currentLocale],
-          localeModule,
-        })
-      );

Add this effect near the other hooks:

+  useEffect(() => {
+    if (localeModule && localizationStatus === "succeeded" && Array.isArray(localizationData)) {
+      dispatch(
+        setLocalizationData({
+          localisationData: localizationData,
+          currentLocale,
+          enabledModules: [currentLocale],
+          localeModule,
+        })
+      );
+    }
+  }, [dispatch, localizationStatus, localizationData, currentLocale, localeModule]);

251-254: Add UUID fallback for older browsers and non-secure contexts.

Guard randomUUID and provide a safe fallback.

-    const locVal = newFieldType?.label
-      ? newFieldType?.label
-      : `${campaignNumber}_${flow}_${pageName}_${crypto.randomUUID()}`.toUpperCase();
+    const uid = window.crypto?.randomUUID
+      ? window.crypto.randomUUID()
+      : `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
+    const locVal = newFieldType?.label
+      ? newFieldType?.label
+      : `${campaignNumber}_${flow}_${pageName}_${uid}`.toUpperCase();

271-279: Close popup on overlay click for expected UX.

Mirror onClose behavior in onOverlayClick.

-          onOverlayClick={() => {}}
+          onOverlayClick={() => {
+            dispatch(handleShowAddFieldPopup(null));
+            setNewFieldType({});
+          }}

112-113: Guard metadata spread to avoid undefined props.

Default metadata to {} for clarity and stability.

-      ...selectedFieldType?.metadata, // Include any metadata from field type
+      ...(selectedFieldType?.metadata || {}), // Include any metadata from field type

285-295: Improve typing UX: avoid dispatching on every keystroke (optional).

Use local input state; dispatch onBlur/submit.

-              <TextInput
-                name="fieldLabel"
-                value={fieldDataLabel}
-                placeholder={t("ENTER_FIELD_LABEL")}
-                onChange={(event) => handleFieldChange(event.target.value)}
-              />
+              {/*
+                Keep a local draft to reduce churn; initialize above:
+                const [fieldLabelDraft, setFieldLabelDraft] = useState(fieldDataLabel);
+                useEffect(() => setFieldLabelDraft(fieldDataLabel), [fieldDataLabel]);
+              */}
+              <TextInput
+                name="fieldLabel"
+                value={fieldLabelDraft}
+                placeholder={t("ENTER_FIELD_LABEL")}
+                onChange={(e) => setFieldLabelDraft(e.target.value)}
+                onBlur={() => handleFieldChange(fieldLabelDraft)}
+              />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5748f60 and 317d116.

📒 Files selected for processing (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧠 Learnings (1)
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧬 Code graph analysis (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (10)
  • mdmsContext (62-62)
  • MODULE_CONSTANTS (3-3)
  • currentLocale (38-38)
  • response (39-43)
  • response (65-83)
  • response (96-96)
  • fetchLocalization (6-57)
  • fetchLocalization (6-57)
  • fetchAppScreenConfig (60-89)
  • fetchAppScreenConfig (60-89)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (76-79)
  • useFieldDataLabel (76-79)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)
  • response (378-396)
  • getFieldPanelMaster (364-416)
  • getFieldPanelMaster (364-416)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
  • response (16-34)
  • getFieldMaster (5-43)
  • getFieldMaster (5-43)

Comment on lines +236 to +247
if (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading")) {
return <Loader />;
}

if (pageConfigError) {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50vh", flexDirection: "column" }}>
<h3 style={{ color: "#d32f2f" }}>Error Loading Configuration</h3>
<p>{pageConfigError}</p>
</div>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Error view is unreachable due to loader gating; honor pageConfigError.

The loader returns when !currentData, even if pageConfigError is set. Gate loader behind !pageConfigError so the error UI can render.

-  if (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading")) {
+  if (!pageConfigError && (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading"))) {
     return <Loader />;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading")) {
return <Loader />;
}
if (pageConfigError) {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50vh", flexDirection: "column" }}>
<h3 style={{ color: "#d32f2f" }}>Error Loading Configuration</h3>
<p>{pageConfigError}</p>
</div>
);
}
if (!pageConfigError && (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading"))) {
return <Loader />;
}
if (pageConfigError) {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "50vh", flexDirection: "column" }}>
<h3 style={{ color: "#d32f2f" }}>Error Loading Configuration</h3>
<p>{pageConfigError}</p>
</div>
);
}
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 236 to 247, the Loader branch runs when !currentData even if
pageConfigError is set, making the error UI unreachable; change the control flow
so the pageConfigError case is checked and returned before showing the Loader,
or alternatively add a guard to the Loader condition to only show the Loader
when pageConfigError is falsy (e.g., wrap the existing loader condition with &&
!pageConfigError) so the error view can render.

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: 13

♻️ Duplicate comments (28)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (2)

57-71: Clean up visibility types and casing.

Use “textarea” (lowercase) consistently; remove duplicates per prior guidance. Also ensure text-like defaults are empty strings, numeric defaults are 0, toggles are booleans where stored as such.

-      visibilityEnabledFor: [
+      visibilityEnabledFor: [
         "checkbox",
         "numeric",
         "dob",
         "date",
         "select",
         "dropdown",
         "mobileNumber",
         "number",
-        "textArea",
+        "textarea",
         "text",
         "latLng",
         "administrativeArea",
         "searchableDropdown",
       ],

Also applies to: 87-87, 103-103


448-449: Exported action name is wrong; dispatch will be undefined.

Slice defines clearFieldPanelMasterData, but you export clearFieldMasterData.

-export const { clearFieldMasterData } = fieldPanelMasterSlice.actions;
+export const { clearFieldPanelMasterData } = fieldPanelMasterSlice.actions;
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (8)

31-37: Do not hardcode tenant; use resolved tenantId.

Replace "mz" with tenantId for multi-tenant correctness.

-              tenantId: "mz",
+              tenantId,
@@
-            tenantId: "mz",
+            tenantId,

Also applies to: 105-111


115-127: Transform result is an array; payload construction is incorrect.

Flatten input, map each transformed config to its own MDMS create call, and use a deterministic uniqueIdentifier.

-      const fullData = response?.mdms && response?.mdms?.map((item) => item.data);
-      const transformedData = transformMdmsToAppConfig(fullData);
-
-      // Create single MDMS record for the transformed config
-      const payload = {
-        Mdms: {
-          tenantId: "mz",
-          schemaCode: "HCM-ADMIN-CONSOLE.NewAppConfig",
-          uniqueIdentifier: `${transformedData.project}.${transformedData.name}`,
-          data: transformedData,
-          isActive: true
-        }
-      };
-
-      const createResponse = await Digit.CustomService.getResponse({
-        url: "/mdms-v2/v2/_create/HCM-ADMIN-CONSOLE.NewAppConfig",
-        body: payload,
-      });
+      const fullData = (response?.mdms || []).flatMap((i) => Array.isArray(i?.data) ? i.data : [i?.data].filter(Boolean));
+      const transformedConfigs = transformMdmsToAppConfig(fullData); // array
+
+      await Promise.all(
+        transformedConfigs.map((cfg) =>
+          Digit.CustomService.getResponse({
+            url: "/mdms-v2/v2/_create/HCM-ADMIN-CONSOLE.NewAppConfig",
+            body: {
+              Mdms: {
+                tenantId,
+                schemaCode: "HCM-ADMIN-CONSOLE.NewAppConfig",
+                uniqueIdentifier: `${tenantId}.${campaignNumber}.${cfg.name}`,
+                data: cfg,
+                isActive: true
+              }
+            }
+          })
+        )
+      );

Also applies to: 129-133


305-317: Make flow items accessible; add role, keyboard, stable keys.

Add role/button semantics and Enter/Space support; avoid index keys.

-          {flowConfig.flows?.map((flow, index) => (
+          {flowConfig.flows?.map((flow) => (
             <div
-              key={index}
+              key={flow.id ?? flow.name}
               style={{
                 ...styles.flowItem,
                 backgroundColor: selectedFlow === flow.id ? "#F47738" : "transparent",
                 color: selectedFlow === flow.id ? "#FFFFFF" : "#505A5F",
                 fontWeight: selectedFlow === flow.id ? "700" : "400",
               }}
               onClick={() => handleFlowClick(flow)}
+              role="button"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handleFlowClick(flow);
+                }
+              }}
             >

325-336: Make page tabs accessible; add role, keyboard, stable keys.

Same a11y fix and stable keys for pages.

-          {activeFlow?.pages?.map((page, index) => (
+          {activeFlow?.pages?.map((page) => (
             <div
-              key={index}
+              key={page.name}
               style={{
                 ...styles.pageTab,
                 ...(selectedPageName === page.name ? styles.pageTabActive : {}),
               }}
               onClick={() => handlePageClick(page)}
+              role="button"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handlePageClick(page);
+                }
+              }}
             >

342-357: Navigation arrows: add roles, keyboard, and labels; title SVGs.

Ensure keyboard accessibility and meaningful labels.

-          <div
+          <div
             style={{
               ...styles.navArrow,
               opacity: !previousRoute ? 0.3 : 1,
               cursor: !previousRoute ? "not-allowed" : "pointer",
             }}
+            role="button"
+            tabIndex={previousRoute ? 0 : -1}
+            aria-disabled={!previousRoute}
+            aria-label={t("PREVIOUS_PAGE")}
+            onKeyDown={(e) => {
+              if (!previousRoute) return;
+              if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelectedPageName(previousRoute); }
+            }}
             onClick={() => {
               if (previousRoute) {
                 setSelectedPageName(previousRoute);
               }
             }}
           >
-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+              <title>{t("PREVIOUS_PAGE")}</title>
               <path d="M15 18L9 12L15 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>
           </div>
@@
-          <div
+          <div
             style={{
               ...styles.navArrow,
               opacity: !nextRoute ? 0.3 : 1,
               cursor: !nextRoute ? "not-allowed" : "pointer",
             }}
+            role="button"
+            tabIndex={nextRoute ? 0 : -1}
+            aria-disabled={!nextRoute}
+            aria-label={t("NEXT_PAGE")}
+            onKeyDown={async (e) => {
+              if (!nextRoute) return;
+              if (e.key === "Enter" || e.key === " ") {
+                e.preventDefault();
+                if (window.__appConfig_onNext && typeof window.__appConfig_onNext === "function") await window.__appConfig_onNext();
+                setSelectedPageName(nextRoute);
+              }
+            }}
             onClick={async () => {
               if (nextRoute) {
                 // Call MDMS update if available
                 if (window.__appConfig_onNext && typeof window.__appConfig_onNext === "function") {
                   await window.__appConfig_onNext();
                 }
                 // Navigate to next page
                 setSelectedPageName(nextRoute);
               }
             }}
           >
-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+              <title>{t("NEXT_PAGE")}</title>
               <path d="M9 18L15 12L9 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>
           </div>

Also applies to: 370-391


298-299: Localize user-visible string.

Replace hardcoded “No roles assigned” with i18n.

-            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>No roles assigned</div>
+            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>{t("NO_ROLES_ASSIGNED")}</div>

360-368: Props passed to AppConfigurationStore are dropped.

onPageChange, nextRoute, previousRoute are not in AppConfigurationStore API and won’t be forwarded.

Either extend AppConfigurationStore to accept/forward these, or remove them here.

To confirm current signature:

#!/bin/bash
rg -nC2 'function\\s+AppConfigurationStore|const\\s+AppConfigurationStore' health/micro-ui/web | sed -n '1,120p'

165-283: Heavy inline styles object recreated each render.

Move styles to module scope or memoize with useMemo([]) to avoid churn.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1)

15-25: Avoid leaking handlers on window; prefer context/props.

Use React context or provider to expose onNext/isUpdating instead of globals; if unavoidable, namespace and guard multiple mounts.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (6)

373-376: Conditional hooks inside switch-case; violates Rules of Hooks.

Hoist useRef/useState to component top and reuse inside case.

-      case "fieldTypeDropdown": {
-        const switchRef = useRef(null);
-        const [showTooltip, setShowTooltip] = useState(false);
+      case "fieldTypeDropdown": {
+        // assume at top:
+        // const switchRef = useRef(null);
+        // const [showTooltip, setShowTooltip] = useState(false);

Top additions:

 const RenderField = React.memo((...) => {
+  const switchRef = useRef(null);
+  const [showTooltip, setShowTooltip] = useState(false);

395-401: Tooltip wrapper is interactive without keyboard semantics.

Add role, tabIndex, focus/blur handlers.

-          <div
+          <div
             ref={switchRef}
             className="drawer-container-tooltip"
             onMouseEnter={() => setShowTooltip(true)}
             onMouseLeave={() => setShowTooltip(false)}
+            role="button"
+            tabIndex={0}
+            aria-label={t("FIELD_TYPE_TOOLTIP")}
+            onFocus={() => setShowTooltip(true)}
+            onBlur={() => setShowTooltip(false)}
           >

291-294: Specify radix for parseInt.

Avoid env-dependent parsing.

-              const value = parseInt(event.target.value) || 0;
+              const value = parseInt(event.target.value, 10) || 0;

522-536: Delete control not keyboard-accessible.

Use a button with role semantics and key handling.

-              <div
-                onClick={() => {
+              <button
+                type="button"
+                aria-label={t("DELETE_OPTION")}
+                onClick={() => {
                   const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
                   onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
                 }}
+                onKeyDown={(e) => {
+                  if (e.key === "Enter" || e.key === " ") {
+                    e.preventDefault();
+                    const filtered = (selectedField[cField.bindTo] || []).filter((i) => i.code !== item.code);
+                    onFieldChange({ ...selectedField, [cField.bindTo]: filtered });
+                  }
+                }}
                 style={{
                   cursor: "pointer",
                   color: "#c84c0e",
                   fontWeight: 600,
                   display: "flex",
                   alignItems: "center",
                   gap: "0.5rem",
                 }}
               >
                 <DustbinIcon />
-              </div>
+              </button>

556-574: Block-scope switch case variables.

Prevent leakage into other cases.

-    case "dropdown":
-      const dropdownOptionKey = cField.optionKey || "schemaCode";
-      const bindValue = selectedField[cField.bindTo];
-      return (
+    case "dropdown": {
+      const dropdownOptionKey = cField.optionKey || "schemaCode";
+      const bindValue = selectedField[cField.bindTo];
+      return (
         <div style={{ marginTop: "8px" }}>
@@
-      );
+      );
+    }

45-66: Hooks run after an early return; violates Rules of Hooks.

Move all hooks (effects/callbacks) above the visibility guard; compute isFieldVisible and return null after hooks.

-  if (!isFieldVisible()) {
-    return null;
-  }
-
-  const getFieldValue = () => {
+  const getFieldValue = () => {
     const bindTo = panelItem.bindTo;
     return getFieldValueByPath(selectedField, bindTo, panelItem.defaultValue || "");
   };
-  // Get localized field value for text fields
+  // Get localized field value for text fields
   const fieldValue = getFieldValue();
   const localizedFieldValue = useCustomT(fieldValue);
@@
-  }, []);
+  }, []);
+
+  if (!isFieldVisible()) {
+    return null;
+  }
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)

5-34: Return a stable translator function from useCustomT (keep string-in back-compat).

Prevents hooks misuse in loops and aligns with project pattern.

-export const useCustomT = (code) => {
+export const useCustomT = (maybeCode) => {
   const dispatch = useDispatch();
   const { data: locState, currentLocale } = useSelector((state) => state.localization);
-
-  const translatedValue = useMemo(() => {
-    if (!code) {
-      return "";
-    }
-
-    if (!Array.isArray(locState)) {
-      return ""; // Return empty string if locState not ready
-    }
-
-    const entry = locState.find((item) => item.code === code);
-
-    if (!entry) {
-      // Add the missing key to Redux store with empty message for current locale
-      dispatch(addMissingKey({ code }));
-
-      return ""; // Return empty string when entry not found
-    }
-
-    // Get current locale from Redux state or session storage as fallback
-    const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
-
-    return entry[locale] || ""; // Return the message or empty string
-  }, [code, locState, currentLocale, dispatch]);
-
-  return translatedValue;
+  const translate = useMemo(() => {
+    const list = Array.isArray(locState) ? locState : [];
+    return (code) => {
+      if (!code) return "";
+      const entry = list.find((item) => item.code === code);
+      if (!entry) {
+        dispatch(addMissingKey({ code }));
+        return "";
+      }
+      const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
+      return entry[locale] || "";
+    };
+  }, [locState, currentLocale, dispatch]);
+
+  if (typeof maybeCode === "string") return translate(maybeCode);
+  return translate;

36-67: useCustomTranslate duplicates useCustomT; alias or remove.

Simplify exports and avoid divergence.

-export const useCustomTranslate = () => {
-  const dispatch = useDispatch();
-  const { data: locState, currentLocale } = useSelector((state) => state.localization);
-
-  return useCallback(
-    (code) => {
-      if (!code) {
-        return "";
-      }
-      if (!Array.isArray(locState)) {
-        return "";
-      }
-      const entry = locState?.find((item) => item.code === code);
-      if (!entry) {
-        dispatch(addMissingKey({ code }));
-        return "";
-      }
-      const locale = currentLocale || Digit?.SessionStorage.get("locale") || Digit?.SessionStorage.get("initData")?.selectedLanguage;
-      return entry[locale] || "";
-    },
-    [dispatch, locState, currentLocale]
-  );
-};
+export const useCustomTranslate = useCustomT;

69-73: useFieldDataLabel should use the translator function.

Avoid calling translation hook with dynamic args in render.

-export const useFieldDataLabel = (label) => {
-  const customT = useCustomT(label || "");
-  return label ? customT : "";
-};
+export const useFieldDataLabel = (label) => {
+  const t = useCustomT();
+  return label ? t(label) : "";
+};
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (2)

6-6: Extract shared defaults for module/master.

Avoid scattering "HCM-ADMIN-CONSOLE" and "AppFlowConfig". Move to a shared constants module and import here.

-async ({ tenantId, campaignNumber, flowId, moduleName = "HCM-ADMIN-CONSOLE", masterName = "AppFlowConfig" }, { rejectWithValue }) => {
+import { DEFAULT_MODULE_NAME, DEFAULT_MASTER_NAME } from "../../constants/flowConstants";
+async ({ tenantId, campaignNumber, flowId, moduleName = DEFAULT_MODULE_NAME, masterName = DEFAULT_MASTER_NAME }, { rejectWithValue }) => {

27-31: Rename to reflect dynamic flowId.

Variable name suggests REGISTRATION only; it finds any flow by id. Rename for clarity.

-        // Find the REGISTRATION flow and return its pages
-        const registrationFlow = flows.find((flow) => flow?.id === flowId);
-        const pages = registrationFlow?.pages || [];
+        // Find the target flow and return its pages
+        const targetFlow = flows.find((flow) => flow?.id === flowId);
+        const pages = targetFlow?.pages || [];
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)

23-24: MDMS context mismatch for v1 searches; pass V1 context (or omit to use default).

You derive MDMS_V2 context (Line 23) but pass it to thunks that call /v1/_search, producing /<v2>/v1/_search. Use V1 context or let slices default.

-  const mdmsContext = window.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH");
+  const mdmsV1Context = window.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
...
-      getFieldMaster({
+      getFieldMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
         name: "NewFieldType",
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })
...
-      getFieldPanelMaster({
+      getFieldPanelMaster({
         tenantId,
         moduleName: MODULE_CONSTANTS,
         name: "NewDrawerPanelConfig",
-        mdmsContext: mdmsContext,
+        mdmsContext: mdmsV1Context,
         limit: 10000,
       })
#!/bin/bash
# Verify no v1 searches use a V2 context path
rg -nP '/v1/_search' -A2 -B2 | rg -nP 'MDMS_V2_CONTEXT_PATH'

Also applies to: 169-178, 181-189


191-211: Avoid hydrating localization with stale data.

You dispatch fetchLocalization and immediately call setLocalizationData with the pre-fetch selector value.

-      // Set localization context data
-      dispatch(
-        setLocalizationData({
-          localisationData: localizationData,
-          currentLocale,
-          localeModule,
-        })
-      );
+      // Defer setLocalizationData until fetch completes

Add this effect after the existing one:

+  useEffect(() => {
+    if (localeModule && localizationStatus === "succeeded") {
+      dispatch(
+        setLocalizationData({
+          localisationData: localizationData,
+          currentLocale,
+          localeModule,
+        })
+      );
+    }
+  }, [localeModule, localizationStatus, localizationData, currentLocale, dispatch]);

222-224: Error view is unreachable when loader gates on !currentData.

Gate the loader behind !pageConfigError so the error UI can render.

-  if (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading")) {
+  if (!pageConfigError && (isLoadingPageConfig || !currentData || (localeModule && localizationStatus === "loading"))) {
     return <Loader />;
   }

Also applies to: 226-233


235-241: Add UUID fallback for older browsers.

crypto.randomUUID may be unavailable; add a safe fallback.

-    const locVal = newFieldType?.label
-      ? newFieldType?.label
-      : `${campaignNumber}_${flow}_${pageName}_${crypto.randomUUID()}`.toUpperCase();
+    const uid = window.crypto?.randomUUID
+      ? window.crypto.randomUUID()
+      : `${Date.now()}_${Math.random().toString(36).slice(2,8)}`;
+    const locVal = newFieldType?.label
+      ? newFieldType?.label
+      : `${campaignNumber}_${flow}_${pageName}_${uid}`.toUpperCase();

260-266: Close popup on overlay click for expected UX.

Overlay currently does nothing.

-          onOverlayClick={() => {}}
+          onOverlayClick={() => {
+            dispatch(handleShowAddFieldPopup(null));
+            setNewFieldType({});
+          }}
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)

31-36: Use MDMS v1 context for v1 searches.

You read MDMS_V2_CONTEXT_PATH but call /v1/_search.

-    const mdmsContext = window?.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH") || "egov-mdms-service";
+    const mdmsContext = window?.globalConfigs?.getConfig("MDMS_V1_CONTEXT_PATH") || "egov-mdms-service";
     const url = `/${mdmsContext}/v1/_search`;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 317d116 and 1ea0976.

📒 Files selected for processing (9)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
🧠 Learnings (4)
📚 Learning: 2025-06-04T04:08:48.234Z
Learnt from: jagankumar-egov
PR: egovernments/DIGIT-Frontend#2488
File: micro-ui/web/micro-ui-internals/packages/modules/core/src/pages/employee/Login/login.js:152-152
Timestamp: 2025-06-04T04:08:48.234Z
Learning: In the employee login component (micro-ui/web/micro-ui-internals/packages/modules/core/src/pages/employee/Login/login.js), prefer using a `defaultTenant` variable to store `Digit.ULBService.getStateId()` instead of calling the function multiple times inline in the `defaultValue` object. This improves code readability and avoids function call duplication.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
📚 Learning: 2024-10-17T11:59:00.327Z
Learnt from: Ramkrishna-egov
PR: egovernments/DIGIT-Frontend#1545
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ActivityCard.js:1-1
Timestamp: 2024-10-17T11:59:00.327Z
Learning: In `ActivityCard.js`, `React.Fragment` is used in the code.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
📚 Learning: 2025-02-05T10:18:29.947Z
Learnt from: Tulika-eGov
PR: egovernments/DIGIT-Frontend#2188
File: micro-ui/web/micro-ui-internals/packages/modules/pgr/src/pages/employee/new-inbox.js:34-47
Timestamp: 2025-02-05T10:18:29.947Z
Learning: In the DIGIT-Frontend codebase, translations within FormComposer are handled internally by the component itself, so the translation function 't' does not need to be added to useMemo dependency arrays even when used within the memoized function that generates form configurations.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js
📚 Learning: 2024-11-07T11:02:33.520Z
Learnt from: rachana-egov
PR: egovernments/DIGIT-Frontend#1770
File: health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js:320-322
Timestamp: 2024-11-07T11:02:33.520Z
Learning: In `health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/viewVillage.js`, the `data?.additionalFields` object is guaranteed to be defined, so checking for its existence before accessing its keys is unnecessary.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
🧬 Code graph analysis (9)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (1)
  • response (8-20)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (2)
  • transformMdmsToAppConfig (47-127)
  • transformMdmsToAppConfig (47-127)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (38-38)
  • useCustomTranslate (37-67)
  • useCustomTranslate (37-67)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js (2)
  • dispatch (13-13)
  • SidePanelApp (11-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (3)
  • useSelector (189-189)
  • NewLayoutRenderer (187-243)
  • isFieldSelected (32-47)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js (1)
  • state (74-74)
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js (1)
  • AppPreview (6-64)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js (6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (1)
  • response (378-396)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (1)
  • response (14-29)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • tenantId (21-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (2)
  • tenantId (13-13)
  • campaignNumber (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/Module.js (2)
  • tenantId (95-95)
  • moduleName (96-96)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (6)
  • tenantId (27-27)
  • tenantId (71-71)
  • moduleName (82-82)
  • masterName (83-83)
  • campaignNumber (86-86)
  • flowId (85-85)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.js (3)
  • useCustomT (12-37)
  • useCustomT (12-37)
  • currentLocale (14-14)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/useCustomT.js (4)
  • useCustomT (3-18)
  • useCustomT (3-18)
  • entry (12-12)
  • currentLocale (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • locale (9-9)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (9)
  • mdmsContext (34-34)
  • MODULE_CONSTANTS (3-3)
  • response (12-16)
  • response (37-55)
  • response (68-68)
  • fetchLocalization (6-29)
  • fetchLocalization (6-29)
  • fetchAppScreenConfig (32-61)
  • fetchAppScreenConfig (32-61)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (2)
  • useFieldDataLabel (70-73)
  • useFieldDataLabel (70-73)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)
  • response (378-396)
  • getFieldPanelMaster (364-414)
  • getFieldPanelMaster (364-414)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (3)
  • response (16-34)
  • getFieldMaster (5-43)
  • getFieldMaster (5-43)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (5)
  • MODULE_CONSTANTS (24-24)
  • currentLocale (26-26)
  • localeModule (30-30)
  • mdmsContext (23-23)
  • tenantId (21-21)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js (3)
  • MODULE_CONSTANTS (338-338)
  • currentLocale (347-347)
  • tenantId (345-345)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (4)
  • response (378-396)
  • url (376-376)
  • data (397-397)
  • data (403-403)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (9)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js (2)
  • t (13-13)
  • useSelector (12-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.js (1)
  • FilterIcon (4-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.js (2)
  • iconName (6-6)
  • IconComponent (7-7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.js (3)
  • panelConfig (4-4)
  • panelTypes (5-5)
  • panelType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (8)
  • fieldTypeConfig (19-21)
  • useSelector (189-189)
  • isFieldSelected (32-47)
  • renderTemplateComponent (61-133)
  • isSelected (64-64)
  • Component (71-71)
  • uniqueKey (65-65)
  • renderSection (146-181)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.js (3)
  • cardConfig (4-4)
  • cardTypes (5-5)
  • cardType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.js (4)
  • infoConfig (4-4)
  • infoTypes (5-5)
  • infoType (6-6)
  • variantMap (7-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.js (4)
  • variantMap (7-12)
  • tagConfig (4-4)
  • tagTypes (5-5)
  • tagType (6-6)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (2)
  • useSelector (8-8)
  • component (17-17)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (7)
  • existing (99-99)
  • mdmsContext (34-34)
  • url (35-35)
  • response (12-16)
  • response (37-55)
  • response (68-68)
  • data (56-56)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.js (5)
  • existing (10-10)
  • contextPath (13-13)
  • url (14-14)
  • response (16-34)
  • data (35-35)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)
  • mdmsContext (23-23)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (6)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.js (4)
  • dispatch (6-6)
  • dispatch (38-38)
  • useCustomT (5-34)
  • useCustomT (5-34)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.js (2)
  • dispatch (70-70)
  • selectedField (74-74)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.js (4)
  • getFieldValueByPath (8-18)
  • getFieldValueByPath (8-18)
  • value (12-12)
  • keys (11-11)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js (1)
  • existing (99-99)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/deliveryRule/AddDeliverycontext.js (1)
  • DustbinIcon (21-28)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-21)
  • getFieldTypeFromMasterData (1-21)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 305-314: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 305-314: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 326-333: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 326-333: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 342-353: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 342-353: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 354-354: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 371-387: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 371-387: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 388-388: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js

[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js

[error] 31-31: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 237-252: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 237-252: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 268-288: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 268-288: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js

[error] 395-401: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 522-536: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 522-536: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 557-558: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 558-559: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 56-56: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 118-118: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 182-182: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 215-215: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 374-374: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 375-375: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 595-596: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (2)

152-218: Well-structured component setup with good performance optimization.

The component demonstrates solid React patterns:

  • Appropriate use of useMemo for expensive computations (template extraction and registry building)
  • Clean registry pattern for mapping template formats to components
  • Defensive isFieldSelected helper with multiple fallback strategies (componentName → id → reference equality)

368-417: Clean component composition with good separation of concerns.

The main render logic effectively separates header, body, and footer rendering with clear visual hierarchy. The MobileBezelFrame wrapper and fixed footer positioning create a good preview experience.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js (1)

68-76: Endpoint pattern is correct and consistent with codebase conventions.

The URL /mdms-v2/v2/_update/HCM-ADMIN-CONSOLE.AppConfigCache properly embeds the schema in the path, matching the established pattern across similar update operations (useUpdateFormBuilderConfig, useUpdateAppConfig, useUpdateAppConfigForFeatures). The body structure with the Mdms wrapper object containing all required fields (id, tenantId, schemaCode, uniqueIdentifier, data, isActive, auditDetails) aligns with MDMS v2 API expectations. No schema placement or payload structure issues identified.

Comment on lines +28 to +31
// Generate unique localeModule based on flow, pageName, and campaignNumber
// Format: hcm-{flow}-{pageName}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment/code mismatch for localeModule.

Comment says include pageName; code omits it. Align one of them.

-  // Format: hcm-{flow}-{pageName}-{campaignNumber}
-  const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
+  // Format: hcm-{flow}-{campaignNumber}
+  const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, "")}-${campaignNumber}`;

Or include pageName:

-  const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
+  const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, "")}-${pageName?.toLowerCase?.() ?? "page"}-${campaignNumber}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Generate unique localeModule based on flow, pageName, and campaignNumber
// Format: hcm-{flow}-{pageName}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
// Generate unique localeModule based on flow, and campaignNumber
// Format: hcm-{flow}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, "")}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
Suggested change
// Generate unique localeModule based on flow, pageName, and campaignNumber
// Format: hcm-{flow}-{pageName}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
// Generate unique localeModule based on flow, pageName, and campaignNumber
// Format: hcm-{flow}-{pageName}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, "")}-${pageName?.toLowerCase?.() ?? "page"}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 28 to 31, the localeModule generation omits pageName despite the
comment saying it should be included; update the template to include pageName
(normalized the same way as flow: toLowerCase and remove underscores) so the
module becomes hcm-{flow}-{pageName}-{campaignNumber}, or alternatively update
the comment if you purposely want to omit pageName—ensure code and comment
match.

// Generate unique localeModule based on flow, pageName, and campaignNumber
// Format: hcm-{flow}-{pageName}-{campaignNumber}
const localeModule = `hcm-${flow?.toLowerCase()?.replace(/_/g, '')}-${campaignNumber}`;
const [newFieldType, setNewFieldType] = useState(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix null-spread crashes for newFieldType.

newFieldType starts as null; spreading it or prev will throw. Initialize to {} and guard spreads.

-  const [newFieldType, setNewFieldType] = useState(null);
+  const [newFieldType, setNewFieldType] = useState({});
...
-    setNewFieldType((prev) => ({
-      ...prev,
+    setNewFieldType((prev) => ({
+      ...(prev || {}),
       label: locVal,
     }));
...
-                  const updatedData = { ...newFieldType, field: value };
+                  const updatedData = { ...(newFieldType || {}), field: value };
...
-      ...selectedFieldType?.metadata, // Include any metadata from field type
+      ...(selectedFieldType?.metadata || {}), // Include any metadata from field type

Also applies to: 248-251, 293-296, 103-104

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
around lines 31, 103-104, 248-251 and 293-296, newFieldType is initialized to
null and later spread/merged which crashes; change the initial state to an empty
object (useState({})) and update places that spread newFieldType or prev to
guard against null by ensuring you spread only objects (e.g., ...prev || {} or
...newFieldType || {}) so all merges safely handle initial/undefined values.

Comment on lines +118 to +146
useEffect(() => {
const fetchPageConfig = async () => {
try {
setIsLoadingPageConfig(true);
setPageConfigError(null);

// Clean up page name - if it contains a dot, take only the part after the dot
// e.g., "HOUSEHOLD.beneficiaryLocation" -> "beneficiaryLocation"
const cleanedPageName = pageName?.includes('.')
? pageName.split('.').pop()
: pageName;

// Fetch page configuration from MDMS
const response = await Digit.CustomService.getResponse({
url: "/mdms-v2/v2/_search",
body: {
MdmsCriteria: {
tenantId: tenantId,
schemaCode: `${MODULE_CONSTANTS}.NewFormConfig`,
filters: {
flow: flow,
project: campaignNumber,
page: cleanedPageName,
},
isActive: true,
},
},
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use configured MDMS v2 context and add a limit.

Avoid hardcoded "/mdms-v2/v2/_search" and add limit to bound results.

-        const response = await Digit.CustomService.getResponse({
-          url: "/mdms-v2/v2/_search",
+        const mdmsV2 = window?.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH") || "mdms-v2";
+        const response = await Digit.CustomService.getResponse({
+          url: `/${mdmsV2}/v2/_search`,
           body: {
             MdmsCriteria: {
               tenantId: tenantId,
               schemaCode: `${MODULE_CONSTANTS}.NewFormConfig`,
               filters: {
                 flow: flow,
                 project: campaignNumber,
                 page: cleanedPageName,
               },
+              limit: 100,
               isActive: true,
             },
           },
         });

// Get the component from registry
const Component = templateComponentRegistry[componentType];
if (!Component) {
console.warn(`Component type "${componentType}" not found in registry`);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider removing or gating the console.warn for production.

While this warning helps during development to identify missing component types, consider removing it or gating it behind a development-only flag for production builds.

-    if (!Component) {
-      console.warn(`Component type "${componentType}" not found in registry`);
-      return null;
-    }
+    if (!Component) {
+      if (process.env.NODE_ENV === 'development') {
+        console.warn(`Component type "${componentType}" not found in registry`);
+      }
+      return null;
+    }
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
around line 231, the unconditional console.warn for missing component types
should not run in production; replace or guard it so warnings appear only in
development (e.g., wrap the console.warn in a check like process.env.NODE_ENV
!== 'production' or use an existing __DEV__ flag or debug logger), or remove it
entirely and log via a proper logger that respects environment/log-levels.

<div
style={{
position: "absolute",
bottom: 60,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting magic number to a named constant.

The value 60 for footer positioning could be more maintainable as a named constant.

+const MOBILE_BEZEL_BOTTOM_OFFSET = 60;
+
 const LayoutRenderer = ({ data = {}, selectedField, t, onFieldClick }) => {
   // ...
   
           <div
             style={{
               position: "absolute",
-              bottom: 60,
+              bottom: MOBILE_BEZEL_BOTTOM_OFFSET,
               left: 0,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bottom: 60,
const MOBILE_BEZEL_BOTTOM_OFFSET = 60;
const LayoutRenderer = ({ data = {}, selectedField, t, onFieldClick }) => {
// ... existing code ...
<div
style={{
position: "absolute",
bottom: MOBILE_BEZEL_BOTTOM_OFFSET,
left: 0,
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
around line 399, the hard-coded footer positioning value `60` should be
extracted to a named constant; replace the literal with a clearly named constant
(e.g., FOOTER_BOTTOM_OFFSET or FOOTER_HEIGHT_OFFSET) declared near the top of
the module (or in a constants file if appropriate), update the reference at line
399 to use that constant, and ensure any related values or comments clarify the
unit/meaning so future changes are easier and more maintainable.

Comment on lines +409 to +412
// Fallback to dummy data on error
console.error("Failed to fetch from MDMS, using fallback:", err);
return dummyPanelConfig;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fallback shape mismatch will create an extra “id” tab.

Consumers expect { content, validation }, but you return dummyPanelConfig (with id).

-      return dummyPanelConfig;
+      const { content, validation } = dummyPanelConfig || {};
+      return { content, validation };
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.js
around lines 409 to 412, the fallback returned on MDMS error is dummyPanelConfig
which includes an extra "id" field but consumers expect an object shaped {
content, validation }; update the fallback to return an object matching that
exact shape (e.g. construct and return { content: dummyPanelConfig.content,
validation: dummyPanelConfig.validation } or remove the id and any other
extraneous properties before returning) so callers receive only content and
validation.

Comment on lines +9 to +13
url: "/mdms-v2/v2/_search",
body: {
MdmsCriteria: {
tenantId: tenantId,
schemaCode: `${moduleName}.${masterName}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use configured MDMS v2 context and add a bounded limit.

Hardcoding the service path bypasses env config, and the query is unbounded. Use MDMS_V2_CONTEXT_PATH and cap results.

-      const response = await Digit.CustomService.getResponse({
-        url: "/mdms-v2/v2/_search",
+      const mdmsV2 = window?.globalConfigs?.getConfig("MDMS_V2_CONTEXT_PATH") || "mdms-v2";
+      const response = await Digit.CustomService.getResponse({
+        url: `/${mdmsV2}/v2/_search`,
         body: {
           MdmsCriteria: {
             tenantId: tenantId,
             schemaCode: `${moduleName}.${masterName}`,
             filters: {
               project: campaignNumber, // FIXED: Use parameter instead of hardcoding
             },
+            limit: 100,
             isActive: true,
           },
         },
       });

Also applies to: 17-18

@@ -0,0 +1,179 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

const MODULE_CONSTANTS = "HCM-ADMIN-CONSOLE";
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extract MODULE_CONSTANTS to a shared constants module.

Reduce duplication across slices/components.

-const MODULE_CONSTANTS = "HCM-ADMIN-CONSOLE";
+import { MODULE_CONSTANTS } from "../../constants/flowConstants";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js
around line 3, the MODULE_CONSTANTS string is defined inline; extract this
constant into a shared constants module (e.g.,
health/micro-ui/web/packages/modules/shared/constants/moduleConstants.js or
existing shared constants file), export MODULE_CONSTANTS from there, then
replace the inline declaration in this file with an import of MODULE_CONSTANTS;
update any other slices/components that declare the same literal to import the
shared constant as well to reduce duplication.

Comment on lines +63 to +74
// Async thunk to upsert localization
export const upsertLocalization = createAsyncThunk(
"localization/upsert",
async ({ tenantId, localeModule, currentLocale, data }, { rejectWithValue }) => {
try {
const response = await Digit.Hooks.campaign.useUpsertLocalisationParallel(tenantId, localeModule, currentLocale)(data);
return response;
} catch (error) {
return rejectWithValue(error.message || "Failed to upsert localization");
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Don’t call React hooks inside thunks.

useUpsertLocalisationParallel is a hook; calling it in a thunk violates the Rules of Hooks and can break at runtime.

-export const upsertLocalization = createAsyncThunk(
-  "localization/upsert",
-  async ({ tenantId, localeModule, currentLocale, data }, { rejectWithValue }) => {
-    try {
-      const response = await Digit.Hooks.campaign.useUpsertLocalisationParallel(tenantId, localeModule, currentLocale)(data);
-      return response;
-    } catch (error) {
-      return rejectWithValue(error.message || "Failed to upsert localization");
-    }
-  }
-);
+export const upsertLocalization = createAsyncThunk(
+  "localization/upsert",
+  async ({ tenantId, localeModule, currentLocale, data }, { rejectWithValue }) => {
+    try {
+      // Replace hook with a service/API call. Adjust payload shape per API.
+      const response = await Digit.CustomService.getResponse({
+        url: "/localization/messages/v1/_upsert",
+        body: data,
+        params: { tenantId, module: localeModule, locale: currentLocale },
+      });
+      return response;
+    } catch (error) {
+      return rejectWithValue(error.message || "Failed to upsert localization");
+    }
+  }
+);

Also applies to: 68-69


🏁 Script executed:

#!/bin/bash
# Find other occurrences of React hooks used outside React components/effects
rg -nP '\bDigit\.Hooks\.[\w.]+' -C2

Length of output: 258595


Replace hook call with service invocation in Redux thunk.

The useUpsertLocalisationParallel hook cannot be called inside a Redux thunk. Hooks must execute within React component context. Convert the hook call to a service-based API call.

-export const upsertLocalization = createAsyncThunk(
-  "localization/upsert",
-  async ({ tenantId, localeModule, currentLocale, data }, { rejectWithValue }) => {
-    try {
-      const response = await Digit.Hooks.campaign.useUpsertLocalisationParallel(tenantId, localeModule, currentLocale)(data);
-      return response;
-    } catch (error) {
-      return rejectWithValue(error.message || "Failed to upsert localization");
-    }
-  }
-);
+export const upsertLocalization = createAsyncThunk(
+  "localization/upsert",
+  async ({ tenantId, localeModule, currentLocale, data }, { rejectWithValue }) => {
+    try {
+      // Call the API service directly instead of using the hook
+      const response = await Digit.CustomService.getResponse({
+        url: "/localization/messages/v1/_upsert",
+        body: data,
+        params: { tenantId, module: localeModule, locale: currentLocale },
+      });
+      return response;
+    } catch (error) {
+      return rejectWithValue(error.message || "Failed to upsert localization");
+    }
+  }
+);

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

Comment on lines +89 to +96
setLocalizationData(state, action) {
state.data = action.payload.localisationData || [];
// Ensure currentLocale is a string
state.currentLocale = typeof action.payload.currentLocale === 'string'
? action.payload.currentLocale
: String(action.payload.currentLocale);
state.localeModule = action.payload.localeModule;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Safer default for currentLocale in setLocalizationData.

String(undefined) => "undefined". Default to a known locale.

-      state.currentLocale = typeof action.payload.currentLocale === 'string'
-        ? action.payload.currentLocale
-        : String(action.payload.currentLocale);
+      const incoming = action.payload.currentLocale ?? "en_IN";
+      state.currentLocale = typeof incoming === "string" ? incoming : String(incoming);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setLocalizationData(state, action) {
state.data = action.payload.localisationData || [];
// Ensure currentLocale is a string
state.currentLocale = typeof action.payload.currentLocale === 'string'
? action.payload.currentLocale
: String(action.payload.currentLocale);
state.localeModule = action.payload.localeModule;
},
setLocalizationData(state, action) {
state.data = action.payload.localisationData || [];
// Ensure currentLocale is a string
const incoming = action.payload.currentLocale ?? "en_IN";
state.currentLocale = typeof incoming === "string" ? incoming : String(incoming);
state.localeModule = action.payload.localeModule;
},
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.js
around lines 89 to 96, avoid calling String(undefined) which yields "undefined"
by defaulting currentLocale to a known locale; change the assignment so it uses
a nullish fallback (e.g., 'en' or 'en-US') when action.payload.currentLocale is
null or undefined, and then coerce to string only after applying the fallback;
also guard against missing action.payload before accessing properties.

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: 11

♻️ Duplicate comments (9)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (9)

31-38: Do not hardcode tenant ("mz"); use resolved tenantId.

Blocks multi-tenant flows.

-              tenantId: "mz",
+              tenantId,

117-119: Flatten MDMS data before transform (transform expects flat array).

Prevents array-of-arrays bug.

-      const fullData = response?.mdms && response?.mdms?.map((item) => item.data);
-      const transformedData = transformMdmsToAppConfig(fullData);
+      const fullData = (response?.mdms || []).flatMap((item) =>
+        Array.isArray(item?.data) ? item.data : [item?.data].filter(Boolean)
+      );
+      const transformedData = transformMdmsToAppConfig(fullData);

333-345: Make flow items accessible and use stable keys.
Add role, keyboard support, aria-selected; avoid index keys.

-          {flowConfig.flows?.map((flow, index) => (
+          {flowConfig.flows?.map((flow) => (
             <div
-              key={index}
+              key={flow.id ?? flow.name}
               style={{
                 ...styles.flowItem,
                 backgroundColor: selectedFlow === flow.id ? "#F47738" : "transparent",
                 color: selectedFlow === flow.id ? "#FFFFFF" : "#505A5F",
                 fontWeight: selectedFlow === flow.id ? "700" : "400",
               }}
-              onClick={() => handleFlowClick(flow)}
+              role="button"
+              tabIndex={0}
+              aria-selected={selectedFlow === flow.id}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handleFlowClick(flow);
+                }
+              }}
+              onClick={() => handleFlowClick(flow)}
             >

354-364: Make page tabs accessible and use stable keys.
Add role, keyboard support, aria-selected; avoid index keys.

-          {activeFlow?.pages?.map((page, index) => (
+          {activeFlow?.pages?.map((page) => (
             <div
-              key={index}
+              key={page.name}
               style={{
                 ...styles.pageTab,
                 ...(selectedPageName === page.name ? styles.pageTabActive : {}),
               }}
-              onClick={() => handlePageClick(page)}
+              role="tab"
+              aria-selected={selectedPageName === page.name}
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault();
+                  handlePageClick(page);
+                }
+              }}
+              onClick={() => handlePageClick(page)}
             >

370-381: Navigation arrows: use real buttons or add full a11y.
Keyboardability and labels are required.

-          <div
+          <button
             style={{
               ...styles.navArrow,
               opacity: !previousRoute ? 0.3 : 1,
               cursor: !previousRoute ? "not-allowed" : "pointer",
             }}
-            onClick={() => {
+            type="button"
+            aria-label="Previous page"
+            disabled={!previousRoute}
+            onClick={() => {
               if (previousRoute) {
                 setSelectedPageName(previousRoute);
               }
             }}
-          >
+          >
@@
-          </div>
+          </button>
@@
-          <div
+          <button
             style={{
               ...styles.navArrow,
               opacity: !nextRoute ? 0.3 : 1,
               cursor: !nextRoute ? "not-allowed" : "pointer",
             }}
-            onClick={async () => {
+            type="button"
+            aria-label="Next page"
+            disabled={!nextRoute}
+            onClick={async () => {
               if (nextRoute) {
                 // Call MDMS update if available
                 if (window.__appConfig_onNext && typeof window.__appConfig_onNext === "function") {
                   await window.__appConfig_onNext();
                 }
                 // Navigate to next page
                 setSelectedPageName(nextRoute);
               }
             }}
-          >
+          >
@@
-          </div>
+          </button>

Also applies to: 399-415


382-385: SVG a11y: add title or hide if decorative.
Provide accessible name.

-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="prevIconTitle" role="img">
+              <title id="prevIconTitle">Previous page</title>
               <path d="M15 18L9 12L15 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>
@@
-            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="nextIconTitle" role="img">
+              <title id="nextIconTitle">Next page</title>
               <path d="M9 18L15 12L9 6" stroke="#505A5F" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
             </svg>

Also applies to: 416-419


387-396: Props dropped by AppConfigurationStore (no-op).
These props aren’t accepted/forwarded; remove or extend the component API.

           <AppConfigurationStore
             flow={selectedFlow}
             flowName={activeFlow?.name}
             pageName={selectedPageName}
             campaignNumber={flowConfig?.project}
-            onPageChange={setSelectedPageName}
-            nextRoute={nextRoute}
-            previousRoute={previousRoute}
           />

193-311: Large styles object recreated each render.
Move to module scope or memoize.

-  const styles = {
+  // consider moving to top-level const or:
+  const styles = React.useMemo(() => ({
     ...
-  };
+  }), []);

320-323: Avoid index keys for roles; use stable key.
Prevents reconciliation glitches.

-            currentPageRoles.map((role, index) => (
-              <div key={index} style={styles.roleItem}>
+            currentPageRoles.map((role) => (
+              <div key={role} style={styles.roleItem}>
                 {role}
               </div>
             ))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea0976 and d701b5b.

📒 Files selected for processing (2)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1 hunks)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
🧠 Learnings (1)
📚 Learning: 2025-06-04T04:08:48.234Z
Learnt from: jagankumar-egov
PR: egovernments/DIGIT-Frontend#2488
File: micro-ui/web/micro-ui-internals/packages/modules/core/src/pages/employee/Login/login.js:152-152
Timestamp: 2025-06-04T04:08:48.234Z
Learning: In the employee login component (micro-ui/web/micro-ui-internals/packages/modules/core/src/pages/employee/Login/login.js), prefer using a `defaultTenant` variable to store `Digit.ULBService.getStateId()` instead of calling the function multiple times inline in the `defaultValue` object. This improves code readability and avoids function call duplication.

Applied to files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
🧬 Code graph analysis (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (3)
  • page (94-101)
  • transformMdmsToAppConfig (6-60)
  • transformMdmsToAppConfig (6-60)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js (1)
  • AppConfigurationStore (6-12)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/pageFieldsSlice.js (1)
  • pageData (34-34)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js

[error] 333-342: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 333-342: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 354-361: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 354-361: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 370-381: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 370-381: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 382-382: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 399-415: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 399-415: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 416-416: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🔇 Additional comments (5)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js (4)

65-88: LGTM! Template transformation is well-structured.

The function correctly handles optional fields, uses appropriate precedence for name (page over flow), and defaults screenType to "TEMPLATE". The strict inequality check for navigateTo properly allows falsy values.


93-119: LGTM! Form page transformation is safe and comprehensive.

The function safely accesses footer[0] after validating the array length, and handles all optional fields appropriately.


170-219: LGTM! Validation builder correctly handles edge cases.

The function properly uses !== undefined for min and max (lines 201, 210) to allow zero values, and provides sensible default messages with interpolated values.


6-9: The validation is correct and the review suggestion does not apply.

The past review suggested normalizing fullData to handle nested arrays, but this is based on a misunderstanding of the data flow. In the actual call site (FullConfigWrapper.js line 117), fullData is constructed as:

const fullData = response?.mdms && response?.mdms?.map((item) => item.data);

This extracts a flat array of .data objects from response.mdms. By the time transformMdmsToAppConfig receives fullData, it's already a normalized flat array—not a nested structure. The validation correctly expects a non-empty array, which matches the actual input type.

Likely an incorrect or invalid review comment.

health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js (1)

189-191: Verify route vs page-name semantics.
previousRoute/nextRoute are set into selectedPageName; confirm they match page.name values to avoid navigation breakage.

Would you confirm that pages use name strings equal to route identifiers, or adjust mapping accordingly?

Also applies to: 370-381, 399-415

@@ -0,0 +1,449 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use useLocation and drop magic campaign default.

Avoid global location and hardcoded campaign number.

-import { useNavigate } from "react-router-dom";
+import { useNavigate, useLocation } from "react-router-dom";
@@
-  const searchParams = new URLSearchParams(location.search);
-  const campaignNumber = searchParams.get("campaignNumber") || "CMP-2025-08-04-004846";
+  const { search } = useLocation();
+  const searchParams = new URLSearchParams(search);
+  const campaignNumber = searchParams.get("campaignNumber"); // no magic default
   const flowModule = searchParams.get("flow");
   const tenantId = searchParams.get("tenantId") || Digit?.ULBService?.getCurrentTenantId();

Also applies to: 11-15

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 2 and 11-15, the component currently reads global
location/hardcoded campaign id; replace that by importing useLocation from
react-router-dom, read the campaign id from location.state (or parse from
location.search as a fallback), and remove the magic default campaign number so
the component uses the campaign id provided via routing state or query param;
update any navigate calls to pass the campaign id in state/query instead of
relying on a hardcoded value.

import AppConfigurationStore from "./AppConfigurationStore";
import { Loader, Button } from "@egovernments/digit-ui-components";
import { useTranslation } from "react-i18next";
import transformMdmsToAppConfig from "./transformers/mdmsToAppConfig";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix import: use named export from transformer.

Current import will be undefined; transformer exports a named function.

-import transformMdmsToAppConfig from "./transformers/mdmsToAppConfig";
+import { transformMdmsToAppConfig } from "./transformers/mdmsToAppConfig";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import transformMdmsToAppConfig from "./transformers/mdmsToAppConfig";
import { transformMdmsToAppConfig } from "./transformers/mdmsToAppConfig";
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around line 6, the import of the MDMS transformer uses a default import but the
module exports a named function; replace the default import with a named import
(e.g., import { transformMdmsToAppConfig } from
"./transformers/mdmsToAppConfig";) and update any local usages if they assumed a
default export so they call the named function correctly.

Comment on lines +47 to +51
if (configData.flows && configData.flows.length > 0) {
const firstFlow = configData.flows[0];
setSelectedFlow(firstFlow.id);
setSelectedPageName(firstFlow.indexRoute || firstFlow.pages?.[0]?.name);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Respect flow= query param when selecting initial flow/page.

Preselect requested flow if present; fallback to first.

-          if (configData.flows && configData.flows.length > 0) {
-            const firstFlow = configData.flows[0];
-            setSelectedFlow(firstFlow.id);
-            setSelectedPageName(firstFlow.indexRoute || firstFlow.pages?.[0]?.name);
-          }
+          if (configData.flows?.length) {
+            const initial = (flowModule && configData.flows.find(f => f.name === flowModule)) || configData.flows[0];
+            setSelectedFlow(initial.id);
+            setSelectedPageName(initial.indexRoute || initial.pages?.[0]?.name);
+          }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 47-51, the code always selects the first flow/page from configData
but should respect a flow= query parameter; read the current query param (e.g.,
via the router or location.search / URLSearchParams), look up a flow in
configData.flows whose id matches the flow query value (handle string/number
type differences), and if found call setSelectedFlow(matching.id) and
setSelectedPageName(matching.indexRoute || matching.pages?.[0]?.name); if no
match, keep the existing fallback to the first flow/page. Ensure no other
behavior changes and keep the fallback logic intact.

Comment on lines +63 to +65
fetchFlowConfig();
}, [campaignNumber]);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Effect dependencies missing tenant/flow param.

Ensure refetch when tenant or flow changes.

-  }, [campaignNumber]);
+  }, [campaignNumber, tenantId, flowModule]);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 63 to 65, the useEffect only depends on campaignNumber so it won't
refetch when tenant or flow change; update the dependency array to include the
tenant and flow parameters used by fetchFlowConfig (e.g., [campaignNumber,
tenant, flow]), or memoize fetchFlowConfig with useCallback including those
params, so fetchFlowConfig is re-run whenever tenant or flow change.

Comment on lines +126 to +132
schemaCode: "HCM-ADMIN-CONSOLE.NewApkConfig",
filters: {
project: campaignNumber,
name: flowModule,
},
isActive: true,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard optional name filter; avoid passing undefined.

If flowModule absent, omit name filter.

-            filters: {
-              project: campaignNumber,
-              name: flowModule,
-            },
+            filters: {
+              project: campaignNumber,
+              ...(flowModule ? { name: flowModule } : {}),
+            },
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
around lines 126-132, the filters object currently always includes name:
flowModule which can pass undefined; change the filters construction to only
include the name property when flowModule is present (e.g., build filters with
project: campaignNumber and conditionally spread name: flowModule when truthy)
so that the name filter is omitted instead of being set to undefined.

Comment on lines +318 to +331
<div style={styles.sectionTitle}>Roles</div>
{currentPageRoles.length > 0 ? (
currentPageRoles.map((role, index) => (
<div key={index} style={styles.roleItem}>
{role}
</div>
))
) : (
<div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>No roles assigned</div>
)}
</div>

<div style={styles.sidebarSection}>
<div style={styles.sectionTitle}>Flows</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Localize user-visible text.
Replace hardcoded strings with t(...).

-          <div style={styles.sectionTitle}>Roles</div>
+          <div style={styles.sectionTitle}>{t("ROLES")}</div>
@@
-          <div style={styles.sectionTitle}>Flows</div>
+          <div style={styles.sectionTitle}>{t("FLOWS")}</div>
@@
-            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>No roles assigned</div>
+            <div style={{ fontSize: "13px", color: "#999", padding: "8px 0" }}>{t("NO_ROLES_ASSIGNED")}</div>
@@
-        <h2 style={{ color: "#d32f2f" }}>Error</h2>
-        <p>{error || "Failed to load flow configuration"}</p>
+        <h2 style={{ color: "#d32f2f" }}>{t("ERROR")}</h2>
+        <p>{error || t("FAILED_TO_LOAD_FLOW_CONFIGURATION")}</p>

Also applies to: 326-326, 180-182


fullData.forEach(item => {
// Check if it's a template (has screenType: TEMPLATE or type: template)
const isTemplate = item.screenType === "TEMPLATE" || item.type === "template" || (!item.type && !item.flow);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify template detection fallback.

The condition (!item.type && !item.flow) treats items with neither type nor flow as templates. This is ambiguous—what if an item is malformed or incomplete? Consider explicitly checking for expected template indicators or documenting this fallback behavior.

Apply this diff to make the logic more explicit:

-    const isTemplate = item.screenType === "TEMPLATE" || item.type === "template" || (!item.type && !item.flow);
+    // Treat as template if explicitly marked, or if it lacks both type and flow (legacy/default behavior)
+    const isTemplate = item.screenType === "TEMPLATE" || item.type === "template" || (!item.type && !item.flow);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isTemplate = item.screenType === "TEMPLATE" || item.type === "template" || (!item.type && !item.flow);
// Treat as template if explicitly marked, or if it lacks both type and flow (legacy/default behavior)
const isTemplate = item.screenType === "TEMPLATE" || item.type === "template" || (!item.type && !item.flow);

Comment on lines +22 to +24
} else if (item.type === "object" && item.flow) {
// It's a form page - group pages by flow name
const flowName = item.flow;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate flow name before using as object key.

If item.flow is an empty string, null, or undefined, it will create a form with an invalid key. This can cause silent failures or incorrect grouping.

Apply this diff to validate the flow name:

     } else if (item.type === "object" && item.flow) {
       // It's a form page - group pages by flow name
       const flowName = item.flow;
+      if (!flowName || typeof flowName !== "string" || flowName.trim() === "") {
+        console.warn("Skipping form item with invalid flow name:", item);
+        return;
+      }
       if (!forms[flowName]) {
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
around lines 22-24, the code uses item.flow directly as an object key which can
be empty, null or undefined; validate that item.flow is a non-empty string
before using it: if typeof item.flow === 'string' && item.flow.trim().length > 0
use that as the flowName, otherwise either skip grouping for this item or assign
a safe fallback key like 'ungrouped' (and optionally log a warning) so invalid
keys are never created.

Comment on lines +41 to +44
if (item.onAction && (!forms[flowName].onAction || item.order >= forms[flowName].lastOrder)) {
forms[flowName].onAction = item.onAction;
forms[flowName].lastOrder = item.order;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix onAction storage logic for undefined orders.

The lastOrder field is never initialized when creating the form object (line 26-35), causing item.order >= forms[flowName].lastOrder to compare against undefined. If item.order is also undefined, the comparison will behave unexpectedly.

Apply this diff to initialize lastOrder and handle undefined orders:

       if (!forms[flowName]) {
         forms[flowName] = {
           name: flowName,
           project: item.project,
           version: item.version,
           disabled: item.disabled || false,
           isSelected: item.isSelected !== undefined ? item.isSelected : true,
           screenType: "FORM",
           pages: [],
-          wrapperConfig: item.wrapperConfig
+          wrapperConfig: item.wrapperConfig,
+          lastOrder: -1
         };
       }
       // Add page to form
       forms[flowName].pages.push(transformFormPage(item));

       // Store onAction from the last page (or first one that has it)
-      if (item.onAction && (!forms[flowName].onAction || item.order >= forms[flowName].lastOrder)) {
+      const currentOrder = item.order ?? -1;
+      if (item.onAction && (!forms[flowName].onAction || currentOrder >= forms[flowName].lastOrder)) {
         forms[flowName].onAction = item.onAction;
-        forms[flowName].lastOrder = item.order;
+        forms[flowName].lastOrder = currentOrder;
       }
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
around lines 41 to 44, the code compares item.order against
forms[flowName].lastOrder which is never initialized; update the form object
creation (lines ~26-35) to initialize lastOrder to Number.NEGATIVE_INFINITY (or
a safe default) and change the condition to compare using a safe fallback for
undefined orders, e.g. compare (item.order ?? Number.NEGATIVE_INFINITY) >=
forms[flowName].lastOrder, and when assigning lastOrder set it to item.order ??
forms[flowName].lastOrder so undefined orders don’t overwrite or break the
comparison.

Comment on lines +124 to +165
const transformFormProperties = (body) => {
if (!body || !Array.isArray(body)) return [];

const properties = [];

body.forEach(bodySection => {
if (bodySection.fields && Array.isArray(bodySection.fields)) {
bodySection.fields.forEach(field => {
const property = {
type: field.type,
label: field.label,
order: field.order,
value: field.value !== undefined ? field.value : "",
format: field.format,
hidden: field.hidden !== undefined ? field.hidden : false,
tooltip: field.tooltip || "",
helpText: field.helpText || "",
infoText: field.infoText || "",
readOnly: field.readOnly !== undefined ? field.readOnly : false,
fieldName: field.fieldName,
deleteFlag: field.deleteFlag !== undefined ? field.deleteFlag : false,
innerLabel: field.innerLabel || "",
systemDate: field.systemDate !== undefined ? field.systemDate : false,
validations: buildValidations(field),
errorMessage: field.errorMessage || "",
isMultiSelect: field.isMultiSelect !== undefined ? field.isMultiSelect : false
};

// Add optional fields
if (field.enums) property.enums = field.enums;
if (field.schemaCode) property.schemaCode = field.schemaCode;
if (field.includeInForm !== undefined) property.includeInForm = field.includeInForm;
if (field.includeInSummary !== undefined) property.includeInSummary = field.includeInSummary;
if (field.visibilityCondition) property.visibilityCondition = field.visibilityCondition;

properties.push(property);
});
}
});

return properties;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM! Property transformation is thorough and handles defaults correctly.

The function properly uses !== undefined checks for boolean and numeric fields that can be falsy values, and conditionally adds optional fields to keep the output clean.

For maintainability, consider extracting the property construction into a helper function:

const buildProperty = (field) => {
  const property = {
    type: field.type,
    label: field.label,
    order: field.order,
    value: field.value !== undefined ? field.value : "",
    format: field.format,
    hidden: field.hidden !== undefined ? field.hidden : false,
    tooltip: field.tooltip || "",
    helpText: field.helpText || "",
    infoText: field.infoText || "",
    readOnly: field.readOnly !== undefined ? field.readOnly : false,
    fieldName: field.fieldName,
    deleteFlag: field.deleteFlag !== undefined ? field.deleteFlag : false,
    innerLabel: field.innerLabel || "",
    systemDate: field.systemDate !== undefined ? field.systemDate : false,
    validations: buildValidations(field),
    errorMessage: field.errorMessage || "",
    isMultiSelect: field.isMultiSelect !== undefined ? field.isMultiSelect : false
  };

  // Add optional fields
  if (field.enums) property.enums = field.enums;
  if (field.schemaCode) property.schemaCode = field.schemaCode;
  if (field.includeInForm !== undefined) property.includeInForm = field.includeInForm;
  if (field.includeInSummary !== undefined) property.includeInSummary = field.includeInSummary;
  if (field.visibilityCondition) property.visibilityCondition = field.visibilityCondition;

  return property;
};
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.js
around lines 124 to 165, extract the inline property construction into a new
helper function (e.g., buildProperty(field)) that returns the property object
and conditionally adds optional fields; then replace the current block inside
bodySection.fields.forEach with a call to this helper and push its result to
properties, ensuring buildValidations(field) is still used and that all current
!== undefined checks and optional field conditions are preserved exactly.

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

♻️ Duplicate comments (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (2)

82-110: Keyboard-enable the selectable wrapper (fallback path).

This wrapper handles clicks but exposes no role or keyboard handler. Keyboard users can't focus or activate it.

Apply this diff to add keyboard accessibility:

  return (
    <div
      key={uniqueKey}
+     role="button"
+     tabIndex={0}
      onClick={(e) => {
        e.stopPropagation();
        onFieldClick && onFieldClick(field, data, null, index, null);
      }}
+     onKeyDown={(e) => {
+       if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
+         e.preventDefault();
+         e.stopPropagation();
+         onFieldClick && onFieldClick(field, data, null, index, null);
+       }
+     }}
      style={{
        cursor: "pointer",
        border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
        borderRadius: "4px",
        padding: "8px",
        margin: "4px 0",
        backgroundColor: isSelected ? "#f0f8ff" : "transparent",
        transition: "all 0.2s ease",
      }}
    >

115-133: Apply the same keyboard fix to the custom-component wrapper.

The custom component path has identical accessibility gap: clickable div with no keyboard support.

Apply this diff:

  return (
    <div
      key={uniqueKey}
+     role="button"
+     tabIndex={0}
      onClick={(e) => {
        e.stopPropagation();
        onFieldClick && onFieldClick(field, data, null, index, null);
      }}
+     onKeyDown={(e) => {
+       if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
+         e.preventDefault();
+         e.stopPropagation();
+         onFieldClick && onFieldClick(field, data, null, index, null);
+       }
+     }}
      style={{
        cursor: "pointer",
        border: isSelected ? "2px solid #0B4B66" : "2px solid transparent",
        borderRadius: "4px",
        padding: "8px",
        margin: "4px 0",
        backgroundColor: isSelected ? "#f0f8ff" : "transparent",
        transition: "all 0.2s ease",
      }}
    >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d701b5b and 6b765e0.

📒 Files selected for processing (1)
  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.js

⚙️ CodeRabbit configuration file

check

Files:

  • health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
🧬 Code graph analysis (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js (4)
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js (3)
  • fieldTypeMasterData (11-11)
  • fieldType (14-14)
  • useSelector (8-8)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js (5)
  • fieldTypeConfig (155-155)
  • isFieldSelected (203-218)
  • renderTemplateComponent (220-256)
  • renderSection (350-367)
  • useSelector (154-154)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js (3)
  • fieldType (621-626)
  • useSelector (16-16)
  • useSelector (607-607)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js (2)
  • getFieldTypeFromMasterData (1-21)
  • getFieldTypeFromMasterData (1-21)
🪛 Biome (2.1.2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js

[error] 82-97: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 82-97: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 115-130: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 115-130: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

* @returns {string|null} - Component name or null
*/
const getComponentName = (field, fieldTypeMasterData) => {
console.log("Get ComponentName called with field:", {field, fieldTypeMasterData});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug log.

This console.log should be removed or gated behind an environment flag to avoid polluting production logs.

Apply this diff:

-  console.log("Get ComponentName called with field:", {field, fieldTypeMasterData});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Get ComponentName called with field:", {field, fieldTypeMasterData});
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around line 14, remove the debug console.log statement (console.log("Get
ComponentName called with field:", {field, fieldTypeMasterData})); or guard it
behind a dev-only flag (e.g., process.env.NODE_ENV !== 'production') so it does
not run in production; update the file to either delete the line or replace it
with a conditional debug log that only executes in non-production environments.

Comment on lines +20 to +22
const fieldTypeConfig = fieldTypeMasterData.find(
(item) => item?.metadata?.format === field?.format
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete matching logic may resolve wrong components.

The getComponentName function matches only on format, while the related getFieldTypeFromMasterData helper (used elsewhere in the codebase) matches on both type and format. This inconsistency can cause the wrong component to be resolved when multiple field types share the same format.

Apply this diff to align with the established pattern:

  const fieldTypeConfig = fieldTypeMasterData.find(
-    (item) =>  item?.metadata?.format === field?.format 
+    (item) => item?.metadata?.type === field?.type && item?.metadata?.format === field?.format 
  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fieldTypeConfig = fieldTypeMasterData.find(
(item) => item?.metadata?.format === field?.format
);
const fieldTypeConfig = fieldTypeMasterData.find(
(item) => item?.metadata?.type === field?.type && item?.metadata?.format === field?.format
);
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around lines 20 to 22, the current lookup only matches field metadata.format
which can select the wrong component when multiple field types share the same
format; update the matching to match both item.type and item.metadata.format
against field.type and field.format (i.e., require item?.type === field?.type &&
item?.metadata?.format === field?.format), preserving optional chaining to avoid
crashes when properties are undefined.

Comment on lines +153 to +161
if (sectionName === "body") {
// Body has nested structure: body[0].fields[]

fieldsToRender = section;

} else {
// Footer has direct structure: footer[]
fieldsToRender = section;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Body rendering logic contradicts its own comment.

The comment states "Body has nested structure: body[0].fields[]" but line 156 assigns section directly without extracting fields from the first element. This mismatch will cause incorrect rendering if the body structure is actually nested.

Verify the expected structure of data.body and apply the appropriate fix:

If body is nested (as the comment suggests):

  if (sectionName === "body") {
-   // Body has nested structure: body[0].fields[]
-   
-       fieldsToRender = section;
+   // Body has nested structure: body[0].fields[]
+   fieldsToRender = section[0]?.fields || [];
  } else {

If body is flat (as the current code assumes):

  if (sectionName === "body") {
-   // Body has nested structure: body[0].fields[]
-   
-       fieldsToRender = section;
+   // Body has flat structure
+   fieldsToRender = section;
  } else {

Run this script to check how body is structured in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for usages where data.body is populated to determine its structure
rg -n -A5 -B2 'body.*fields|\.body\s*=' --type=js -g '!**/node_modules/**'

Length of output: 41445



---

🏁 Script executed:

```shell
#!/bin/bash
# Find renderSection function definition and usages
rg -n -B5 -A15 'renderSection\s*=' --type=js -g '!**/node_modules/**' health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/

Length of output: 133



---

🏁 Script executed:

```shell
#!/bin/bash
# Find renderSection function definition
rg -n 'function renderSection|const renderSection|renderSection\s*\(' --type=js -g '!**/node_modules/**' -A10 | head -50

Length of output: 6943


🏁 Script executed:

#!/bin/bash
# See what happens after fieldsToRender is assigned (lines 156-180)
sed -n '153,180p' health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js

Length of output: 790


Body rendering logic has a critical bug: assigns card objects instead of fields array.

The comment at line 154 correctly describes the nested structure (body[0].fields[]), but line 156 assigns the entire body array (which contains card objects) to fieldsToRender instead of extracting the fields from the first card. The subsequent code at lines 166-171 treats fieldsToRender as an array of field objects, causing it to fail on card objects that lack the expected properties like .hidden.

Fix line 156:

  if (sectionName === "body") {
    // Body has nested structure: body[0].fields[]
-   fieldsToRender = section;
+   fieldsToRender = section[0]?.fields || [];
  } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (sectionName === "body") {
// Body has nested structure: body[0].fields[]
fieldsToRender = section;
} else {
// Footer has direct structure: footer[]
fieldsToRender = section;
}
if (sectionName === "body") {
// Body has nested structure: body[0].fields[]
fieldsToRender = section[0]?.fields || [];
} else {
// Footer has direct structure: footer[]
fieldsToRender = section;
}
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around lines 153 to 161, the body branch currently assigns the entire body array
(card objects) to fieldsToRender instead of the card's fields array; change the
assignment so when sectionName === "body" you set fieldsToRender to the first
card's fields (e.g., section[0].fields) and guard with optional chaining/default
to an empty array (section && section[0] && section[0].fields ?
section[0].fields : []) to avoid runtime errors when structure is missing.

const { byName } = useSelector((state) => state.fieldTypeMaster);
const fieldTypeMasterData = byName?.fieldTypeMappingConfig || [];

console.log("NewLayoutRenderer rendering with data:", {data, selectedField, fieldTypeMasterData});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug log.

This console.log should be removed or gated behind an environment flag to keep production logs clean.

Apply this diff:

-  console.log("NewLayoutRenderer rendering with data:", {data, selectedField, fieldTypeMasterData});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("NewLayoutRenderer rendering with data:", {data, selectedField, fieldTypeMasterData});
🤖 Prompt for AI Agents
In
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
around line 191, remove the debug console.log statement or wrap it so it only
runs in non-production builds; replace it with either nothing or a guarded log
such as checking process.env.NODE_ENV !== 'production' or an application-level
DEBUG flag before calling console.log, or use the existing logger utility if one
exists to emit debug-level messages that are disabled in production.

Comment on lines +216 to +226
<div style={{
position: "absolute",
bottom: 60,
left: 0,
right: 0,
backgroundColor: "#fff",
borderTop: "1px solid #e0e0e0",
display: "flex",
flexDirection: "column",
gap: "8px"
}}>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider more flexible footer positioning.

The absolute positioning with hardcoded bottom: 60 is fragile and may break if the parent container dimensions change. Consider using CSS flexbox with position: sticky or adjusting the parent layout to accommodate the footer naturally.

Alternative approach using sticky positioning:

-        {data?.footer?.length > 0 && (
-          <div style={{
-            position: "absolute",
-            bottom: 60,
-            left: 0,
-            right: 0,
-            backgroundColor: "#fff",
-            borderTop: "1px solid #e0e0e0",
-            display: "flex",
-            flexDirection: "column",
-            gap: "8px"
-          }}>
+        {data?.footer?.length > 0 && (
+          <div style={{
+            position: "sticky",
+            bottom: 0,
+            left: 0,
+            right: 0,
+            backgroundColor: "#fff",
+            borderTop: "1px solid #e0e0e0",
+            padding: "8px",
+            display: "flex",
+            flexDirection: "column",
+            gap: "8px"
+          }}>

And remove the hardcoded paddingBottom on the Card:

        <Card className="app-card" style={{ 
          flex: 1,
          overflow: "auto",
-         paddingBottom: data?.footer?.length > 0 ? "80px" : "0"
        }}>

Committable suggestion skipped: line range outside the PR's diff.

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.

4 participants