From abdc2874cf87f0395e2a746dfab6da1d4e123650 Mon Sep 17 00:00:00 2001 From: Swathi-eGov Date: Fri, 15 Nov 2024 19:40:34 +0530 Subject: [PATCH 1/4] added minor css changes and basic logic for geospatial view --- .../example/src/UICustomizations.js | 6 +- .../css/src/components/microplan.scss | 4 + .../css/src/components/microplanning.scss | 9 +- .../packages/css/src/index.scss | 3 +- .../css/src/pages/employee/mapview.scss | 304 +++++++ .../packages/modules/microplan/package.json | 9 +- .../packages/modules/microplan/src/Module.js | 14 + .../src/components/BaseMapSwitcher.js | 59 ++ .../src/components/BoundaryFilter.js | 198 +++++ .../src/components/ChoroplethSelection.js | 76 ++ .../src/components/CustomScaleControl.js | 41 + .../microplan/src/components/FilterSection.js | 81 ++ .../src/components/MapFilterIndex.js | 41 + .../src/components/MapViewComponent.js | 441 ++++++++++ .../microplan/src/components/fileData.js | 503 ++++++++++++ .../microplan/src/configs/UICustomizations.js | 6 +- .../microplan/src/configs/constants.js | 38 + .../src/pages/employee/ChooseActivity.js | 9 +- .../microplan/src/pages/employee/index.js | 3 + .../microplan/src/utils/mappingUtils.js | 762 ++++++++++++++++++ .../src/utils/processHierarchyAndData.js | 352 ++++++++ 21 files changed, 2944 insertions(+), 15 deletions(-) create mode 100644 health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/mapview.scss create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BaseMapSwitcher.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BoundaryFilter.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ChoroplethSelection.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/CustomScaleControl.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/FilterSection.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapFilterIndex.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapViewComponent.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/fileData.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/configs/constants.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/mappingUtils.js create mode 100644 health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/processHierarchyAndData.js diff --git a/health/micro-ui/web/micro-ui-internals/example/src/UICustomizations.js b/health/micro-ui/web/micro-ui-internals/example/src/UICustomizations.js index e5ba3cd51e5..4592c1e1e5e 100644 --- a/health/micro-ui/web/micro-ui-internals/example/src/UICustomizations.js +++ b/health/micro-ui/web/micro-ui-internals/example/src/UICustomizations.js @@ -980,7 +980,7 @@ export const UICustomizations = { return row.status === "EXECUTION_TO_BE_DONE" ? ( div:first-child { + white-space: normal; + } .__rdt_custom_sort_icon__{ display: flex; align-items: center; diff --git a/health/micro-ui/web/micro-ui-internals/packages/css/src/components/microplanning.scss b/health/micro-ui/web/micro-ui-internals/packages/css/src/components/microplanning.scss index 0c91156b430..6b894172d3f 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/css/src/components/microplanning.scss +++ b/health/micro-ui/web/micro-ui-internals/packages/css/src/components/microplanning.scss @@ -2686,7 +2686,10 @@ $border-color: rgba(214, 213, 212, 1); } .digit-card-component.primary.microPlanBulkTable { - max-width: 70vw !important; + overflow: hidden; + >div:first-child { + overflow: auto; + } } .loader-overlay { @@ -2760,12 +2763,12 @@ $border-color: rgba(214, 213, 212, 1); margin-top: 0.5rem; width: 14rem } - .digit-dropdown-employee-select-wrap{ + /*.digit-dropdown-employee-select-wrap{ width: 27rem; } .digit-text-input-field{ width: 27rem; - } + }*/ } .popUpClass.new-assumption-pop{ .digit-popup-footer{ diff --git a/health/micro-ui/web/micro-ui-internals/packages/css/src/index.scss b/health/micro-ui/web/micro-ui-internals/packages/css/src/index.scss index 40cf979617e..b2aa35877ec 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/css/src/index.scss +++ b/health/micro-ui/web/micro-ui-internals/packages/css/src/index.scss @@ -16,4 +16,5 @@ @import "./pages/employee/formulaConfig.scss"; @import "./pages/employee/microplanInbox.scss"; -@import "./pages/employee/checklist.scss"; \ No newline at end of file +@import "./pages/employee/checklist.scss"; +@import "./pages/employee/mapview.scss"; \ No newline at end of file diff --git a/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/mapview.scss b/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/mapview.scss new file mode 100644 index 00000000000..22614b4df67 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/css/src/pages/employee/mapview.scss @@ -0,0 +1,304 @@ +.leaflet-control-layers { + &.leaftlet-control { + border-radius: 0rem !important; + background-color: transparent !important; + border: none !important; + } +} + +.leaflet-bottom { + &.leaflet-left { + left: 1.5rem; + width: 32px; + height: 64px; + bottom: 88px; + + .leaflet-control { + margin-left: 0px !important; + + a { + color: theme(digitv2.lightTheme.primary) !important; + } + } + } +} + +.leaflet-bottom .leaflet-control { + margin-bottom: 0px !important; +} + +.map-container-main { + position: relative; + + .top-right-map-subcomponents { + position: absolute; + top: 1rem; + right: 1rem; + z-index: 550; + gap:1.5rem; + display: flex; + flex-direction: column; + align-items: flex-end !important; + } + + .bottom-left-map-subcomponents { + position: absolute; + bottom: 1.5rem; + left: 1.5rem; + z-index: 500; + + .north-arrow{ + width: 40px; + height: 40px; + } + + .custom-scale { + width: 7.5rem; + margin-left: 0rem; + text-align: center; + border-bottom: 0.063rem solid #FFFFFF; + padding: 0 0; + color: #ffffff; + font-family: Roboto; + font-size: 0.75rem; + font-weight: 400; + position: relative; + + .border-spikes { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 20%; + border-left: 0.063rem solid #ccc; + border-right: 0.063rem solid #ccc; + } + } + } + + .base-map-selector { + display: flex !important; + flex-direction: column; + align-items: flex-end !important; + position: relative; + border-radius: 0.25rem; + + .icon-first { + display: flex !important; + align-items: center; + margin-top: 0; + cursor: pointer; + box-shadow: none; + + .map-filter-layers { + @extend .typography.heading-s; + margin: 0; + padding: 0; + color: theme(digitv2.lightTheme.paper); + } + + .layer-icon { + margin-left: 0.5rem; + height: 1.667rem; + } + } + + .base-map-area-wrapper { + position: absolute; + top: 2rem; + right: 1rem; + background-color: theme(digitv2.lightTheme.paper); + border-radius: 0.25rem; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; + max-width: 27.5rem; + max-height: 9rem; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + @include custom-scrollbar; + } + + .base-map-area { + display: flex !important; + width: fit-content; + white-space: nowrap; + padding: 0.5rem; + gap: 0.5rem; + + + .base-map-entity { + display: flex !important; + flex-direction: column; + align-items: center; + justify-content: center; + width: 5.927rem !important; + cursor: pointer; + gap: 0.5rem; + + .base-map-img { + width: 5.927rem; + height: 5.839rem; + border: 0.063rem solid rgb(0, 0, 0, 0.1); + border-radius: 0.25rem; + object-fit: cover; + } + + p { + position: relative; + margin-left: auto; + margin-right: auto; + color: theme(digitv2.lightTheme.text-primary); + padding: 0; + margin: 0; + font-family: Roboto; + font-size: 12px; + font-weight: 400; + line-height: 14.06px; + text-align: center; + text-underline-position: from-font; + text-decoration-skip-ink: none; + } + } + + .selected { + background-color: rgba(255, 255, 255, 1); + + .base-map-img { + border: 0.125rem solid theme(digitv2.lightTheme.primary) !important; + } + + p { + @extend .typography.heading-xs; + color: theme(digitv2.lightTheme.primary) !important; + font-size: 0.75rem; + } + } + } + } + + .filter-section, + .choropleth-section { + /*width: 14.438rem;*/ + display: flex !important; + flex-direction: column; + + .icon-rest { + display: flex !important; + align-items: center; + width: fit-content; + align-self: flex-end; + justify-content: flex-end; + cursor: pointer; + + p { + margin: 0; + padding: 0; + @extend .typography.heading-s; + margin: 0; + padding: 0; + color: theme(digitv2.lightTheme.paper); + } + + .filter-icon { + margin-left: 0.5rem; + height: 1.667rem; + } + } + + .filter-section-option-wrapper, + .choropleth-section-option-wrapper { + position: absolute; + /*margin-top: 2rem;*/ + max-height: 12.19rem; + /*width: 14.438rem;*/ + + .custom-box-wrapper { + display: block; + max-height: 9.752rem; + overflow-x: auto; + background-color: rgba(255, 255, 255, 1); + + @include custom-scrollbar; + + .custom-box { + padding: 0.7rem; + border-bottom: 0.063rem solid rgba(214, 213, 212, 1); + + .mainClassName { + margin-bottom: 0; + display: flex; + align-items: center; + } + + .inputWrapperClassName { + margin: 0; + padding: 0; + } + + .labelClassName { + margin: 0; + margin-right: auto; + } + + input:checked~.inputIconClassname { + border-color: $primary-theme-color; + } + + .inputIconClassname { + margin: 0; + } + + .inputClassName { + margin: 0; + } + } + } + + .button-primary { + background-color: rgba(250, 250, 250, 1) !important; + } + } + } + +} + +.map-filter-by-boundary { + display: flex; + flex-direction: column; + gap: 0.5rem; + position: absolute; + top: 48px; + z-index: 30; + left: 48px; + + .map-filter-boundary-selection { + &.display-none { + display: none; + } + + width: 17.5rem; + padding: 1rem; + + button{ + width: 100%; + } + + .map-header-section { + display: flex; + align-items: center; + gap: 0.5rem; + + .map-header-section-header { + @extend .typography.heading-m; + color: theme(digitv2.lightTheme.text-primary) + } + } + } +} + +.hierarchy-selection-element{ + display: flex; + flex-direction: column; + gap: 0.5rem; +} \ No newline at end of file diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/package.json b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/package.json index 16442a67efc..334060a6208 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/package.json +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/package.json @@ -31,7 +31,14 @@ "react-drag-drop-files": "^2.3.10", "@cyntler/react-doc-viewer": "1.10.3", "react-data-table-component": "7.6.2", - "styled-components": "5.0.0" + "styled-components": "5.0.0", + "leaflet": "^1.9.4", + "chroma-js": "^2.4.2", + "axios": "^1.6.8", + "exceljs": "^4.4.0", + "focus-trap-react": "^10.2.3", + "geojson-validation": "^1.0.2", + "jszip": "^3.10.1" }, "author": "Jagankumar ", "license": "MIT" diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/Module.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/Module.js index a3271d69782..caeb757fedb 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/Module.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/Module.js @@ -27,6 +27,13 @@ import UserAccessMgmt from "./components/UserAccessMgmt"; import UserAccessMgmtTableWrapper from "./components/UserAccessMgmtTableWrapper"; import { DataMgmtComponent } from "./components/DataMgmtComponent"; import { ShowMoreWrapper } from "./components/ShowMoreWrapper"; +import MapViewComponent from "./components/MapViewComponent"; +import BoundaryFilter from "./components/BoundaryFilter"; +import BaseMapSwitcher from "./components/BaseMapSwitcher"; +import CustomScaleControl from "./components/CustomScaleControl"; +import MapFilterIndex from "./components/MapFilterIndex"; +import FilterSection from "./components/FilterSection"; +import ChoroplethSelection from "./components/ChoroplethSelection"; export const MicroplanModule = ({ stateCode, userType, tenants }) => { const { path, url } = useRouteMatch(); @@ -91,6 +98,13 @@ const componentsToRegister = { UserAccessMgmtTableWrapper, DataMgmtComponent, ShowMoreWrapper, + MapViewComponent, + BoundaryFilter, + BaseMapSwitcher, + CustomScaleControl, + MapFilterIndex, + FilterSection, + ChoroplethSelection }; export const initMicroplanComponents = () => { diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BaseMapSwitcher.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BaseMapSwitcher.js new file mode 100644 index 00000000000..749800380fa --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BaseMapSwitcher.js @@ -0,0 +1,59 @@ +import "leaflet/dist/leaflet.css"; +import React from "react"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; + + +export const generatePreviewUrl = (baseMapUrl, center = [0, 0], zoom = 5) => { + const lon = Math.floor(((center[1] + 180) / 360) * Math.pow(0, zoom)); + const lat = Math.floor( + ((1 - Math.log(Math.tan((center[0] * Math.PI) / 180) + 1 / Math.cos((center[0] * Math.PI) / 180)) / Math.PI) / 2) * Math.pow(2, zoom) + ); + if (baseMapUrl) { + const updatedUrl = baseMapUrl.replace("{z}", zoom).replace("{x}", lat).replace("{y}", lon).replace("{s}", "a"); + return updatedUrl; + } + // Return a default preview URL or handle this case as needed + return "default-preview-url.jpg"; // todo +}; + + + +const BaseMapSwitcher = ({ baseMaps, showBaseMapSelector, setShowBaseMapSelector, handleBaseMapToggle, selectedBaseMapName, basemapRef, t }) => { + if (!baseMaps) return null; + return ( +
+
setShowBaseMapSelector((previous) => !previous)} + onKeyUp={() => setShowBaseMapSelector((previous) => !previous)} + tabIndex={0} + style={{display:"flex"}} + > +

{t("LAYERS")}

+
{DigitSvgs.Layers && }
+
+
+ {showBaseMapSelector && ( +
+ {Object.entries(baseMaps).map(([name, baseMap], index) => { + return ( +
+ {t("ERROR_LOADING_BASE_MAP")} handleBaseMapToggle(name)} + /> +

{t(name)}

+
+ ); + })} +
+ )} +
+
+ ); +}; + +export default BaseMapSwitcher; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BoundaryFilter.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BoundaryFilter.js new file mode 100644 index 00000000000..34b491ddb63 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/BoundaryFilter.js @@ -0,0 +1,198 @@ +import React, { memo, useEffect, useRef, useState } from "react"; +import L from "leaflet"; +import "leaflet/dist/leaflet.css"; +import { Card, Button, MultiSelectDropdown, TooltipWrapper, Tooltip } from "@egovernments/digit-ui-components"; +import { useTranslation } from "react-i18next"; +import { InfoIconOutline, CardLabel } from "@egovernments/digit-ui-react-components"; +// import { fetchDropdownValues } from "../utils/processHierarchyData"; +import { useMyContext } from "../utils/context"; + +export function checkTruthyKeys(obj) { + for (let key in obj) { + if (Object.hasOwn(obj, key)) { + if (obj[key] && !(Array.isArray(obj[key]) && obj[key].length === 0)) { + return true; + } + } + } + return false; +} + +export function generateHierarchyOptions(boundaryHierarchy, boundaryArray, hierarchyConfig) { + const { highestHierarchy } = hierarchyConfig; + + // Initialize the options object + const options = {}; + + // Create a map for easy lookup of boundaries by code + const boundaryMap = new Map(); + boundaryArray.forEach((boundary) => { + boundaryMap.set(boundary.code, boundary); + }); + + // Helper function to create formatted options + const createOptions = (parentCode, childType) => { + return boundaryArray + .filter((boundary) => boundary.type === childType && boundary.parent === parentCode) + .map((boundary) => ({ + code: `${parentCode}.${boundary.code}`, + name: boundary.name, + })); + }; + + // Process the highest level (Country) + options[highestHierarchy] = boundaryArray + .filter((boundary) => boundary.type === highestHierarchy) + .map((boundary) => ({ + code: boundary.code, + name: boundary.name, + })); + + // Process each hierarchy level from Province to Village + boundaryHierarchy.slice(1).forEach(({ boundaryType, parentBoundaryType }) => { + options[boundaryType] = boundaryArray + .filter((boundary) => boundary.type === parentBoundaryType) + .map((parentBoundary) => ({ + name: parentBoundary.name, + code: parentBoundary.code, + options: createOptions(parentBoundary.code, boundaryType), + })); + }); + + return options; +} + +const BoundaryFilter = ({ + t, + boundary, + setBoundary, + hierarchy, + isboundarySelectionSelected, + setIsboundarySelectionSelected, + boundarySelections, + setBoundarySelections, +}) => { + const [processedHierarchy, setProcessedHierarchy] = useState([]); + const itemRefs = useRef([]); + const [expandedIndex, setExpandedIndex] = useState(null); + const scrollContainerRef = useRef(null); + const [changedBoundaryType, setChangedBoundaryType] = useState(""); + const [isScrollable, setIsScrollable] = useState(false); + const { state, dispatch } = useMyContext(); + + const toggleExpand = (index) => { + setExpandedIndex(index === expandedIndex ? null : index); + }; + + // Filtering out dropdown values + useEffect(() => { + if (!boundary || !hierarchy) return; + + // First, generate the hierarchy options + const hierarchyOptions = generateHierarchyOptions(state?.boundaryHierarchy, boundary, state?.hierarchyConfig[0]); + + // Now, reorder processedHierarchy based on the generated options + const processedHierarchyTemp = reorderProcessedHierarchy(hierarchyOptions); + setProcessedHierarchy(processedHierarchyTemp); + // setIsLoading(false); + }, [boundary, hierarchy]); + + // Function to reorder processedHierarchy + const reorderProcessedHierarchy = (hierarchyOptions) => { + // Reorder based on hierarchyOptions, not on the state value directly + return state?.boundaryHierarchy.map((level) => ({ + boundaryType: level.boundaryType, + options: hierarchyOptions[level.boundaryType] || [], + })); + }; + + // Function to update boundarySelections + const handleSelect = (selectedOptions, boundaryType) => { + // Extract only the second element (item[1]) from each array in selectedOptions + const transformedOptions = selectedOptions.map((option) => option[1]); + + // Now use the transformed options in your logic + setBoundarySelections((prevSelections) => { + const selectionsArray = Array.isArray(prevSelections) ? prevSelections : []; + + // Find the index of the existing selection for this boundaryType + const index = selectionsArray.findIndex((item) => item.boundaryType === boundaryType); + + // Create a new selection object + const newSelection = { + boundaryType, + options: transformedOptions, // Use the extracted options + }; + + // If the boundaryType already exists, update it; otherwise, add a new entry + if (index !== -1) { + const updatedSelections = [...selectionsArray]; + updatedSelections[index] = newSelection; + return updatedSelections; + } else { + return [...selectionsArray, newSelection]; + } + }); + }; + + + const renderDropdowns = () => { + return processedHierarchy.map(({ boundaryType, options }) => ( +
+ {t(boundaryType)} + 5 ? { height: "13.75rem" } : {}} + type="multiselectdropdown" + t={t} + options={options} + optionsKey="name" + addSelectAllCheck={boundaryType === state?.hierarchyConfig[0]?.highestHierarchy ? undefined : true} + variant={boundaryType === state?.hierarchyConfig[0]?.highestHierarchy ? undefined : "nestedmultiselect"} + onSelect={(e) => handleSelect(e, boundaryType)} + /> +
+ )); + }; + + return ( +
+
+ ); +}; + +export default BoundaryFilter; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ChoroplethSelection.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ChoroplethSelection.js new file mode 100644 index 00000000000..0b66ec4faa0 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/ChoroplethSelection.js @@ -0,0 +1,76 @@ +// Importing necessary modules +import { Button, RadioButtons } from "@egovernments/digit-ui-components"; +import "leaflet/dist/leaflet.css"; +import React, { memo, useCallback } from "react"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; + +const ChoroplethSelection = memo( + ({ + choroplethProperties, + showChoroplethOptions, + showChoroplethOptionRef, + setShowChoroplethOptions, + choroplethProperty, + setChoroplethProperty, + t, + }) => { + const handleChange = useCallback( + (value) => { + setChoroplethProperty(value?.code); + }, + [choroplethProperties] + ); + + return ( +
+
setShowChoroplethOptions((previous) => !previous)} + onKeyUp={() => setShowChoroplethOptions((previous) => !previous)} + tabIndex={0} + style={{display:"flex"}} + > +

