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: } /> } /> } /> + } /> } /> } /> } />