diff --git a/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/UpdateBoundaryForm.js b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/UpdateBoundaryForm.js
new file mode 100644
index 00000000000..deee96fd74d
--- /dev/null
+++ b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/UpdateBoundaryForm.js
@@ -0,0 +1,336 @@
+import React, { useState, useEffect } from "react";
+import { TextInput, Dropdown, Button } from "@egovernments/digit-ui-components";
+import { useTranslation } from "react-i18next";
+
+const UpdateBoundaryForm = ({ node, allBoundaries, onClose, onSave, hierarchyType }) => {
+ const { t } = useTranslation();
+ console.log("node.code:", node.code);
+ console.log("t(node.code):", t(node.code));
+ const [code, setCode] = useState(node.code || "");
+ const [msg, setMsg] = useState(t(node.code) || "");
+ const [selectedParent, setSelectedParent] = useState(null);
+ const [currentParent, setCurrentParent] = useState(null);
+ const [possibleParents, setPossibleParents] = useState([]);
+
+ console.log("Current node:", node);
+ console.log("All Boundaries:", allBoundaries);
+
+ useEffect(() => {
+ if (!node || !allBoundaries || allBoundaries.length === 0) return;
+
+ // Recursive function to find a node and its parent in the tree
+ const findNodeWithParent = (nodes, targetNode, parent = null) => {
+ for (const n of nodes) {
+ if (n.id === targetNode.id || n.code === targetNode.code) {
+ return { node: n, parent };
+ }
+ if (n.children && n.children.length > 0) {
+ const result = findNodeWithParent(n.children, targetNode, n);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
+ // Recursive function to collect all nodes at a specific boundary type
+ const collectNodesAtLevel = (nodes, boundaryType, collected = []) => {
+ for (const n of nodes) {
+ if (n.boundaryType === boundaryType) {
+ collected.push(n);
+ }
+ if (n.children && n.children.length > 0) {
+ collectNodesAtLevel(n.children, boundaryType, collected);
+ }
+ }
+ return collected;
+ };
+
+ // Find the current node and its parent
+ const result = findNodeWithParent(allBoundaries, node);
+ console.log("Found node with parent:", result);
+
+ if (result && result.parent) {
+ const currentParent = result.parent;
+ setCurrentParent(currentParent);
+ console.log("Current parent:", currentParent);
+
+ // Find ALL nodes that have the same boundary type as the current parent
+ // These are all possible parents for the current node
+ const allPossibleParents = collectNodesAtLevel(allBoundaries, currentParent.boundaryType);
+
+ console.log("All possible parents at level:", currentParent.boundaryType, allPossibleParents);
+
+ // Create dropdown options from all possible parents
+ const parentOptions = allPossibleParents.map(p => ({
+ name: `${p.code} (${t(p.boundaryType)})`,
+ code: p.code,
+ node: p
+ }));
+
+ console.log("Parent options created:", parentOptions);
+ setPossibleParents(parentOptions);
+
+ // Set initial selected parent to current parent
+ const currentParentOption = parentOptions.find(
+ opt => opt.code === currentParent.code
+ );
+ setSelectedParent(currentParentOption || null);
+ } else if (!result?.parent) {
+ // Node is at root level, no parent to change
+ console.log("Node is at root level, no parent available");
+ setPossibleParents([]);
+ }
+ }, [node, allBoundaries, t]);
+
+ // const handleSave = async () => {
+ // if (!selectedParent) {
+ // alert(t("HCM_BOUNDARY_SELECT_PARENT_ERROR"));
+ // return;
+ // }
+
+ // try {
+ // const localizationPayload = {
+ // tenantId: node.tenantId || "dev",
+ // messages: [
+ // {
+ // code: code,
+ // message: msg, // You can customize this message
+ // module: `hcm-boundary-${(hierarchyType || "ADMIN").toLowerCase()}`,
+ // locale: "en_IN"
+ // }
+ // ]
+ // };
+
+ // console.log("Calling Localization API with payload:", localizationPayload);
+
+ // const localizationResponse = await Digit.CustomService.getResponse({
+ // url: "/localization/messages/v1/_upsert",
+ // body: localizationPayload,
+ // });
+
+ // console.log("Localization API response:", localizationResponse);
+
+ // // Now update the boundary relationship with new parent
+ // const payload = {
+ // BoundaryRelationship: {
+ // tenantId: node.tenantId || "dev",
+ // code,
+ // boundaryType: node.boundaryType,
+ // hierarchyType: hierarchyType || "ADMIN",
+ // parent: selectedParent.code,
+ // },
+ // };
+
+ // console.log("Update payload:", payload);
+
+ // const response = await Digit.CustomService.getResponse({
+ // url: "/boundary-service/boundary-relationships/_update",
+ // body: payload,
+ // });
+
+ // console.log("Update response:", response);
+ // onSave({ ...node, code, parent: selectedParent.code });
+ // onClose();
+ // } catch (error) {
+ // console.error("Error updating boundary:", error);
+ // alert(t("HCM_BOUNDARY_UPDATE_ERROR") + ": " + error.message);
+ // }
+ // };
+
+ const handleSave = async () => {
+ console.log("cuurrent selectedParent:", selectedParent);
+ console.log("current parent:", currentParent);
+ if (!selectedParent) {
+ alert(t("HCM_BOUNDARY_SELECT_PARENT_ERROR"));
+ return;
+ }
+
+ try {
+ let localizationResponse = null;
+ let updateResponse = null;
+
+ // ✅ Call localization API only if msg changed
+ if (msg !== t(node.code)) {
+ const localizationPayload = {
+ tenantId: node.tenantId || "dev",
+ messages: [
+ {
+ code: code,
+ message: msg,
+ module: `hcm-boundary-${(hierarchyType || "ADMIN").toLowerCase()}`,
+ locale: "en_IN",
+ },
+ ],
+ };
+
+ console.log("Calling Localization API with payload:", localizationPayload);
+
+ localizationResponse = await Digit.CustomService.getResponse({
+ url: "/localization/messages/v1/_upsert",
+ body: localizationPayload,
+ });
+
+ console.log("Localization API response:", localizationResponse);
+ } else {
+ console.log("No change in msg, skipping Localization API call");
+ }
+
+ // ✅ Call boundary update API only if parent changed
+ if (selectedParent.code !== currentParent.code) {
+ const payload = {
+ BoundaryRelationship: {
+ tenantId: node.tenantId || "dev",
+ code,
+ boundaryType: node.boundaryType,
+ hierarchyType: hierarchyType || "ADMIN",
+ parent: selectedParent.code,
+ },
+ };
+
+ console.log("Calling Update API with payload:", payload);
+
+ updateResponse = await Digit.CustomService.getResponse({
+ url: "/boundary-service/boundary-relationships/_update",
+ body: payload,
+ });
+
+ console.log("Update API response:", updateResponse);
+ } else {
+ console.log("No change in parent, skipping Update API call");
+ }
+
+ // ✅ Trigger onSave only if something actually changed
+ if (msg !== t(node.code) || selectedParent.code !== node.parent) {
+ onSave({ ...node, code, parent: selectedParent.code });
+ }
+
+ onClose();
+ } catch (error) {
+ console.error("Error updating boundary:", error);
+ alert(t("HCM_BOUNDARY_UPDATE_ERROR") + ": " + error.message);
+ }
+};
+
+
+ const handleCancel = () => {
+ onClose();
+ };
+
+ const deleteNode = async () => {
+ const confirmDelete = window.confirm(t("HCM_BOUNDARY_DELETE_CONFIRM") || "Are you sure you want to delete this node?");
+ if (!confirmDelete) return;
+
+ try {
+ const payload = {
+ BoundaryRelationship: {
+ tenantId: node.tenantId || "dev",
+ code,
+ boundaryType: node.boundaryType,
+ hierarchyType: hierarchyType || "ADMIN",
+ parent: null, // Remove parent
+ },
+ };
+
+ console.log("Delete node payload (parent=null):", payload);
+
+ const response = await Digit.CustomService.getResponse({
+ url: "/boundary-service/boundary-relationships/_update",
+ body: payload,
+ });
+
+ console.log("Delete node response:", response);
+
+ onSave({ ...node, code, parent: null });
+ onClose();
+ } catch (error) {
+ console.error("Error deleting node:", error);
+ alert(t("HCM_BOUNDARY_DELETE_ERROR") + ": " + error.message);
+ }
+};
+
+
+ return (
+
+
+
+ {t("HCM_BOUNDARY_CURRENT_NODE")}: {t(node.code)}
+
+
+ {t("HCM_BOUNDARY_TYPE")}: {t(node.boundaryType)}
+
+
+ {t("HCM_BOUNDARY_ID")}: {node.id}
+
+ {selectedParent && (
+
+ {t("HCM_BOUNDARY_CURRENT_PARENT")}: {t(selectedParent.code)}
+
+ )}
+
+
+
setMsg(e.target.value)}
+ required
+ />
+
+ {possibleParents.length > 0 ? (
+ <>
+ {
+ console.log("Selected value:", value);
+ const selected = typeof value === 'object' ? value : possibleParents.find(opt => opt.code === value);
+ setSelectedParent(selected);
+ }}
+ optionKey="code"
+ t={t}
+ />
+
+
+ {t("HCM_BOUNDARY_FOUND_PARENTS", {
+ count: possibleParents.length,
+ type: t(possibleParents[0]?.node?.boundaryType)
+ })}
+
+
+
+ {t("HCM_BOUNDARY_OPTIONS")}: {possibleParents.map(p => t(p.code)).join(", ")}
+
+
+ >
+ ) : (
+
+
+ {t("HCM_BOUNDARY_NO_PARENT_AVAILABLE")}
+
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default UpdateBoundaryForm;
\ No newline at end of file
diff --git a/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/ViewBoundaryV2.js b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/ViewBoundaryV2.js
new file mode 100644
index 00000000000..9dea3192b60
--- /dev/null
+++ b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/boundaryTree/ViewBoundaryV2.js
@@ -0,0 +1,342 @@
+import React, { useState, useEffect } from "react";
+import { useLocation } from "react-router-dom";
+import { Card, PopUp, Button } from "@egovernments/digit-ui-components";
+import { useTranslation } from "react-i18next";
+import UpdateBoundaryForm from "./UpdateBoundaryForm";
+
+// Recursive tree node with lazy loading
+const TreeNode = ({ node, hierarchyType, onUpdateClick, onExpand, onDataUpdate }) => {
+ const [expanded, setExpanded] = useState(false);
+ const [children, setChildren] = useState(node.children || []);
+ const [loading, setLoading] = useState(false);
+ const { t } = useTranslation();
+
+ if (!node) return null;
+
+ const handleToggle = async () => {
+ if (!expanded && node.hasChildren && (!children || children.length === 0)) {
+ setLoading(true);
+ try {
+ const childrenData = await onExpand(node);
+ console.log("Children fetched for", node.code, ":", childrenData);
+ setChildren(childrenData || []);
+ // Update the node's children in the main state
+ if (onDataUpdate) {
+ onDataUpdate(node, childrenData);
+ }
+ } catch (error) {
+ console.error("Error fetching children:", error);
+ } finally {
+ setLoading(false);
+ }
+ }
+ setExpanded(!expanded);
+ };
+
+ return (
+
+
+
+
+ {node.hasChildren && (
+
+ )}
+
+ {t(`${node.code}`)} ({node.boundaryType})
+
+
+
+
+ {expanded && children.length > 0 && (
+
+ {children.map((child) => (
+
+ ))}
+
+ )}
+
+ );
+};
+
+const BoundaryTree = ({ data, hierarchyType, onUpdateClick, onExpand, onDataUpdate }) => {
+ return (
+
+ {data?.map((node) => (
+
+ ))}
+
+ );
+};
+
+const ViewBoundaryV2 = () => {
+ const location = useLocation();
+ const searchParams = new URLSearchParams(location.search);
+ const boundaryType = searchParams.get("type");
+ const hierarchyType = searchParams.get("hierarchyType");
+
+ const [boundaryHierarchy, setBoundaryHierarchy] = useState([]);
+ const [rootBoundary, setRootBoundary] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // Popup state
+ const [popupOpen, setPopupOpen] = useState(false);
+ const [selectedNode, setSelectedNode] = useState(null);
+
+ const tenantId = window.localStorage.getItem("Employee.tenant-id")
+
+ // Fetch hierarchy definition first
+ useEffect(() => {
+ if (hierarchyType) {
+ fetchHierarchyDefinition();
+ }
+ }, [hierarchyType]);
+
+ // Fetch root level data after hierarchy is loaded
+ useEffect(() => {
+ if (boundaryHierarchy.length > 0) {
+ fetchRootBoundary();
+ }
+ }, [boundaryHierarchy]);
+
+ const fetchHierarchyDefinition = async () => {
+ try {
+ const response = await Digit.CustomService.getResponse({
+ url: "/boundary-service/boundary-hierarchy-definition/_search",
+ params: {},
+ body: {
+ BoundaryTypeHierarchySearchCriteria: {
+ tenantId: tenantId,
+ hierarchyType,
+ },
+ }
+ });
+
+ if (response?.BoundaryHierarchy?.[0]?.boundaryHierarchy) {
+ setBoundaryHierarchy(response.BoundaryHierarchy[0].boundaryHierarchy);
+ }
+ } catch (err) {
+ console.error("Error fetching hierarchy:", err);
+ setError("Failed to fetch hierarchy definition");
+ }
+ };
+
+ const fetchRootBoundary = async () => {
+ try {
+ setLoading(true);
+ const rootType = boundaryHierarchy[0]?.boundaryType;
+
+ if (!rootType) {
+ setError("No root boundary type found");
+ return;
+ }
+
+ console.log("Fetching root boundary of type:", rootType);
+
+ const response = await Digit.CustomService.getResponse({
+ url: "/boundary-service/boundary-relationships/_search",
+ params: {
+ boundaryType: rootType,
+ hierarchyType,
+ includeChildren: false,
+ tenantId: tenantId
+ },
+ body: {}
+ });
+
+ console.log("Root boundary response:", response);
+
+ if (response?.TenantBoundary?.[0]?.boundary) {
+ const boundaries = response.TenantBoundary[0].boundary.map(b => ({
+ ...b,
+ hasChildren: hasChildrenInHierarchy(b.boundaryType),
+ children: []
+ }));
+ setRootBoundary(boundaries);
+ }
+ } catch (err) {
+ console.error("Error fetching root boundary:", err);
+ setError("Failed to fetch boundaries");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const hasChildrenInHierarchy = (boundaryType) => {
+ const currentIndex = boundaryHierarchy.findIndex(h => h.boundaryType === boundaryType);
+ return currentIndex !== -1 && currentIndex < boundaryHierarchy.length - 1;
+ };
+
+ const getChildBoundaryType = (parentBoundaryType) => {
+ const currentIndex = boundaryHierarchy.findIndex(h => h.boundaryType === parentBoundaryType);
+ if (currentIndex !== -1 && currentIndex < boundaryHierarchy.length - 1) {
+ return boundaryHierarchy[currentIndex + 1].boundaryType;
+ }
+ return null;
+ };
+
+ // Recursive helper to find node in nested boundaries
+ const findNodeRecursive = (nodes, target) => {
+ for (const node of nodes) {
+ if (node.code === target.code || node.id === target.id) {
+ return node;
+ }
+ if (node.children?.length) {
+ const found = findNodeRecursive(node.children, target);
+ if (found) return found;
+ }
+ }
+ return null;
+ };
+
+ const fetchChildren = async (parentNode) => {
+ try {
+ const childBoundaryType = getChildBoundaryType(parentNode.boundaryType);
+ if (!childBoundaryType) return [];
+
+ const response = await Digit.CustomService.getResponse({
+ url: "/boundary-service/boundary-relationships/_search",
+ params: {
+ boundaryType: childBoundaryType,
+ hierarchyType,
+ includeChildren: false,
+ includeParents: parentNode.boundaryType !== "COUNTRY",
+ tenantId: tenantId,
+ },
+ body: {},
+ });
+
+ const boundaries = response?.TenantBoundary?.[0]?.boundary || [];
+
+ if (parentNode.boundaryType === "COUNTRY") {
+ return boundaries.map((child) => ({
+ ...child,
+ hasChildren: hasChildrenInHierarchy(child.boundaryType),
+ children: [],
+ }));
+ } else {
+ const foundNode = findNodeRecursive(boundaries, parentNode);
+
+ if (foundNode?.children) {
+ return foundNode.children.map((child) => ({
+ ...child,
+ hasChildren: hasChildrenInHierarchy(child.boundaryType),
+ children: [],
+ }));
+ }
+ }
+
+ return [];
+ } catch (error) {
+ console.error("Error fetching children for", parentNode.code, ":", error);
+ return [];
+ }
+ };
+
+ // Update node's children in the main state
+ const updateNodeChildren = (parentNode, children) => {
+ const updateRecursive = (nodes) => {
+ return nodes.map(node => {
+ if (node.id === parentNode.id) {
+ return { ...node, children };
+ }
+ if (node.children?.length) {
+ return { ...node, children: updateRecursive(node.children) };
+ }
+ return node;
+ });
+ };
+
+ setRootBoundary(prevBoundary => updateRecursive(prevBoundary));
+ };
+
+ const handleUpdateClick = (node) => {
+ setSelectedNode(node);
+ setPopupOpen(true);
+ };
+
+ const handleClose = () => {
+ setPopupOpen(false);
+ setSelectedNode(null);
+ };
+
+ const handleSave = (updatedNode) => {
+ console.log("Updated Node:", updatedNode);
+ // Refresh the tree or update the specific node
+ // You might want to refetch the data here
+ handleClose();
+ };
+
+ if (loading) return Loading boundaries...
;
+ if (error) return Error: {error}
;
+ if (!rootBoundary.length) return No boundaries found
;
+
+ return (
+
+
Boundary Explorer
+
+ Hierarchy: {hierarchyType} | Type: {boundaryType || "All"}
+
+
+
+ {popupOpen && selectedNode && (
+
+
+
+ )}
+
+ );
+};
+
+export default ViewBoundaryV2;
\ No newline at end of file
diff --git a/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
index c7d469725e6..f5d83d370ac 100644
--- a/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
+++ b/health/micro-ui/web/packages/modules/campaign-manager/src/pages/employee/index.js
@@ -50,6 +50,12 @@ const ViewBoundary = lazyWithFallback(
{ loaderText: "Loading View Boundary..." }
);
+const ViewBoundaryV2 = lazyWithFallback(
+ () => import(/* webpackChunkName: "view-boundary-v2" */ "./boundaryTree/ViewBoundaryV2"),
+ () => require("./boundaryTree/ViewBoundaryV2").default,
+ { loaderText: "Loading View Boundary V2..." }
+);
+
const ViewHierarchy = lazyWithFallback(
() => import(/* webpackChunkName: "view-hierarchy" */ "./ViewHierarchy"),
() => require("./ViewHierarchy").default,
@@ -343,6 +349,7 @@ const App = ({ path, BOUNDARY_HIERARCHY_TYPE: BoundaryHierarchy, hierarchyData:
} />
} />
} />
+ } />
} />
} />
} />