{"id":157,"date":"2026-03-31T07:09:38","date_gmt":"2026-03-31T07:09:38","guid":{"rendered":"https:\/\/ph-taskportal.zyneventures.com\/?page_id=157"},"modified":"2026-06-24T12:42:58","modified_gmt":"2026-06-24T12:42:58","slug":"resource-calculator","status":"publish","type":"page","link":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/resource-calculator\/","title":{"rendered":"Resource Calculator"},"content":{"rendered":"<style>.kadence-column157_7420bb-46 > .kt-inside-inner-col,.kadence-column157_7420bb-46 > .kt-inside-inner-col:before{border-top-left-radius:0px;border-top-right-radius:0px;border-bottom-right-radius:0px;border-bottom-left-radius:0px;}.kadence-column157_7420bb-46 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column157_7420bb-46 > .kt-inside-inner-col{flex-direction:column;}.kadence-column157_7420bb-46 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column157_7420bb-46 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column157_7420bb-46{position:relative;}@media all and (max-width: 1024px){.kadence-column157_7420bb-46 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column157_7420bb-46 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column157_7420bb-46\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id157_e1c627-fa > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id157_e1c627-fa > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id157_e1c627-fa > .kt-row-column-wrap{column-gap:var(--global-kb-gap-md, 2rem);row-gap:var(--global-kb-gap-md, 2rem);padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;grid-template-columns:minmax(0, 1fr);}.kb-row-layout-id157_e1c627-fa > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id157_e1c627-fa > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id157_e1c627-fa > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id157_e1c627-fa alignnone wp-block-kadence-rowlayout\"><div class=\"kt-row-column-wrap kt-has-1-columns kt-row-layout-equal kt-tab-layout-inherit kt-mobile-layout-row kt-row-valign-top\">\n<style>.kadence-column157_3df17e-58 > .kt-inside-inner-col,.kadence-column157_3df17e-58 > .kt-inside-inner-col:before{border-top-left-radius:0px;border-top-right-radius:0px;border-bottom-right-radius:0px;border-bottom-left-radius:0px;}.kadence-column157_3df17e-58 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column157_3df17e-58 > .kt-inside-inner-col{flex-direction:column;}.kadence-column157_3df17e-58 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column157_3df17e-58 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column157_3df17e-58{position:relative;}@media all and (max-width: 1024px){.kadence-column157_3df17e-58 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column157_3df17e-58 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column157_3df17e-58\"><div class=\"kt-inside-inner-col\">\n<style>\n    :root {\n        --primary-blue: #0180FF;\n        --hover-blue:   #3499FF;\n        --text-dark:    #212529;\n        --text-muted:   #8D8C9C;\n        --border-color: #EFEFEF;\n        --error-red:    #D93025;\n        --input-bg:     #ebf2ff;\n        --sub-row-bg:   #F5F5F5;\n    }\n    * { box-sizing: border-box; margin: 0; padding: 0; }\n\n    body {\n        font-family: 'Sora', sans-serif;\n        color: var(--text-dark);\n        background: #fff;\n        min-height: 100vh;\n    }\n\n    \/* \u2500\u2500 Page Title \u2500\u2500 *\/\n    .page-title {\n        font-size: 21px;\n        font-weight: 500;\n        text-transform: uppercase;\n        letter-spacing: 0.5px;\n        margin-bottom: 32px;\n    }\n\n    \/* \u2500\u2500 Filter Row \u2500\u2500 *\/\n    .filter-row {\n        display: flex;\n        align-items: flex-end;\n        gap: 20px;\n        margin-bottom: 32px;\n        flex-wrap: wrap;\n    }\n    .filter-group {\n        display: flex;\n        flex-direction: column;\n        gap: 8px;\n        min-width: 185px;\n        flex: 1;\n    }\n    .filter-group label { font-size: 14px; font-weight: 400; }\n    .filter-group label span { color: var(--error-red); }\n\n    \/* \u2500\u2500 Custom Multi-Select \u2500\u2500 *\/\n    .cs-wrapper { position: relative; }\n    .cs-trigger {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        gap: 8px;\n        padding: 10px 14px;\n        background: #fff;\n        border: 1px solid #D0D5DD;\n        border-radius: 6px;\n        cursor: pointer;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        color: #444;\n        user-select: none;\n        min-height: 44px;\n        transition: border-color 0.2s, box-shadow 0.2s;\n    }\n    .cs-trigger:hover          { border-color: var(--primary-blue); }\n    .cs-trigger.open           { border-color: var(--primary-blue); box-shadow: 0 0 0 3px rgba(1,128,255,.1); }\n    .cs-tags                   { display: flex; flex-wrap: wrap; gap: 4px; flex: 1; min-width: 0; }\n    .ph                        { color: #99abb4; font-size: 12px; }\n    .s-tag {\n        background: var(--input-bg);\n        color: var(--primary-blue);\n        border-radius: 4px;\n        padding: 2px 6px;\n        font-size: 12px;\n        font-weight: 400;\n        display: flex; align-items: center; gap: 3px;\n        white-space: nowrap;\n    }\n    .rm { cursor: pointer; font-size: 12px; color: #99abb4; line-height: 1; }\n    .rm:hover { color: var(--error-red); }\n    .chv {\n        width: 16px;\n        height: 16px;\n        color: #99abb4;\n        flex-shrink: 0;\n        transition: transform .2s;\n    }\n    .cs-trigger.open .chv { transform: rotate(180deg); }\n    .cs-dropdown {\n        display: none;\n        position: absolute;\n        top: calc(100% + 4px); left: 0; right: 0;\n        background: #fff;\n        border: 1px solid #D0D5DD;\n        border-radius: 6px;\n        box-shadow: 0 8px 24px rgba(0,0,0,.1);\n        z-index: 300;\n        max-height: 220px;\n        overflow-y: auto;\n    }\n    .cs-dropdown.open { display: block; }\n    .dd-opt {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        padding: 9px 14px;\n        font-size: 12px;\n        cursor: pointer;\n        color: var(--text-dark);\n        transition: background .12s;\n    }\n    .dd-opt:hover { background: #f5f9ff; }\n    .dd-opt.selected { background: #EBF2FF; color: var(--primary-blue); }\n    .dd-opt input[type=\"checkbox\"] { accent-color: var(--primary-blue); width:14px; height:14px; flex-shrink:0; }\n\n    \/* \u2500\u2500 Native Select \u2500\u2500 *\/\n    .native-sel {\n        appearance: none; -webkit-appearance: none;\n        padding: 10px 38px 10px 14px;\n        background: #fff url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2399abb4' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C\/polyline%3E%3C\/svg%3E\") no-repeat right 12px center;\n        border: 1px solid #D0D5DD;\n        border-radius: 6px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px; \n        color: #444;\n        width: 100%; \n        min-height: 44px;\n        outline: none; \n        cursor: pointer;\n        transition: border-color .2s;\n    }\n    .native-sel:hover, .native-sel:focus { border-color: var(--primary-blue); }\n\n    \/* \u2500\u2500 Search Btn \u2500\u2500 *\/\n    .btn-search {\n        padding: 10px 32px;\n        background: var(--primary-blue);\n        color: #fff; \n        border: none; \n        border-radius: 6px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px; \n        font-weight: 400;\n        cursor: pointer; \n        height: 44px; \n        white-space: nowrap;\n        align-self: flex-end;\n        transition: background .2s;\n    }\n    .btn-search:hover    { background: var(--hover-blue); }\n    .btn-search:disabled { background: #ccc; cursor: not-allowed; }\n\n    \/* \u2500\u2500 Tabs \u2500\u2500 *\/\n    .tabs-bar {\n        display: flex;\n        border-bottom: 1px solid var(--border-color);\n        margin-bottom: 28px;\n    }\n    .tab-item {\n        padding: 12px 22px;\n        font-size: 16px;\n        font-weight: 400;\n        color: var(--text-muted);\n        cursor: pointer;\n        border-bottom: 2px solid transparent;\n        margin-bottom: -1px;\n        white-space: nowrap;\n        transition: color .15s;\n    }\n    .tab-item:hover { color: var(--text-dark); }\n    .tab-item.active { color: var(--text-dark); font-weight: 600; border-bottom-color: var(--primary-blue); }\n\n    \/* \u2500\u2500 No Data \/ Loading \u2500\u2500 *\/\n    .no-data {\n        padding: 80px 20px; \n        text-align: center;\n        font-size: 24px; \n        font-weight: 400; \n        color: #C8C8D0;\n    }\n    .loading-wrap {\n        padding: 60px 20px; \n        text-align: center;\n        display: flex; \n        flex-direction: column; \n        align-items: center; \n        gap: 12px;\n    }\n    .spinner {\n        width: 28px; height: 28px;\n        border: 3px solid #e0e8f0;\n        border-top-color: var(--primary-blue);\n        border-radius: 50%;\n        animation: spin .7s linear infinite;\n    }\n    @keyframes spin { to { transform: rotate(360deg); } }\n\n    \/* CATEGORY STATS TABLE  - Simple 2-column flat list *\/\n    .t-stats {\n        width: 100%;\n        border-collapse: collapse;\n        font-size: 14px;\n    }\n    .t-stats thead tr { border-bottom: 1px solid var(--border-color); background: #F8FAFB; }\n    .t-stats thead th {\n        padding: 14px 20px;\n        text-align: left;\n        font-size: 12px;\n        font-weight: 500;\n        color: var(--text-dark);\n    }\n    .t-stats tbody tr { border-bottom: 1px solid var(--border-color); }\n    .t-stats tbody tr:hover { background: #fafcff; }\n    .t-stats tbody td { padding: 14px 20px; font-size: 12px; color: #555; }\n    .t-stats tbody td:nth-child(2) { color: var(--text-dark); }\n\n    \/*  ACCOUNT STATS TABLE -  Multi-column flat table, one row per account  *\/\n    .t-acct-stats {\n        width: 100%;\n        border-collapse: collapse;\n        font-size: 12px;\n        min-width: 680px;\n    }\n    .t-acct-stats thead tr { border-bottom: 1px solid var(--border-color); background: #F8FAFB; }\n    .t-acct-stats thead th {\n        padding: 12px 16px;\n        text-align: left;\n        font-size: 12px;\n        font-weight: 500;\n        color: var(--text-dark);\n        white-space: nowrap;\n    }\n    .t-acct-stats tbody tr { border-bottom: 1px solid var(--border-color); }\n    .t-acct-stats tbody tr:hover { background: #fafcff; }\n    .t-acct-stats tbody td {\n        padding: 12px 16px;\n        font-size: 12px;\n        color: #555;\n        white-space: nowrap;\n    }\n    .t-acct-stats tbody td:first-child { color: var(--text-dark); }\n\n    \/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n       GROUPED TABLES  (Account Wise \/ Category Wise)\n       Images 2 & 4\n    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    .t-grouped {\n        width: 100%;\n        border-collapse: collapse;\n        font-size: 13px;\n    }\n\n    \/* Column headers *\/\n    .t-grouped thead th {\n        padding: 11px 12px;\n        text-align: left;\n        font-size: 12px;\n         font-weight: 500;\n        color: var(--text-muted);\n        background: #fff;\n        border-bottom: 1px solid var(--border-color);\n        white-space: nowrap;\n    }\n    .t-grouped thead th.col-label { min-width: 180px; }\n    .t-grouped thead th.col-month { min-width: 68px; text-align: right; }\n\n    \/* Year row *\/\n    .t-grouped tr.r-year td {\n        padding: 22px 12px 10px;\n        font-size: 20px;\n        font-weight: 600;\n        color: var(--text-dark);\n        border: none; background: #fff;\n    }\n\n    \/* Entity header row (white bg, completedDOS) *\/\n    .t-grouped tr.r-entity td {\n        padding: 10px 12px;\n        border-bottom: 1px solid var(--border-color);\n        background: #fff;\n    }\n    .t-grouped tr.r-entity td.col-label {\n        font-size: 12px; font-weight: 400;\n        color: var(--text-muted);\n    }\n    .t-grouped tr.r-entity td.col-val {\n        font-size: 13px; text-align: right;\n        color: var(--text-dark);\n    }\n\n    \/* Sub-metric rows (gray bg) *\/\n    .t-grouped tr.r-sub td {\n        padding: 9px 12px;\n        background: var(--sub-row-bg);\n        border-bottom: 1px solid #e8e8e8;\n    }\n    .t-grouped tr.r-sub td.col-label {\n        font-size: 12px; font-weight: 500;\n        color: var(--text-dark);\n    }\n    .t-grouped tr.r-sub td.col-val {\n        font-size: 12px; text-align: right;\n        color: var(--text-dark);\n    }\n    .t-grouped tr.r-sub td.col-val.zero { color: var(--text-muted); }\n\n    \/* Separator row *\/\n    .t-grouped tr.r-sep td {\n        padding: 5px 12px;\n        text-align: center;\n        font-size: 12px; color: #D0D0D8;\n        border: none; background: #fff;\n    }\n    .t-grouped tr.r-stats-qc td { background: rgba(135, 129, 129, 0.6); }\n    .t-stats-summary { margin-bottom: 28px; }\n    .t-stats-summary h3 { font-size: 20px; font-weight: 600; margin: 22px 0 10px; }\n\n    \/* \u2500\u2500 Table wrapper \u2500\u2500 *\/\n    .t-wrap { overflow-x: auto; width: 100%; }\n\n    \/* \u2500\u2500 Pagination \u2500\u2500 *\/\n    .pag-footer {\n        margin-top: 28px;\n        display: flex;\n        justify-content: space-between;\n         align-items: center;\n        color: var(--text-muted); font-size: 12px;\n        flex-wrap: wrap; gap: 10px;\n    }\n    .pag-controls { display: flex; align-items: center; gap: 8px; }\n    .pn {\n        cursor: pointer;\n        width: 32px;\n        height: 32px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 50%;\n        font-weight: 400;\n        font-size: 12px;\n        transition: background .15s, color .15s;\n    }\n    .pn:hover { background: var(--input-bg); color: var(--primary-blue); }\n    .pn.active { background: var(--primary-blue); color: #fff; }\n    .nav-btn {\n        cursor: pointer;\n        color: var(--text-dark);\n        font-weight: 400;\n        font-size: 12px;\n        padding: 4px 8px;\n    }\n    .nav-btn:hover { color: var(--primary-blue); }\n    .nav-btn.disabled { color: #ccc; pointer-events: none; }\n\n    @media (max-width: 768px) {\n        body { padding: 16px; }\n        .filter-row { flex-direction: column; }\n        .filter-group { min-width: 100%; }\n        .tabs-bar { overflow-x: auto; }\n        .tab-item { padding: 10px 14px; font-size: 12px; }\n    }\n<\/style>\n\n\n<h2 class=\"page-title\" id=\"pageTitle\">RESOURCE CALCULATOR | ACCOUNT WISE<\/h2><br>\n\n<!-- \u2500\u2500 Filters \u2500\u2500 -->\n<div class=\"filter-row\">\n<!-- Year -->\n<div class=\"filter-group\">\n    <label>Year <span>*<\/span><\/label>\n    <div class=\"cs-wrapper\">\n        <div class=\"cs-trigger\" id=\"yearTrigger\" onclick=\"toggleDD('year')\">\n            <div class=\"cs-tags\" id=\"yearTags\"><span class=\"ph\">Select Year<\/span><\/div>\n            <svg class=\"chv\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"><\/polyline><\/svg>\n        <\/div>\n        <div class=\"cs-dropdown\" id=\"yearDropdown\"><\/div>\n    <\/div>\n<\/div>\n\n<!-- Project Type -->\n<div class=\"filter-group\">\n    <label>Project Type <span>*<\/span><\/label>\n    <div class=\"cs-wrapper\">\n        <div class=\"cs-trigger\" id=\"projectTypeTrigger\" onclick=\"toggleDD('projectType')\">\n            <div class=\"cs-tags\" id=\"projectTypeTags\"><span class=\"ph\">Select Project Type<\/span><\/div>\n            <svg class=\"chv\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"><\/polyline><\/svg>\n        <\/div>\n        <div class=\"cs-dropdown\" id=\"projectTypeDropdown\">\n            <div class=\"loading-wrap\" style=\"padding:14px;flex-direction:row;gap:8px;\">\n                <div class=\"spinner\" style=\"width:14px;height:14px;border-width:2px;\"><\/div>\n                <span style=\"font-size:12px;color:#8D8C9C;\">Loading\u2026<\/span>\n            <\/div>\n        <\/div>\n    <\/div>\n<\/div>\n\n<!-- Working Hours -->\n<div class=\"filter-group\">\n    <label>Working Hours <span>*<\/span><\/label>\n    <select class=\"native-sel\" id=\"hoursSelect\">\n        <option value=\"7\" selected>7<\/option>\n        <option value=\"8\">8<\/option>\n    <\/select>\n<\/div>\n\n<button class=\"btn-search\" id=\"searchBtn\" onclick=\"handleSearch()\">Search<\/button>\n<\/div>\n\n<!-- \u2500\u2500 Tabs \u2500\u2500 -->\n<div class=\"tabs-bar\">\n<div class=\"tab-item active\" data-tab=\"accountWise\" onclick=\"switchTab(this)\">Account Wise<\/div>\n<div class=\"tab-item\" data-tab=\"accountStats\" onclick=\"switchTab(this)\">Account Stats<\/div>\n<div class=\"tab-item\" data-tab=\"categoryWise\" onclick=\"switchTab(this)\">Category Wise<\/div>\n<div class=\"tab-item\" data-tab=\"categoryStats\" onclick=\"switchTab(this)\">Category Stats<\/div>\n<\/div>\n\n<!-- \u2500\u2500 Content \u2500\u2500 -->\n<div id=\"contentArea\"><div class=\"no-data\">No Data Found<\/div><\/div>\n\n<!-- \u2500\u2500 Pagination \u2500\u2500 -->\n<div class=\"pag-footer\" id=\"pagFooter\" style=\"display:none;\">\n<div class=\"pag-controls\">\n    <span class=\"nav-btn disabled\" id=\"prevBtn\" onclick=\"changePage(-1)\">\u00ab Previous<\/span>\n    <div id=\"pageNums\" style=\"display:flex;gap:5px;\"><\/div>\n    <span class=\"nav-btn disabled\" id=\"nextBtn\" onclick=\"changePage(1)\">Next \u00bb<\/span>\n<\/div>\n<div id=\"entryInfo\">Showing 0 to 0 of 0 entries<\/div>\n<\/div>\n\n<script>\n\/\/ CONSTANTS \u2014 PHP api\/v1\/resource-calculator parity\nconst BASE_URL          = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\nconst GET_PROJECT_TYPES = `${BASE_URL}\/projecttype\/all`;\nconst GET_CALCULATOR    = `${BASE_URL}\/resource-calculator`;\n\nconst MONTHS     = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\nconst MONTH_KEYS = ['1','2','3','4','5','6','7','8','9','10','11','12'];\nconst QC_PCT     = 30; \/\/ PHP $percentage = 30\n\nconst TAB_TITLES = {\n  accountWise:   'RESOURCE CALCULATOR | ACCOUNT WISE',\n  accountStats:  'RESOURCE CALCULATOR | ACCOUNT STATS WISE',\n  categoryWise:  'RESOURCE CALCULATOR | CATEGORY WISE',\n  categoryStats: 'RESOURCE CALCULATOR | CATEGORY STATS WISE',\n};\n\nconst TAB_API_TYPE = {\n  accountWise:   '',\n  accountStats:  'accountStats',\n  categoryWise:  'category',\n  categoryStats: 'categoryStats',\n};\n\nconst S = {\n  activeTab:   'accountWise',\n  years:       [],\n  ptypes:      [],\n  hours:       '7',\n  ptypeList:   [],\n  finalResult: null,\n  stats:       null,\n  cache:       {},\n  hasSearched: false,\n  pageItems:   [],\n  page:        1,\n  flatPer:     20,\n  blockPer:    10,\n};\n\nconst getToken = () =>\n  localStorage.getItem('TOKEN') ||\n  (document.cookie.match(\/(?:^|;\\s*)TOKEN=([^;]*)\/) || [])[1] || '';\nconst getHdrs = () => ({\n  'Content-Type': 'application\/json',\n  'Authorization': `Bearer ${getToken()}`,\n});\n\n\/\/ YEAR DROPDOWN \u2014 PHP: (Y-2) \u2026 (Y+5)\nfunction buildYears() {\n  const cur = new Date().getFullYear();\n  const dd  = document.getElementById('yearDropdown');\n  dd.innerHTML = '';\n  for (let y = cur - 2; y <= cur + 5; y++)\n    dd.appendChild(mkOpt(String(y), String(y), 'year'));\n}\n\nasync function loadPTypes() {\n  try {\n    const r  = await fetch(`${GET_PROJECT_TYPES}?page=1&#038;limit=100`, { headers: getHdrs() });\n    const js = await r.json();\n    const ts = js?.data?.data || [];\n    S.ptypeList = ts;\n    const dd = document.getElementById('projectTypeDropdown');\n    dd.innerHTML = '';\n    if (!ts.length) {\n      dd.innerHTML = '<div class=\"dd-opt\" style=\"color:#ccc;pointer-events:none;\">No types found<\/div>';\n      return;\n    }\n    ts.forEach(pt => dd.appendChild(mkOpt(String(pt.id), pt.type, 'projectType')));\n  } catch (e) {\n    document.getElementById('projectTypeDropdown').innerHTML =\n      '<div class=\"dd-opt\" style=\"color:var(--error-red);pointer-events:none;\">Failed to load<\/div>';\n  }\n}\n\nfunction mkOpt(value, label, type) {\n  const el = document.createElement('div');\n  el.className   = 'dd-opt';\n  el.dataset.val = value;\n  el.innerHTML   = `<input type=\"checkbox\"><span>${label}<\/span>`;\n  el.addEventListener('click', e => {\n    if (e.target.tagName !== 'INPUT') el.querySelector('input').checked = !el.querySelector('input').checked;\n    toggleSel(type, value);\n  });\n  return el;\n}\n\nfunction toggleDD(type) {\n  const tr  = document.getElementById(type + 'Trigger');\n  const dd  = document.getElementById(type + 'Dropdown');\n  const was = dd.classList.contains('open');\n  closeAllDD();\n  if (!was) { dd.classList.add('open'); tr.classList.add('open'); }\n}\nfunction closeAllDD() {\n  document.querySelectorAll('.cs-dropdown').forEach(d => d.classList.remove('open'));\n  document.querySelectorAll('.cs-trigger').forEach(t => t.classList.remove('open'));\n}\ndocument.addEventListener('click', e => { if (!e.target.closest('.cs-wrapper')) closeAllDD(); });\n\nfunction toggleSel(type, val) {\n  const arr = type === 'year' ? S.years : S.ptypes;\n  const idx = arr.indexOf(val);\n  if (idx === -1) arr.push(val); else arr.splice(idx, 1);\n  const ddId = type === 'year' ? 'yearDropdown' : 'projectTypeDropdown';\n  document.getElementById(ddId).querySelectorAll('.dd-opt').forEach(opt => {\n    const v  = opt.dataset.val;\n    const cb = opt.querySelector('input');\n    const ok = arr.includes(v);\n    if (cb) cb.checked = ok;\n    opt.classList.toggle('selected', ok);\n  });\n  syncTags(type);\n}\n\nfunction syncTags(type) {\n  const arr = type === 'year' ? S.years : S.ptypes;\n  const el  = document.getElementById(type === 'year' ? 'yearTags' : 'projectTypeTags');\n  if (!arr.length) {\n    el.innerHTML = `<span class=\"ph\">${type === 'year' ? 'Select Year' : 'Select Project Type'}<\/span>`;\n    return;\n  }\n  if (arr.length >= 2) {\n    el.innerHTML = `<span style=\"font-size:13px;color:#444;\">${arr.length} selected<\/span>`;\n    return;\n  }\n  const label = type === 'year' ? arr[0] :\n    (S.ptypeList.find(p => String(p.id) === arr[0])?.type || arr[0]);\n  el.innerHTML = `<span class=\"s-tag\">${label}<span class=\"rm\" onclick=\"rmTag(event,'${type}','${arr[0]}')\">\u00d7<\/span><\/span>`;\n}\nfunction rmTag(e, type, val) { e.stopPropagation(); toggleSel(type, val); }\n\nasync function switchTab(el) {\n  document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));\n  el.classList.add('active');\n  S.activeTab = el.dataset.tab;\n  S.page      = 1;\n  document.getElementById('pageTitle').textContent = TAB_TITLES[S.activeTab];\n  if (S.hasSearched) {\n    loading();\n    const ok = await loadTabData(S.activeTab);\n    if (ok) dispatch();\n  }\n}\n\nfunction buildQuery(type) {\n  const qs = new URLSearchParams({\n    year:         S.years.join(','),\n    project_type: S.ptypes.join(','),\n    hours:        S.hours,\n  });\n  if (type) qs.set('type', type);\n  return qs.toString();\n}\n\nasync function loadTabData(tabKey) {\n  const apiType = TAB_API_TYPE[tabKey] ?? '';\n  if (S.cache[tabKey]) {\n    S.finalResult = S.cache[tabKey].finalResult;\n    S.stats       = S.cache[tabKey].stats;\n    return true;\n  }\n  const res = await fetch(`${GET_CALCULATOR}?${buildQuery(apiType)}`, { headers: getHdrs() });\n  const js  = await res.json();\n  if (!res.ok || !js.finalResult) return false;\n  S.finalResult = js.finalResult;\n  S.stats       = js.stats || {};\n  S.cache[tabKey] = { finalResult: S.finalResult, stats: S.stats };\n  return true;\n}\n\nasync function handleSearch() {\n  if (!S.years.length)  { alert('Please select at least one Year.'); return; }\n  if (!S.ptypes.length) { alert('Please select at least one Project Type.'); return; }\n  S.hours = document.getElementById('hoursSelect').value;\n  S.page  = 1;\n  S.cache = {};\n  S.hasSearched = true;\n\n  const btn = document.getElementById('searchBtn');\n  btn.disabled = true; btn.textContent = 'Searching\u2026';\n  loading();\n  try {\n    const ok = await loadTabData(S.activeTab);\n    if (!ok) noData(); else dispatch();\n  } catch (e) {\n    console.error(e); noData();\n  } finally {\n    btn.disabled = false; btn.textContent = 'Search';\n  }\n}\n\nfunction dispatch() {\n  const t = S.activeTab;\n  if      (t === 'accountWise')   renderAccountWise();\n  else if (t === 'accountStats')  renderAccountStats();\n  else if (t === 'categoryWise')  renderCategoryWise();\n  else if (t === 'categoryStats') renderCategoryStats();\n}\n\nconst fi  = v => (v == null || v === '' ? '0' : parseInt(v, 10).toLocaleString());\nconst fir = v => (v == null || v === '' ? '0' : String(parseInt(v, 10))); \/\/ PHP raw integer (no commas)\nconst fd  = v => (v == null || v === '' ? '0.00' : parseFloat(v).toFixed(2));\nconst num = v => (v == null || v === '' ? 0 : parseFloat(v) || 0);\n\nfunction monthCell(monthData, key) {\n  return monthData?.[key] ?? monthData?.[Number(key)] ?? null;\n}\n\nfunction entityMonthCells(monthData) {\n  return MONTH_KEYS.map(mk => {\n    const e = monthCell(monthData, mk);\n    return `<td class=\"col-val\">${e ? fir(e.completedDOS) : '0'}<\/td>`;\n  }).join('');\n}\n\nfunction subMonthCells(monthData, fn) {\n  return MONTH_KEYS.map(mk => {\n    const e   = monthCell(monthData, mk);\n    const val = fn(e);\n    const z   = parseFloat(val) === 0;\n    return `<td class=\"col-val${z ? ' zero' : ''}\">${val}<\/td>`;\n  }).join('');\n}\n\n\/\/ PHP list.blade.php sub-rows: resource, 30% DOS, QCCompletedDOS, qc_resource\nconst SUB_DEFS = [\n  { label: 'Ops. Resource count', fn: e => e ? fd(e.resource) : '0.00' },\n  { label: 'To be QC',            fn: e => e ? fd((QC_PCT \/ 100) * num(e.completedDOS)) : '0.00' },\n  { label: 'QC Performed',        fn: e => e ? fd(e.QCCompletedDOS) : '0.00' },\n  { label: 'QC. Resource count',  fn: e => e ? fd(e.qc_resource) : '0.00' },\n];\n\nfunction subRows(monthData) {\n  return SUB_DEFS.map(sd =>\n    `<tr class=\"r-sub\"><td class=\"col-label\">${sd.label}<\/td>${subMonthCells(monthData, sd.fn)}<\/tr>`\n  ).join('');\n}\n\nconst COL_HEADERS = `<thead><tr>\n  <th class=\"col-label\"><\/th>\n  ${MONTHS.map(m => `<th class=\"col-month\">${m}<\/th>`).join('')}\n<\/tr><\/thead>`;\n\nfunction statMonthCells(fn) {\n  return MONTH_KEYS.map(mk => {\n    const row = S.stats?.[mk] || S.stats?.[Number(mk)];\n    return `<td class=\"col-val\">${fn(row)}<\/td>`;\n  }).join('');\n}\n\n\/\/ PHP summary table (per year block in list.blade.php)\nfunction renderStatsSummary(year) {\n  const s = n => (n == null ? '0' : fd(n));\n  return `<div class=\"t-stats-summary\">\n    <h3>${year}<\/h3>\n    <div class=\"t-wrap\"><table class=\"t-grouped\">\n      <thead><tr><th class=\"col-label\">Summary<\/th>${MONTHS.map(m => `<th class=\"col-month\">${m}<\/th>`).join('')}<\/tr><\/thead>\n      <tbody>\n        <tr class=\"r-entity\"><td class=\"col-label\">DOS Completed<\/td>${statMonthCells(r => fir(r?.total_dos))}<\/tr>\n        <tr class=\"r-sub\"><td class=\"col-label\">Ops.Resource To be<\/td>${statMonthCells(r => s(r?.total_resource))}<\/tr>\n        <tr class=\"r-sub\"><td class=\"col-label\">Ops.Resource Count<\/td>${statMonthCells(r => s(r?.total_resource_count))}<\/tr>\n        <tr class=\"r-sub\"><td class=\"col-label\">Ops.Diff Count<\/td>${statMonthCells(r => s(num(r?.total_resource) - num(r?.total_resource_count)))}<\/tr>\n        <tr class=\"r-stats-qc\"><td class=\"col-label\">Qc DOS Completed<\/td>${statMonthCells(r => fir(r?.total_qc_dos))}<\/tr>\n        <tr class=\"r-stats-qc\"><td class=\"col-label\">QC.Resource To be<\/td>${statMonthCells(r => s(r?.total_qc_resource))}<\/tr>\n        <tr class=\"r-stats-qc\"><td class=\"col-label\">QC.Resource Count<\/td>${statMonthCells(r => s(r?.total_qc_resource_count))}<\/tr>\n        <tr class=\"r-stats-qc\"><td class=\"col-label\">QC.Diff Count<\/td>${statMonthCells(r => s(num(r?.total_qc_resource) - num(r?.total_qc_resource_count)))}<\/tr>\n      <\/tbody>\n    <\/table><\/div>\n  <\/div>`;\n}\n\nfunction renderAccountWise() {\n  const data = S.finalResult?.data;\n  if (!data || !Object.keys(data).length) { noData(); return; }\n\n  const accounts = S.finalResult.accounts || {};\n  const blocks   = [];\n  Object.keys(data).sort().forEach(yr => {\n    Object.keys(data[yr]).sort((a, b) => {\n      const na = accounts[a] || a, nb = accounts[b] || b;\n      return String(na).localeCompare(String(nb));\n    }).forEach(aid => blocks.push({ yr: +yr, aid, months: data[yr][aid] }));\n  });\n\n  S.pageItems = blocks;\n  const total = blocks.length;\n  const start = (S.page - 1) * S.blockPer;\n  const vis   = blocks.slice(start, start + S.blockPer);\n  const visYrs = [...new Set(vis.map(b => b.yr))];\n\n  let html = '';\n  Object.keys(data).sort().forEach(yr => {\n    if (visYrs.includes(+yr)) html += renderStatsSummary(yr);\n  });\n\n  let tbody = '';\n  visYrs.forEach(yr => {\n    tbody += `<tr class=\"r-year\"><td colspan=\"13\">${yr}<\/td><\/tr>`;\n    vis.filter(b => b.yr === yr).forEach(b => {\n      const name = accounts[b.aid] || b.aid;\n      tbody += `<tr class=\"r-entity\"><td class=\"col-label\">${name}<\/td>${entityMonthCells(b.months)}<\/tr>`;\n      tbody += subRows(b.months);\n      tbody += `<tr class=\"r-sep\"><td colspan=\"13\">-<\/td><\/tr>`;\n    });\n  });\n\n  html += `<div class=\"t-wrap\"><table class=\"t-grouped\">${COL_HEADERS}<tbody>${tbody}<\/tbody><\/table><\/div>`;\n  paint(html, total);\n}\n\nfunction renderAccountStats() {\n  const rows = S.finalResult?.accountsDetail;\n  if (!rows || !rows.length) { noData(); return; }\n\n  S.pageItems = rows;\n  const total = rows.length;\n  const start = (S.page - 1) * S.flatPer;\n  const slice = rows.slice(start, start + S.flatPer);\n\n  const html = `<div class=\"t-wrap\"><table class=\"t-acct-stats\">\n    <thead><tr>\n      <th>Account<\/th><th>Benchmark\/Hour<\/th><th>Benchmark\/Day<\/th><th>Benchmark\/Month<\/th>\n    <\/tr><\/thead>\n    <tbody>${slice.map(a => `<tr>\n      <td>${a.name ?? '-'}<\/td>\n      <td>${a.hrs ?? '-'}<\/td>\n      <td>${fd(a.hrs_per_day)}<\/td>\n      <td>${fd(a.hrs_per_month)}<\/td>\n    <\/tr>`).join('')}<\/tbody>\n  <\/table><\/div>`;\n  paint(html, total, true);\n}\n\nfunction renderCategoryWise() {\n  const data = S.finalResult?.data;\n  if (!data || !Object.keys(data).length) { noData(); return; }\n\n  const categories = S.finalResult.categories || {};\n  const blocks = [];\n  Object.keys(data).sort().forEach(yr => {\n    Object.keys(data[yr]).sort((a, b) => {\n      const na = categories[a] || data[yr][a]?.['1']?.category_name || a;\n      const nb = categories[b] || data[yr][b]?.['1']?.category_name || b;\n      return String(na).localeCompare(String(nb));\n    }).forEach(ck => blocks.push({ yr: +yr, ck, months: data[yr][ck] }));\n  });\n\n  S.pageItems = blocks;\n  const total = blocks.length;\n  const start = (S.page - 1) * S.blockPer;\n  const vis   = blocks.slice(start, start + S.blockPer);\n  const visYrs = [...new Set(vis.map(b => b.yr))];\n\n  let tbody = '';\n  visYrs.forEach(yr => {\n    tbody += `<tr class=\"r-year\"><td colspan=\"13\">${yr}<\/td><\/tr>`;\n    vis.filter(b => b.yr === yr).forEach(b => {\n      const name = categories[b.ck] ||\n        monthCell(b.months, '1')?.category_name || b.ck;\n      tbody += `<tr class=\"r-entity\"><td class=\"col-label\">${name}<\/td>${entityMonthCells(b.months)}<\/tr>`;\n      tbody += subRows(b.months);\n      tbody += `<tr class=\"r-sep\"><td colspan=\"13\">-<\/td><\/tr>`;\n    });\n  });\n\n  paint(`<div class=\"t-wrap\"><table class=\"t-grouped\">${COL_HEADERS}<tbody>${tbody}<\/tbody><\/table><\/div>`, total);\n}\n\nfunction renderCategoryStats() {\n  const rows = S.finalResult?.categoriesDetail;\n  if (!rows || !rows.length) { noData(); return; }\n\n  S.pageItems = rows;\n  const total = rows.length;\n  const start = (S.page - 1) * S.flatPer;\n  const slice = rows.slice(start, start + S.flatPer);\n\n  const html = `<div class=\"t-wrap\"><table class=\"t-stats\">\n    <thead><tr><th>Category<\/th><th>Benchmark\/Month<\/th><\/tr><\/thead>\n    <tbody>${slice.map(c => `<tr>\n      <td>${c.category_name ?? '-'}<\/td>\n      <td>${fd(c.hrs_per_months)}<\/td>\n    <\/tr>`).join('')}<\/tbody>\n  <\/table><\/div>`;\n  paint(html, total, true);\n}\n\nfunction loading() {\n  document.getElementById('contentArea').innerHTML =\n    `<div class=\"loading-wrap\"><div class=\"spinner\"><\/div><span style=\"color:var(--text-muted);font-size:14px;\">Loading data\u2026<\/span><\/div>`;\n  document.getElementById('pagFooter').style.display = 'none';\n}\nfunction noData() {\n  document.getElementById('contentArea').innerHTML = `<div class=\"no-data\">No Data Found<\/div>`;\n  document.getElementById('pagFooter').style.display = 'none';\n}\nfunction paint(html, total, flat = false) {\n  if (!html || total === 0) { noData(); return; }\n  document.getElementById('contentArea').innerHTML = html;\n  renderPag(total, flat);\n}\n\nfunction renderPag(total, flat) {\n  const per  = flat ? S.flatPer : S.blockPer;\n  const tpgs = Math.ceil(total \/ per);\n  const foot = document.getElementById('pagFooter');\n  if (tpgs <= 1) { foot.style.display = 'none'; return; }\n  foot.style.display = 'flex';\n  const s = (S.page - 1) * per + 1;\n  const e = Math.min(S.page * per, total);\n  document.getElementById('entryInfo').textContent = `Showing ${s} to ${e} of ${total} entries`;\n  document.getElementById('prevBtn').className = S.page <= 1 ? 'nav-btn disabled' : 'nav-btn';\n  document.getElementById('nextBtn').className = S.page >= tpgs ? 'nav-btn disabled' : 'nav-btn';\n  const nums = document.getElementById('pageNums');\n  nums.innerHTML = '';\n  pgRange(S.page, tpgs).forEach(p => {\n    if (p === '\u2026') {\n      const el = document.createElement('span');\n      el.style.cssText = 'width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:#ccc;font-size:13px;';\n      el.textContent = '\u2026'; nums.appendChild(el);\n    } else {\n      const el = document.createElement('span');\n      el.className = p === S.page ? 'pn active' : 'pn';\n      el.textContent = p;\n      el.onclick = () => gotoPage(p);\n      nums.appendChild(el);\n    }\n  });\n}\n\nfunction pgRange(cur, tot) {\n  if (tot <= 7) return Array.from({ length: tot }, (_, i) => i + 1);\n  if (cur <= 4)       return [1, 2, 3, 4, 5, '\u2026', tot];\n  if (cur >= tot - 3) return [1, '\u2026', tot - 4, tot - 3, tot - 2, tot - 1, tot];\n  return [1, '\u2026', cur - 1, cur, cur + 1, '\u2026', tot];\n}\n\nfunction gotoPage(p) { S.page = p; dispatch(); window.scrollTo({ top: 0, behavior: 'smooth' }); }\nfunction changePage(d) {\n  const flat = ['accountStats', 'categoryStats'].includes(S.activeTab);\n  const per  = flat ? S.flatPer : S.blockPer;\n  const tpgs = Math.ceil(S.pageItems.length \/ per);\n  const next = S.page + d;\n  if (next >= 1 && next <= tpgs) gotoPage(next);\n}\n\nbuildYears();\nloadPTypes();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>RESOURCE CALCULATOR | ACCOUNT WISE Year * Select Year Project Type * Select Project Type Loading\u2026 Working Hours * 78 Search Account Wise Account Stats Category Wise Category Stats No Data Found \u00ab Previous Next \u00bb Showing 0 to 0 of 0 entries<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-157","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/comments?post=157"}],"version-history":[{"count":1,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/157\/revisions"}],"predecessor-version":[{"id":1179,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/157\/revisions\/1179"}],"wp:attachment":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}