{t("VISUALIZATIONS")}

+
+ {DigitSvgs.FilterAlt && } +
+
+ {showChoroplethOptions && ( +
+
+ ({ name: item, id: item, code: item }))} + optionsKey="name" + onSelect={handleChange} + selectedOption={choroplethProperty} + /> +
+
+ )} +
+ ); + } +); + +export default ChoroplethSelection; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/CustomScaleControl.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/CustomScaleControl.js new file mode 100644 index 00000000000..f2deacbd46d --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/CustomScaleControl.js @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from "react"; + +const CustomScaleControl = ({ map }) => { + if (!map) return null; + const [scaleText, setScaleText] = useState(""); + // Function to calculate and update the scale text + const updateScale = () => { + // Calculate the scale based on the map's current zoom level + const maxWidthMeters = map.containerPointToLatLng([0, map.getSize().y]).distanceTo(map.containerPointToLatLng([100, map.getSize().y])); + const scale = maxWidthMeters / 1000; // Convert to kilometers + + // Format the scale text + const scaleTextData = scale < 1 ? `${Math.round(scale * 1000)} m` : `${Math.round(Math.round(scale.toFixed(0) / 10) * 10)} km`; + + // Update the scale text in the container element + setScaleText(scaleTextData); + }; + + // Effect to update the scale text when the map component mounts and on map zoom change + useEffect(() => { + // Update the scale text initially + updateScale(); + + // Register the map's zoom events to update the scale text + map.on("zoomend", updateScale); + + // Clean up event listener when the component unmounts + return () => { + map.off("zoomend", updateScale); + }; + }, [map]); + + return ( +
+ {scaleText} + + ); +}; + +export default CustomScaleControl; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/FilterSection.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/FilterSection.js new file mode 100644 index 00000000000..34cba3fb0cb --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/FilterSection.js @@ -0,0 +1,81 @@ +// Importing necessary modules +import { Button, CheckBox } from "@egovernments/digit-ui-components"; +import "leaflet/dist/leaflet.css"; +import React, { memo, useCallback } from "react"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; + +const FilterSection = memo( + ({ filterProperties, showFilterOptionRef, showFilterOptions, setShowFilterOptions, filterSelections, setFilterSelections, t }) => { + const handleChange = useCallback( + (e, item) => { + let tempFilterSelections = [...filterSelections]; // Clone the array to avoid mutating state directly + if (filterSelections.includes(item)) { + tempFilterSelections = tempFilterSelections.filter((element) => element !== item); + } else { + tempFilterSelections.push(item); + } + setFilterSelections(tempFilterSelections); + }, + [filterSelections] + ); + + return ( +
+
setShowFilterOptions((previous) => !previous)} + onKeyUp={() => setShowFilterOptions((previous) => !previous)} + tabIndex={0} + style={{display:"flex"}} + > +

{t("FILTERS")}

