-
Notifications
You must be signed in to change notification settings - Fork 46
New App Config wth dummy config for Form, Redux setup (Config, panel config, field config, localisation) setup initial commit #3242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 eachwindow.location.pathnamecheck 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
⛔ Files ignored due to path filters (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyFieldTypeConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/fullData.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationScreenParent/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/OldAppPreview.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/useCustomT.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.jshealth/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.jshealth/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.jshealth/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 contextaddMissingKeyis defined as a single-arg(code)function that closes overenabledModulesand dispatches{ code, enabledModules }, matching its use inuseCustomT. 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. TheOldAppPreviewmodule’s default export (AppPreview) destructures exactlydata,selectedField, andt, 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 withtype="button"ortype="select"in the campaign-manager module. Please manually verify that FieldV1 now supports rendering buttons and selects natively; if it does not, reintroduce the custompopulators.componentfor those types.health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.js (1)
8-30: Do not removeindex
indexis used in theuseDrag({ 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. Ifidis 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 goodUsing 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.
health/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.js
Show resolved
Hide resolved
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
Outdated
Show resolved
Hide resolved
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
Outdated
Show resolved
Hide resolved
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
Outdated
Show resolved
Hide resolved
health/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.js
Outdated
Show resolved
Hide resolved
| 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 }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
...es/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
Outdated
Show resolved
Hide resolved
...es/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
Outdated
Show resolved
Hide resolved
| <button key={tab} className={`drawer-tab ${activeTab === tab ? "active" : ""}`} onClick={() => onTabChange(tab)}> | ||
| {t(`TAB_${tab.toUpperCase()}`)} | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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).
...es/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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...
health/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.js
Outdated
Show resolved
Hide resolved
| <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", | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| {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} | ||
| /> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
Outdated
Show resolved
Hide resolved
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
Show resolved
Hide resolved
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
Show resolved
Hide resolved
| 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 }; | ||
| } | ||
| }, |
There was a problem hiding this comment.
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.
| state.currentData.cards[cardIndex].headerFields[fieldIndex].value = value; | ||
| // Ensure reactivity by creating new reference | ||
| state.currentData = { ...state.currentData }; | ||
| } |
There was a problem hiding this comment.
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).
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.js
Show resolved
Hide resolved
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/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/getFieldPanelMasterhit/v1/_search, but you passMDMS_V2_CONTEXT_PATH, which routes to the wrong service and leaves the dropdown empty. Fall back toMDMS_V1_CONTEXT_PATH(oregov-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
fieldTypequery param is absent (default flow), you never fetchFieldTypeMappingConfig, so the ADD FIELD dropdown receivesundefinedoptions. 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.
setLocalizationDataruns immediately afterfetchLocalization, butlocalizationDatastill holds stale state, so you populate the localisation store with an empty array. Await/unwrap the thunk or watchlocalizationStatus === "succeeded"before dispatchingsetLocalizationData.- 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.logcalls 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?.labeldirectly 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 therequired=nullrelaxation, and coercing booleans to""breaks checkbox rendering. Reuse thefieldTypeyou 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: PassprojectTypeinto LocalizedHeaderField and sanitise the fallback key.
projectTypeis currently an undeclared variable inside this memo, so any header lacking a pre-existing localisation code throws aReferenceError. AcceptprojectTypevia 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: PreferuseLocationover the globallocation.Directly touching the global
locationbreaks in SSR/tests. Useconst { search } = useLocation(); const searchParams = new URLSearchParams(search || "");to stay router-safe.
137-139: Adddispatchto theuseCallbackdependency 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), dispatchupdateLocalizationEntrywith that code, and when the card had no code, also callhandleUpdateHeaderField/handleUpdateActionLabelwith 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"}
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
Show resolved
Hide resolved
.../modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
Outdated
Show resolved
Hide resolved
.../modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
Fragmentimport is unused in this file.-import React, { Fragment } from "react"; +import React from "react";
9-9: Remove unused constant.
MODULE_CONSTANTSis 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 thetprop violates the Rules of Hooks. WhenAppPreviewcallst(someKey), it's actually invokinguseCustomT(someKey), which calls hooks (useSelector,useDispatch) conditionally or in a non-component context.Solution: Create a wrapper function at this component level that calls
useCustomTand 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,
useCustomTshould 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, andaria-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
locationcan break in SSR or test environments. UseuseLocationfrom 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
dispatchis 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.logwill 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 likefalseor0. 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
panelItemlacksbindTo,.includeswill 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
handleFieldChangeWithLocat 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. Addtype="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
⛔ Files ignored due to path filters (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/dummyConfig.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDraggableField.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.jshealth/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
ComponentToRenderfor field rendering.
6-9: LGTM: onFieldClick prop enables field interaction.The new
onFieldClickcallback 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.headerFieldsand 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
showPanelPropertiesprop. 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
selectFieldaction 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
typeprop, with proper value binding and change handling.
25-25: LGTM: Proper memoization.Using
React.memois appropriate for this presentational component to prevent unnecessary re-renders.
| const HeaderFieldWrapper = ({ label, type, value, onChange, currentCard }) => { | ||
| const { t } = useTranslation(); | ||
| const localizedValue = useCustomT(value ? value : `${currentCard?.flow}_${currentCard?.parent}_${currentCard?.name}_${label}`); | ||
|
|
There was a problem hiding this comment.
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.
...ackages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
Outdated
Show resolved
Hide resolved
| <div | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onToggle(); | ||
| }} | ||
| className="appConfigLabelField-toggleVisibility" | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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)
| <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", | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| <div | ||
| ref={componentRef} | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onSelectField(); | ||
| }} | ||
| className="app-config-field-wrapper" | ||
| style={{}} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
...ackages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.js
Show resolved
Hide resolved
...web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/SidePanelApp.js
Show resolved
Hide resolved
There was a problem hiding this 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
Fragmentimport is not used in this component.Apply this diff:
-import React, { Fragment } from "react"; +import React from "react";
9-10: Remove unused constant.
MODULE_CONSTANTSis 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).
useCustomTis a React hook (it callsuseDispatchanduseSelectorinternally), but you're passing it directly as thetprop toAppPreview. 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 acodeparameter 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
useCustomTinside a callback—it's a hook and must be called at the component's top level.The correct solution is to refactor
useCustomTto return a stable translation function instead of requiring the hook to be called per translation. ModifyuseCustomTinhooks/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
📒 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)
...packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
Outdated
Show resolved
Hide resolved
...packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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.
useCustomTat 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
codeparameter 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.bindTois 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.bindTois 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/helpers/fieldTypeHelpers.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/fieldTypeHelpers.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.jshealth/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
getFieldTypeFromMasterDatais deduplicated (by removing the local copy and importing fromsrc/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 returndefaultValuefor 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/nulland 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.jsshould 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
getFieldTypeFromMasterDataduplication is resolved, this import should reference the canonical version from../../../helpers/fieldTypeHelpersinstead 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.
...ampaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.js
Show resolved
Hide resolved
There was a problem hiding this 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.bindTois undefined or null, calling.includeswill throw a TypeError. Add a guard at the start of bothhandleFieldChangeandhandleFieldChangeWithLoc.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,
parseIntcan 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]), butbindTomay 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
typeattribute, buttons default totype="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
getFieldTypeFromMasterDataalready present inhealth/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
dummyFieldTypeConfigforFieldTypeMappingConfigwill 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
statusanderrorthat 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
statusByNameanderrorByNamekeyed 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
useMemoimproves this implementation, the hook signatureuseCustomT(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
useCustomTto match the pattern inuseCustomTranslate:
- Return a translator function instead of accepting
codeparam- Update
useFieldDataLabelto call the returned function- Migrate call sites to use
const t = useCustomT(); t(code)Option 2: Deprecate
useCustomT(code)in favor ofuseCustomTranslate()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.
currentDatais assigned ininitializeConfig(line 21) and used throughout the slice but is not declared ininitialState. This will cause undefined reference errors untilinitializeConfigis dispatched.initialState: { remoteData: null, + currentData: null, parentData: [],
19-22: Object aliasing creates mutation hazard.Both
remoteDataandcurrentDatapoint to the samedummyConfigobject. Mutations tocurrentDatawill corruptremoteData, 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.cardselsewhere). 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..incan traverse prototype properties, enabling prototype pollution attacks. Additionally, updating bothselectedFieldand the same object incurrentData.cardsis 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
preparecallback withnanoid:+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.
deleteFieldsplices 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-resolvedfieldTypevariable.Line 38 calls
getFieldTypeFromMasterData(field)again without thefieldTypeMasterDataparameter, causing it to return the default"text"instead of using the already-computedfieldTypefrom 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 || ""coercestrueto an empty string and converts0orfalseto"", 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 (
??) preserves0,false, and other falsy values while only defaultingnullorundefinedto"".
25-25: Apply internationalization to labels for MDMS fields.The
labelprop currently passes rawfield?.labelwithout translation. Whenfield?.isMdmsis 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'suseLocationfor SSR-safe routing.Accessing the global
locationobject directly can break in SSR and test environments. Import and useuseLocationfromreact-router-dominstead.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
⛔ Files ignored due to path filters (3)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullData.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldValueByPath.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldMasterSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/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
AppConfigurationWrapperwith the ReduxProvider, 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 stabletfunction- Memoizes
handleFieldClickwith 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:handleAddFielddependencies look correct.The
dispatchdependency is properly included in theuseCallbackarray. 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
defaultValuewhen intermediate values are0,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
== nullchecks bothnullandundefinedwhile preserving0,false, and""as valid intermediate values.Likely an incorrect or invalid review comment.
.../modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 missingcurrentDatatoinitialState.The
currentDataproperty is referenced throughout the reducers but is not initialized ininitialState. This will cause issues when selectors or components try to access it beforeinitializeConfigruns.Apply this diff:
initialState: { remoteData: null, + currentData: null, parentData: [], pageType: "object", // "object" or "template"
20-31: Avoid aliasingremoteDataandcurrentData.Lines 23-24 and 27-28 assign the same object reference to both
state.remoteDataandstate.currentData. This means mutations tocurrentDatawill affectremoteData, 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 indeleteField.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'spreparecallback pattern withnanoid()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), butcurrentDatais 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 inupdateSelectedField.Lines 64 and 70 use
for...inloops without checkinghasOwnProperty, allowing potentially dangerous keys like__proto__,constructor, orprototypeto 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
⛔ Files ignored due to path filters (1)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfig.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/utils/configUtils.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.jshealth/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
useCallbackforhandleFieldClick- Clean conditional rendering based on
pageType- No unused imports, props, or debug statements
| const AppConfigurationStore = lazyWithFallback( | ||
| () => import(/* webpackChunkName: "app-configuration-store" */ "./NewAppConfiguration/AppConfigurationStore"), | ||
| () => require("./NewAppConfiguration/AppConfigurationStore").default, | ||
| { loaderText: "Loading App Configuration..." } | ||
| ); |
There was a problem hiding this comment.
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.
...ges/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.js
Show resolved
Hide resolved
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
Outdated
Show resolved
Hide resolved
| <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)} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
...b/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.js
Outdated
Show resolved
Hide resolved
| <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", | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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)
| <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", | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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 }; |
There was a problem hiding this comment.
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
| if (!fullParentConfig || !fullParentConfig.flows || !flow || !pageName) { | ||
| console.warn("Invalid parameters for getPageFromConfig:", { fullParentConfig, flow, pageName }); | ||
| return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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
locationobject can break in SSR or test environments. Use react-router'suseLocationhook 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
ConditionalFieldas 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/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
dispatchin 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}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect value passed to updateHeaderField and sanitize localization key.
Two issues:
- Line 44: Unsanitized key generation creates malformed keys when
currentCardproperties are undefined. - Line 51: Passing
value: labelupdates 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.
| 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.
...es/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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: NormalizeFieldPropertiesPanelConfigresponse 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 runtimeundefined.The reducer registers
clearFieldPanelMasterData, but you exportclearFieldMasterData. Any importer will receiveundefinedand 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 useNumber(...)) so numeric fields behave predictably.- const value = parseInt(event.target.value) || 0; + const value = parseInt(event.target.value, 10) || 0;
333-385: Handle dottedbindTopaths 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 (getFieldValueByPathand a matching setter) so nested properties resolve correctly.
403-407: Add an explicit button type for accessibility.Without
type="button"the tab element defaults tosubmit, 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?.labeland forcesfalse/0to"", 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 computedfieldType, and use nullish coalescing sofalse/0survive.- 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 thevariationspelling.
"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()insideaddFieldmakes the reducer impure and non-deterministic. Use apreparecallback (withnanoidorcryptothere) so the ID is generated before the reducer runs.
20-37: Avoid aliasing config state and ensure the fallback shape is correct.
initializeConfigassigns the same object to bothremoteDataandcurrentData, so any edit to the working copy mutates the supposed “pristine” copy. On top of that,setCurrentDatafalls back to[], which breaks later.cardsreads. Clone the payload (ordummyConfig) for each state branch, and default tonull(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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/AppPreview.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.jshealth/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
nullas a fallback when the label is falsy improves predictability and null safety.
| <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> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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(); |
There was a problem hiding this comment.
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" -C3Length 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.
| value={ | ||
| isLocalisable | ||
| ? useT(drawerState?.[cField?.bindTo]) | ||
| : drawerState?.[cField?.bindTo] === true | ||
| ? "" | ||
| : drawerState?.[cField?.bindTo] | ||
| } |
There was a problem hiding this comment.
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.
| item?.name | ||
| ? item?.name | ||
| : `${projectType}_${state?.currentScreen?.parent}_${state?.currentScreen?.name}_${item?.code}`, |
There was a problem hiding this comment.
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).
...es/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.js
Show resolved
Hide resolved
There was a problem hiding this 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_CONFIGURATIONto 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
dummyFieldTypeConfigon 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
mdmsContextV1or 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
fieldTypevariable. Line 42 coerces booleans to empty strings and uses||which discards0/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_PATHbut 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
nullcausesTypeError: Cannot convert undefined or null to objectwhen 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
getPageFromConfigreturnsnull, 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.
setLocalizationDatais dispatched immediately afterfetchLocalizationstarts, 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
fieldTypeDropdowncase, 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 tosubmit, 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
clearFieldPanelMasterDatabut you exportclearFieldMasterData, which will beundefinedat 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/CampaignCard.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/PanelFieldDisplay.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.jshealth/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)
| useEffect(() => { | ||
| sessionStorage.removeItem("HCM_SELECTED_TAB_INDEX"); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
...s/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.js
Outdated
Show resolved
Hide resolved
| <div | ||
| ref={switchRef} | ||
| className="drawer-container-tooltip" | ||
| onMouseEnter={() => setShowTooltip(true)} | ||
| onMouseLeave={() => setShowTooltip(false)} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| <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", | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| 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> | ||
| ); |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this 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
⛔ Files ignored due to path filters (2)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfig.jsonis 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
useCustomTranslatehookhandleFieldClickcorrectly memoized withuseCallback- Clear conditional rendering based on
pageType- Appropriate use of
React.memofor performance optimizationAll previously flagged issues from past reviews have been successfully addressed.
...packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 (
useCustomTat line 47,useEffectat lines 50 and 59,useCallbackat 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 theisFieldVisible()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
useRefanduseStatehooks 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
RenderFieldalong with the other hooks, and guard their usage conditionally insiderenderMainField: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 hooksThen 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
constdeclarations 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 whenfieldKeyis absent we dispatchvalue: 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 bothupdateHeaderPropertyandupdateHeaderField.- 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 triggerhandleFlowClickon Enter/Space (prevent default on Space). Includearia-selected={selectedFlow === flow.id}to expose state.
Based on static analysis hints
269-276: Make page tabs keyboard accessible.
divtabs fireonClickonly. Addrole="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 addrole="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>oraria-hidden="true"to satisfy the a11y requirement.
Based on static analysis hintshealth/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.jsexportsstoreviaexport const store = ..., so the default import here resolves toundefined, 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 SVGThis 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 viaaria-labelledby, or mark the icon decorative witharia-hidden="true"if that’s the intent.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (7)
health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/dummyFieldTypeConfigSchema.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fieldPanelPropertiesSchema.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfigSchema.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/fullParentConfigSkull.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/newFullParentConfig.jsonis excluded by!**/*.jsonhealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/configs/newFullParentConfigSchema.jsonis 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/InfoCardTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/PanelCardTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/HeaderFieldWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/ButtonTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getComponentFromMasterData.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewAppFieldScreenWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/Module.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/CardTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationStore.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/TagTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/components/ComponentToRender.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SearchBar.jshealth/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.
| required={getFieldTypeFromMasterData(field) === "custom" ? null : field?.required} | ||
| type={fieldType} | ||
| value={field?.value === true ? "" : field?.value || ""} | ||
| disabled={field?.readOnly || false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
...packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.js
Outdated
Show resolved
Hide resolved
| // 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, | ||
| }) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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, | ||
| })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| /** | ||
| * 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; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| /** | |
| * 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.
| className={"app-config-drawer-button"} | ||
| type={"button"} | ||
| size={"medium"} | ||
| icon={"AddIcon"} | ||
| variation={"teritiary"} | ||
| label={t("ADD_FIELD")} | ||
| onClick={() => handleAddField(currentCard, card[index])} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| export const { clearFieldMasterData } = fieldPanelMasterSlice.actions; | ||
| export default fieldPanelMasterSlice.reducer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| setCurrentData(state, action) { | ||
| state.currentData = action.payload || []; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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:
Line 45: Calling
getFieldTypeFromMasterData(field)without thefieldTypeMasterDataparameter forces the default return value"text", causingrequiredto be incorrectly computed for custom/component types. Use the already-resolvedfieldTypevariable.Line 47: Converting
trueto""and using||coercesfalse/0to"", 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
isMdmstranslation logic and is shown for all field types. Align with previous feedback: translate MDMS labels viat()and suppress labels forcustom/checkbox/buttontypes.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
useCustomTto return a stable translator function (following the pattern inappConfigurationRedesign/useCustomT.js). Instead of creating a separateuseCustomTranslatehook, consolidate both by accepting an optionalmaybeCodeparameter:
- 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
appConfigurationRedesignpattern:+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
useCustomTto 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: addcurrentData, stabilize popup state, and consider dropping redundant flags.
currentDatais missing ininitialStatebut used throughout. Add it to avoid undefined shape issues.- Keep
showAddFieldPopuptype stable. Prefer boolean flag + separate context object.isFieldSelectedis 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
addFieldContextchange, updatehandleShowAddFieldPopupaccordingly (see separate comment). Do not forget to replace UI uses ofisFieldSelected && selectedFieldwith!!selectedField.
Run to check external usage:#!/bin/bash rg -n --glob '!**/dist/**' '\bisFieldSelected\b|showAddFieldPopup\.(currentCard|card)'
21-34: Avoid aliasingremoteDataandcurrentData; deep‑clone on init.Both are set to the same object, so edits to
currentDatamutateremoteData. 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:setCurrentDatashould not default to an array.Downstream reducers expect an object with
body, etc. Default tonull(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..inwithObject.entriesand skip dangerous keys.- Update via the stored path; then rebind
selectedFieldto 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 === lenand 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.
showAddFieldPopupflips between boolean and object. Use a boolean flag and move payload toaddFieldContext.- 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 RTKpreparewithnanoid.Random IDs inside reducers hinder determinism/time‑travel. Use a
preparecallback and also setorder.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
idon cards to simplify selection/update flows (can be done later with aprepare).- 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: isparentDatastill used? If not, remove to reduce state surface.If unused, delete it from
initialStateand clean up selectors/usages.Quick check:
#!/bin/bash rg -n --glob '!**/dist/**' '\bparentData\b' -C2health/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,useStatearen’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.jsBased 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/transformers/mdmsToAppConfig.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/remoteConfigSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/IntermediateWrapper.jshealth/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.jshealth/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
fieldTypeMappingConfigextraction 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| // 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]); |
There was a problem hiding this comment.
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.
| const locVal = newFieldType?.label | ||
| ? newFieldType?.label | ||
| : `${campaignNumber}_${flow}_${pageName}_${crypto.randomUUID()}`.toUpperCase(); | ||
|
|
There was a problem hiding this comment.
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.
| onOverlayClick={() => {}} | ||
| onClose={() => { | ||
| dispatch(handleShowAddFieldPopup(null)); | ||
| setNewFieldType(null); | ||
| }} |
There was a problem hiding this comment.
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.
| <TextInput | ||
| name="fieldLabel" | ||
| value={fieldDataLabel} | ||
| placeholder={t("ENTER_FIELD_LABEL")} | ||
| onChange={(event) => handleFieldChange(event.target.value)} | ||
| /> |
There was a problem hiding this comment.
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.
| 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] | ||
| ); | ||
| }; |
There was a problem hiding this comment.
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.
...ackages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.js
Outdated
Show resolved
Hide resolved
| 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; | ||
| }, |
There was a problem hiding this comment.
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.
| 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) | ||
| }) | ||
| }; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| export const transformMdmsToAppConfig = (fullData) => { | ||
| if (!fullData || !Array.isArray(fullData) || fullData.length === 0) { | ||
| throw new Error("Invalid fullData: Expected non-empty array"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]>
There was a problem hiding this 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> <pathhealth/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
getFieldTypeFromMasterDatain 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
constdeclarations 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/SwitchTemplate.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/components/Filter.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDependentFieldWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/store.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/helpers/getFieldTypeFromMasterData.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewLayoutRenderer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/NewDrawerFieldComposer.jshealth/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.valueis 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
SwitchTemplateby design:
searchByProximityandsearchByIDare 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.jsand active in deployment configsNo 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
fetchPageFieldsprevents redundant API calls, and theclearPageFieldsreducer 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
clearFlowPagesreducer provides a clean way to reset state when needed.
| 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> | ||
| ); |
There was a problem hiding this comment.
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.
| const SwitchTemplate = ({ field, t }) => ( | ||
| <Switch label={t(field?.label)} onToggle={() => {}} isCheckedInitially={field?.value || false} shapeOnOff /> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 }) { |
There was a problem hiding this comment.
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.
| 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> | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const moduleName = "HCM-ADMIN-CONSOLE"; | ||
| const masterName = "AppFlowConfig"; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
...ages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.js
Outdated
Show resolved
Hide resolved
| // 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 }) => { |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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.
AppConfigurationStoreis 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,previousRoutearen’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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/transformMdmsConfig.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/DummyLoader.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewCampaignCreate/CampaignDetails.jshealth/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
transformMdmsToAppConfigboth 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.
| {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} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
...ackages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/FullConfigWrapper.js
Show resolved
Hide resolved
| 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 | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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> |
There was a problem hiding this comment.
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}`, |
There was a problem hiding this comment.
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 catLength 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.
| 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.
| // 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Length of output: 115
🏁 Script executed:
#!/bin/bash
# Locate and cat the specific file mentioned in the review
fd -t f "transformMdmsConfig.js" | xargs catLength 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.
| 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, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
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.
| 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, | |
| }; | |
| }; |
There was a problem hiding this 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
📒 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)
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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 completesAdd 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
📒 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.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfiguration.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/flowPagesSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/hooks/useCustomT.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/AppConfigurationWrapper.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/localizationSlice.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/LayoutRenderer.jshealth/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/NewAppConfiguration/redux/fieldPanelPropertiesSlice.jshealth/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
useMemofor expensive computations (template extraction and registry building)- Clean registry pattern for mapping template formats to components
- Defensive
isFieldSelectedhelper 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.AppConfigCacheproperly embeds the schema in the path, matching the established pattern across similar update operations (useUpdateFormBuilderConfig,useUpdateAppConfig,useUpdateAppConfigForFeatures). The body structure with theMdmswrapper 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.
| // 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| // 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); |
| // 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 typeAlso 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.
| 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, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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`); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
| 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.
| // Fallback to dummy data on error | ||
| console.error("Failed to fetch from MDMS, using fallback:", err); | ||
| return dummyPanelConfig; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| url: "/mdms-v2/v2/_search", | ||
| body: { | ||
| MdmsCriteria: { | ||
| tenantId: tenantId, | ||
| schemaCode: `${moduleName}.${masterName}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"; | |||
There was a problem hiding this comment.
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.
| // 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"); | ||
| } | ||
| } | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.]+' -C2Length 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)
| 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; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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
📒 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.jshealth/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 defaultsscreenTypeto "TEMPLATE". The strict inequality check fornavigateToproperly 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
!== undefinedforminandmax(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
fullDatato handle nested arrays, but this is based on a misunderstanding of the data flow. In the actual call site (FullConfigWrapper.jsline 117),fullDatais constructed as:const fullData = response?.mdms && response?.mdms?.map((item) => item.data);This extracts a flat array of
.dataobjects fromresponse.mdms. By the timetransformMdmsToAppConfigreceivesfullData, 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"; | |||
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| if (configData.flows && configData.flows.length > 0) { | ||
| const firstFlow = configData.flows[0]; | ||
| setSelectedFlow(firstFlow.id); | ||
| setSelectedPageName(firstFlow.indexRoute || firstFlow.pages?.[0]?.name); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| fetchFlowConfig(); | ||
| }, [campaignNumber]); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| schemaCode: "HCM-ADMIN-CONSOLE.NewApkConfig", | ||
| filters: { | ||
| project: campaignNumber, | ||
| name: flowModule, | ||
| }, | ||
| isActive: true, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); |
| } else if (item.type === "object" && item.flow) { | ||
| // It's a form page - group pages by flow name | ||
| const flowName = item.flow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| if (item.onAction && (!forms[flowName].onAction || item.order >= forms[flowName].lastOrder)) { | ||
| forms[flowName].onAction = item.onAction; | ||
| forms[flowName].lastOrder = item.order; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; | ||
| }; |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
📒 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}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| const fieldTypeConfig = fieldTypeMasterData.find( | ||
| (item) => item?.metadata?.format === field?.format | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| if (sectionName === "body") { | ||
| // Body has nested structure: body[0].fields[] | ||
|
|
||
| fieldsToRender = section; | ||
|
|
||
| } else { | ||
| // Footer has direct structure: footer[] | ||
| fieldsToRender = section; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.jsLength 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.
| 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}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| <div style={{ | ||
| position: "absolute", | ||
| bottom: 60, | ||
| left: 0, | ||
| right: 0, | ||
| backgroundColor: "#fff", | ||
| borderTop: "1px solid #e0e0e0", | ||
| display: "flex", | ||
| flexDirection: "column", | ||
| gap: "8px" | ||
| }}> |
There was a problem hiding this comment.
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.
Choose the appropriate template for your PR:
Feature/Bugfix Request
JIRA ID
Module
Description
Summary by CodeRabbit
New Features
UI/UX Improvements