diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/AppPreview.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/AppPreview.js index a87d726fc39..0ca062574d7 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/AppPreview.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/components/AppPreview.js @@ -137,7 +137,7 @@ const getFieldType = (field) => { return "text"; case "number": return "number"; - case "textarea": + case "textArea": return "textarea"; case "time": return "time"; @@ -232,6 +232,8 @@ const AppPreview = ({ data = {}, selectedField, t }) => { 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 @@ -239,7 +241,7 @@ const AppPreview = ({ data = {}, selectedField, t }) => { : selectedField?.id && selectedField?.id === field?.id ? `app-preview-selected` : `` - }`, + } ${field?.["toArray.required"] && getFieldType(field) !== "custom" ? `required` : ``}`, mdmsConfig: field?.isMdms ? { moduleName: field?.schemaCode?.split(".")[0], @@ -253,7 +255,6 @@ const AppPreview = ({ data = {}, selectedField, t }) => { ? renderField(field, t) : null, }} - required={getFieldType(field) === "custom" ? null : field?.["toArray.required"]} type={getFieldType(field) === "button" || getFieldType(field) === "select" ? "custom" : getFieldType(field) || "text"} value={field?.value === true ? "" : field?.value || ""} disabled={field?.readOnly || false} diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationParentLayer.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationParentLayer.js index feb69b21809..5d9c2426f38 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationParentLayer.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationParentLayer.js @@ -66,16 +66,6 @@ const AppConfigurationParentRedesign = ({ const [localeModule, setLocaleModule] = useState(null); const [changeLoader, setChangeLoader] = useState(false); - useEffect(() => { - if (currentStep === parentState?.currentTemplate?.length) { - const event = new CustomEvent("lastButtonDisabled", { detail: true }); - window.dispatchEvent(event); - } else { - const event = new CustomEvent("lastButtonDisabled", { detail: false }); - window.dispatchEvent(event); - } - }, [currentStep, parentState]); - useEffect(() => { const handleResetStep = () => { setCurrentStep(1); @@ -100,7 +90,7 @@ const AppConfigurationParentRedesign = ({ const { isLoading: isLoadingAppConfigMdmsData, data: AppConfigMdmsData } = Digit.Hooks.useCustomMDMS( Digit.ULBService.getCurrentTenantId(), MODULE_CONSTANTS, - [{ name: fieldTypeMaster, limit: 100 }], + [{ name: fieldTypeMaster, limit: 1000 }], { cacheTime: Infinity, staleTime: Infinity, @@ -136,7 +126,7 @@ const AppConfigurationParentRedesign = ({ }, }; - const { isLoading: isCacheLoading, data: cacheData, refetch: refetchCache, revalidate } = Digit.Hooks.useCustomAPIHook(reqCriteriaForm); + const { isLoading: isCacheLoading, data: cacheData, refetch: refetchCache } = Digit.Hooks.useCustomAPIHook(reqCriteriaForm); const { mutate: updateMutate } = Digit.Hooks.campaign.useUpdateAppConfig(tenantId); @@ -167,6 +157,7 @@ const AppConfigurationParentRedesign = ({ formId && AppConfigMdmsData?.[fieldTypeMaster]?.length > 0 ) { + const fieldTypeMasterData = AppConfigMdmsData?.[fieldTypeMaster] || []; const temp = restructure(formData?.data?.pages, fieldTypeMasterData, formData?.data); parentDispatch({ @@ -187,19 +178,86 @@ const AppConfigurationParentRedesign = ({ ); }, [parentState?.currentTemplate]); + const currentTabPages = React.useMemo(() => { + const activeParent = numberTabs.find((j) => j.active)?.parent; + return (parentState?.currentTemplate || []) + .filter((i) => i.parent === activeParent) + .sort((a, b) => Number(a.order) - Number(b.order)); + }, [parentState?.currentTemplate, numberTabs]); + + useEffect(() => { + const last = currentTabPages.length + ? Number(currentTabPages[currentTabPages.length - 1].order) + : null; + + const isLast = last != null && Math.abs(Number(currentStep) - last) < 1e-6; + + window.dispatchEvent(new CustomEvent("lastButtonDisabled", { detail: isLast })); +}, [currentStep, currentTabPages]); + + + // Build the ordered list of valid steps once. + // 👉 Replace p.step / p.order / p.name with whatever your source-of-truth field is. + const availableSteps = React.useMemo(() => { + const raw = (parentState?.steps + || parentState?.stepOrder + || (parentState?.currentTemplate || []).map((p) => p?.step || p?.order || p?.name) + ); + + return (raw || []) + .map((x) => parseFloat(String(x))) + .filter((n) => Number.isFinite(n)) + .sort((a, b) => a - b); + }, [parentState]); + + const round1 = (n) => Number(n.toFixed(1)); + + const nextStepFrom = (current) => { + const cur = Number(current); + + // Prefer the next known step from the canonical list + if (availableSteps.length) { + const next = availableSteps.find((s) => s > cur + 1e-9); + if (next != null) return round1(next); + } + + // Fallbacks if no canonical "next" exists: + // - if we're on an integer, try the nearest .1 + // - otherwise jump to next integer + const frac10 = Math.round((cur - Math.floor(cur)) * 10); + if (frac10 === 0) return round1(cur + 0.1); + return Math.floor(cur) + 1; + }; + + const prevStepFrom = (current) => { + const cur = Number(current); + let prev = null; + for (const s of availableSteps) { + if (s < cur - 1e-9) prev = s; else break; + } + return prev != null ? round1(prev) : cur; + }; + useEffect(() => { setStepper( - (parentState?.currentTemplate || []) - ?.filter((i) => i.parent === numberTabs.find((j) => j.active)?.parent) - .sort((a, b) => a.order - b.order) - ?.map((k, j, t) => ({ - name: k.name, - isLast: j === t.length - 1 ? true : false, - isFirst: j === 0 ? true : false, - active: j === currentStep - 1 ? true : false, - })) + currentTabPages.map((k, j, t) => ({ + name: k.name, + isLast: j === t.length - 1, + isFirst: j === 0, + // active by exact order match (works for 4.1, 4.2, …) + active: Number(k.order) === Number(currentStep), + })) ); - }, [parentState?.currentTemplate, numberTabs, currentStep]); + }, [currentTabPages, currentStep]); + + const mainPagesCount = React.useMemo(() => { + const ints = new Set(); + for (const p of currentTabPages) { + const n = parseFloat(String(p?.order || p?.step || p?.name)); + if (Number.isFinite(n)) ints.add(Math.floor(n)); + } + return ints.size; + }, [currentTabPages]); useEffect(() => { if (variant === "app" && parentState?.currentTemplate?.length > 0 && currentStep && numberTabs?.length > 0) { @@ -210,11 +268,13 @@ const AppConfigurationParentRedesign = ({ } }, [parentState?.currentTemplate, currentStep, numberTabs]); + if (isCacheLoading || isLoadingAppConfigMdmsData || !parentState?.currentTemplate || parentState?.currentTemplate?.length === 0) { return ; } const submit = async (screenData, finalSubmit, tabChange) => { + parentDispatch({ key: "SETBACK", data: screenData, @@ -233,15 +293,15 @@ const AppConfigurationParentRedesign = ({ const reverseFormat = cacheData && cacheData?.filteredCache?.data?.data ? { - ...parentState?.actualTemplate?.actualTemplate, - version: parentState?.actualTemplate?.version + 1, - pages: reverseData, - } + ...parentState?.actualTemplate?.actualTemplate, + version: parentState?.actualTemplate?.version + 1, + pages: reverseData, + } : { - ...parentState?.actualTemplate, - version: parentState?.actualTemplate?.version + 1, - pages: reverseData, - }; + ...parentState?.actualTemplate, + version: parentState?.actualTemplate?.version + 1, + pages: reverseData, + }; const updatedFormData = { ...formData, data: reverseFormat }; @@ -340,15 +400,15 @@ const AppConfigurationParentRedesign = ({ const reverseFormat = cacheData && cacheData?.filteredCache?.data?.data ? { - ...parentState?.actualTemplate?.actualTemplate, - version: parentState?.actualTemplate?.version + 1, - pages: reverseData, - } + ...parentState?.actualTemplate?.actualTemplate, + version: parentState?.actualTemplate?.version + 1, + pages: reverseData, + } : { - ...parentState?.actualTemplate, - version: parentState?.actualTemplate?.version + 1, - pages: reverseData, - }; + ...parentState?.actualTemplate, + version: parentState?.actualTemplate?.version + 1, + pages: reverseData, + }; const updatedFormData = { ...formData, data: reverseFormat }; @@ -392,7 +452,6 @@ const AppConfigurationParentRedesign = ({ setShowToast({ key: "success", label: "APP_CONFIGURATION_SUCCESS" }); setChangeLoader(false); revalidateForm(); - revalidate(); }, } ); @@ -426,21 +485,22 @@ const AppConfigurationParentRedesign = ({ }, } ); - setCurrentStep((prev) => prev + 1); + setCurrentStep((prev) => nextStepFrom(prev)); } }; const back = () => { - if (stepper?.find((i) => i.active)?.isFirst && isPreviousTabAvailable) { - tabStateDispatch({ key: "PREVIOUS_TAB" }); - setCurrentStep(1); - return; - } else if (stepper?.find((i) => i.active)?.isFirst && !isPreviousTabAvailable) { - setShowToast({ key: "error", label: "CANNOT_GO_BACK" }); - } else { - setCurrentStep((prev) => prev - 1); - } - }; + const activeStep = stepper?.find((i) => i.active); + if (activeStep?.isFirst && isPreviousTabAvailable) { + tabStateDispatch({ key: "PREVIOUS_TAB" }); + setCurrentStep(availableSteps[0] || 1); + return; + } else if (activeStep?.isFirst && !isPreviousTabAvailable) { + setShowToast({ key: "error", label: "CANNOT_GO_BACK" }); + } else { + setCurrentStep((prev) => prevStepFrom(prev)); + } +}; if (changeLoader) { return ; } @@ -479,7 +539,8 @@ const AppConfigurationParentRedesign = ({ parentDispatch={parentDispatch} AppConfigMdmsData={AppConfigMdmsData} localeModule={localeModule} - pageTag={`${t("CMN_PAGE")} ${currentStep} / ${stepper?.length}`} + parentState={parentState} + pageTag={`${t("CMN_PAGE")} ${currentStep} / ${mainPagesCount}`} /> diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js index fa26458f268..08f1fbf289c 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/campaign-manager/src/pages/employee/appConfigurationRedesign/AppConfigurationWrapper.js @@ -280,6 +280,30 @@ const reducer = (state = initialState, action, updateLocalization) => { }, ], }; + case "PATCH_PAGE_CONDITIONAL_NAV": { + const { pageName, data } = action; // data is the array from onConditionalNavigateChange + + const patchArray = (arr) => { + if (!Array.isArray(arr) || arr.length === 0) return arr; + + // If pageName is provided, try to patch by name + if (pageName) { + const idx = arr.findIndex((p) => p?.name === pageName); + if (idx !== -1) { + return arr.map((p, i) => (i === idx ? { ...p, conditionalNavigateTo: data } : p)); + } + } + + // Fallback: patch the first page (your “current page is first” invariant) + return arr; + }; + + return { + ...state, + screenConfig: patchArray(state.screenConfig), + screenData: patchArray(state.screenData), + }; + } default: return state; } @@ -287,7 +311,8 @@ const reducer = (state = initialState, action, updateLocalization) => { const MODULE_CONSTANTS = "HCM-ADMIN-CONSOLE"; -function AppConfigurationWrapper({ screenConfig, localeModule, pageTag }) { +function AppConfigurationWrapper({ screenConfig, localeModule, pageTag , parentState}) { + const useT = useCustomT(); const queryClient = useQueryClient(); const { locState, addMissingKey, updateLocalization, onSubmit, back, showBack, parentDispatch } = useAppLocalisationContext(); const [state, dispatch] = useReducer((state, action) => reducer(state, action, updateLocalization), initialState); @@ -298,7 +323,7 @@ function AppConfigurationWrapper({ screenConfig, localeModule, pageTag }) { const [popupData, setPopupData] = useState(null); const [addFieldData, setAddFieldData] = useState(null); const addFieldDataLabel = useMemo(() => { - return addFieldData?.label ? useCustomT(addFieldData?.label) : null; + return addFieldData?.label ? useT(addFieldData?.label) : null; }, [addFieldData]); const searchParams = new URLSearchParams(location.search); const fieldMasterName = searchParams.get("fieldType"); @@ -554,7 +579,7 @@ function AppConfigurationWrapper({ screenConfig, localeModule, pageTag }) { return ( {loading && } - +
+ +
onDelete(idx)} + style={{ display: "inline-flex", alignItems: "center", cursor: "pointer" }} + > + {SVG?.Delete ? ( + + ) : ( +
+ + + ); + + // ---------- UI ---------- + return ( + + {/* Title */} +
+

{displayLogicLabel}

+
+ + {/* Rules list with center-placed joiners */} +
+ {(!rules || rules.length === 0) ? ( +

{noLogicAddedLabel}

+ ) : ( + <> + {/* First condition row */} + + + {/* Subsequent conditions: joiner centered, then condition row */} + {rules.slice(1).map((r, i) => ( + + + + + ))} + + )} +
+ + {/* Add Logic button */} +
+
+ + {/* Single-condition editor popup */} + {showPopUp && draftRule && ( + +
+ +
+ {/* Join-with: only when adding and there is at least one existing rule */} + {editorIndex === "new" && rules.length > 0 && ( +
+ {joinWithLabel} +
+ + setDraftRule((prev) => ({ + ...prev, + joiner: { code: e.code, name: e.code === "||" ? "OR" : "AND" }, + })) + } + selected={draftRule.joiner} + /> +
+
+ )} + +
+ {/* Page */} +
+ +

{selectPageLabel}

+
+ + setDraftRule((prev) => ({ + ...prev, + selectedPage: e, + selectedField: {}, + comparisonType: {}, + fieldValue: "", + })) + } + selected={ + draftRule?.selectedPage?.code + ? pageOptions.find((p) => p.code === draftRule.selectedPage.code) || + draftRule.selectedPage + : draftRule.selectedPage + } + /> +
+
+
+ + {/* Field */} +
+ +

{selectFieldLabel}

+
+ { + const nextOps = getOperatorOptions(e); + const canKeep = + draftRule?.comparisonType?.code && + nextOps.some((o) => o.code === draftRule.comparisonType.code); + + const isCk = isCheckboxField(e); + setDraftRule((prev) => ({ + ...prev, + selectedField: e, + fieldValue: isCk + ? (["true", "false"].includes(String(prev.fieldValue).toLowerCase()) + ? prev.fieldValue + : "false") + : "", + // store the actual option object so Dropdown shows the label properly + comparisonType: canKeep + ? nextOps.find((o) => o.code === prev.comparisonType.code) + : (isCk ? nextOps.find((o) => o.code === "==") : {}), + })); + }} + selected={ + draftRule?.selectedField?.code + ? (draftRule?.selectedPage?.code ? getFieldOptions(draftRule.selectedPage.code) : []).find( + (f) => f.code === draftRule.selectedField.code + ) || draftRule.selectedField + : draftRule.selectedField + } + disabled={!draftRule?.selectedPage?.code} + /> +
+
+
+ + {/* Operator */} +
+ +

{comparisonTypeLabel}

+
+ {(() => { + const selectedFieldObj = + draftRule?.selectedPage?.code && draftRule?.selectedField?.code + ? getFieldOptions(draftRule.selectedPage.code).find( + (f) => f.code === draftRule.selectedField.code + ) + : null; + const operatorOptions = getOperatorOptions(selectedFieldObj); + const selectedOperator = getSelectedOperatorFromOptions( + selectedFieldObj, + draftRule?.comparisonType + ); + + return ( + setDraftRule((prev) => ({ ...prev, comparisonType: e }))} + disabled={!draftRule?.selectedField?.code} + selected={selectedOperator} + /> + ); + })()} +
+
+
+ + {/* Value */} +
+ +

{selectValueLabel}

+
+ {(() => { + const selectedFieldObj = + draftRule?.selectedPage?.code && draftRule?.selectedField?.code + ? getFieldOptions(draftRule.selectedPage.code).find( + (f) => f.code === draftRule.selectedField.code + ) + : null; + + if (selectedFieldObj && isCheckboxField(selectedFieldObj)) { + const boolVal = String(draftRule.fieldValue).toLowerCase() === "true"; + return ( + { + const checked = typeof v === "boolean" ? v : !!v?.target?.checked; + setDraftRule((prev) => ({ ...prev, fieldValue: checked ? "true" : "false" })); + }} + value={boolVal} + label={t(selectedFieldObj?.label) || selectedFieldObj?.label || ""} + isLabelFirst={false} + disabled={!draftRule?.selectedField?.code} + /> + ); + } + + if (selectedFieldObj && isDobLike(selectedFieldObj)) { + return ( + + setDraftRule((prev) => ({ + ...prev, + fieldValue: sanitizeIntegerInput(event.target.value), + })) + } + disabled={!draftRule?.selectedField?.code} + /> + ); + } + + if (selectedFieldObj && isDatePickerNotDob(selectedFieldObj)) { + const iso = toISOFromDDMMYYYY(draftRule.fieldValue); + return ( + + setDraftRule((prev) => ({ + ...prev, + fieldValue: toDDMMYYYY(event?.target?.value), + })) + } + disabled={!draftRule?.selectedField?.code} + /> + ); + } + + const isSelect = + selectedFieldObj && isSelectLike(selectedFieldObj); + + if (isSelect) { + if (Array.isArray(selectedFieldObj.enums) && selectedFieldObj.enums.length > 0) { + const enumOptions = selectedFieldObj.enums.map((en) => ({ + code: String(en.code), + name: en.name, + })); + const selectedEnum = + enumOptions.find((o) => String(o.code) === String(draftRule.fieldValue)) || + (draftRule.fieldValue + ? { code: String(draftRule.fieldValue), name: String(draftRule.fieldValue) } + : undefined); + return ( + + setDraftRule((prev) => ({ ...prev, fieldValue: e.code })) + } + disabled={!draftRule?.selectedField?.code} + selected={selectedEnum} + /> + ); + } + if (selectedFieldObj.schemaCode) { + return ( + setDraftRule((prev) => ({ ...prev, fieldValue: code }))} + t={useT} + /> + ); + } + return ( + + setDraftRule((prev) => ({ ...prev, fieldValue: event.target.value })) + } + disabled={!draftRule?.selectedField?.code} + /> + ); + } + + const numericValue = isNumericField(selectedFieldObj); + return ( + { + const raw = event.target.value; + const next = numericValue ? sanitizeIntegerInput(raw) : raw; + setDraftRule((prev) => ({ ...prev, fieldValue: next })); + }} + disabled={!draftRule?.selectedField?.code} + /> + ); + })()} +
+
+
+ + {/* Per-condition inline helper */} + {validationStarted && !isRuleComplete(draftRule) && ( +
+ {completeAllMsg} + {SVG?.Close ? ( + { + setGlobalFormError(null); + setValidationStarted(false); + }} + style={{ cursor: "pointer" }} + /> + ) : ( + { + setGlobalFormError(null); + setValidationStarted(false); + }} + > + Ă— + + )} +
+ )} +
+
+ +
, + ]} + onOverlayClick={discardAndCloseEditor} + onClose={discardAndCloseEditor} + equalWidthButtons={false} + footerChildren={[ +