diff --git a/web/rainmaker/packages/employee/src/modules/Routes/employee.js b/web/rainmaker/packages/employee/src/modules/Routes/employee.js index a040c7d596..cb70031abb 100644 --- a/web/rainmaker/packages/employee/src/modules/Routes/employee.js +++ b/web/rainmaker/packages/employee/src/modules/Routes/employee.js @@ -13,6 +13,7 @@ import { ImageModalDisplay } from "modules/common"; import { PrivacyPolicy } from "modules/common"; import LandingPage from "modules/employee/LandingPage"; import Inbox from "modules/employee/Inbox"; +import ModuleInbox from "modules/employee/ModuleInbox"; import MDMS from "modules/common/MDMS"; import Home from "modules/employee/Home"; import Report from "modules/employee/reports/report"; @@ -120,6 +121,18 @@ const routes = [ isHomeScreen: true, }, }, + { + path: ":moduleName/inbox", + component: ModuleInbox, + needsAuthentication: true, + options: { + hideFooter: true, + redirectionUrl, + title: "Inbox", + hideTitle: true, + isHomeScreen: true, + }, + }, { path: "image", component: ImageModalDisplay, diff --git a/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredFilter.js b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredFilter.js new file mode 100644 index 0000000000..9d990f69c4 --- /dev/null +++ b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredFilter.js @@ -0,0 +1,102 @@ +import React from "react"; +import { MultiSelectDropdown } from "egov-ui-kit/components"; +import Label from "egov-ui-kit/utils/translationNode"; +import "../Inbox/components/Filter/index.css"; + +/** + * FilteredFilter - Module filter component for module-specific inbox + * Difference from original Filter: + * - Module dropdown is disabled when preselectedModule is set + * - This component is used for /employee/:moduleName/inbox route + */ + +const FilteredFilter = ({ filter, handleChangeFilter, clearFilter, preselectedModule }) => { + return ( +
+
+
+ { handleChangeFilter('moduleFilter', e.target.value) }} + floatingLabelText={
+
+ { handleChangeFilter('businessServiceFilter', e.target.value) }} + floatingLabelText={
+
+ { + handleChangeFilter('localityFilter', e.target.value) + }} + floatingLabelText={
+
+ } + className="filter-fields" + dropDownData={filter.statusFilter.dropdownData} + onChange={(e, index, value) => { + + handleChangeFilter('statusFilter', e.target.value) }} + value={filter.statusFilter.selectedValue} + underlineStyle={{ + position: "absolute", + bottom: -1, + borderBottom: "1px solid #FE7A51", + width: "100%" + }} + prefix ={"COMMON_"} + /> +
+
+
+
+
+
+
+
+
+ ); +}; + +export default FilteredFilter; diff --git a/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredInbox.js b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredInbox.js new file mode 100644 index 0000000000..1c1c9192f8 --- /dev/null +++ b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredInbox.js @@ -0,0 +1,133 @@ +import LoadingIndicator from "egov-ui-framework/ui-molecules/LoadingIndicator"; +import MenuButton from "egov-ui-framework/ui-molecules/MenuButton"; +import { setRoute } from "egov-ui-framework/ui-redux/app/actions"; +import { prepareFinalObject } from "egov-ui-framework/ui-redux/screen-configuration/actions"; +import { fetchLocalizationLabel } from "egov-ui-kit/redux/app/actions"; +import { getLocale, getTenantId } from "egov-ui-kit/utils/localStorageUtils"; +import Label from "egov-ui-kit/utils/translationNode"; +import React, { Component } from "react"; +import { connect } from "react-redux"; +import FilterDialog from "../Inbox/components/FilterDialog"; +import FilteredTableData from "./FilteredTableData"; +import "../Inbox/index.css"; + +/** + * FilteredInbox - Module-specific inbox component + * Shows only workflow tasks for a specific module (e.g., PT, TL, WS) + * Does NOT show module cards (ServiceList) + * Module filter is auto-selected based on URL parameter + */ +class FilteredInbox extends Component { + state = { + actionList: [], + hasWorkflow: false, + filterPopupOpen: false + }; + + componentDidMount = () => { + const { fetchLocalizationLabel } = this.props + const tenantId = getTenantId(); + fetchLocalizationLabel(getLocale(), tenantId, tenantId); + } + + + componentWillReceiveProps(nextProps) { + const { menu } = nextProps; + const workflowList = menu && menu.filter((item) => item.name === "rainmaker-common-workflow"); + if (workflowList && workflowList.length > 0) { + this.setState({ + hasWorkflow: true, + }); + } else { + this.setState({ + hasWorkflow: false, + }); + } + + const list = menu && menu.filter((item) => item.url === "card"); + this.setState({ + actionList: list, + }); + } + + handleClose = () => { + this.setState({ filterPopupOpen: false }); + }; + + onPopupOpen = () => { + this.setState({ filterPopupOpen: true }); + } + + render() { + const { name, history, setRoute, menu, Loading, preselectedModule, businessServices } = this.props; + const { hasWorkflow } = this.state; + const a = menu ? menu.filter(item => item.url === "quickAction") : []; + const downloadMenu = a.map((obj, index) => { + return { + labelName: obj.displayName, + labelKey: `ACTION_TEST_${obj.displayName.toUpperCase().replace(/[._:-\s\/]/g, "_")}`, + link: () => { + if (obj.navigationURL === "tradelicence/apply") { + this.props.setRequiredDocumentFlag(); + } + if (obj.navigationURL && obj.navigationURL.includes('digit-ui')) { + window.location.href = obj.navigationURL; + return; + } else { + setRoute(obj.navigationURL) + } + } + } + }) + const { isLoading } = Loading; + const buttonItems = { + label: { labelName: "Take Action", labelKey: "INBOX_QUICK_ACTION" }, + rightIcon: "arrow_drop_down", + props: { variant: "outlined", style: { marginLeft: 5, marginRight: 15, marginTop: 10, backgroundColor: "#FE7A51", color: "#fff", border: "none", height: "40px", width: "200px" } }, + menu: downloadMenu + } + + return ( +
+
+ {Loading && isLoading && } +
+
+
+ +
+
+ + {/* ServiceList (module cards) hidden for module-specific inbox */} + + {hasWorkflow && } + +
+ ); + } +} + +const mapStateToProps = (state) => { + const { auth, app, screenConfiguration } = state; + const { menu } = app; + const { userInfo } = auth; + const name = auth && userInfo.name; + const { preparedFinalObject } = screenConfiguration; + const { Loading = {} } = preparedFinalObject; + const { isLoading } = Loading; + return { name, menu, Loading, isLoading }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + setRoute: url => dispatch(setRoute(url)), + fetchLocalizationLabel: (locale, tenantId, module) => dispatch(fetchLocalizationLabel(locale, tenantId, module)), + setRequiredDocumentFlag: () => dispatch(prepareFinalObject("isRequiredDocuments", true)) + }; +} + +export default connect( + mapStateToProps, mapDispatchToProps +)(FilteredInbox); diff --git a/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredTableData.js b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredTableData.js new file mode 100644 index 0000000000..dd37ad3bd7 --- /dev/null +++ b/web/rainmaker/packages/employee/src/modules/employee/ModuleInbox/FilteredTableData.js @@ -0,0 +1,1022 @@ +import Hidden from "@material-ui/core/Hidden"; +import { withStyles } from "@material-ui/core/styles"; +import Tab from "@material-ui/core/Tab"; +import Tabs from "@material-ui/core/Tabs"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import FilterListIcon from '@material-ui/icons/FilterList'; +import { prepareFinalObject } from "egov-ui-framework/ui-redux/screen-configuration/actions"; +import { getLocaleLabels, transformById } from "egov-ui-framework/ui-utils/commons"; +import TextFieldIcon from "egov-ui-kit/components/TextFieldIcon"; +import { toggleSnackbarAndSetText } from "egov-ui-kit/redux/app/actions"; +import { httpRequest, multiHttpRequest } from "egov-ui-kit/utils/api"; +import { getLocale, getLocalization, getTenantId, localStorageGet, localStorageSet } from "egov-ui-kit/utils/localStorageUtils"; +import Label from "egov-ui-kit/utils/translationNode"; +import cloneDeep from "lodash/cloneDeep"; +import get from "lodash/get"; +import isEmpty from "lodash/isEmpty"; +import orderBy from "lodash/orderBy"; +import set from "lodash/set"; +import uniq from "lodash/uniq"; +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { Taskboard } from "../Inbox/components/actionItems"; +import FilteredFilter from "./FilteredFilter"; +import InboxData from "../Inbox/components/Table"; +import "../Inbox/components/TableData/index.css"; + +const getWFstatus = (status) => { + switch (status) { + case "INITIATED": + return "Initiated"; + case "CORRECTIONPENDING": + case "PENDING_FOR_CITIZEN_ACTION": + return "Pending for Citizen Action"; + case "OPEN": + case "APPLIED": + case "DOCUMENTVERIFY": + case "PENDING_FOR_DOCUMENT_VERIFICATION": + return "Pending for Document Verification"; + case "REJECTED": + return "REJECTED"; + case "DOCVERIFIED": + case "FIELDINSPECTION": + case "PENDING_FOR_FIELD_INSPECTION": + return "Pending for Field Inspection"; + case "PENDING_APPROVAL_FOR_CONNECTION": + return "Pending Approval for Connection" + case "PENDINGPAYMENT": + case "PENDING_FOR_PAYMENT": + return "Pending for Payment"; + case "PAID": + case "VERIFIED": + case "FIELDVERIFIED": + case "APPROVALPENDING": + case "PENDING_FOR_APPROVAL": + case "PENDINGAPPROVAL": + return "Pending for Approval"; + case "PENDING_FOR_CONNECTION_ACTIVATION": + return "Pending for Connection Activation"; + case "CONNECTION_ACTIVATED": + return "Connnection Activated" + case "APPROVED": + return "Approved"; + case "FIELDINSPECTION_PENDING": + return "Field Inspection Pending" + default: + return 'NA'; + } +}; + +const styles = (theme) => ({ + textColorPrimary: { + color: "red", + }, +}); + +let localizationLabels = transformById( + JSON.parse(getLocalization(`localization_${getLocale()}`)), + "code" +); + +class TableData extends Component { + state = { + businessServiceSla: {}, + searchFilter: { + value: '', + typing: false + }, + filter: { + localityFilter: { + selectedValue: ["ALL"], + dropdownData: [ + { + value: "ALL", + label: "CS_INBOX_SELECT_ALL", + } + ] + }, + moduleFilter: { + selectedValue: ["ALL"], + dropdownData: [ + { + value: "ALL", + label: "CS_INBOX_SELECT_ALL", + } + ] + }, + businessServiceFilter: { + selectedValue: ["ALL"], + dropdownData: [ + { + value: "ALL", + label: "CS_INBOX_SELECT_ALL", + } + ] + }, + statusFilter: { + selectedValue: ["ALL"], + dropdownData: [ + { + value: "ALL", + label: "CS_INBOX_SELECT_ALL", + } + ] + } + }, + showFilter: false, + value: 0, + totalRowCount: 0, + tabData: [{ label: "COMMON_INBOX_TAB_ASSIGNED_TO_ME", dynamicArray: [0] } + , { label: "COMMON_INBOX_TAB_ALL", dynamicArray: [0] }], + taskboardData: [{ head: 0, body: "WF_TOTAL_TASK", color: "rgb(171,211,237)", baseColor: "rgb(53,152,219)" }, + { head: 0, body: "WF_TOTAL_NEARING_SLA", color: "rgb(238, 167, 58 ,0.38)", baseColor: "#EEA73A" }, + { head: 0, body: "WF_ESCALATED_SLA", color: "rgb(244, 67, 54 ,0.38)", baseColor: "#F44336" }], + taskboardLabel: '', + inboxData: [{ headers: [], rows: [] }], + initialInboxData: [{ headers: [], rows: [] }], + moduleName: "", + loaded: true, // UI loads immediately + dataLoading: false, // API loading state for spinner + showLocality: !Boolean(localStorage.getItem('disableLocality')), + color: "rgb(53,152,219)", + timeoutForTyping: false, + loadLocalityForInitialData: false, + showLoadingTaskboard:false + }; + + getUniqueList = (list = []) => { + let newList = []; + list.map(element => { + if (!JSON.stringify(newList).includes(JSON.stringify(element))) { + newList.push(element); + } + }) + return newList; + } + checkMatch = (row, value) => { + if (value.length <= 2) { + return true; + } + if (row[5].hiddenField.length !== 6) { + if (row[0].text.toLowerCase().includes(value.toLowerCase()) || + row[3].text.props.label.toLowerCase().includes(value.toLowerCase()) || + String(row[4].text).toLowerCase().includes(value.toLowerCase()) || + getLocaleLabels("", `CS_COMMON_INBOX_${row[2].text.props.label.split('_')[1]}`).toLowerCase().includes(value.toLowerCase(), localizationLabels) || + getLocaleLabels("", row[1].text.props.label).toLowerCase().includes(value.toLowerCase(), localizationLabels) || + getLocaleLabels("", row[2].text.props.label).toLowerCase().includes(value.toLowerCase(), localizationLabels) + ) { + return true; + } + + + } + if ( + row[5].hiddenField[0].includes(value.toLowerCase()) || + row[5].hiddenField[1].includes(value.toLowerCase()) || + row[5].hiddenField[2].includes(value.toLowerCase()) || + row[5].hiddenField[3].includes(value.toLowerCase()) || + row[5].hiddenField[4].includes(value.toLowerCase()) || + row[5].hiddenField[5].includes(value.toLowerCase()) + + ) { + return true; + } + return false; + } + handleChangeSearch = (value) => { + this.setState({ + searchFilter: { value, typing: true } + }) + } + + checkSLA = (taskboardLabel, row) => { + const MAX_SLA = this.state.businessServiceSla[row[2].text.props.label.split('_')[1]]; + if (taskboardLabel === '' || taskboardLabel === 'WF_TOTAL_TASK') { + return true; + } else if ((taskboardLabel === 'WF_TOTAL_NEARING_SLA' && row[4].text > 0 && row[4].text <= (MAX_SLA - MAX_SLA / 3))) { + return true; + } else if ((taskboardLabel === 'WF_ESCALATED_SLA' && row[4].text <= 0)) { + return true; + } else { + return false; + } + } + /** + * Check if businessService matches module pattern + * Module "PT" matches: "PT", "PT.CREATE", "PT.MUTATION" + * Module "TL" matches: "NewTL", "ModifyTL", etc. + */ + matchesModulePattern = (businessService, modulePattern) => { + // Exact match + if (businessService === modulePattern) { + return true; + } + // Prefix match with dot: PT matches PT.CREATE + if (businessService.startsWith(modulePattern + '.')) { + return true; + } + // Contains pattern for New/Modify: TL matches NewTL, ModifyTL + if (businessService.includes(modulePattern) && + (businessService.startsWith('New') || businessService.startsWith('Modify'))) { + return true; + } + return false; + } + + checkRow = (row, filter, searchFilter, taskboardLabel) => { + const businessService = row[2].text.props.label.split('_')[1]; + + // If we have preselectedModule (from URL), skip module matching check + // because we already filtered at API level by businessServices + const { preselectedModule } = this.props; + const moduleMatches = preselectedModule + ? true // Always pass - data is already filtered by API + : (filter.moduleFilter.selectedValue.includes('ALL') || + filter.moduleFilter.selectedValue.some(module => this.matchesModulePattern(businessService, module))); + + // Business Service filter - filter on frontend based on dropdown selection + const businessServiceMatches = filter.businessServiceFilter.selectedValue.includes('ALL') || + filter.businessServiceFilter.selectedValue.includes(row[0].subtext); + + if ((filter.localityFilter.selectedValue.includes('ALL') || filter.localityFilter.selectedValue.includes(row[1].text.props.label)) && + moduleMatches && + businessServiceMatches && + (filter.statusFilter.selectedValue.includes('ALL') || filter.statusFilter.selectedValue.includes(row[2].text.props.label.split('_')[2])) && + (searchFilter.value === '' || this.checkMatch(row, searchFilter.value) + ) + ) { + return true; + } + return false; + } + convertMillisecondsToDays = (milliseconds) => { + return (milliseconds / (1000 * 60 * 60 * 24)); + } + applyFilter = (inboxData) => { + this.showLoading(); + let initialInboxData = inboxData ? cloneDeep(inboxData) : cloneDeep(this.state.initialInboxData); + const { filter, searchFilter, taskboardLabel, totalRowCount } = this.state; + let ESCALATED_SLA = []; + let NEARING_SLA = []; + let totalRows = [] + if (initialInboxData.length === 2) { + initialInboxData.map((row, ind) => { + row.rows = row.rows.filter((eachRow) => { + let isValid = this.checkRow(eachRow, filter, searchFilter, taskboardLabel); + if (isValid && ind === 1) { + let MAX_SLA = this.state.businessServiceSla[eachRow[2].text.props.label.split('_')[1]]; + if (eachRow[4].text <= 0) { + ESCALATED_SLA.push(eachRow[4].text); + } + if (eachRow[4].text > 0 && eachRow[4].text <= (MAX_SLA - MAX_SLA / 3)) { + NEARING_SLA.push(eachRow[4].text); + } + totalRows.push(1); + } + if (isValid) { + return this.checkSLA(taskboardLabel, eachRow); + } + return isValid; + } + + ) + }) + } + + if (initialInboxData.length === 2) { + initialInboxData.map((row, ind) => { + row.rows = row.rows.filter((eachRow) => { + let isValid = this.checkSLA(taskboardLabel, eachRow); + return isValid; + } + ) + }) + } + + + + let { taskboardData, tabData , showLoadingTaskboard } = this.state; +if(totalRows.length == totalRowCount && showLoadingTaskboard==false){ + + this.setState({showLoadingTaskboard:true}) +} + taskboardData[0].head = showLoadingTaskboard?totalRows.length: totalRowCount; + taskboardData[1].head = totalRows.length == totalRowCount || showLoadingTaskboard ? NEARING_SLA.length : 'LOADING'; + taskboardData[2].head = totalRows.length == totalRowCount || showLoadingTaskboard ? ESCALATED_SLA.length : 'LOADING'; + tabData[0].dynamicArray = [initialInboxData[0].rows.length]; + tabData[1].dynamicArray = [showLoadingTaskboard?totalRows.length: totalRowCount]; + this.hideLoading(); + return { + inboxData: initialInboxData, + taskboardData, + tabData, + } + + } + handleChangeFilter = (filterName, value) => { + const filter = { ...this.state.filter } + + if (value.includes('ALL') && this.state.filter[filterName].selectedValue.includes('ALL') && value.length > 1) { + value.shift() + } else if (value.includes('ALL') && value.length > 1 && !this.state.filter[filterName].selectedValue.includes('ALL')) { + value = ['ALL'] + } + filter[filterName].selectedValue = value + this.setState({ filter }); + } + clearFilter = () => { + const { preselectedModule } = this.props; + const initialInboxData = cloneDeep(this.state.initialInboxData); + const tempObject = cloneDeep(this.state.initialInboxData); + const filter = { + localityFilter: { + selectedValue: ["ALL"], + dropdownData: [...this.state.filter.localityFilter.dropdownData] + }, + moduleFilter: { + // Keep preselected module if it exists, otherwise clear to ALL + selectedValue: preselectedModule ? [preselectedModule] : ["ALL"], + dropdownData: [...this.state.filter.moduleFilter.dropdownData] + }, + businessServiceFilter: { + selectedValue: ["ALL"], + dropdownData: [...this.state.filter.businessServiceFilter.dropdownData] + }, + statusFilter: { + selectedValue: ["ALL"], + dropdownData: [...this.state.filter.statusFilter.dropdownData] + } + } + + this.setState({ + searchFilter: { + value: '', typing: false + }, filter, inboxData: initialInboxData, + initialInboxData: tempObject + }); + } + prepareInboxDataRows = async (data, all, loadLocality = false) => { + const { toggleSnackbarAndSetText, businessServices: configuredBusinessServices } = this.props; + const uuid = get(this.props, "userInfo.uuid"); + if (isEmpty(data)) return{ allData: [], assignedToMe: [] }; + let businessServices = []; + let businessIds = []; + let ptApplicationNo = [] + if (this.state.showLocality && loadLocality) { + businessIds = data.map((item) => { + businessServices.push(item.moduleName); + if (item.moduleName == 'PT') { + ptApplicationNo.push(item.businessId); + } + return item.businessId; + }); + } + // const businessServiceData = this.getBussinessServiceData(); + // const modules =this.state.showLocality&& + // businessServiceData && + // businessServiceData.map((item, index) => { + // return item.business; + // })||[]; + // const uniqueModules = uniq(modules) + const uniqueModules = uniq(businessServices) + let localitymap = []; + if (this.state.showLocality && loadLocality) { + try { + let requestBodies = [] + let endpoints = [] + let queries = [] + uniqueModules.map((uniqueModule, ind) => { + // if (uniqueModule == "PT") { + // const acknowledgementIds = [...ptApplicationNo]; + // for (let i = 0; i <= ptApplicationNo.length + 50; i += 50) { + // let acknowledgementId = acknowledgementIds.splice(0, 50); + // if (acknowledgementId && acknowledgementId.length > 0) { + // const query = [{ key: "tenantId", value: getTenantId() }, + // { key: "acknowledgementIds", value: acknowledgementId.join(',') }] + // requestBodies.push(undefined) + // queries.push(query) + // endpoints.push("property-services/property/_search") + // } + // } + // } else if (uniqueModule == "pt-services" || uniqueModule == "pgr-services") { + + // } else { + requestBodies.push({ + searchCriteria: { + "referenceNumber": businessIds + } + }) + queries.push([]) + endpoints.push(`egov-searcher/locality/${uniqueModule}/_get`) + // } + + }) + const resp = await multiHttpRequest(endpoints, "search", queries, requestBodies) + resp && resp.map(res => { + if (res && res.Localities) { + let locality = res.Localities; + localitymap = [...localitymap, ...locality]; + } else if (res && res.Properties) { + const localities = res.Properties.map(property => { + return { + "referencenumber": property.acknowldgementNumber, + "locality": property.address.locality.code + } + }) + localitymap = [...localitymap, ...localities]; + } + }); + /* for (var i = 0; i < uniqueModules.length; i++) { + try { + if (uniqueModules[i] != 'PT') { + const requestBody = { + searchCriteria: { + "referenceNumber": businessIds + } + } + const moduleWiseLocality = await httpRequest(`egov-searcher/locality/${uniqueModules[i]}/_get`, "search", [], requestBody); + localitymap = [...localitymap, ...moduleWiseLocality.Localities]; + + + } else { + const acknowledgementIds = [...businessIds]; + for (let i = 0; i <= businessIds.length + 200; i += 200) { + let acknowledgementId = acknowledgementIds.splice(0, 200); + if (acknowledgementId && acknowledgementId.length > 0) { + const query = [{ key: "tenantId", value: getTenantId() }, + { key: "acknowledgementIds", value: acknowledgementId.join(',') }] + const propertyResponse = await httpRequest("property-services/property/_search", "_search", query); + + const localities = propertyResponse.Properties && propertyResponse.Properties.map(property => { + return { + "referencenumber": property.acknowldgementNumber, + "locality": property.address.locality.code + } + }) + localitymap = [...localitymap, ...localities]; + } + } + } + + } catch (e) { + console.log("error"); + } + } */ + } catch (e) { + toggleSnackbarAndSetText( + true, + { + labelName: "Locality Empty!", + labelKey: "Locality Empty!", + }, + "error" + ); + } + } + let localityDropdownList = []; + let moduleDropdownList = []; + let businessServiceDropdownList = []; + let statusDropdownList = []; + + let assignedToMe = []; + const initialData = data.map((item) => { + const locality = this.state.showLocality && localitymap.find(locality => { + return locality.referencenumber === item.businessId; + }) + var sla = item.businesssServiceSla && item.businesssServiceSla / (1000 * 60 * 60 * 24); + let row0 = { text: item.businessId, subtext: item.businessService, hiddenText: item.moduleName }; + let localityString = locality && locality.locality ? `${item.tenantId.toUpperCase().replace(/[.]/g, "_")}_REVENUE_${locality.locality.replace("-","_")}` : "NA"; + let row1 = {text: locality ?