+
+ {DigitSvgs.FilterAlt && } +
+
+ {showFilterOptions && ( +
+
+ {filterProperties?.map((item) => ( +
+ handleChange(e, item)} + label={t(item)} + checked={!!filterSelections.includes(item)} + mainClassName="mainClassName" + labelClassName="labelClassName" + inputWrapperClassName="inputWrapperClassName" + inputClassName="inputClassName" + inputIconClassname="inputIconClassname" + iconFill={"#C84C0E"} + onLabelClick={(e) => handleChange(e, item)} + /> +
+ ))} +
+
+ )} +
+ ); + } +); +export default FilterSection; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapFilterIndex.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapFilterIndex.js new file mode 100644 index 00000000000..cfa951ba695 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapFilterIndex.js @@ -0,0 +1,41 @@ +import React,{Fragment} from "react"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; + +const IconCollection = {...DigitSvgs }; + +export const FilterItemBuilder = ({ item, MapFilters, t }) => { + let temp = MapFilters?.find((e) => e?.name === item)?.icon?.index; + let DynamicIcon = IconCollection?.[temp]; + // let icon; + // if (typeof DynamicIcon === "function") icon = DynamicIcon({}); + return DynamicIcon && typeof DynamicIcon === "function" ? ( +
+ +

{t(item)}

+
+ ) : ( + //
+ "" + ); + }; + +const MapFilterIndex = ({ filterSelections, MapFilters, t }) => { + return ( +
+ {filterSelections && filterSelections.length > 0 ? ( + <> + {filterSelections.map((item, index) => ( + //
+ + //

{t(item)}

+ //
+ ))} + + ) : ( + "" + )} +
+ ); + }; + +export default MapFilterIndex; \ No newline at end of file diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapViewComponent.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapViewComponent.js new file mode 100644 index 00000000000..a2321ca97fe --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/MapViewComponent.js @@ -0,0 +1,441 @@ +import React, { memo, useEffect, useRef, useState, Fragment } from "react"; +import L from "leaflet"; +import "leaflet/dist/leaflet.css"; +import { fileData } from "./fileData"; +import { Card, Button } from "@egovernments/digit-ui-components"; +import { useTranslation } from "react-i18next"; +import BoundaryFilter from "./BoundaryFilter"; +import BaseMapSwitcher from "./BaseMapSwitcher"; +import { useMyContext } from "../utils/context"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; +import CustomScaleControl from "./CustomScaleControl"; +import MapFilterIndex from "./MapFilterIndex"; +import FilterSection from "./FilterSection"; +import ChoroplethSelection from "./ChoroplethSelection"; +import { FilterAlt } from "@egovernments/digit-ui-svg-components"; +import { + enableMapInteractions, + disableMapInteractions, + removeAllLayers, + filterBoundarySelection, + findBounds, + addGeojsonToMap, + addFilterProperties, + addChoroplethProperties, + prepareGeojson, + extractGeoData, +} from "../utils/mappingUtils"; +import { CardSectionHeader, InfoIconOutline, LoaderWithGap, Modal, Header } from "@egovernments/digit-ui-react-components"; + +// Utility function to introduce a delay +export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +const MapViewComponent = () => { + const { t } = useTranslation(); + const url = Digit.Hooks.useQueryParams(); + const tenantId = Digit.ULBService.getCurrentTenantId(); + + const { + isLoading: isLoadingCampaignObject, + data: campaignObject, + error: errorCampaign, + refetch: refetchCampaign, + } = Digit.Hooks.microplanv1.useSearchCampaign( + { + CampaignDetails: { + tenantId, + ids: [url?.campaignId], + }, + }, + { + enabled: url?.campaignId ? true : false, + } + ); + + const [jurisdiction, setjurisdiction] = useState([]); + const [isboundarySelectionSelected, setIsboundarySelectionSelected] = useState(false); + const [filterSelections, setFilterSelections] = useState([]); + const [filterProperties, setFilterProperties] = useState(); + const [showFilterOptions, setShowFilterOptions] = useState(false); + const { state, dispatch } = useMyContext(); + const hierarchy = state?.hierarchyType; + const BoundaryMain = campaignObject?.boundaries; + const [boundary, setBoundary] = useState(BoundaryMain); + const [boundarySelections, setBoundarySelections] = useState({}); + const [baseMaps, setBaseMaps] = useState({}); + const [showBaseMapSelector, setShowBaseMapSelector] = useState(false); + const [selectedBaseMap, setSelectedBaseMap] = useState({}); + const [selectedBaseMapName, setSelectedBaseMapName] = useState(""); + const basemapRef = useRef(); + const showFilterOptionRef = useRef(); + var [map, setMap] = useState(null); + var [_mapNode, set__mapNode] = useState("map"); + const [loader, setLoader] = useState(false); + + const [choroplethProperties, setChoroplethProperties] = useState([]); + const [showChoroplethOptions, setShowChoroplethOptions] = useState(false); + const [choroplethProperty, setChoroplethProperty] = useState(); + const [dataCompleteness, setDataCompleteness] = useState(); + const filterBoundaryRef = useRef(); + const showChoroplethOptionRef = useRef(); + + const MapFilters = [ + { + name: "Warehouse", + icon: { + index: "Warehouse", + marker: "WarehouseMarker", + }, + }, + { + name: "Church", + icon: { + index: "Church", + marker: "ChurchMarker", + }, + }, + ]; + + // Effect to initialize map when data is fetched + useEffect(() => { + if (!state || !boundary) return; + const BaseMapLayers = [ + { + name: "Satellite", + url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", + minZoom: 1, + maxZoom: 20, + attribution: "© OpenTopoMap", + }, + { + name: "Street Map", + url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + minZoom: 1, + maxZoom: 20, + attribution: "© OpenStreetMap contributors", + }, + { + name: "Topography", + url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", + minZoom: 1, + maxZoom: 20, + attribution: "© OpenTopoMap contributors", + }, + { + name: "Light Theme", + url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", + minZoom: 1, + maxZoom: 20, + attribution: "© CARTO", + }, + ]; + if (!BaseMapLayers || (BaseMapLayers && BaseMapLayers.length === 0)) return; + let baseMaps = {}; + let defaultBaseMap = undefined; + BaseMapLayers.forEach((item) => { + if (item.url) { + const layer = L.tileLayer(item.url, { + minZoom: item?.minZoom, + maxZoom: item?.maxZoom, + attribution: item?.attribution, + }); + baseMaps[item?.name] = { + metadata: item, + layer, + }; + if (!defaultBaseMap) + defaultBaseMap = { + name: item?.name, + layer, + }; + } + }); + setSelectedBaseMapName(defaultBaseMap?.name); + setBaseMaps(baseMaps); + if (!map) { + init(_mapNode, defaultBaseMap, boundary); + } + }, [boundary]); + + // Function to initialize map + const init = (id, defaultBaseMap, boundary) => { + if (map !== null) return; + + // let bounds = findBounds(boundary); + + let mapConfig = { + center: [0, 0], + zoomControl: false, + zoom: 3, + scrollwheel: true, + minZoom: 3, + }; + + let map_i = L.map(id, mapConfig); + + // Add the zoom control manually at the bottom left + L.control + .zoom({ + position: "bottomleft", // Position it at the bottom left + }) + .addTo(map_i); + + var verticalBounds = L.latLngBounds(L.latLng(-90, -170), L.latLng(85, 190)); + map_i.on("drag", () => { + map_i.panInsideBounds(verticalBounds, { animate: true }); + }); + map_i.on("zoom", () => { + map_i.panInsideBounds(verticalBounds, { animate: true }); + }); + const defaultBaseLayer = defaultBaseMap?.layer.addTo(map_i); + // if (bounds) map_i.fitBounds(bounds); + setSelectedBaseMap(defaultBaseLayer); + setMap(map_i); + }; + useEffect(() => { + if (campaignObject?.boundaries) { + setBoundary(campaignObject?.boundaries); + const boundaryCodes = campaignObject?.boundaries.map((boundary) => boundary.code); + setjurisdiction(boundaryCodes); + } + }, [campaignObject]); + + // Custom hook to fetch census data based on microplanId and boundaryCode + const reqCriteriaResource = { + url: `/census-service/_search`, + body: { + CensusSearchCriteria: { + tenantId: tenantId, + source: url?.microplanId, + jurisdiction: jurisdiction, + }, + }, + config: { + enabled: jurisdiction && jurisdiction.length > 0 ? true : false, + }, + }; + + const { isLoading, data: censusData, isFetching, refetch } = Digit.Hooks.useCustomAPIHook(reqCriteriaResource); + + + const { isLoading: isLoadingPlanObject, data: planObject, error: errorPlan, refetch: refetchPlan } = Digit.Hooks.microplanv1.useSearchPlanConfig( + { + PlanConfigurationSearchCriteria: { + tenantId, + id: url?.microplanId, + }, + }, + { + enabled: true, + } + ); + + const reqCriteriaForPlanFacility = { + url: `/plan-service/plan/facility/_search`, + params: {}, + body: { + PlanFacilitySearchCriteria: { + tenantId: tenantId, + planConfigurationId: url?.microplanId, + }, + }, + config: { + enabled: true, + }, + }; + const { + isLoading: isFacilitySearchLoading, + data: facilitySearchData, + isFetching: isFacilitySearchFetching, + refetch2, + revalidate, + } = Digit.Hooks.useCustomAPIHook(reqCriteriaForPlanFacility); + + + // showing selected boundary data + useEffect(async () => { + if (!boundarySelections && !choroplethProperty && !filterSelections) return; + // setLoader("LOADING"); + await delay(100); + try { + removeAllLayers(map, layers); + const { filteredSelection, childrenList } = filterBoundarySelection(boundary, boundarySelections); + let newLayer = []; + let addOn = { + fillColor: "rgba(255, 107, 43, 0)", + weight: 3.5, + opacity: 1, + color: "rgba(176, 176, 176, 1)", + fillOpacity: 0, + fill: "rgb(4,136,219,1)", + child: !childrenList || childrenList.length === 0, // so that this layer also has mounse in and mouse out events + }; + let geojsonsBase = prepareGeojson(boundary, "ALL", addOn); + if (geojsonsBase) { + let baseLayer = addGeojsonToMap(map, geojsonsBase, t); + if (baseLayer) newLayer.push(baseLayer); + let bounds = findBounds(geojsonsBase); + if (bounds) map.fitBounds(bounds); + } + + addOn = { + fillColor: "rgba(255, 107, 43, 1)", + weight: 2.5, + opacity: 1, + color: "rgba(255, 255, 255, 1)", + fillOpacity: 0.22, + fill: "rgb(4,136,219)", + }; + + let geojsonLayer; + if (choroplethProperty) { + if (dataCompleteness === "partial" || dataCompleteness === "false" || dataCompleteness === undefined) { + // setToast({ + // state: "warning", + // message: t("DISPLAYING_DATA_ONLY_FOR_UPLOADED_BOUNDARIES"), + // }); + } + + let choroplethGeojson = prepareGeojson(boundary, "ALL", { ...addOn, child: true, fillColor: "rgb(0,0,0,0)" }) || []; + if (choroplethGeojson && choroplethGeojson.length !== 0) + choroplethGeojson = addChoroplethProperties(choroplethGeojson, choroplethProperty, filteredSelection); + geojsonLayer = addGeojsonToMap(map, choroplethGeojson, t); + if (geojsonLayer) { + newLayer.push(geojsonLayer); + } + } + geojsonLayer = null; + const geojsons = prepareGeojson(boundaryData, filteredSelection, addOn); + if (geojsons && geojsons.length > 0) { + geojsonLayer = addGeojsonToMap(map, geojsons, t); + newLayer.push(geojsonLayer); + let bounds = findBounds(geojsons); + if (bounds) map.fitBounds(bounds); + } + + const childrenGeojson = prepareGeojson(boundary, childrenList, { ...addOn, opacity: 0, fillOpacity: 0, child: true }); + let childrenGeojsonLayer = addGeojsonToMap(map, childrenGeojson, t); + if (childrenGeojsonLayer) newLayer.push(childrenGeojsonLayer); + + //filters + const filterGeojsons = prepareGeojson(filterData, filteredSelection && filteredSelection.length !== 0 ? filteredSelection : "ALL", addOn); + const filterGeojsonWithProperties = addFilterProperties(filterGeojsons, filterSelections, filterPropertyNames, state?.MapFilters); + let filterGeojsonLayer = addGeojsonToMap(map, filterGeojsonWithProperties, t); + if (filterGeojsonLayer) newLayer.push(filterGeojsonLayer); + + setLayer(newLayer); + } catch (error) { + console.error("Error while adding geojson to map: ", error.message); + } + setLoader(false); + }, [boundarySelections, choroplethProperty, filterSelections]); + + useEffect(() => { + // Fetch the GeoJSON data from the URL + const fetchGeoJson = async () => { + try { + const response = await fetch(fileData?.url); + const data = await response.json(); + setGeoJsonData(data); + } catch (error) { + console.error("Error fetching GeoJSON data:", error); + } + }; + + fetchGeoJson(); + }, [fileData?.url]); + + const handleBaseMapToggle = (newBaseMap) => { + const currentBaseLayer = selectedBaseMap; + if (currentBaseLayer) { + currentBaseLayer.remove(); + } + const newBaseLayer = baseMaps[newBaseMap].layer.addTo(map); + // Add the new base layer to the bottom of the layer stack + newBaseLayer.addTo(map); + + // Update the baseLayer state + setSelectedBaseMap(newBaseLayer); + setSelectedBaseMapName(newBaseMap); + }; + + return ( + <> +
+ {t("MAPPING")} +
+ + + +
+
+
+ +
+ {/* */} + {filterProperties && Object.keys(filterProperties).length !== 0 && ( + + )} + +
+
+
{DigitSvgs.NorthArrow && }
+ +
+
+ {filterSelections && filterSelections.length > 0 && ( + + )} +
+
+
+
+ {/* {loader && } */} + + ); +}; + +export default MapViewComponent; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/fileData.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/fileData.js new file mode 100644 index 00000000000..69e9dfcdcfa --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/components/fileData.js @@ -0,0 +1,503 @@ +const fileData = { + type: "FeatureCollection", + name: "boundary_country_default", + crs: { type: "name", properties: { name: "urn:ogc:def:crs:EPSG::3857" } }, + features: [ + { + type: "Feature", + properties: { + country_name: "India", + country_code: "IND", + global_id: "3c1b3b06-3958-424b-a160-3e25998cd027", + source: "WFP, INE", + source_date: "2007-01-01", + properties: { ADM0_EN: "Mozambique", OBJECTID: 1, ADM0_PCODE: "MZ", Shape_Area: 67.0418795361, Shape_Leng: 96.6578168932 }, + population_1: 26876090.0, + }, + geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [3673024.669982502236962, -2995400.459623530041426], + [3673017.879480754956603, -2995492.088904835749418], + [3672998.843871004879475, -2995563.659168403595686], + [3672972.906434878706932, -2995627.800390315707773], + [3672953.314202011562884, -2995692.808601502329111], + [3672946.523700263816863, -2995719.059607736300677], + [3672940.623795507941395, -2995806.233158790506423], + [3672953.314202011562884, -2995877.309635194484144], + [3672998.843871004879475, -2995961.759908633772284], + [3673043.816916874609888, -2996020.330577704124153], + [3673063.297825119458139, -2996129.919026762712747], + [3673011.534277502913028, -2996253.501086337026209], + [3672901.995952890254557, -2996390.457927237264812], + [3672785.667026418726891, -2996558.86956185894087], + [3672643.73473780695349, -2996863.625347666908056], + [3672501.913673711009324, -2997122.93905570730567], + [3672211.48114388436079, -2997687.649535528384149], + [3671850.138031950686127, -2998375.362734653521329], + [3671597.442847861908376, -2998884.883457168005407], + [3671559.705502124037594, -2998936.406272142659873], + [3671249.680739423260093, -2999598.292035735677928], + [3671036.503894837107509, -3000182.049698479939252], + [3670836.462755250744522, -3000779.580292371101677], + [3670720.133928890805691, -3001168.657234229147434], + [3670681.505986155942082, -3001275.435252913273871], + [3670539.239723676349968, -3001723.490739672444761], + [3670474.674444933887571, -3002054.493283920455724], + [3670455.193536688573658, -3002326.411515710875392], + [3670481.019648184999824, -3002541.224350988399237], + [3670474.674444933887571, -3002710.202880749944597], + [3670435.601303814444691, -3002950.046517744194716], + [3670358.345518461894244, -3003229.29110443778336], + [3670286.989737971220165, -3003631.318417289759964], + [3670106.540831258520484, -3004228.619103302713484], + [3669957.929365526884794, -3004858.035352502018213], + [3669899.263991534244269, -3005253.664617907255888], + [3669886.573585036676377, -3005429.241697436664253], + [3669854.290845553390682, -3005598.129793357104063], + [3669815.663002930115908, -3005819.186655185651034], + [3669776.58986181113869, -3006130.951866385992616], + [3669679.741943695582449, -3006572.217600867152214], + [3669679.741943695582449, -3006747.315034652128816], + [3669672.951441954821348, -3006760.326533137355], + [3669647.45930433133617, -3006856.984253545291722], + [3669589.239228833466768, -3006961.697547391988337], + [3669531.130377853289247, -3007046.088298911228776], + [3669524.339876112062484, -3007149.563669012859464], + [3669524.339876112062484, -3007266.547559079248458], + [3669492.057236740365624, -3007390.224321136716753], + [3669440.627662990707904, -3007545.751107535324991], + [3669379.95854587526992, -3007614.530500079505146], + [3669369.271882506087422, -3007646.503735883161426], + [3669298.361400511581451, -3007743.786915341392159], + [3669201.624706907197833, -3007853.959513968788087], + [3669117.467195296194404, -3007945.171574036590755], + [3669027.07580505637452, -3008061.542194154579192], + [3668956.165222955867648, -3008204.806997095234692], + [3668930.227786829695106, -3008327.872060823254287], + [3668891.154645710717887, -3008457.754529596772045], + [3668852.526803093962371, -3008594.082874624524266], + [3668794.306727602612227, -3008698.312823025975376], + [3668717.050942243542522, -3008756.934835224878043], + [3668633.004755255766213, -3008737.352824060246348], + [3668587.475086261983961, -3008646.135784449521452], + [3668555.637745392974466, -3008508.939479074440897], + [3668542.50204039318487, -3008386.492464958224446], + [3668510.219401021488011, -3008301.969980579335243], + [3668471.591458292212337, -3008184.482105302624404], + [3668458.901051788590848, -3008080.87541986303404], + [3668484.727163285948336, -3008028.948362520895898], + [3668497.528894411865622, -3007996.974395606201142], + [3668517.009802657179534, -3007957.316708031110466], + [3668542.947338888421655, -3007886.181035927031189], + [3668562.094273267313838, -3007776.008304154966027], + [3668587.920384763740003, -3007652.328254773281515], + [3668620.203024135436863, -3007529.268919433932751], + [3668652.930962002836168, -3007393.322369433939457], + [3668697.904007872100919, -3007359.738705246243626], + [3668723.841443991754204, -3007359.738705246243626], + [3668768.814489867072552, -3007347.470140823628753], + [3668807.887630979996175, -3007302.113777381833643], + [3668807.887630979996175, -3007263.07763713831082], + [3668762.469286615028977, -3007198.017673841211945], + [3668711.151037493720651, -3007120.317843807395548], + [3668723.841443991754204, -3007054.762808467727154], + [3668736.9771489915438, -3006997.139091287739575], + [3668723.841443991754204, -3006899.736989237833768], + [3668665.62136850040406, -3006828.110795880202204], + [3668614.303119379561394, -3006776.188331617973745], + [3668568.884775014594197, -3006691.551299335900694], + [3668596.046681886538863, -3006614.969386513344944], + [3668640.240555499214679, -3006591.548842980526388], + [3668652.930962002836168, -3006565.773896362166852], + [3668652.930962002836168, -3006468.002808066084981], + [3668627.438824378885329, -3006402.946407867595553], + [3668588.36568325990811, -3006331.322776873596013], + [3668530.590806152671576, -3006298.980673110578209], + [3668452.889822409488261, -3006266.390910340938717], + [3668394.781071547418833, -3006240.616429517045617], + [3668323.870489440858364, -3006182.376341053750366], + [3668246.503479571547359, -3006155.982519296463579], + [3668168.802495834883302, -3006110.877718012779951], + [3668052.918867859523743, -3006083.86442779796198], + [3667968.761356248520315, -3006072.092619863338768], + [3667897.850874253548682, -3005993.53149446984753], + [3667768.831541284918785, -3005838.02122441213578], + [3667756.029810158535838, -3005779.410999424755573], + [3667756.029810158535838, -3005746.822402752004564], + [3667742.894105158746243, -3005682.140982386656106], + [3667704.266262541525066, -3005642.861365664750338], + [3667613.763547678943723, -3005584.747590015642345], + [3667562.445198446046561, -3005545.46823672298342], + [3667542.852965572383255, -3005480.539884105790406], + [3667568.790401692036539, -3005428.993869248777628], + [3667588.382634565699846, -3005376.952494157478213], + [3667601.073041062802076, -3005325.035107326693833], + [3667588.382634565699846, -3005260.231652840506285], + [3667549.643467319663614, -3005207.943058048374951], + [3667497.879919703584164, -3005169.036459469236434], + [3667452.906873827334493, -3005117.987195922061801], + [3667439.65984421223402, -3005071.646507929079235], + [3667414.167606476694345, -3004987.019410037435591], + [3667349.713652355130762, -3004954.680411451961845], + [3667278.357871864922345, -3004915.898400988895446], + [3667265.556140745058656, -3004851.592297149822116], + [3667278.80317036062479, -3004773.161682154517621], + [3667291.49357686471194, -3004720.750821274705231], + [3667317.431012984365225, -3004649.507208481896669], + [3667323.7762162364088, -3004559.059177999850363], + [3667323.7762162364088, -3004519.287084486335516], + [3667343.257124481722713, -3004467.868386708199978], + [3667343.257124481722713, -3004429.335487050004303], + [3667298.2840786119923, -3004377.29769344534725], + [3667246.520530989393592, -3004312.374450997915119], + [3667214.23789162421599, -3004247.327706092037261], + [3667201.436060385778546, -3004136.56306466460228], + [3667188.300455498043448, -3004078.331412589643151], + [3667188.745653882157058, -3003948.612014095298946], + [3667188.745653882157058, -3003851.230244074016809], + [3667169.153421014547348, -3003766.858055126853287], + [3667117.835171893239021, -3003669.973099600989372], + [3667079.207329269964248, -3003552.522695405874401], + [3667053.269893150310963, -3003487.603249726351351], + [3666995.049817658960819, -3003240.936713401228189], + [3666969.557579923421144, -3003150.869135051500052], + [3666917.794032307341695, -3003065.757648811675608], + [3666853.228753563482314, -3003013.848631934262812], + [3666775.972968204878271, -3002909.659365149680525], + [3666691.9267812166363, -3002831.858580219559371], + [3666633.706705725286156, -3002799.524378137197345], + [3666543.649189247284085, -3002760.252554384060204], + [3666440.45606788713485, -3002708.716189921367913], + [3666317.782038275152445, -3002676.134480557404459], + [3666162.825269180350006, -3002663.374309531413019], + [3665995.066768964752555, -3002657.056117740925401], + [3665904.564054102636874, -3002571.947634685318917], + [3665801.370832623913884, -3002519.916380739305168], + [3665614.131524275522679, -3002519.297045732848346], + [3665446.373024054337293, -3002494.02476839069277], + [3665272.26932058762759, -3002441.746127549558878], + [3664871.853167653083801, -3002357.134345368482172], + [3664562.273603335954249, -3002298.785935712512583], + [3664478.22741634817794, -3002273.018493011128157], + [3664394.515103121288121, -3002233.87209001975134], + [3664323.604621126316488, -3002207.361554043833166], + [3664252.248840635176748, -3002143.067435569129884], + [3664245.903637389652431, -3002090.66617128951475], + [3664220.411499766167253, -3001974.095609928481281], + [3664174.993155394680798, -3001914.881660176906735], + [3664123.563481539487839, -3001863.348292811308056], + [3664033.172091299667954, -3001837.581733329687268], + [3663949.459778072778136, -3001863.348292811308056], + [3663910.386636953335255, -3001882.673270238097757], + [3663813.650043461471796, -3001922.438259874004871], + [3663729.492531850468367, -3002019.063796656671911], + [3663665.038477617781609, -3002109.743679318577051], + [3663645.891543246340007, -3002187.540599160827696], + [3663639.101041498128325, -3002252.206463939044625], + [3663626.2993103726767, -3002369.150919523555785], + [3663658.13675136025995, -3002563.89513868605718], + [3663684.074187479913235, -3002680.84210198558867], + [3663722.702030102722347, -3002778.711483768653125], + [3663819.549948217347264, -3002901.854467129334807], + [3663896.805733569897711, -3003004.804855114780366], + [3663987.308448432479054, -3003031.812308803200722], + [3664019.591087804175913, -3002986.345527155790478], + [3664096.84687315672636, -3002979.779519965872169], + [3664168.202653647400439, -3003031.812308803200722], + [3664271.395775014068931, -3002999.353768488857895], + [3664316.368820882868022, -3002954.258655938319862], + [3664354.996763617731631, -3002921.924181543756276], + [3664374.58899648534134, -3002863.077935705892742], + [3664400.526432604994625, -3002791.967322472017258], + [3664452.289980228524655, -3002765.207985821645707], + [3664497.263026097323745, -3002785.277400129009038], + [3664542.347396595403552, -3002856.883660670369864], + [3664620.048480443190783, -3002954.506441914010793], + [3664652.331119814887643, -3002967.266868543345481], + [3664697.304165683686733, -3002955.00201389240101], + [3664787.806880546268076, -3002993.035430639050901], + [3664819.978195295669138, -3003006.539251688402146], + [3664891.000102024525404, -3003071.456548321060836], + [3664949.108852893579751, -3003175.027500692289323], + [3665032.821066008880734, -3003279.590357277542353], + [3665142.359490732196718, -3003454.771888166666031], + [3665297.31625982793048, -3003772.5572295980528], + [3665451.938954937271774, -3004168.156933392863721], + [3665496.912100918125361, -3004356.730348951183259], + [3665503.257304170168936, -3004571.573200283106416], + [3665483.776395918801427, -3004752.841593820601702], + [3665451.493756553623825, -3004902.144986013881862], + [3665315.572597207967192, -3005083.913149522617459], + [3665186.553364343941212, -3005271.75497717037797], + [3665116.422054612543434, -3005336.062941290438175], + [3665057.868005248252302, -3005436.304468386806548], + [3665025.140067381318659, -3005473.22926269331947], + [3664844.245862166862935, -3005589.951815882697701], + [3664811.963222795166075, -3005621.796566062606871], + [3664650.549925826024264, -3005745.45931794308126], + [3664527.875896214041859, -3005836.410407331306487], + [3664314.699051627889276, -3005959.331475109327585], + [3664127.459643167909235, -3006043.716400428209454], + [3663843.372316586785018, -3006205.796335994731635], + [3663739.73389672441408, -3006283.615192964673042], + [3663713.796460604760796, -3006387.580817697569728], + [3663687.970349107403308, -3006471.472539966925979], + [3663597.467634244821966, -3006523.517837871797383], + [3663565.184894762001932, -3006588.94655032036826], + [3663487.929209521505982, -3006659.828016079962254], + [3663429.709134030155838, -3006757.22861993080005], + [3663461.546474899165332, -3006854.629765823017806], + [3663455.201271653641015, -3006964.795615669805557], + [3663377.945586413145065, -3006984.870833334047347], + [3663332.527242042589933, -3007036.918083260767162], + [3663267.961863187141716, -3007133.081930786836892], + [3663216.086990947835147, -3007244.365148692857474], + [3663132.04080395353958, -3007322.189572999253869], + [3663112.893869582097977, -3007380.310303553938866], + [3663138.386007205583155, -3007471.519386988133192], + [3663112.559895708225667, -3007549.345029241871089], + [3663061.130321965087205, -3007542.405162436887622], + [3663035.192885845433921, -3007490.480031658895314], + [3663035.192885845433921, -3007438.926991835236549], + [3662983.429238110780716, -3007374.113990284502506], + [3662990.219739858061075, -3007315.249814794864506], + [3663035.192885845433921, -3007282.905518924817443], + [3663041.983287480659783, -3007230.857498442288488], + [3662990.219739858061075, -3007172.489508353173733], + [3662964.282303738407791, -3007100.861969190649688], + [3662964.282303738407791, -3007062.446010328829288], + [3662977.529333360493183, -3007023.162770641036332], + [3662983.874536612536758, -3006931.956392975058407], + [3662997.010241605807096, -3006860.949783661868423], + [3662997.010241605807096, -3006809.027090509887785], + [3663003.355444857850671, -3006718.193944914266467], + [3663003.355444857850671, -3006555.364746982697397], + [3662984.319835108704865, -3006451.893612080253661], + [3662868.324882510118186, -3006302.450364618096501], + [3662836.042243137955666, -3006302.450364618096501], + [3662713.368213526438922, -3006237.518627203535289], + [3662597.039287055376917, -3006178.658948694355786], + [3662461.563526317011565, -3006139.87366949301213], + [3662384.196416342165321, -3006068.251336191780865], + [3662396.998247580602765, -3005990.557552317623049], + [3662429.280886951833963, -3005951.277101336978376], + [3662455.106998449191451, -3005873.583943332545459], + [3662455.106998449191451, -3005789.200006686616689], + [3662448.761795197147876, -3005679.043187777511775], + [3662423.269557461608201, -3005581.649816189426929], + [3662384.641714838333428, -3005529.360064165201038], + [3662332.878167221322656, -3005477.442132499534637], + [3662249.165954106487334, -3005438.65872613247484], + [3662177.810173615813255, -3005380.545767611358315], + [3662145.972732627764344, -3005314.998586684465408], + [3662145.972732627764344, -3005243.875971611123532], + [3662158.774463754612952, -3005191.959060484077781], + [3662191.057103126309812, -3005120.341402070596814], + [3662281.448593478184193, -3005035.961841944139451], + [3662378.296511586289853, -3004951.582772010006011], + [3662423.714855957310647, -3004900.038634397089481], + [3662449.65229207649827, -3004873.771052453201264], + [3662559.636015302501619, -3004743.67282626638189], + [3662643.236903795041144, -3004634.019454677589238], + [3662682.310044907964766, -3004562.404547140933573], + [3662740.530120405368507, -3004452.256920790299773], + [3662792.293668022379279, -3004316.33928633062169], + [3662843.723341877572238, -3004128.014201041311026], + [3662863.204250122886151, -3003959.019295381382108], + [3662854.743979126214981, -3003752.486340347211808], + [3662863.649548624642193, -3003654.610400357283652], + [3662844.168640372809023, -3003549.549252978526056], + [3662766.80153039842844, -3003432.966990184504539], + [3662702.681550150737166, -3003212.566023910418153], + [3662683.200641905423254, -3003043.086189688183367], + [3662676.855438654311001, -3002939.640070469584316], + [3662689.991143653634936, -3002809.930725085549057], + [3662703.126848653424531, -3002686.788520572241396], + [3662754.556422396562994, -3002570.089288849383593], + [3662780.382533893920481, -3002446.949116311967373], + [3662845.393111132550985, -3002265.214048251975328], + [3662890.477481623645872, -3002161.277920140419155], + [3662935.895825995132327, -3002063.908125669695437], + [3663039.089047466870397, -3001940.772241012658924], + [3663097.197798335459083, -3001856.535025606863201], + [3663149.07267058128491, -3001752.477936586365104], + [3663239.464060821104795, -3001642.351716686040163], + [3663329.966875795274973, -3001550.683589630294591], + [3663370.264487667940557, -3001457.901341158896685], + [3663414.013062783982605, -3001405.874074364081025], + [3663504.515777646563947, -3001243.104512497317046], + [3663678.953454986680299, -3000931.196401031687856], + [3663859.847660201601684, -3000568.385566949378699], + [3663982.633014435879886, -3000276.557989372871816], + [3664073.024504787754267, -2999978.419239054899663], + [3664092.616737661883235, -2999842.047962148673832], + [3664098.961940907407552, -2999705.553986991755664], + [3664079.814906423445791, -2999543.918117354623973], + [3664086.271434297785163, -2999413.743719429243356], + [3664151.170787026174366, -2999296.823139205574989], + [3664202.600360769312829, -2999244.927463741507381], + [3664281.191941503901035, -2999223.624322888907045], + [3664335.961053756531328, -2999130.361365077085793], + [3664498.598821478895843, -2999127.017357058357447], + [3664572.848942089360207, -2999126.026474012527615], + [3664695.634396435227245, -2999104.84736745711416], + [3664804.282224162016064, -2999075.246147220022976], + [3664984.842455503065139, -2999031.773376557976007], + [3665079.018682748544961, -2998988.053017378319055], + [3665187.332536607515067, -2998894.296247309539467], + [3665267.148588094860315, -2998799.301505353301764], + [3665346.63076581992209, -2998741.215193536598235], + [3665356.760806078091264, -2998724.495162791572511], + [3665469.304795431904495, -2998499.087713503278792], + [3665508.82323504704982, -2998360.005516620352864], + [3665527.524971035309136, -2998342.047609536908567], + [3665570.828247655648738, -2998297.833909305743873], + [3665585.299748030491173, -2998210.645437625236809], + [3665701.183376005850732, -2998152.561364055145532], + [3665758.958153001498431, -2998101.784434219822288], + [3665831.093205861281604, -2998021.284865341614932], + [3665867.272006860468537, -2997970.879833759739995], + [3665947.088058347348124, -2997941.900171597488225], + [3666033.69461158849299, -2997920.103649157099426], + [3666069.762188070919365, -2997883.817281937226653], + [3666120.301164823118597, -2997825.610663390252739], + [3666156.814039807766676, -2997796.631290463265032], + [3666178.521340426057577, -2997796.631290463265032], + [3666207.353016552980989, -2997811.244818901643157], + [3666272.363593792077154, -2997832.917440320830792], + [3666351.734446901828051, -2997789.572229748591781], + [3666409.509223897010088, -2997745.855602369643748], + [3666489.325275383889675, -2997702.386802806984633], + [3666539.864352247677743, -2997658.670437280088663], + [3666619.235205350443721, -2997629.567549093160778], + [3666691.481482720933855, -2997629.072158534545451], + [3666756.492059959564358, -2997636.874227609951049], + [3666814.600910940673202, -2997600.58856647927314], + [3666865.139887692406774, -2997513.404271503444761], + [3666937.386265174485743, -2997462.629571395460516], + [3667023.992818409111351, -2997412.226585761178285], + [3667067.741393525153399, -2997389.316134694963694], + [3667103.808869902044535, -2997375.446102703455836], + [3667147.112146516796201, -2997339.03724808152765], + [3667175.943922762293369, -2997288.263156173750758], + [3667212.122823873069137, -2997273.650152769871056], + [3667269.786276239901781, -2997244.548034341074526], + [3667342.477852112613618, -2997259.284853016957641], + [3667364.073928220197558, -2997316.869982061907649], + [3667407.377204840537161, -2997346.34378076530993], + [3667450.791806083638221, -2997368.387246249709278], + [3667479.623482210561633, -2997397.365766588132828], + [3667530.162559074349701, -2997433.774767100345343], + [3667544.634059449192137, -2997455.075288027524948], + [3667595.173136312980205, -2997491.360586413182318], + [3667638.47641292726621, -2997484.79705937532708], + [3667703.375665543600917, -2997485.292445018421859], + [3667761.595741034951061, -2997477.490457196719944], + [3667840.966594144236296, -2997513.899658120237291], + [3667905.9771713828668, -2997557.615588843356818], + [3667934.80894762231037, -2997600.960108667612076], + [3667956.516248246654868, -2997651.982986968941987], + [3667978.112224242649972, -2997746.47473415452987], + [3667999.819524861406535, -2997833.660425192210823], + [3667999.819524861406535, -2997993.54349875729531], + [3667985.347924374509603, -2998045.063157993368804], + [3667948.83514950145036, -2998095.468335112556815], + [3667934.363649126607925, -2998153.056772433687001], + [3667948.83514950145036, -2998211.140847973991185], + [3667999.819524861406535, -2998262.661277507431805], + [3668079.190277852118015, -2998306.379457273054868], + [3668209.21143233589828, -2998291.765431664418429], + [3668295.817985570058227, -2998269.968273860868067], + [3668346.802360930014402, -2998240.864163293503225], + [3668375.634137168992311, -2998211.883963530883193], + [3668426.173113921657205, -2998182.779969124123454], + [3668438.863520418759435, -2998188.97225371748209], + [3668469.476390541996807, -2998182.779969124123454], + [3668491.183691160287708, -2998226.497908915858716], + [3668491.183691160287708, -2998313.686475576832891], + [3668476.712190785445273, -2998415.489588393364102], + [3668476.712190785445273, -2998466.267612795345485], + [3668527.251267649233341, -2998525.096070013474673], + [3668570.554544263053685, -2998604.48383370321244], + [3668592.150520259514451, -2998662.56971108308062], + [3668621.093621126841754, -2998713.596311402041465], + [3668707.700174361933023, -2998735.394133716821671], + [3668801.987726230174303, -2998720.903534192591906], + [3668924.661855953279883, -2998707.032233132515103], + [3669004.143933566752821, -2998612.038713697809726], + [3669032.975709812249988, -2998553.209904995746911], + [3669061.807486051693559, -2998524.600648978725076], + [3669120.027561542578042, -2998502.926997501868755], + [3669221.105615159031004, -2998481.129523410927504], + [3669278.880492266267538, -2998466.019903272856027], + [3669314.947968636639416, -2998357.65240491554141], + [3669358.696543753147125, -2998205.072406483348459], + [3669409.235520504880697, -2998132.250637685880065], + [3669503.077873982489109, -2998066.859978954307735], + [3669531.909650228451937, -2998052.370045096147805], + [3669763.342832188587636, -2997957.876200604718179], + [3669886.128286534454674, -2997900.288535586558282], + [3670009.247614642139524, -2997856.07617737678811], + [3670074.258191886823624, -2997849.512485869694501], + [3670218.639622229151428, -2997790.934457465540618], + [3670291.219873472582549, -2997754.524563570506871], + [3670399.533727331552655, -2997703.005932744592428], + [3670507.84758119052276, -2997631.053609479684383], + [3670594.899432920850813, -2997558.234712629113346], + [3670667.034485780633986, -2997514.766473367810249], + [3670760.876839264761657, -2997478.481116630602628], + [3670777.463407390750945, -2997471.050671330653131], + [3670808.410251380875707, -2997427.458846492692828], + [3670890.45269524725154, -2997289.873026470653713], + [3670985.074321093969047, -2997206.034269687719643], + [3671057.320598464459181, -2997082.567967855837196], + [3671122.219851080793887, -2996966.28521312167868], + [3671136.691451573278755, -2996937.307550879660994], + [3671151.608250444289297, -2996857.185944714583457], + [3671202.147327308077365, -2996791.801013583317399], + [3671187.675726821180433, -2996718.986314624547958], + [3671158.732726064976305, -2996668.214385170023888], + [3671137.136750069446862, -2996558.621884800959378], + [3671101.069173580501229, -2996479.24527825973928], + [3671115.429449444636703, -2996384.885481526143849], + [3671115.429449444636703, -2996370.149547890294343], + [3671137.136750069446862, -2996326.6848557991907], + [3671108.304973823484033, -2996261.302306905388832], + [3671108.304973823484033, -2996181.184420541860163], + [3671129.900949825998396, -2996086.702720748260617], + [3671144.372450200840831, -2995999.651324575766921], + [3671223.743303303606808, -2995897.864993053954095], + [3671281.518080299254507, -2995810.690893503371626], + [3671303.114056295249611, -2995694.17063304875046], + [3671307.010217923205346, -2995630.029230197425932], + [3671301.889585542026907, -2995575.174923980608582], + [3671360.109661033377051, -2995452.093825576361269], + [3671431.020143028348684, -2995407.022211077623069], + [3671495.585421765223145, -2995381.267081326339394], + [3671605.123846495058388, -2995368.265649436973035], + [3671766.425818835850805, -2995394.020867562387139], + [3671901.901579567696899, -2995433.272702212911099], + [3672031.032237165607512, -2995466.333624660968781], + [3672172.85320114903152, -2995466.333624660968781], + [3672327.809970243833959, -2995432.777386378496885], + [3672463.28583108773455, -2995368.513306258711964], + [3672566.478952454403043, -2995290.133808153215796], + [3672637.389434448909014, -2995225.870246031787246], + [3672701.954813297372311, -2995160.492638975847512], + [3672798.802731411997229, -2995148.234363357536495], + [3672889.194121652748436, -2995193.552942269016057], + [3672960.104703759308904, -2995245.06258348049596], + [3673005.189074250403792, -2995309.202402367722243], + [3673024.669982502236962, -2995400.459623530041426], + ], + ], + ], + }, + }, + ], +}; + +export { fileData }; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/configs/UICustomizations.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/configs/UICustomizations.js index 17fb49c3d0a..262993c1e0f 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/configs/UICustomizations.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/configs/UICustomizations.js @@ -264,7 +264,7 @@ export const UICustomizations = { return row.status === "EXECUTION_TO_BE_DONE" ? ( { const hasValidCurrentStatus = validStatuses?.includes(planObject?.status); // Disable if either hasValidRole or hasValidNextAction is false - return !(hasValidRole && hasValidCurrentStatus /*hasValidNextAction*/ ); + return !(hasValidRole && hasValidCurrentStatus) /*hasValidNextAction*/; }; // Usage in activityCardData @@ -85,9 +85,10 @@ const ChooseActivity = () => { }, { name: t("GEOSPATIAL_MAP_VIEW"), - link: null, - icon: , - disable: isCardDisabled([], workflowData), + link: `map-view?campaignId=${campaignId}µplanId=${microplanId}`, + icon: , + disable: isCardDisabled(["POPULATION_DATA_APPROVER","ROOT_POPULATION_DATA_APPROVER","FACILITY_CATCHMENT_MAPPER","ROOT_FACILITY_CATCHMENT_MAPPER","MICROPLAN_VIEWER","PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS","RESOURCE_ESTIMATIONS_APPROVED"]), optionKey: "GEOSPATIAL_MAP_VIEW" }, //commenting this as for now we are not showing viewer diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/index.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/index.js index 655651e41ef..d20f110df84 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/index.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/index.js @@ -21,6 +21,8 @@ import Response from "../../components/Response"; import FacilityCatchmentMapping from "./FacilityCatchmentMapping"; import PlanInbox from "./PlanInbox"; +import MapViewComponent from "../../components/MapViewComponent"; + @@ -221,6 +223,7 @@ const App = ({ path, stateCode, userType, tenants, BOUNDARY_HIERARCHY_TYPE, hier } /> } /> } /> + } /> diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/mappingUtils.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/mappingUtils.js new file mode 100644 index 00000000000..6ca60e9ff3b --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/mappingUtils.js @@ -0,0 +1,762 @@ +import L from "leaflet"; +import "leaflet/dist/leaflet.css"; +import chroma from "chroma-js"; +import * as DigitSvgs from "@egovernments/digit-ui-svg-components"; +import { processHierarchyAndData, findChildren, calculateAggregateForTree } from "./processHierarchyAndData"; +import { EXCEL, GEOJSON, SHAPEFILE, MapChoroplethGradientColors } from "../configs/constants"; + +const IconCollection = {...DigitSvgs }; + +export const generatePreviewUrl = (baseMapUrl, center = [0, 0], zoom = 5) => { + const lon = Math.floor(((center[1] + 180) / 360) * Math.pow(0, zoom)); + const lat = Math.floor( + ((1 - Math.log(Math.tan((center[0] * Math.PI) / 180) + 1 / Math.cos((center[0] * Math.PI) / 180)) / Math.PI) / 2) * Math.pow(2, zoom) + ); + if (baseMapUrl) { + return baseMapUrl.replace("{z}", zoom).replace("{x}", lat).replace("{y}", lon); + } + // Return a default preview URL or handle this case as needed + return "default-preview-url.jpg"; // todo +}; + +// get schema for validation +export const getSchema = (campaignType, type, section, schemas) => { + return schemas.find((schema) => { + if (!schema.campaignType) { + return schema.type === type && schema.section === section; + } + return schema.campaignType === campaignType && schema.type === type && schema.section === section; + }); +}; + +export const calculateAggregateForTreeMicroplanWrapper = (entity) => { + if (!entity || typeof entity !== "object") return {}; + let newObject = {}; + for (let [key, value] of Object.entries(entity)) { + if (!value?.["hierarchicalData"]) continue; + let aggregatedTree = calculateAggregateForTree(value?.["hierarchicalData"]); + newObject[key] = { ...value, hierarchicalData: aggregatedTree }; + } + return newObject; +}; + +export const extractGeoData = ( + campaignType, + microplanData, + filterDataOrigin, + validationSchemas, + setToast, + setDataAvailability, + hierarchy, + setBoundaryData, + setFilterData, + setFilterProperties, + setFilterSelections, + setFilterPropertyNames, + state, + setChoroplethProperties, + setDataCompleteness, + t +) => { + if (!hierarchy) return; + + const initializeDataAvailability = (microplanData) => (microplanData?.upload ? "initialStage" : undefined); + + const checkFileActivity = (fileData) => fileData.active; + + const checkFileSection = (fileData, filterDataOrigin) => + filterDataOrigin?.boundriesDataOrigin?.includes(fileData?.section) || filterDataOrigin?.layerDataOrigin?.includes(fileData?.section); + + const getFileValidationSchema = (campaignType, fileData, validationSchemas) => + getSchema(campaignType, fileData?.fileType, fileData?.section, validationSchemas); + + const updateDataAvailabilityCheck = (dataAvailabilityCheck, condition, partialState) => + condition ? partialState : dataAvailabilityCheck === "initialStage" ? "false" : partialState; + + const handleFileDataError = (dataAvailabilityCheck, fileData) => + fileData?.error ? updateDataAvailabilityCheck(dataAvailabilityCheck, true, "partial") : dataAvailabilityCheck; + + const addResourcesToFilteredData = (data, resources, hypothesisAssumptionsList, formulaConfiguration, microplanData, t) => + Digit.Utils.microplan.addResourcesToFilteredDataToShow( + data, + resources, + hypothesisAssumptionsList, + formulaConfiguration, + microplanData?.microplanPreview?.userEditedResources || [], + t + ); + + const processFileData = ( + fileData, + schema, + filterDataOrigin, + virtualizationPropertiesCollector, + filterPropertiesCollector, + filterPropertieNameCollector, + resources, + hypothesisAssumptionsList, + formulaConfiguration, + t + ) => { + const properties = Object.entries(schema?.schema?.Properties || {}); + const latLngColumns = []; + const filterProperty = []; + + for (const [key, value] of properties) { + if (value?.isLocationDataColumns) latLngColumns.push(t(key)); + if (filterDataOrigin?.layerDataOrigin?.includes(fileData?.section) && value?.isFilterPropertyOfMapSection) filterProperty.push(key); + if (value?.isVisualizationPropertyOfMapSection && filterDataOrigin?.boundriesDataOrigin?.includes(fileData?.section)) + virtualizationPropertiesCollector.add(key); + } + + filterProperty.forEach((property) => filterPropertieNameCollector.add(property)); + + return { latLngColumns, filterProperty }; + }; + + const processExcelFile = (fileData, latLngColumns, resources, formulaConfiguration, hypothesisAssumptionsList, schema, t) => { + let dataAvailabilityCheck = "true"; + const columnList = Object.values(fileData?.data)?.[0]?.[0]; + const check = latLngColumns?.every((colName) => columnList?.includes(t(colName))); + + if (!check) dataAvailabilityCheck = "partial"; + + let dataWithResources = Object.values(fileData?.data); + if (resources && formulaConfiguration && hypothesisAssumptionsList && schema?.showResourcesInMappingSection) { + dataWithResources = dataWithResources.map((item) => + addResourcesToFilteredData(item, resources, hypothesisAssumptionsList, formulaConfiguration, microplanData, t) + ); + } + + const hasLocationData = dataWithResources.some((item) => item.some((row) => row.includes("lat") && row.includes("long"))); + + const convertedData = dataWithResources.map((item) => + item.map((row, rowIndex) => { + if (rowIndex === 0) { + if (row.indexOf("features") === -1) row.push("feature"); + return row; + } + const latIndex = item[0].findIndex((cell) => cell === "lat"); + const lonIndex = item[0].findIndex((cell) => cell === "long"); + const properties = item[0].reduce((acc, cell, index) => ({ ...acc, [cell]: row[index] }), {}); + const feature = + latIndex !== -1 && lonIndex !== -1 + ? { + type: "Feature", + properties, + geometry: { + type: "Point", + coordinates: [row[lonIndex], row[latIndex]], + }, + } + : null; + row.push(feature); + return row; + }) + ); + + return { dataAvailabilityCheck, hasLocationData, convertedData }; + }; + + const processGeoJsonFile = (fileData, filterProperty, resources, formulaConfiguration, hypothesisAssumptionsList, t) => { + const dataAvailabilityCheck = "true"; + const keys = [...Object.keys(fileData?.data.features[0].properties), "feature"]; + const values = fileData?.data.features.map((feature) => keys.map((key) => (key === "feature" ? feature : feature.properties[key] || null))); + + let dataWithResources = [[...keys], ...values]; + if (resources && formulaConfiguration && hypothesisAssumptionsList) { + dataWithResources = addResourcesToFilteredData(dataWithResources, resources, hypothesisAssumptionsList, formulaConfiguration, microplanData, t); + } + const processedDataWithResources = dataWithResources.map((item, index) => { + if (index === 0) return item; + const featureIndex = keys.length - 1; + const newProperties = keys.concat(resources).reduce((acc, key, i) => (key !== "feature" ? { ...acc, [key]: item[i] } : acc), {}); + item[featureIndex] = { ...item[featureIndex], properties: newProperties }; + return item; + }); + + return { dataAvailabilityCheck, dataWithResources: processedDataWithResources }; + }; + + const updateFilterPropertiesCollector = (fileData, filterProperty, filterPropertiesCollector) => { + filterProperty.forEach((item) => { + Object.values(fileData?.data).forEach((data) => { + const filterPropertyIndex = data[0].indexOf(item); + if (filterPropertyIndex !== -1) data.slice(1).forEach((e) => filterPropertiesCollector.add(e[filterPropertyIndex])); + }); + }); + }; + + const setAvailabilityAndToastMessages = (dataAvailabilityCheck, combineList, files, setToast, t) => { + if (dataAvailabilityCheck === "true") { + const sectionWiseCheck = combineList.every((item) => Object.keys(files).includes(item)); + if (!sectionWiseCheck) dataAvailabilityCheck = "partial"; + } + + if (dataAvailabilityCheck === "initialStage" && (combineList.length === 0 || Object.keys(files).length === 0)) dataAvailabilityCheck = "false"; + + const toastMessages = { + false: { state: "warning", message: t("MAPPING_NO_DATA_TO_SHOW") }, + partial: { state: "warning", message: t("MAPPING_PARTIAL_DATA_TO_SHOW") }, + undefined: { state: "error", message: t("MAPPING_NO_DATA_TO_SHOW") }, + }; + + setToast(toastMessages[dataAvailabilityCheck]); + return dataAvailabilityCheck; + }; + + const setFinalDataAndProperties = ( + dataAvailabilityCheck, + setBoundary, + setFilter, + setBoundaryData, + setFilterData, + setFilterProperties, + setFilterSelections, + setFilterPropertyNames, + filterPropertiesCollector, + filterPropertieNameCollector, + virtualizationPropertiesCollector, + setChoroplethProperties, + resources + ) => { + setDataCompleteness(dataAvailabilityCheck); + setBoundary = calculateAggregateForTreeMicroplanWrapper(setBoundary); + setFilter = calculateAggregateForTreeMicroplanWrapper(setFilter); + setBoundaryData((previous) => ({ ...previous, ...setBoundary })); + setFilterData((previous) => ({ ...previous, ...setFilter })); + setFilterProperties([...filterPropertiesCollector]); + setFilterSelections([...filterPropertiesCollector]); + setFilterPropertyNames([...filterPropertieNameCollector]); + const tempVirtualizationPropertiesCollectorArray = [...virtualizationPropertiesCollector]; + if (tempVirtualizationPropertiesCollectorArray.length !== 0) + setChoroplethProperties([...tempVirtualizationPropertiesCollectorArray, ...(resources || [])]); + }; + + let setBoundary = {}; + let setFilter = {}; + const virtualizationPropertiesCollector = new Set(); + const filterPropertiesCollector = new Set(); + const filterPropertieNameCollector = new Set(); + const resources = state?.Resources?.find((item) => item.campaignType === campaignType)?.data; + const hypothesisAssumptionsList = microplanData?.hypothesis; + const formulaConfiguration = microplanData?.ruleEngine; + + let dataAvailabilityCheck = initializeDataAvailability(microplanData); + if (!dataAvailabilityCheck) return setToast({ state: "error", message: t("MAPPING_NO_DATA_TO_SHOW") }); + + const files = _.cloneDeep(microplanData.upload); + for (const fileData of files) { + if (!checkFileActivity(fileData) || !checkFileSection(fileData, filterDataOrigin)) { + dataAvailabilityCheck = "false"; + continue; + } + + if (!fileData?.fileType || !fileData?.section) continue; + + const schema = getFileValidationSchema(campaignType, fileData, validationSchemas); + dataAvailabilityCheck = handleFileDataError(dataAvailabilityCheck, fileData); + + const { latLngColumns, filterProperty } = processFileData( + fileData, + schema, + filterDataOrigin, + virtualizationPropertiesCollector, + filterPropertiesCollector, + filterPropertieNameCollector, + resources, + hypothesisAssumptionsList, + formulaConfiguration, + t + ); + + if (fileData?.data && Object.keys(fileData?.data).length > 0) { + switch (fileData?.fileType) { + case EXCEL: + const { dataAvailabilityCheck: excelDataAvailabilityCheck, hasLocationData, convertedData } = processExcelFile( + fileData, + latLngColumns, + resources, + formulaConfiguration, + hypothesisAssumptionsList, + schema, + t + ); + dataAvailabilityCheck = excelDataAvailabilityCheck; + if (hasLocationData) updateFilterPropertiesCollector(fileData, filterProperty, filterPropertiesCollector); + const { hierarchyLists: excelHierarchyLists, hierarchicalData: excelHierarchicalData } = processHierarchyAndData(hierarchy, convertedData); + if (filterDataOrigin?.boundriesDataOrigin?.includes(fileData.section)) + setBoundary = { ...setBoundary, [fileData.section]: { hierarchyLists: excelHierarchyLists, hierarchicalData: excelHierarchicalData } }; + else if (filterDataOrigin?.layerDataOrigin?.includes(fileData.section)) + setFilter = { ...setFilter, [fileData.section]: { hierarchyLists: excelHierarchyLists, hierarchicalData: excelHierarchicalData } }; + break; + case GEOJSON: + case SHAPEFILE: + const { dataAvailabilityCheck: geoJsonDataAvailabilityCheck, dataWithResources } = processGeoJsonFile( + fileData, + filterProperty, + resources, + formulaConfiguration, + hypothesisAssumptionsList, + t + ); + dataAvailabilityCheck = geoJsonDataAvailabilityCheck; + const { hierarchyLists: geoJsonHierarchyLists, hierarchicalData: geoJsonHierarchicalData } = processHierarchyAndData(hierarchy, [ + dataWithResources, + ]); + if (filterDataOrigin?.boundriesDataOrigin?.includes(fileData.section)) + setBoundary = { + ...setBoundary, + [fileData.section]: { hierarchyLists: geoJsonHierarchyLists, hierarchicalData: geoJsonHierarchicalData }, + }; + else if (filterDataOrigin?.layerDataOrigin?.includes(fileData.section)) + setFilter = { ...setFilter, [fileData.section]: { hierarchyLists: geoJsonHierarchyLists, hierarchicalData: geoJsonHierarchicalData } }; + break; + default: + break; + } + } + } + + const combineList = [...(filterDataOrigin?.boundriesDataOrigin || []), ...(filterDataOrigin?.layerDataOrigin || [])]; + dataAvailabilityCheck = setAvailabilityAndToastMessages(dataAvailabilityCheck, combineList, files, setToast, t); + + setFinalDataAndProperties( + dataAvailabilityCheck, + setBoundary, + setFilter, + setBoundaryData, + setFilterData, + setFilterProperties, + setFilterSelections, + setFilterPropertyNames, + filterPropertiesCollector, + filterPropertieNameCollector, + virtualizationPropertiesCollector, + setChoroplethProperties, + resources + ); +}; + +//prepare geojson to show on the map +export const prepareGeojson = (boundaryData, selection, style = {}) => { + if (!boundaryData || Object.keys(boundaryData).length === 0) return []; + let geojsonRawFeatures = []; + if (selection === "ALL") { + for (let data of Object.values(boundaryData)) { + const templist = fetchFeatures(data?.hierarchicalData, selection, [], style); + if (templist?.length !== 0) geojsonRawFeatures = [...geojsonRawFeatures, ...templist]; + } + } else if (Array.isArray(selection)) { + for (let data of Object.values(boundaryData)) { + const templist = fetchFeatures(data?.hierarchicalData, selection, [], style); + if (templist?.length !== 0) geojsonRawFeatures = [...geojsonRawFeatures, ...templist]; + } + } + + return geojsonRawFeatures.filter(Boolean); +}; +export const fetchFeatures = (data, parameter = "ALL", outputList = [], addOn = {}) => { + let tempStorage = []; + if (parameter === "ALL") { + // outputList(Object.values(data).flatMap(item=>item?.data?.feature)) + for (let [entityKey, entityValue] of Object.entries(data)) { + if (entityValue?.data?.feature) { + let feature = entityValue.data.feature; + feature.properties["name"] = entityKey; + feature.properties["addOn"] = addOn; + if (entityValue?.children) tempStorage = [...tempStorage, feature, ...fetchFeatures(entityValue?.children, parameter, outputList, addOn)]; + else tempStorage = [...tempStorage, feature]; + } else { + tempStorage = [...tempStorage, ...fetchFeatures(entityValue?.children, parameter, outputList, addOn)]; + } + } + return tempStorage; + } + if (Array.isArray(parameter)) { + for (let [entityKey, entityValue] of Object.entries(data)) { + if (parameter.includes(entityKey) && entityValue && entityValue.data && entityValue.data.feature) { + let feature = entityValue.data.feature; + feature.properties["name"] = entityKey; + feature.properties["addOn"] = addOn; + if (entityValue?.children) tempStorage = [...tempStorage, feature, ...fetchFeatures(entityValue?.children, parameter, outputList, addOn)]; + else tempStorage = [...tempStorage, feature]; + } + if (entityValue?.children) tempStorage = [...tempStorage, ...fetchFeatures(entityValue?.children, parameter, outputList, addOn)]; + } + return tempStorage; + } +}; + +export const addChoroplethProperties = (geojson, choroplethProperty, filteredSelection) => { + // Calculate min and max values of the property + const values = geojson.map((feature) => feature.properties[choroplethProperty]).filter((item) => !!item || item === 0) || []; + if (!values || values.length === 0) return []; + const convertedValues = values.map((item) => (!isNaN(item) ? item : 0)); + const minValue = Math.min(...convertedValues); + const maxValue = Math.max(...convertedValues); + + // Create a new geojson object + const newGeojson = geojson.map((feature) => { + const newFeature = { ...feature, properties: { ...feature.properties, addOn: { ...feature.properties.addOn } } }; + let color; + + if (choroplethProperty) { + color = interpolateColor(newFeature.properties[choroplethProperty], minValue, maxValue, MapChoroplethGradientColors); + } + + newFeature.properties.addOn.fillColor = color; + newFeature.properties.addOn.color = "rgba(0, 0, 0, 1)"; + if (!filteredSelection || filteredSelection.length === 0 || filteredSelection.includes(newFeature.properties.name)) { + newFeature.properties.addOn.fillOpacity = 1; + } else { + newFeature.properties.addOn.fillOpacity = 0.4; + newFeature.properties.addOn.opacity = 0.7; + } + + return newFeature; + }); + return newGeojson; +}; + +/** + * filterGeojsons : json + * filterSelection : array + * MapFilters : + */ +export const addFilterProperties = (filterGeojsons, filterSelections, filterPropertyNames, iconMapping) => { + try { + if (!filterGeojsons || !iconMapping || !filterSelections) return []; + let newFilterGeojson = []; + filterGeojsons.forEach((item) => { + if (filterPropertyNames && filterPropertyNames.length !== 0 && item.properties) { + let icon; + filterPropertyNames.forEach((name) => { + if (item.properties[name]) { + let temp = item.properties[name]; + if (!filterSelections.includes(temp)) return; + temp = iconMapping?.find((e) => e?.name == temp)?.icon?.marker; + let DynamicIcon = IconCollection?.[temp]; + if (typeof DynamicIcon === "function") { + icon = L.divIcon({ + className: "custom-svg-icon", + html: DynamicIcon({}), + iconAnchor: [25, 50], + }); + newFilterGeojson.push({ ...item, properties: { ...item?.properties, addOn: { ...item?.properties?.addOn, icon: icon } } }); + } else { + icon = DefaultMapMarker({}); + newFilterGeojson.push({ ...item, properties: { ...item?.properties, addOn: { ...item?.properties?.addOn, icon: icon } } }); + } + } + }); + } + return item; + }); + return newFilterGeojson; + } catch (error) { + console.error(error.message); + } +}; + +/** + * map: map + * geojson: geojson + * t: translator + */ + +export const addGeojsonToMap = (map, geojson, t) => { + try { + if (!map || !geojson) return false; + const geojsonLayer = L.geoJSON(geojson, { + style: (feature) => { + if (Object.keys(feature.properties.addOn).length !== 0) { + return feature.properties.addOn; + } + return { + weight: 2, + opacity: 1, + color: "rgba(176, 176, 176, 1)", + fillColor: "rgb(0,0,0,0)", + // fillColor: choroplethProperty ? color : "rgb(0,0,0,0)", + fillOpacity: 0, + // fillOpacity: choroplethProperty ? (feature?.properties?.style?.fillOpacity ? feature.properties.style.fillOpacity : 0.7) : 0, + }; + }, + pointToLayer: (feature, latlng) => { + if (feature.properties.addOn.icon) { + let icon = feature.properties.addOn.icon; + if (icon) { + return L.marker(latlng, { + icon: icon, + }); + } + } + return L.marker(latlng, { + icon: MapMarker(feature.properties.addOn), + }); + }, + onEachFeature: (feature, layer) => { + let popupContent; + popupContent = "
"; + popupContent += ""; + popupContent += `
${feature.properties["name"]}
`; + for (let prop in feature.properties) { + if (prop !== "name" && prop !== "addOn" && prop !== "feature") { + let data = feature.properties[prop] ? feature.properties[prop] : t("NO_DATA"); + popupContent += + ""; + } + } + popupContent += "
" + + t(prop) + + "" + + data + + "
"; + layer.bindPopup(popupContent, { + minWidth: "28rem", + padding: "0", + }); + // Adjust map here when pop up closes + layer.on("popupclose", () => { + map.fitBounds(geojsonLayer.getBounds()); + }); + layer.on({ + mouseover: (e) => { + const layer = e.target; + if (layer.feature.properties.addOn && !layer.feature.properties.addOn.child) { + return; + } + if (layer.setStyle) + layer.setStyle({ + weight: 2.7, + opacity: 1, + color: "rgba(255, 255, 255, 1)", + }); + // layer.openPopup(); + }, + mouseout: (e) => { + const layer = e.target; + if (layer.feature.properties.addOn && !layer.feature.properties.addOn.child) { + return; + } + if (layer.setStyle) { + if (layer.feature.properties.addOn && Object.keys(layer.feature.properties.addOn).length !== 0) + layer.setStyle({ + ...layer.feature.properties.addOn, + }); + else + layer.setStyle({ + weight: 2, + color: "rgba(176, 176, 176, 1)", + }); + } + // layer.closePopup(); + }, + }); + }, + }); + geojsonLayer.addTo(map); + return geojsonLayer; + } catch (error) { + console.error(error.message); + } +}; + +export const interpolateColor = (value, minValue, maxValue, colors) => { + // Handle case where min and max values are the same + if (minValue === maxValue) { + // Return a default color or handle the case as needed + return colors[0].color; + } + + // Normalize the value to a percentage between 0 and 100 + const percent = !isNaN(value) ? ((value - minValue) / (maxValue - minValue)) * 100 : 0; + // Find the two colors to interpolate between + let lowerColor, upperColor; + for (let i = 0; i < colors.length - 1; i++) { + if (!isNaN(percent) && percent >= colors[i].percent && percent <= colors[i + 1].percent) { + lowerColor = colors[i]; + upperColor = colors[i + 1]; + break; + } + } + // Interpolate between the two colors + const t = (percent - lowerColor.percent) / (upperColor.percent - lowerColor.percent); + return chroma.mix(lowerColor.color, upperColor.color, t, "lab").hex(); +}; + +// Find bounds for multiple geojson together +export const findBounds = (data, buffer = 0.1) => { + if (!Array.isArray(data) || data.length === 0) { + return null; + } + + // Initialize variables to store bounds + var minLat = Number.MAX_VALUE; + var maxLat = -Number.MAX_VALUE; + var minLng = Number.MAX_VALUE; + var maxLng = -Number.MAX_VALUE; + + // Iterate through the data to find bounds + data.forEach(function (feature) { + if (!feature || !feature.geometry || !feature.geometry.type || !feature.geometry.coordinates) { + return null; + } + + var coords = feature.geometry.coordinates; + var geometryType = feature.geometry.type; + + switch (geometryType) { + case "Point": + var coord = coords; + var lat = coord[1]; + var lng = coord[0]; + minLat = Math.min(minLat, lat); + maxLat = Math.max(maxLat, lat); + minLng = Math.min(minLng, lng); + maxLng = Math.max(maxLng, lng); + break; + case "MultiPoint": + coords.forEach(function (coord) { + var lat = coord[1]; + var lng = coord[0]; + minLat = Math.min(minLat, lat); + maxLat = Math.max(maxLat, lat); + minLng = Math.min(minLng, lng); + maxLng = Math.max(maxLng, lng); + }); + break; + case "LineString": + case "MultiLineString": + case "Polygon": + case "MultiPolygon": + coords.forEach(function (polygons) { + if ((geometryType === "Polygon" || geometryType === "MultiPolygon") && Array.isArray(polygons[0][0])) { + polygons.forEach(function (coordinates) { + coordinates.forEach(function (coord) { + if (!Array.isArray(coord) || coord.length !== 2 || typeof coord[0] !== "number" || typeof coord[1] !== "number") { + return null; + } + + var lat = coord[1]; + var lng = coord[0]; + minLat = Math.min(minLat, lat); + maxLat = Math.max(maxLat, lat); + minLng = Math.min(minLng, lng); + maxLng = Math.max(maxLng, lng); + }); + }); + } else { + polygons.forEach(function (coord) { + if (!Array.isArray(coord) || coord.length !== 2 || typeof coord[0] !== "number" || typeof coord[1] !== "number") { + return null; + } + + var lat = coord[1]; + var lng = coord[0]; + minLat = Math.min(minLat, lat); + maxLat = Math.max(maxLat, lat); + minLng = Math.min(minLng, lng); + maxLng = Math.max(maxLng, lng); + }); + } + }); + break; + default: + return null; + } + }); + + // Check if valid bounds found + if (minLat === Number.MAX_VALUE || maxLat === -Number.MAX_VALUE || minLng === Number.MAX_VALUE || maxLng === -Number.MAX_VALUE) { + return null; + } + // Apply buffer to bounds + minLat -= buffer; + maxLat += buffer; + minLng -= buffer; + maxLng += buffer; + + // Set bounds for the Leaflet map + var bounds = [ + [minLat, minLng], + [maxLat, maxLng], + ]; + + return bounds; +}; + +export const filterBoundarySelection = (boundaryData, boundarySelections) => { + if (Object.keys(boundaryData).length === 0 || Object.keys(boundarySelections).length === 0) return []; + let selectionList = []; + Object.values(boundarySelections).forEach((item) => (selectionList = [...selectionList, ...item.map((e) => e.name)])); + let childrenList = []; + const set1 = new Set(selectionList); + selectionList = selectionList.filter((item) => { + const children = findChildren([item], Object.values(boundaryData)?.[0]?.hierarchicalData); + if (children) { + let childrenKeyList = getAllKeys(children); + childrenList = [...childrenList, ...childrenKeyList]; + const nonePresent = childrenKeyList.every((item) => !set1.has(item)); + const allPresent = childrenKeyList.every((item) => set1.has(item)); + return nonePresent ? true : allPresent ? true : false; + } + return true; + }); + return { filteredSelection: selectionList, childrenList }; +}; + +// Recursive function to extract all keys +export const getAllKeys = (obj, keys = []) => { + for (let [key, value] of Object.entries(obj)) { + keys.push(key); + if (value.children) { + getAllKeys(value.children, keys); + } + } + return keys; +}; + +// Remove all layers from the map +export const removeAllLayers = (map, layer) => { + if (!map) return; + layer.forEach((layer) => { + map.removeLayer(layer); + }); +}; +// Map-Marker +export const MapMarker = (style = {}) => { + return L.divIcon({ + className: "custom-svg-icon", + html: DigitSvgs.Population(style), + iconAnchor: [25, 50], + }); +}; +export const DefaultMapMarker = (style = {}) => { + return L.divIcon({ + className: "custom-svg-icon", + html: IconCollection.DefaultMapMarkerSvg(style), + iconAnchor: [25, 50], + }); +}; + +export const disableMapInteractions = (map) => { + if (!map) return; + map.dragging.disable(); + map.scrollWheelZoom.disable(); + map.touchZoom.disable(); + map.doubleClickZoom.disable(); + map.boxZoom.disable(); + map.keyboard.disable(); +}; + +export const enableMapInteractions = (map) => { + if (!map) return; + map.dragging.enable(); + map.scrollWheelZoom.enable(); + map.touchZoom.enable(); + map.doubleClickZoom.enable(); + map.boxZoom.enable(); + map.keyboard.enable(); +}; diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/processHierarchyAndData.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/processHierarchyAndData.js new file mode 100644 index 00000000000..e5e1cc25f65 --- /dev/null +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/utils/processHierarchyAndData.js @@ -0,0 +1,352 @@ +export const processHierarchyAndData = (hierarchy, allData) => { + const hierarchyLists = {}; + let hierarchicalData = {}; + try { + // Process hierarchy + hierarchy.forEach((item) => { + hierarchyLists[item.boundaryType] = []; + }); + + // Process all sets of data + allData.forEach((data) => { + const dataHierarchicalData = {}; + + // Process data for this set + data.slice(1).forEach((row) => { + // Exclude the header row + let currentNode = dataHierarchicalData; + let parent = null; + hierarchy.forEach((item, index) => { + const boundaryType = item.boundaryType; + const dataIndex = data?.[0].indexOf(boundaryType); + if (dataIndex === -1) return; + const cellValue = row[dataIndex]; + if (!cellValue) return; + // Populate hierarchy lists + if (!hierarchyLists[boundaryType].includes(cellValue) && cellValue !== null && cellValue !== "" && cellValue !== undefined) { + hierarchyLists[boundaryType].push(cellValue); + } + + // Populate hierarchical data + if (!currentNode[cellValue]) { + currentNode[cellValue] = { + name: cellValue, + boundaryType: boundaryType, + children: {}, + data: null, + }; + } + + // Assign row data to the correct hierarchical level + if (cellValue) { + if (index === hierarchy.length - 1) { + currentNode[cellValue].data = createDataObject(data[0], row); + } else if (index + 1 < hierarchy.length) { + let nextHierarchyList = hierarchy.slice(index + 1); + let check = true; + nextHierarchyList.forEach((e) => { + const boundaryType = e.boundaryType; + const dataIndex = data?.[0].indexOf(boundaryType); + if (dataIndex === -1) return; + check = check && !row[dataIndex]; + }); + if (check) currentNode[cellValue].data = createDataObject(data[0], row); + } + } + currentNode = currentNode[cellValue].children; + }); + }); + + // Merge dataHierarchicalData into hierarchicalData + hierarchicalData = mergeHierarchicalData(hierarchicalData, dataHierarchicalData); + }); + + // Remove null element from children of each province + Object.values(hierarchicalData).forEach((country) => { + if (country.children[null]) { + country.data = country.children[null].data; + country.children[null] = undefined; + } + }); + } catch (error) { + console.error("Error in processing hierarchy and uploaded data: ", error.message); + // Return empty objects in case of error + return { hierarchyLists: {}, hierarchicalData: {} }; + } + + return { hierarchyLists, hierarchicalData }; + }; + + // Function to merge two hierarchical data objects + const mergeHierarchicalData = (data1, data2) => { + for (const [key, value] of Object.entries(data2)) { + if (!data1[key]) { + if (!value.data) value.data = {}; + data1[key] = value || {}; + } else { + data1[key].data = value.data; // Merge data + mergeHierarchicalData(data1[key].children, value.children); // Recursively merge children + } + if (data1[key].data?.feature) { + const { feature, ...temp } = value.data ? _.cloneDeep(value.data) : {}; + data1[key].data.feature.properties = { ...data1[key].data?.feature?.properties, ...temp }; + } + } + return data1; + }; + + // Function to create a data object with key-value pairs from headers and row data + const createDataObject = (headers, row) => { + const dataObject = {}; + headers.forEach((header, index) => { + dataObject[header] = row[index]; + }); + return dataObject; + }; + + // Find parent in hierarchy + export const findParent = (name, hierarchy, parent, accumulator = []) => { + if (!name || !hierarchy) return null; + for (let key in hierarchy) { + if (hierarchy[key]?.name == name) { + accumulator.push(parent); + } + if (hierarchy[key]?.children) { + let response = findParent(name, hierarchy[key]?.children, hierarchy[key], accumulator); + if (response) + response.forEach((item) => { + if (!accumulator.includes(item)) { + accumulator.push(item); + } + }); + } else { + return accumulator; + } + } + return accumulator; + }; + + /** + * + * @param {Array of parents} parents + * @param {hierarchycal Object data} hierarchy + * @returns An Array containing all the cummulative children + */ + export const findChildren = (parents, hierarchy) => { + const hierarchyTraveller = (parents, hierarchy, accumulator = {}) => { + let tempData = []; + if (accumulator && Object.keys(accumulator).length !== 0) + tempData = { + ...accumulator, + ...hierarchy.reduce((data, item) => { + if (parents.includes(item?.name) && item?.children) { + for (const key in item.children) { + if (!data[key]) { + data[key] = item.children[key]; + } + } + } + return data; + }, {}), + }; + else + tempData = hierarchy.reduce((data, item) => { + if (parents.includes(item?.name) && item?.children) { + for (const key in item.children) { + if (!data[key]) { + data[key] = item.children[key]; + } + } + } + return data; + }, {}); + for (let parent of hierarchy) { + if (parent?.children) tempData = hierarchyTraveller(parents, Object.values(parent?.children), tempData); + } + return tempData; + }; + return hierarchyTraveller(parents, Object.values(hierarchy), {}); + }; + + // Fetched data from tree + export const fetchDropdownValues = (boundaryData, hierarchy, boundarySelections, changedBoundaryType) => { + if ( + !hierarchy || + !boundaryData || + !boundarySelections || + hierarchy.length === 0 || + Object.keys(hierarchy).length === 0 || + Object.keys(boundaryData).length === 0 + ) + return []; + let TempHierarchy = _.cloneDeep(hierarchy); + if (!boundarySelections || Object.values(boundarySelections)?.every((item) => item?.length === 0)) { + for (let i in TempHierarchy) { + if (i === "0") { + TempHierarchy[0].dropDownOptions = findByBoundaryType( + TempHierarchy?.[0]?.boundaryType, + Object.values(boundaryData)?.[0]?.hierarchicalData + ).map((data, index) => ({ + name: data, + code: data, + boundaryType: TempHierarchy?.[0]?.boundaryType, + parentBoundaryType: undefined, + })); + } else TempHierarchy[i].dropDownOptions = []; + } + } else { + const currentHierarchy = findCurrentFilteredHierarchy(Object.values(boundaryData)?.[0]?.hierarchicalData, boundarySelections, TempHierarchy); + let currentDropdownIndex = 0; + hierarchy.forEach((e, index) => { + if (e && e?.boundaryType == changedBoundaryType) { + // && boundarySelections && boundarySelections[e.boundaryType] && boundarySelections[e.boundaryType].length !== 0) { + currentDropdownIndex = index; + } + }); + Object.entries(boundarySelections)?.forEach(([key, value]) => { + let currentindex = hierarchy.findIndex((e) => e?.boundaryType === key); + if (currentDropdownIndex !== currentindex) return; + let childIndex = hierarchy.findIndex((e) => e?.parentBoundaryType === key); + if (childIndex == -1) return; + if (TempHierarchy?.[childIndex]) { + let newDropDownValuesForChild = []; + for (const element of value) { + let tempStore = Object.values(findChildren([element.name], currentHierarchy)).map((value) => ({ + name: value?.name, + code: value?.name, + parent: element, + boundaryType: TempHierarchy[childIndex]?.boundaryType, + parentBoundaryType: TempHierarchy[childIndex]?.parentBoundaryType, + })); + if (tempStore) newDropDownValuesForChild.push(...tempStore); + } + // if (TempHierarchy[childIndex].dropDownOptions) + // TempHierarchy[childIndex].dropDownOptions = [...TempHierarchy[childIndex].dropDownOptions, ...newDropDownValuesForChild]; + TempHierarchy[childIndex].dropDownOptions = newDropDownValuesForChild; + } + }); + } + return TempHierarchy; + }; + + const findByBoundaryType = (boundaryType, hierarchy) => { + for (let [key, value] of Object.entries(hierarchy)) { + if (value?.boundaryType === boundaryType) return Object.keys(hierarchy).filter(Boolean); + if (value?.children) return findByBoundaryType(boundaryType, value?.children); + return []; + } + return []; + }; + + // makes a tree with the boundary selections as there might be duplicates in different branches that are not yet selected + const findCurrentFilteredHierarchy = (hierarchyTree, boundarySelections, hierarchy) => { + const newtree = constructNewHierarchyTree(hierarchy, hierarchyTree, boundarySelections); + return newtree; + }; + + const constructNewHierarchyTree = (hierarchy, oldTree, boundarySelection, level = 0) => { + // let newTree = { ...oldTree }; // Initialize a new hierarchy tree + let newTree = {}; // Initialize a new hierarchy tree + if (!hierarchy?.[level]) return; + const levelName = hierarchy[level].boundaryType; + + // Get the selections for this level from the boundary selection object + const selections = boundarySelection[levelName] || []; + // If there are selections for this level + if (selections.length > 0) { + // Construct the new hierarchy tree based on selections + for (const selection of selections) { + const { name } = selection; + // If the selection exists in the existing hierarchy tree + if (oldTree[name]) { + // Add the selected division to the new hierarchy tree + newTree[name] = { ...oldTree[name] }; + // If there are children, recursively construct the children + if (oldTree[name].children) { + oldTree[name].children; + const nonNullObject = Object.entries(oldTree[name].children).reduce((acc, [key, value]) => { + if (value.name !== null) { + acc[key] = value; + } + return acc; + }, {}); + newTree[name].children = constructNewHierarchyTree(hierarchy, nonNullObject, boundarySelection, level + 1); + } + } + } + } else { + const nonNullObject = Object.entries(oldTree).reduce((acc, [key, value]) => { + if (value.name !== null) { + acc[key] = value; + } + return acc; + }, {}); + newTree = nonNullObject; + } + + return newTree; + }; + + // Recursively calculates aggregate values for numerical properties within the `data` objects of each node in a hierarchical tree structure. + // Updates the `properties` object within the `feature` object of each node with the aggregate values, if present. + export const calculateAggregateForTree = (tree) => { + try { + function calculateAggregate(node) { + if (!node.children || Object.keys(node.children).length === 0) { + // if the node has no children, return a new node with its own data + return { ...node, data: { ...node.data } }; + } + + // Recursively calculate aggregate values for each child + const newChildren = {}; + + for (const childKey in node.children) { + const child = node.children[childKey]; + const newChild = calculateAggregate(child); + newChildren[childKey] = newChild; + } + + // Aggregate numerical values dynamically + const aggregate = {}; + for (const childKey in newChildren) { + const child = newChildren[childKey]; + for (const prop in child.data) { + if (typeof child.data[prop] === "number") { + aggregate[prop] = (aggregate[prop] || 0) + child.data[prop]; + } + } + } + + // Create a new node with updated data + const newNode = { + ...node, + data: { ...node.data, ...aggregate }, + children: newChildren, + }; + + // Update properties in the feature object + if (newNode.data.feature) { + newNode.data.feature.properties = { ...newNode.data.feature.properties, ...aggregate }; + } + + return newNode; + } + + const newTree = {}; + + // Iterate over each node object + for (const nodeKey in tree) { + const node = tree[nodeKey]; + // Calculate aggregate values for the current node + const newNode = calculateAggregate(node); + // Add the updated node to the new tree + newTree[nodeKey] = newNode; + } + + return newTree; + } catch (error) { + console.error("Failed to calculate treenode aggregates"); + return {}; + } + }; + \ No newline at end of file From a86ad275ae19e8eab469407c871713c9fe1e6d19 Mon Sep 17 00:00:00 2001 From: Swathi-eGov Date: Fri, 15 Nov 2024 19:42:06 +0530 Subject: [PATCH 2/4] updated css version --- .../micro-ui/web/micro-ui-internals/example/public/index.html | 2 +- .../micro-ui/web/micro-ui-internals/packages/css/package.json | 2 +- health/micro-ui/web/public/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/health/micro-ui/web/micro-ui-internals/example/public/index.html b/health/micro-ui/web/micro-ui-internals/example/public/index.html index b849bd4ccbd..eda1bcce69d 100644 --- a/health/micro-ui/web/micro-ui-internals/example/public/index.html +++ b/health/micro-ui/web/micro-ui-internals/example/public/index.html @@ -12,7 +12,7 @@ DIGIT - + diff --git a/health/micro-ui/web/micro-ui-internals/packages/css/package.json b/health/micro-ui/web/micro-ui-internals/packages/css/package.json index 25f1663e04d..d643fe1872c 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/css/package.json +++ b/health/micro-ui/web/micro-ui-internals/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@egovernments/digit-ui-health-css", - "version": "0.1.34", + "version": "0.1.35", "license": "MIT", "main": "dist/index.css", "author": "Jagankumar ", diff --git a/health/micro-ui/web/public/index.html b/health/micro-ui/web/public/index.html index a64db5c6649..40581aa7b5e 100644 --- a/health/micro-ui/web/public/index.html +++ b/health/micro-ui/web/public/index.html @@ -10,7 +10,7 @@ - + DIGIT HCM From 1f1bf1204044789c55cd4688cbc9dafd222ee062 Mon Sep 17 00:00:00 2001 From: Swathi-eGov Date: Fri, 15 Nov 2024 19:50:09 +0530 Subject: [PATCH 3/4] updated chooseactivity screen --- .../src/pages/employee/ChooseActivity.js | 145 +++++++++++------- 1 file changed, 87 insertions(+), 58 deletions(-) diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js index d7a4f263a23..e0079e8e040 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js @@ -9,7 +9,11 @@ const ChooseActivity = () => { const { campaignId, microplanId, } = Digit.Hooks.useQueryParams(); const tenantId = Digit.ULBService.getStateId(); const userInfo = Digit.UserService.getUser(); - const { isLoading: isLoadingPlanObject, data: planObject,} = Digit.Hooks.microplanv1.useSearchPlanConfig( + const [activityCardData, setActivityCardData] = useState([]); + + + + const { isLoading: isLoadingPlanObject, data: planObject, refetch: refetchPlanObject} = Digit.Hooks.microplanv1.useSearchPlanConfig( { PlanConfigurationSearchCriteria: { tenantId, @@ -17,10 +21,15 @@ const ChooseActivity = () => { }, }, { - enabled: microplanId ? true : false, + enabled: true, } ); + // // Watch for campaignId or microplanId changes to trigger the fetch + useEffect(() => { + refetchPlanObject(); + }, [microplanId, campaignId]); + const { isLoading: isBusinessServiceLoading, data: workflowData, } = Digit.Hooks.useCustomAPIHook({ url: "/egov-workflow-v2/egov-wf/businessservice/_search", params: { @@ -37,69 +46,86 @@ const ChooseActivity = () => { }, }); + // fetch the process instance for the current microplan to check if current process is done or not + const { isLoading:isProcessLoading, data: processData, revalidate } = Digit.Hooks.useCustomAPIHook({ + url: "/egov-workflow-v2/egov-wf/process/_search", + params: { + tenantId: tenantId, + history: true, + businessIds: microplanId, + }, + config: { + enabled: true, + select: (data) => { + return data?.ProcessInstances; + }, + }, + }); + - if(isLoadingPlanObject || isBusinessServiceLoading){ - return ; - } - else{ - // Merged function to disable a card based on user roles and available actions in the current state - const isCardDisabled = (validRoles = [], currentState, validStatuses = [],) => { - const userRoles = userInfo?.info?.roles?.map((roleData) => roleData?.code) || []; + useEffect(() => { + if(planObject && workflowData && processData){ + const updatedActivityCardData = [ + { + name: t("VALIDATE_N_APPROVE_POPULATION_DATA"), + link: `pop-inbox?campaignId=${campaignId}µplanId=${microplanId}`, + doneLabel: isProcessDone(processData, "APPROVE_CENSUS_DATA") && "CENSUS_VALIDATED_LABEL", + icon: , + disable: isCardDisabled(["POPULATION_DATA_APPROVER", "ROOT_POPULATION_DATA_APPROVER"], isProcessDone(processData, "APPROVE_CENSUS_DATA"), ["EXECUTION_TO_BE_DONE","CENSUS_DATA_APPROVAL_IN_PROGRESS"]), + optionKey: "VALIDATE_N_APPROVE_POPULATION_DATA" + }, + { + name: t("ASSIGN_FACILITIES_TO_VILLAGE"), + link: `assign-facilities-to-villages?campaignId=${campaignId}µplanId=${microplanId}`, + doneLabel: isProcessDone(processData, "FINALIZE_CATCHMENT_MAPPING") && "FACILITY_CATCHEMENT_DONE_LABEL", + icon: , + disable: isCardDisabled(["FACILITY_CATCHMENT_MAPPER", "ROOT_FACILITY_CATCHMENT_MAPPER"], isProcessDone(processData, "FINALIZE_CATCHMENT_MAPPING"), ["CENSUS_DATA_APPROVED"]), + optionKey: "ASSIGN_FACILITIES_TO_VILLAGE" + }, + { + name: t("VALIDATE_N_APPROVE_MICROPLAN_ESTIMATIONS"), + link: `plan-inbox?campaignId=${campaignId}µplanId=${microplanId}`, + doneLabel: isProcessDone(processData, "APPROVE_ESTIMATIONS") && "ESTIMATIONS_APPROVED_LABEL", + icon: , + disable: isCardDisabled(["PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], isProcessDone(processData, "APPROVE_ESTIMATIONS"), ["RESOURCE_ESTIMATION_IN_PROGRESS"]), + optionKey: "VALIDATE_N_APPROVE_MICROPLAN_ESTIMATIONS" + }, + { + name: t("GEOSPATIAL_MAP_VIEW"), + link: `map-view?campaignId=${campaignId}µplanId=${microplanId}`, + icon: , + disable: isCardDisabled(["POPULATION_DATA_APPROVER","ROOT_POPULATION_DATA_APPROVER","FACILITY_CATCHMENT_MAPPER","ROOT_FACILITY_CATCHMENT_MAPPER","MICROPLAN_VIEWER","PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS","RESOURCE_ESTIMATIONS_APPROVED"]), + optionKey: "GEOSPATIAL_MAP_VIEW" + }, + ]; + setActivityCardData(updatedActivityCardData); + } + }, [planObject, workflowData, processData]); + + +// Merged function to disable a card based on user roles +const isCardDisabled = (validRoles = [], isProcessDone, validStatuses = [],) => { + const userRoles = userInfo?.info?.roles?.map((roleData) => roleData?.code) || []; - // Check if user has any valid roles - const hasValidRole = validRoles?.length > 0 && validRoles?.some(role => userRoles.includes(role)); + // Check if user has any valid roles + const hasValidRole = validRoles?.length > 0 && validRoles?.some(role => userRoles.includes(role)); - // Check if there are valid actions in the current state that match user roles - const hasValidNextAction = currentState?.actions?.some(action => - action?.roles.some(role => userRoles?.includes(role)) - ); + const hasValidCurrentStatus = validStatuses?.includes(planObject?.status); + + // Disable if either hasValidRole or hasValidNextAction is false + return !(hasValidRole &&(isProcessDone || hasValidCurrentStatus) /*hasValidNextAction*/ ); +}; - const hasValidCurrentStatus = validStatuses?.includes(planObject?.status); - // Disable if either hasValidRole or hasValidNextAction is false - return !(hasValidRole && hasValidCurrentStatus) /*hasValidNextAction*/; +// Function to check if process is done for the current card +const isProcessDone = (ProcessInstances, process) => { + // Iterate over each process instance in the array + return ProcessInstances.some((instance) => instance.action === process); }; // Usage in activityCardData - const activityCardData = [ - { - name: t("VALIDATE_N_APPROVE_POPULATION_DATA"), - link: `pop-inbox?campaignId=${campaignId}µplanId=${microplanId}`, - icon: , - disable: isCardDisabled(["POPULATION_DATA_APPROVER", "ROOT_POPULATION_DATA_APPROVER"], workflowData, ["EXECUTION_TO_BE_DONE","CENSUS_DATA_APPROVAL_IN_PROGRESS"]), - optionKey: "VALIDATE_N_APPROVE_POPULATION_DATA" - }, - { - name: t("ASSIGN_FACILITIES_TO_VILLAGE"), - link: `assign-facilities-to-villages?campaignId=${campaignId}µplanId=${microplanId}`, - icon: , - disable: isCardDisabled(["FACILITY_CATCHMENT_MAPPER", "ROOT_FACILITY_CATCHMENT_MAPPER"], workflowData, ["CENSUS_DATA_APPROVED"]), - optionKey: "ASSIGN_FACILITIES_TO_VILLAGE" - }, - { - name: t("VALIDATE_N_APPROVE_MICROPLAN_ESTIMATIONS"), - link: `plan-inbox?campaignId=${campaignId}µplanId=${microplanId}`, - icon: , - disable: isCardDisabled(["PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS"]), - optionKey: "VALIDATE_N_APPROVE_MICROPLAN_ESTIMATIONS" - }, - { - name: t("GEOSPATIAL_MAP_VIEW"), - link: `map-view?campaignId=${campaignId}µplanId=${microplanId}`, - icon: , - disable: isCardDisabled(["POPULATION_DATA_APPROVER","ROOT_POPULATION_DATA_APPROVER","FACILITY_CATCHMENT_MAPPER","ROOT_FACILITY_CATCHMENT_MAPPER","MICROPLAN_VIEWER","PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS","RESOURCE_ESTIMATIONS_APPROVED"]), - optionKey: "GEOSPATIAL_MAP_VIEW" - }, - //commenting this as for now we are not showing viewer - // { - // name: t("VIEW_MICROPLAN_ESTIMATIONS"), - // link: `pop-inbox?campaignId=${campaignId}µplanId=${microplanId}`, - // icon: , - // disable: isCardDisabled(["MICROPLAN_VIEWER"], workflowData), - // optionKey: "VIEW_MICROPLAN_ESTIMATIONS" - // } - ]; + const updatePlan = async (req) => { const planRes = await Digit.CustomService.getResponse({ @@ -128,12 +154,15 @@ const ChooseActivity = () => { } } + if(isLoadingPlanObject || isBusinessServiceLoading || isProcessLoading){ + return ; + } + return ( ); } -} export default ChooseActivity; \ No newline at end of file From 9513fd30aeb544b8a389527a07aed639a1afa219 Mon Sep 17 00:00:00 2001 From: Swathi-eGov Date: Sat, 16 Nov 2024 21:28:29 +0530 Subject: [PATCH 4/4] fixed syntax issue --- .../modules/microplan/src/pages/employee/ChooseActivity.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js index 3f3ecc208aa..03b66092d31 100644 --- a/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js +++ b/health/micro-ui/web/micro-ui-internals/packages/modules/microplan/src/pages/employee/ChooseActivity.js @@ -93,8 +93,7 @@ const ChooseActivity = () => { { name: t("GEOSPATIAL_MAP_VIEW"), link: `map-view?campaignId=${campaignId}µplanId=${microplanId}`, - icon: , - fill={isCardDisabled(["POPULATION_DATA_APPROVER","ROOT_POPULATION_DATA_APPROVER","FACILITY_CATCHMENT_MAPPER","ROOT_FACILITY_CATCHMENT_MAPPER","MICROPLAN_VIEWER","PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS","RESOURCE_ESTIMATIONS_APPROVED"]) ? "#C5C5C5" : "#C84C0E"}/>, + icon: , disable: isCardDisabled(["POPULATION_DATA_APPROVER","ROOT_POPULATION_DATA_APPROVER","FACILITY_CATCHMENT_MAPPER","ROOT_FACILITY_CATCHMENT_MAPPER","MICROPLAN_VIEWER","PLAN_ESTIMATION_APPROVER", "ROOT_PLAN_ESTIMATION_APPROVER"], workflowData, ["RESOURCE_ESTIMATION_IN_PROGRESS","RESOURCE_ESTIMATIONS_APPROVED"]), optionKey: "GEOSPATIAL_MAP_VIEW" },