{"id":147,"date":"2026-03-31T07:01:42","date_gmt":"2026-03-31T07:01:42","guid":{"rendered":"https:\/\/ph-taskportal.zyneventures.com\/?page_id=147"},"modified":"2026-06-01T06:10:15","modified_gmt":"2026-06-01T06:10:15","slug":"productivity-executive","status":"publish","type":"page","link":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/productivity-executive\/","title":{"rendered":"Productivity Executive"},"content":{"rendered":"<style>.kadence-column147_2b35aa-72 > .kt-inside-inner-col,.kadence-column147_2b35aa-72 > .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-column147_2b35aa-72 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column147_2b35aa-72 > .kt-inside-inner-col{flex-direction:column;}.kadence-column147_2b35aa-72 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column147_2b35aa-72 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column147_2b35aa-72{position:relative;}@media all and (max-width: 1024px){.kadence-column147_2b35aa-72 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column147_2b35aa-72 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column147_2b35aa-72\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id147_621a96-77 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id147_621a96-77 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id147_621a96-77 > .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-id147_621a96-77 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id147_621a96-77 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id147_621a96-77 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id147_621a96-77 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-column147_e9da26-0b > .kt-inside-inner-col,.kadence-column147_e9da26-0b > .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-column147_e9da26-0b > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column147_e9da26-0b > .kt-inside-inner-col{flex-direction:column;}.kadence-column147_e9da26-0b > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column147_e9da26-0b > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column147_e9da26-0b{position:relative;}@media all and (max-width: 1024px){.kadence-column147_e9da26-0b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column147_e9da26-0b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column147_e9da26-0b\"><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        --success-green: #198754;\n    }\n    * { box-sizing: border-box; margin: 0; padding: 0; }\n    body { \n        font-family: 'Sora', sans-serif; \n        color: var(--text-dark); \n        background: #fff; \n    }\n\n    .page-title {\n        font-size: 21px; \n        font-weight: 500;\n        text-transform: uppercase; \n        letter-spacing: 0.5px;\n        margin-bottom: 28px;\n    }\n\n    \/* FILTER BAR *\/\n    .filter-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }\n\n    \/* \u2500\u2500 CUSTOM MULTI-SELECT \u2500\u2500 *\/\n    .cs-wrap {\n        position: relative;\n        flex: 1 1 160px;\n        min-width: 140px;\n        max-width: 220px;\n    }\n    .cs-wrap.wide { max-width: 240px; }\n    .custom-select-trigger {\n        height: 37px;\n        padding: 0 32px 0 14px;\n        border: 1px solid #D1D5DB;\n        background-color: #fff;\n        border-radius: 6px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        color: var(--text-dark);\n        cursor: text;\n        display: flex;\n        align-items: center;\n        box-sizing: border-box;\n        width: 100%;\n        user-select: none;\n        position: relative;\n        transition: border-color 0.2s;\n        overflow: hidden;\n        white-space: nowrap;\n    }\n    .custom-select-trigger:hover { border-color: #8D8C9C; }\n    .cs-wrap.open .custom-select-trigger {\n        border-color: var(--primary-blue);\n        border-radius: 6px 6px 0 0;\n        box-shadow: 0 0 0 3px rgba(1,128,255,0.1);\n    }\n    .custom-select-trigger.placeholder-shown { color: #8D8C9C; }\n    .custom-select-trigger .cs-arrow {\n        position: absolute;\n        right: 10px;\n        top: 50%;\n        transform: translateY(-50%);\n        pointer-events: none;\n        color: #8D8C9C;\n        transition: transform 0.2s;\n        flex-shrink: 0;\n    }\n    .cs-wrap.open .custom-select-trigger .cs-arrow { transform: translateY(-50%) rotate(180deg); }\n\n    .cs-label {\n        display: flex;\n        flex-wrap: nowrap;\n        align-items: center;\n        gap: 6px;\n        overflow-x: auto;\n        overflow-y: hidden;\n        white-space: nowrap;\n        width: 100%;\n        min-width: 0;\n        scrollbar-width: thin;\n        -ms-overflow-style: auto;\n    }\n    .cs-label::-webkit-scrollbar { display: block; height: 4px; }\n    .cs-label::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }\n\n    \/* Integrated search input inside trigger *\/\n    .cs-search-input {\n        flex: 1;\n        min-width: 60px;\n        border: 0 !important;\n        outline: 0 !important;\n        box-shadow: none !important;\n        background: transparent !important;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        color: var(--text-dark);\n        padding: 0;\n        height: 26px;\n        -webkit-appearance: none;\n        -moz-appearance: none;\n        appearance: none;\n    }\n    .cs-search-input:focus,\n    .cs-search-input:active,\n    .cs-search-input:hover,\n    .cs-search-input:focus-visible {\n        border: 0 !important;\n        outline: 0 !important;\n        box-shadow: none !important;\n        background: transparent !important;\n    }\n    .cs-search-input::placeholder {\n        color: #8D8C9C;\n    }\n\n    .custom-select-dropdown {\n        display: none;\n        position: absolute;\n        top: 100%;\n        left: 0;\n        right: 0;\n        background: #fff;\n        border: 1px solid var(--primary-blue);\n        border-top: none;\n        border-radius: 0 0 6px 6px;\n        z-index: 9999;\n        overflow-y: auto;\n        max-height: 250px;\n        box-shadow: 0 4px 12px rgba(0,0,0,0.10);\n    }\n    .cs-wrap.open .custom-select-dropdown { display: block; }\n\n    .cs-chips {\n        display: flex;\n        align-items: center;\n        gap: 4px;\n    }\n    .cs-chip {\n        display: inline-flex;\n        align-items: center;\n        background-color: #e8f0fe;\n        border: 1px solid #d2e3fc;\n        border-radius: 4px;\n        padding: 0 8px;\n        font-size: 12px;\n        color: #1967d2;\n        gap: 4px;\n        white-space: nowrap;\n        flex-shrink: 0;\n        height: 26px;\n        margin: 0;\n    }\n    .cs-chip-remove {\n        cursor: pointer;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        width: 14px;\n        height: 14px;\n        border-radius: 50%;\n        color: #5f6368;\n        font-weight: bold;\n        transition: background 0.2s;\n    }\n    .cs-chip-remove:hover { background: #dadce0; color: #202124; }\n\n    .custom-select-option {\n        padding: 8px 12px;\n        font-size: 12px;\n        font-family: 'Sora', sans-serif;\n        color: var(--text-dark);\n        cursor: pointer;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        line-height: 1.4;\n        display: flex;\n        align-items: center;\n        gap: 10px;\n    }\n    .custom-select-option:hover { background: #f0f6ff; color: var(--primary-blue); }\n    .custom-select-option.selected { background: #e8f1ff; color: var(--primary-blue); font-weight: 500; }\n    .custom-select-option.highlighted { background: #f0f6ff; color: var(--primary-blue); }\n    .custom-select-option .cs-checkbox {\n        width: 16px;\n        height: 16px;\n        border: 1px solid #ced4da;\n        border-radius: 3px;\n        background: #fff;\n        position: relative;\n        flex-shrink: 0;\n        display: block;\n    }\n    .custom-select-option.selected .cs-checkbox {\n        background: var(--primary-blue);\n        border-color: var(--primary-blue);\n    }\n    .custom-select-option.selected .cs-checkbox::after {\n        content: '';\n        position: absolute;\n        left: 5px;\n        top: 2px;\n        width: 4px;\n        height: 8px;\n        border: solid white;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n    \/* Hide placeholder option in dropdown list *\/\n    .custom-select-option.placeholder-option {\n        display: none;\n    }\n\n    \/* Month\/Year simple selects *\/\n    .sel-wrap { position: relative; flex: 1 1 160px; min-width: 140px; max-width: 220px; }\n    .sel-wrap select {\n        width: 100%;\n        appearance: none;\n        -webkit-appearance: none;\n        padding: 9px 32px 9px 14px;\n        border: 1px solid #D1D5DB;\n        border-radius: 6px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        color: var(--text-dark);\n        background: #fff;\n        cursor: pointer;\n        outline: none;\n    }\n    .sel-wrap select:focus { border-color: var(--primary-blue); }\n    .sel-wrap::after {\n        content: '';\n        position: absolute;\n        right: 12px;\n        top: 50%;\n        transform: translateY(-50%);\n        width: 0; height: 0;\n        border-left: 5px solid transparent;\n        border-right: 5px solid transparent;\n        border-top: 5px solid #8D8C9C;\n        pointer-events: none;\n    }\n\n    \/* BUTTONS *\/\n    .btn-filter {\n        background: var(--primary-blue); \n        color: #fff; \n        border: none;\n        border-radius: 6px; \n        padding: 9px 24px;\n        font-family: 'Sora', \n        sans-serif; \n        font-size: 12px; \n        font-weight: 500;\n        cursor: pointer; \n        transition: background 0.2s; \n        white-space: nowrap;\n    }\n    .btn-filter:hover { background: var(--hover-blue); }\n    .btn-clear {\n        background: #D9DDEF;\n        color: #212529;\n        border: none;\n        border-radius: 6px;\n        padding: 9px 24px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        font-weight: 500;\n        cursor: pointer;\n        transition: background 0.2s;\n        white-space: nowrap;\n    }\n    .btn-clear:hover { background: #c8cade; }\n    .btn-csv {\n        background: var(--primary-blue); \n        color: #fff; \n        border: none;\n        border-radius: 6px; \n        padding: 9px 20px;\n        font-family: 'Sora', sans-serif; \n        font-size: 12px; \n        font-weight: 500;\n        cursor: pointer; \n        transition: background 0.2s; \n        white-space: nowrap;\n    }\n    .btn-csv:hover { background: var(--hover-blue); }\n\n    \/* SUMMARY CARDS *\/\n    .summary-cards { display: flex; gap: 16px; justify-content: flex-end; margin-bottom: 28px; }\n    .summary-card { \n        background: #F8F9FA; \n        border-radius: 12px; \n        padding: 20px 32px; \n        min-width: 180px; \n        text-align: center; \n    }\n    .summary-card .card-label { \n        font-size: 12px; \n        color: var(--text-muted); \n        margin-bottom: 8px; \n        font-weight: 500; \n    }\n    .summary-card .card-value { \n        font-size: 32px; \n        font-weight: 500; \n        color: var(--success-green); \n        line-height: 1; \n    }\n\n    \/* TABS *\/\n    .tab-nav { display: flex; border-bottom: 2px solid var(--border-color); overflow: visible; flex-wrap: wrap; }\n    .tab-btn {\n        background: none; \n        border: none; \n        border-radius: 0px; \n        border-bottom: 3px solid transparent;\n        margin-bottom: -2px; \n        padding: 12px 20px;\n        font-family: 'Sora', sans-serif; \n        font-size: 16px;\n        color: var(--text-muted); \n        cursor: pointer; \n        font-weight: 400;\n        transition: all 0.2s; \n        white-space: nowrap;\n    }\n    .tab-btn.active { \n        color: var(--text-dark); \n        font-weight: 600; \n        background-color: transparent;\n        border-bottom-color: var(--primary-blue); \n    }\n    .tab-btn:hover:not(.active) { \n        color: var(--text-muted); \n        background-color: transparent; \n    }\n\n    \/* TABLE *\/\n    .table-wrapper { overflow-x: auto; }\n    table { width: 100%; border-collapse: collapse; font-size: 12px; }\n    thead tr { background: #F8F9FA; }\n    thead th {\n        padding: 14px 18px; \n        text-align: left; \n        font-weight: 500; \n        font-size: 12px;\n        color: var(--text-dark); \n        text-transform: uppercase; \n        letter-spacing: 0.4px; \n        white-space: nowrap;\n    }\n    tbody tr { border-bottom: 1px solid var(--border-color); }\n    tbody tr:last-child { border-bottom: none; }\n    tbody td { padding: 14px 18px; color: #8D8C9C; font-size: 12px; }\n    tbody tr:hover { background: #FAFAFA; }\n    .state-row td { text-align: center; padding: 40px; color: var(--text-muted); }\n\n    \/* PAGINATION *\/\n    .pagination-footer {\n        display: flex; \n        align-items: center; \n        justify-content: space-between;\n        padding: 16px 0; \n        flex-wrap: wrap; \n        gap: 12px;\n    }\n    .pagination-controls { display: flex; align-items: center; gap: 6px; }\n    .nav-btn {\n        padding: 6px 14px; \n        border-radius: 6px; \n        cursor: pointer; \n        font-size: 12px;\n        color: var(--text-dark); \n        background: transparent; \n        border: none;\n        font-family: 'Sora', sans-serif; \n        transition: all 0.2s; \n        user-select: none;\n    }\n    .nav-btn:hover:not(.disabled) { background: var(--primary-blue); color: #fff; }\n    .nav-btn.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }\n    .page-num {\n        width: 32px; \n        height: 32px; \n        display: flex; \n        align-items: center; \n        justify-content: center;\n        border-radius: 50%; \n        cursor: pointer; \n        font-size: 12px;\n        color: var(--text-dark); \n        background: transparent;\n        transition: all 0.2s; \n        user-select: none;\n    }\n    .page-num.active { background: var(--primary-blue); color: #fff; }\n    .page-num:hover:not(.active) { background: transparent; }\n    .entry-info { font-size: 12px; color: var(--text-muted); }\n\n    @media (max-width: 768px) {\n        body { padding: 20px 16px; }\n        .cs-wrap { min-width: 130px; }\n        .summary-cards { justify-content: flex-start; }\n    }\n    @media (max-width: 480px) {\n        .filter-bar { flex-direction: column; align-items: stretch; }\n        .cs-wrap { min-width: unset; width: 100%; max-width: 100%; }\n        .sel-wrap { min-width: unset; width: 100%; max-width: 100%; }\n        .btn-filter, .btn-clear, .btn-csv { width: 100%; }\n        .summary-cards { flex-direction: column; }\n        .summary-card { min-width: unset; }\n    }\n<\/style>\n\n\n<h2 class=\"page-title\" id=\"pageTitle\">EXECUTIVE WISE PRODUCTIVITY AND QUALITY SUMMARY<\/h2><br>\n\n<div class=\"filter-bar\" id=\"filterBar\"><\/div>\n\n<div class=\"summary-cards\" id=\"summaryCards\">\n    <div class=\"summary-card\">\n        <div class=\"card-label\">Productivity %<\/div>\n        <div class=\"card-value\" id=\"summaryProductivity\">0.00%<\/div>\n    <\/div>\n    <div class=\"summary-card\">\n        <div class=\"card-label\">Quality %<\/div>\n        <div class=\"card-value\" id=\"summaryQuality\">0.00%<\/div>\n    <\/div>\n<\/div>\n\n<div class=\"tab-nav\">\n    <button class=\"tab-btn active\" onclick=\"switchTab('executive')\">Executive Wise<\/button>\n    <button class=\"tab-btn\" onclick=\"switchTab('qc')\">QC Executive Wise<\/button>\n    <button class=\"tab-btn\" onclick=\"switchTab('data')\">Data Team Wise<\/button>\n    <button class=\"tab-btn\" onclick=\"switchTab('teamlead')\">Team Lead Wise<\/button>\n    <button class=\"tab-btn\" onclick=\"switchTab('project')\">Project Wise<\/button>\n<\/div>\n\n<div class=\"table-wrapper\">\n    <table>\n        <thead id=\"tableHead\"><\/thead>\n        <tbody id=\"tableBody\"><\/tbody>\n    <\/table>\n<\/div>\n\n<div class=\"pagination-footer\">\n    <div class=\"pagination-controls\">\n        <span class=\"nav-btn\" id=\"prevBtn\" onclick=\"changePage(-1)\">\u00ab Previous<\/span>\n        <div id=\"pageNumbers\" style=\"display:flex;gap:6px;\"><\/div>\n        <span class=\"nav-btn\" id=\"nextBtn\" onclick=\"changePage(1)\">Next \u00bb<\/span>\n    <\/div>\n    <div class=\"entry-info\" id=\"entryInfo\">Showing 0 to 0 of 0 entries<\/div>\n<\/div>\n\n<script>\n\/\/ \u2500\u2500 ENDPOINTS \u2500\u2500\nconst BASE_URL = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\nconst ENDPOINTS = {\n    EXECUTIVE:   `${BASE_URL}\/productivity\/indexexecutive`,\n    QC:          `${BASE_URL}\/productivity\/qc\/indexexecutive`,\n    DATA:        `${BASE_URL}\/productivity\/data\/indexexecutive`,\n    TEAM_LEAD:   `${BASE_URL}\/productivity\/team-lead-wise`,\n    PROJECT:     `${BASE_URL}\/productivity\/project-wise`,\n    EXECUTIVE_EXPORT: `${BASE_URL}\/productivity\/indexexecutive-export`,\n    QC_EXPORT:        `${BASE_URL}\/productivity\/qc-indexexecutive-export`,\n    DATA_EXPORT:      `${BASE_URL}\/productivity\/data-indexexecutive-export`,\n    TEAM_LEAD_EXPORT: `${BASE_URL}\/productivity\/team-lead-wise-export`,\n    PROJECT_EXPORT:   `${BASE_URL}\/productivity\/project-wise-export`,\n    USER_TEAM_LEADS: `${BASE_URL}\/user\/team-leads`,\n    CLIENT_ALL:  `${BASE_URL}\/client\/all`,\n    USER_TABLE_LIST: `${BASE_URL}\/user\/table\/list`,\n};\n\n\/\/ CONSTANTS\nconst PER_PAGE = 10;\nconst MONTHS = ['January','February','March','April','May','June',\n                'July','August','September','October','November','December'];\nconst CUR_YEAR = new Date().getFullYear();\nconst YEARS = Array.from({length:11}, (_,i) => CUR_YEAR - i);\n\nlet currentTab  = 'executive';\nlet allData     = [];\nlet currentPage = 1;\nlet filterState = {};\n\n\/\/ Lists loaded from APIs\nlet teamLeadsList  = [];  \/\/ from \/user\/team-leads\nlet clientAllList  = [];  \/\/ from \/client\/all\nlet userTableList  = [];  \/\/ from \/user\/table\/list\n\nconst getHeaders = () => ({\n    'Content-Type': 'application\/json',\n    'Authorization': `Bearer ${localStorage.getItem('TOKEN')}`\n});\n\n\/\/ TAB CONFIG\nconst TABS = {\n    executive: {\n        title: 'EXECUTIVE WISE PRODUCTIVITY AND QUALITY SUMMARY',\n        showCards: true, showCSV: true,\n        columns: ['A. MANAGER','T. LEADs','PESUDO NAME','PRODUCTIVITY','QUALITY'],\n        fields:  ['a_manager','team_lead','pesudo_name','productivity_percentage','quality_percentage'],\n    },\n    qc: {\n        title: 'EXECUTIVE WISE PRODUCTIVITY AND QUALITY SUMMARY',\n        showCards: true, showCSV: true,\n        columns: ['A. MANAGER','T. LEADs','PESUDO NAME','PRODUCTIVITY','QUALITY'],\n        fields:  ['a_manager','team_lead','pesudo_name','productivity','qaulity'],\n    },\n    data: {\n        title: 'EXECUTIVE WISE PRODUCTIVITY AND QUALITY SUMMARY',\n        showCards: false, showCSV: true,\n        columns: ['A. MANAGER','T. LEADs','PESUDO NAME','PRODUCTIVITY','QUALITY'],\n        fields:  ['a_manager','team_lead','pesudo_name','productivity_percentage','quality_percentage'],\n    },\n    teamlead: {\n        title: 'TEAM LEAD WISE PRODUCTIVITY AND QUALITY SUMMARY',\n        showCards: false, showCSV: true,\n        columns: ['A. MANAGER','PESUDO NAME','PRODUCTIVITY','QUALITY'],\n        fields:  ['a_manager','pesudo_name','productivity','quality'],\n    },\n    project: {\n        title: 'EXECUTIVE WISE-PRODUCTIVITY',\n        showCards: false, showCSV: true,\n        columns: ['PROJECT NAME','PRODUCTIVITY','QUALITY'],\n        fields:  ['project_name','productivity','quality'],\n    }\n};\n\n\/\/ \u2500\u2500 HELPER: extract rows \u2500\u2500\nfunction extractRows(payload) {\n    if (Array.isArray(payload))                    return payload;\n    if (Array.isArray(payload?.data))              return payload.data;\n    if (Array.isArray(payload?.data?.data))        return payload.data.data;\n    return [];\n}\n\n\/\/ \u2500\u2500 OPTION GENERATORS \u2500\u2500\nfunction monthOptsHTML(id='fMonth') {\n    return `<div class=\"sel-wrap\"><select id=\"${id}\">\n        <option value=\"\">Select Month<\/option>\n        ${MONTHS.map((m,i)=>`<option value=\"${i+1}\">${m}<\/option>`).join('')}\n    <\/select><\/div>`;\n}\nfunction yearOptsHTML(id='fYear') {\n    return `<div class=\"sel-wrap\"><select id=\"${id}\">\n        <option value=\"\">Year<\/option>\n        ${YEARS.map(y=>`<option value=\"${y}\">${y}<\/option>`).join('')}\n    <\/select><\/div>`;\n}\n\n\/\/ \u2500\u2500 MULTI-SELECT BUILDERS \u2500\u2500\nfunction buildMultiSelect(id, placeholder, items, valueKey, labelKey) {\n    const opts = items.map(item => {\n        const val   = item[valueKey] != null ? item[valueKey] : '';\n        const label = item[labelKey] != null ? item[labelKey] : '';\n        return `<div class=\"custom-select-option\" data-value=\"${val}\" onclick=\"csSelect('${id}','${String(val).replace(\/'\/g,\"\\\\'\")}',this,true)\">\n            <div class=\"cs-checkbox\"><\/div><span class=\"opt-label\">${label}<\/span>\n        <\/div>`;\n    }).join('');\n    return `<div class=\"cs-wrap\" id=\"csWrapper_${id}\">\n        <input type=\"hidden\" id=\"${id}\" value=\"\">\n        <div class=\"custom-select-trigger placeholder-shown\" onclick=\"csTriggerClick('${id}', event)\">\n            <div class=\"cs-label\">\n                <input type=\"text\" class=\"cs-search-input\" id=\"csInput_${id}\" placeholder=\"${placeholder}\" autocomplete=\"off\"\n                    oninput=\"csIntegratedFilter('${id}', this.value)\"\n                    onkeydown=\"csKeyDown(event, '${id}')\"\n                    onclick=\"csOpenDropdown('${id}'); event.stopPropagation();\"\n                    onfocus=\"csOpenDropdown('${id}')\">\n            <\/div>\n            <svg class=\"cs-arrow\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polyline points=\"6 9 12 15 18 9\"\/><\/svg>\n        <\/div>\n        <div class=\"custom-select-dropdown\" id=\"csDrop_${id}\">\n            <div class=\"custom-select-options\">\n                <div class=\"custom-select-option placeholder-option\" data-value=\"\" onclick=\"csSelect('${id}','',this,true)\"><span class=\"opt-label\">${placeholder}<\/span><\/div>\n                ${opts}\n            <\/div>\n        <\/div>\n    <\/div>`;\n}\n\n\/\/ Dynamic multi-select built from allData (for Executive, QC, Data tabs)\nfunction buildDynamicMultiSelect(id, placeholder, field) {\n    const seen = new Set();\n    const items = [];\n    allData.forEach(row => {\n        const v = row[field];\n        if (v != null && !seen.has(v)) { seen.add(v); items.push({ v, l: v }); }\n    });\n    return buildMultiSelect(id, placeholder, items, 'v', 'l');\n}\n\n\/\/ \u2500\u2500 MULTI-SELECT LOGIC \u2500\u2500\n\nfunction csTriggerClick(selectId, e) {\n    e.stopPropagation();\n    csToggle(selectId, e);\n}\n\nfunction csToggle(selectId, e) {\n    if (e) e.stopPropagation();\n    const wrapper = document.getElementById('csWrapper_' + selectId);\n    const isOpen  = wrapper.classList.contains('open');\n    document.querySelectorAll('.cs-wrap.open').forEach(w => { if (w !== wrapper) w.classList.remove('open'); });\n    if (isOpen) {\n        wrapper.classList.remove('open');\n    } else {\n        wrapper.classList.add('open');\n        const searchInput = document.getElementById('csInput_' + selectId);\n        if (searchInput) {\n            searchInput.value = '';\n            csIntegratedFilter(selectId, '');\n            setTimeout(() => searchInput.focus(), 50);\n        }\n        csHighlightFirst(selectId);\n    }\n}\n\nfunction csOpenDropdown(selectId) {\n    const wrapper = document.getElementById('csWrapper_' + selectId);\n    if (!wrapper || wrapper.classList.contains('open')) return;\n    document.querySelectorAll('.cs-wrap.open').forEach(w => { if (w !== wrapper) w.classList.remove('open'); });\n    wrapper.classList.add('open');\n    csHighlightFirst(selectId);\n}\n\nfunction csIntegratedFilter(selectId, query) {\n    const drop = document.getElementById('csDrop_' + selectId);\n    if (!drop) return;\n    const options = drop.querySelectorAll('.custom-select-option');\n    const q = (query || '').toLowerCase();\n    options.forEach(opt => {\n        if (opt.classList.contains('placeholder-option')) {\n            opt.style.display = 'none';\n            return;\n        }\n        const text = opt.textContent.toLowerCase();\n        opt.style.display = text.includes(q) ? '' : 'none';\n    });\n    const wrapper = document.getElementById('csWrapper_' + selectId);\n    if (wrapper && !wrapper.classList.contains('open')) {\n        wrapper.classList.add('open');\n    }\n    csHighlightFirst(selectId);\n}\n\n\/\/ Keep old csFilter alias for compatibility\nfunction csFilter(selectId, query) {\n    csIntegratedFilter(selectId, query);\n}\n\nfunction csHighlightFirst(selectId) {\n    const drop = document.getElementById('csDrop_' + selectId);\n    if (!drop) return;\n    drop.querySelectorAll('.custom-select-option').forEach(o => o.classList.remove('highlighted'));\n    const visible = Array.from(drop.querySelectorAll('.custom-select-option'))\n        .filter(o => o.style.display !== 'none' && !o.classList.contains('placeholder-option'));\n    if (visible.length > 0) visible[0].classList.add('highlighted');\n}\n\nfunction csKeyDown(e, selectId) {\n    const wrapper = document.getElementById('csWrapper_' + selectId);\n    const drop    = document.getElementById('csDrop_' + selectId);\n    if (!wrapper || !drop) return;\n    const isOpen = wrapper.classList.contains('open');\n\n    if (e.key === 'Enter') {\n        e.preventDefault();\n        if (!isOpen) {\n            csToggle(selectId, e);\n        } else {\n            const highlighted = drop.querySelector('.custom-select-option.highlighted');\n            if (highlighted) {\n                const multi = true; \/\/ all our selects are multi\n                const value = highlighted.dataset.value;\n                csSelect(selectId, value, highlighted, multi);\n            } else {\n                csToggle(selectId, e);\n            }\n        }\n        return;\n    }\n\n    if (e.key === 'ArrowDown') {\n        e.preventDefault();\n        if (!isOpen) { csToggle(selectId, e); return; }\n        const visibleOptions = Array.from(drop.querySelectorAll('.custom-select-option'))\n            .filter(o => o.style.display !== 'none' && !o.classList.contains('placeholder-option'));\n        if (visibleOptions.length === 0) return;\n        let currentIdx = visibleOptions.findIndex(o => o.classList.contains('highlighted'));\n        visibleOptions.forEach(o => o.classList.remove('highlighted'));\n        currentIdx = (currentIdx + 1) % visibleOptions.length;\n        visibleOptions[currentIdx].classList.add('highlighted');\n        visibleOptions[currentIdx].scrollIntoView({ block: 'nearest' });\n        return;\n    }\n\n    if (e.key === 'ArrowUp') {\n        e.preventDefault();\n        if (!isOpen) { csToggle(selectId, e); return; }\n        const visibleOptions = Array.from(drop.querySelectorAll('.custom-select-option'))\n            .filter(o => o.style.display !== 'none' && !o.classList.contains('placeholder-option'));\n        if (visibleOptions.length === 0) return;\n        let currentIdx = visibleOptions.findIndex(o => o.classList.contains('highlighted'));\n        visibleOptions.forEach(o => o.classList.remove('highlighted'));\n        if (currentIdx <= 0) currentIdx = visibleOptions.length - 1;\n        else currentIdx -= 1;\n        visibleOptions[currentIdx].classList.add('highlighted');\n        visibleOptions[currentIdx].scrollIntoView({ block: 'nearest' });\n        return;\n    }\n\n    if (e.key === 'Escape') {\n        wrapper.classList.remove('open');\n        return;\n    }\n}\n\nfunction csSelect(selectId, value, optionEl, multi) {\n    const wrapper  = document.getElementById('csWrapper_' + selectId);\n    const input    = document.getElementById(selectId);\n    const trigger  = wrapper.querySelector('.custom-select-trigger');\n    const labelEl  = trigger.querySelector('.cs-label');\n\n    \/\/ Helper to rebuild the integrated search input\n    function buildSearchInput(placeholderText) {\n        return `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${placeholderText}\" autocomplete=\"off\"\n            oninput=\"csIntegratedFilter('${selectId}', this.value)\"\n            onkeydown=\"csKeyDown(event, '${selectId}')\"\n            onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\"\n            onfocus=\"csOpenDropdown('${selectId}')\">`;\n    }\n\n    const placeholderLabel = wrapper.querySelector('.placeholder-option .opt-label');\n    const phText = placeholderLabel ? placeholderLabel.textContent.trim() : 'Select';\n\n    if (value === '') {\n        input.value = '';\n        labelEl.innerHTML = buildSearchInput(phText);\n        trigger.classList.add('placeholder-shown');\n        wrapper.querySelectorAll('.custom-select-option').forEach(o => o.classList.remove('selected'));\n        if (!multi) wrapper.classList.remove('open');\n        return;\n    }\n\n    if (!multi) {\n        input.value = value;\n        const selectedText = optionEl.querySelector('.opt-label').textContent.trim();\n        labelEl.innerHTML = buildSearchInput(selectedText);\n        trigger.classList.remove('placeholder-shown');\n        wrapper.querySelectorAll('.custom-select-option').forEach(o => o.classList.remove('selected'));\n        optionEl.classList.add('selected');\n        wrapper.classList.remove('open');\n    } else {\n        let values = input.value ? input.value.split(',') : [];\n        const idx  = values.indexOf(String(value));\n        if (idx > -1) { values.splice(idx, 1); optionEl.classList.remove('selected'); }\n        else          { values.push(String(value)); optionEl.classList.add('selected'); }\n        input.value = values.join(',');\n\n        if (values.length === 0) {\n            labelEl.innerHTML = buildSearchInput(phText);\n            trigger.classList.add('placeholder-shown');\n        } else {\n            trigger.classList.remove('placeholder-shown');\n            const selectedLabels = Array.from(wrapper.querySelectorAll('.custom-select-option.selected'))\n                .map(o => ({ val: o.dataset.value, label: o.querySelector('.opt-label').textContent.trim() }));\n            const chipsHtml = selectedLabels.map(item =>\n                `<div class=\"cs-chip\"><span>${item.label}<\/span><span class=\"cs-chip-remove\" onclick=\"csRemoveChip(event,'${selectId}','${item.val}')\">&times;<\/span><\/div>`\n            ).join('');\n            labelEl.innerHTML = `<div class=\"cs-chips\">${chipsHtml}<\/div>` + buildSearchInput('');\n        }\n\n        \/\/ Keep focus on the search input after selection\n        if (wrapper.classList.contains('open')) {\n            const newInput = document.getElementById('csInput_' + selectId);\n            if (newInput) setTimeout(() => newInput.focus(), 0);\n        }\n    }\n}\n\nfunction csRemoveChip(e, selectId, value) {\n    e.stopPropagation();\n    const wrapper  = document.getElementById('csWrapper_' + selectId);\n    const optionEl = wrapper.querySelector(`.custom-select-option[data-value=\"${value}\"]`);\n    if (optionEl) csSelect(selectId, value, optionEl, true);\n}\n\ndocument.addEventListener('click', function(e) {\n    if (!e.target.closest('.cs-wrap')) {\n        document.querySelectorAll('.cs-wrap.open').forEach(w => w.classList.remove('open'));\n    }\n});\n\n\/\/ \u2500\u2500 CLEAR FILTERS \u2500\u2500\nfunction clearFilters() {\n    \/\/ Reset month and year selects\n    const monthEl = document.getElementById('fMonth');\n    const yearEl  = document.getElementById('fYear');\n    if (monthEl) monthEl.value = '';\n    if (yearEl)  yearEl.value  = '';\n\n    \/\/ Reset all multi-selects by finding hidden inputs inside cs-wrap elements\n    const bar = document.getElementById('filterBar');\n    if (bar) {\n        bar.querySelectorAll('.cs-wrap').forEach(wrapper => {\n            const hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n            if (!hiddenInput) return;\n            const selectId = hiddenInput.id;\n\n            \/\/ Clear hidden value\n            hiddenInput.value = '';\n\n            \/\/ Deselect all options\n            wrapper.querySelectorAll('.custom-select-option').forEach(o => o.classList.remove('selected'));\n\n            \/\/ Reset trigger label to placeholder state\n            const trigger = wrapper.querySelector('.custom-select-trigger');\n            const labelEl = trigger ? trigger.querySelector('.cs-label') : null;\n            const placeholderLabel = wrapper.querySelector('.placeholder-option .opt-label');\n            const phText = placeholderLabel ? placeholderLabel.textContent.trim() : 'Select';\n\n            if (labelEl) {\n                labelEl.innerHTML = `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${phText}\" autocomplete=\"off\"\n                    oninput=\"csIntegratedFilter('${selectId}', this.value)\"\n                    onkeydown=\"csKeyDown(event, '${selectId}')\"\n                    onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\"\n                    onfocus=\"csOpenDropdown('${selectId}')\">`;\n            }\n            if (trigger) trigger.classList.add('placeholder-shown');\n\n            \/\/ Close dropdown if open\n            wrapper.classList.remove('open');\n        });\n    }\n\n    \/\/ Re-fetch data with no filters\n    currentPage = 1;\n    fetchData();\n}\n\n\/\/ \u2500\u2500 RENDER FILTER BAR \u2500\u2500\nfunction renderFilterBar() {\n    filterState = {};\n    const bar = document.getElementById('filterBar');\n    bar.innerHTML = '';\n\n    if (currentTab === 'executive') {\n        bar.innerHTML = `\n            ${monthOptsHTML()}\n            ${yearOptsHTML()}\n            ${buildDynamicMultiSelect('fExecutive','Select Executive','pesudo_name')}\n        `;\n    } else if (currentTab === 'qc') {\n        \/\/ QC Executive dropdown populated from \/user\/table\/list\n        bar.innerHTML = `\n            ${monthOptsHTML()}\n            ${yearOptsHTML()}\n            ${buildMultiSelect('fQCExecutive','Select QC Executive', userTableList, 'id', 'pesudo_name')}\n        `;\n    } else if (currentTab === 'data') {\n        bar.innerHTML = `\n            ${monthOptsHTML()}\n            ${yearOptsHTML()}\n            ${buildDynamicMultiSelect('fData','Select Data','pesudo_name')}\n        `;\n    } else if (currentTab === 'teamlead') {\n        \/\/ Team Lead dropdown populated from \/user\/team-leads\n        bar.innerHTML = `\n            ${monthOptsHTML()}\n            ${yearOptsHTML()}\n            ${buildMultiSelect('fTeamLead','Select Team Lead', teamLeadsList, 'id', 'pesudo_name')}\n        `;\n    } else if (currentTab === 'project') {\n        \/\/ Clients dropdown populated from \/client\/all\n        bar.innerHTML = `\n            ${monthOptsHTML()}\n            ${yearOptsHTML()}\n            ${buildMultiSelect('fClient','Select Clients', clientAllList, 'id', 'name')}\n        `;\n    }\n\n    \/\/ Filter button\n    const btnF = document.createElement('button');\n    btnF.className = 'btn-filter';\n    btnF.textContent = 'Filter';\n    btnF.onclick = () => { currentPage = 1; fetchData(); };\n    bar.appendChild(btnF);\n\n    \/\/ Clear button\n    const btnClear = document.createElement('button');\n    btnClear.className = 'btn-clear';\n    btnClear.textContent = 'Clear';\n    btnClear.onclick = clearFilters;\n    bar.appendChild(btnClear);\n\n    \/\/ CSV button\n    const cfg = TABS[currentTab];\n    if (cfg.showCSV) {\n        const btnC = document.createElement('button');\n        btnC.className = 'btn-csv';\n        btnC.textContent = 'Download CSV';\n        btnC.onclick = downloadCSV;\n        bar.appendChild(btnC);\n    }\n}\n\n\/\/ Rebuild dynamic (data-driven) dropdowns after data loads\nfunction refreshDynamicDropdown() {\n    if (currentTab === 'executive') {\n        const wrap = document.getElementById('csWrapper_fExecutive');\n        if (!wrap) return;\n        const newHtml = buildDynamicMultiSelect('fExecutive','Select Executive','pesudo_name');\n        const temp = document.createElement('div');\n        temp.innerHTML = newHtml;\n        wrap.replaceWith(temp.firstElementChild);\n    } else if (currentTab === 'data') {\n        const wrap = document.getElementById('csWrapper_fData');\n        if (!wrap) return;\n        const newHtml = buildDynamicMultiSelect('fData','Select Data','pesudo_name');\n        const temp = document.createElement('div');\n        temp.innerHTML = newHtml;\n        wrap.replaceWith(temp.firstElementChild);\n    }\n}\n\n\/\/ \u2500\u2500 READ CURRENT FILTER VALUES \u2500\u2500\nfunction readFilter(id) {\n    const el = document.getElementById(id);\n    return el ? el.value : '';\n}\n\n\/\/ \u2500\u2500 BUILD API URL \u2500\u2500\nfunction buildUrl() {\n    const p = new URLSearchParams();\n    const month = readFilter('fMonth');\n    const year  = readFilter('fYear');\n\n    if (currentTab === 'executive') {\n        if (month) p.set('month', month);\n        if (year)  p.set('year',  year);\n        const exec = readFilter('fExecutive');\n        if (exec) p.set('assigned_to', exec);\n        return `${ENDPOINTS.EXECUTIVE}?${p}`;\n    }\n    if (currentTab === 'qc') {\n        if (month) p.set('month', month);\n        if (year)  p.set('year',  year);\n        const qcExec = readFilter('fQCExecutive');\n        if (qcExec) p.set('assigned_to', qcExec);\n        return `${ENDPOINTS.QC}?${p}`;\n    }\n    if (currentTab === 'data') {\n        if (month) p.set('month', month);\n        if (year)  p.set('year',  year);\n        const data = readFilter('fData');\n        if (data) p.set('assigned_to', data);\n        return `${ENDPOINTS.DATA}?${p}`;\n    }\n    if (currentTab === 'teamlead') {\n        if (month) p.set('month', month);\n        if (year)  p.set('year',  year);\n        const tl = readFilter('fTeamLead');\n        if (tl) p.set('team_lead_id', tl);\n        return `${ENDPOINTS.TEAM_LEAD}?${p}`;\n    }\n    if (currentTab === 'project') {\n        if (month) p.set('month', month);\n        if (year)  p.set('year',  year);\n        const client = readFilter('fClient');\n        if (client) p.set('client_id', client);\n        return `${ENDPOINTS.PROJECT}?${p}`;\n    }\n    return ENDPOINTS.PROJECT;\n}\n\nfunction buildExportUrl() {\n    const p = new URLSearchParams();\n    const month = readFilter('fMonth');\n    const year = readFilter('fYear');\n    if (month) p.set('month', month);\n    if (year) p.set('year', year);\n\n    let base = ENDPOINTS.PROJECT_EXPORT;\n    if (currentTab === 'executive') {\n        base = ENDPOINTS.EXECUTIVE_EXPORT;\n        const exec = readFilter('fExecutive');\n        if (exec) p.set('assigned_to', exec);\n    } else if (currentTab === 'qc') {\n        base = ENDPOINTS.QC_EXPORT;\n        const qcExec = readFilter('fQCExecutive');\n        if (qcExec) p.set('assigned_to', qcExec);\n    } else if (currentTab === 'data') {\n        base = ENDPOINTS.DATA_EXPORT;\n        const data = readFilter('fData');\n        if (data) p.set('assigned_to', data);\n    } else if (currentTab === 'teamlead') {\n        base = ENDPOINTS.TEAM_LEAD_EXPORT;\n        const tl = readFilter('fTeamLead');\n        if (tl) p.set('team_lead_id', tl);\n    } else if (currentTab === 'project') {\n        base = ENDPOINTS.PROJECT_EXPORT;\n        const client = readFilter('fClient');\n        if (client) p.set('client_id', client);\n    }\n    const q = p.toString();\n    return q ? `${base}?${q}` : base;\n}\n\n\/\/ \u2500\u2500 FETCH DATA \u2500\u2500\nasync function fetchData() {\n    showLoading();\n    try {\n        const res  = await fetch(buildUrl(), { headers: getHeaders() });\n        const json = await res.json();\n\n        let data = [], summary = null;\n        if (Array.isArray(json))                             { data = json; }\n        else if (Array.isArray(json.data))                   { data = json.data; summary = json.summary; }\n        else if (json.data && Array.isArray(json.data.data)) { data = json.data.data; summary = json.summary; }\n\n        allData = data;\n\n        if (summary && TABS[currentTab].showCards) {\n            document.getElementById('summaryProductivity').textContent =\n                parseFloat(summary.productivity_percentage || 0).toFixed(2) + '%';\n            document.getElementById('summaryQuality').textContent =\n                parseFloat(summary.quality_percentage || 0).toFixed(2) + '%';\n        }\n\n        \/\/ Rebuild dynamic dropdowns after data loads (executive \/ data tabs)\n        refreshDynamicDropdown();\n\n        currentPage = 1;\n        renderCurrentPage();\n    } catch (err) {\n        console.error('API error:', err);\n        renderTableBody([]);\n        renderPagination(0);\n    }\n}\n\n\/\/ \u2500\u2500 LOAD AUXILIARY LISTS \u2500\u2500\nasync function loadAuxLists() {\n    try {\n        const [tlRes, clRes, ulRes] = await Promise.all([\n            fetch(`${ENDPOINTS.USER_TEAM_LEADS}`, { headers: getHeaders() }),\n            fetch(`${ENDPOINTS.CLIENT_ALL}?page=1&limit=500`, { headers: getHeaders() }),\n            fetch(`${ENDPOINTS.USER_TABLE_LIST}?page=1&limit=500`, { headers: getHeaders() }),\n        ]);\n        const tlJson = await tlRes.json();\n        const clJson = await clRes.json();\n        const ulJson = await ulRes.json();\n\n        \/\/ \/user\/team-leads returns { data: [...] }\n        teamLeadsList = Array.isArray(tlJson.data) ? tlJson.data :\n                        Array.isArray(tlJson)       ? tlJson : [];\n\n        \/\/ \/client\/all returns { data: { data: [...] } }\n        clientAllList = Array.isArray(clJson?.data?.data) ? clJson.data.data :\n                        Array.isArray(clJson?.data)        ? clJson.data :\n                        Array.isArray(clJson)               ? clJson : [];\n\n        \/\/ \/user\/table\/list returns { data: [...] }\n        userTableList = Array.isArray(ulJson?.data) ? ulJson.data :\n                        Array.isArray(ulJson)        ? ulJson : [];\n    } catch(e) {\n        console.error('Aux lists error:', e);\n        teamLeadsList = []; clientAllList = []; userTableList = [];\n    }\n}\n\n\/\/ \u2500\u2500 FILTER + PAGE \u2500\u2500\nfunction filtered() {\n    return allData;\n}\n\nfunction renderCurrentPage() {\n    const rows  = filtered();\n    const start = (currentPage - 1) * PER_PAGE;\n    renderTableBody(rows.slice(start, start + PER_PAGE));\n    renderPagination(rows.length);\n    updateCards(rows);\n}\n\nfunction updateCards(rows) {\n    if (!TABS[currentTab].showCards) return;\n    if (!rows.length) {\n        document.getElementById('summaryProductivity').textContent = '0.00%';\n        document.getElementById('summaryQuality').textContent = '0.00%';\n        return;\n    }\n    \/\/ For qc tab, cards are driven by summary.productivity_percentage and summary.quality_percentage\n    \/\/ which are already set in fetchData(); skip recalculation here.\n    if (currentTab === 'qc') return;\n    const avgProd = rows.reduce((s, r) => s + parseFloat(r.productivity_percentage || 0), 0) \/ rows.length;\n    const avgQual = rows.reduce((s, r) => s + parseFloat(r.quality_percentage || 0), 0) \/ rows.length;\n    document.getElementById('summaryProductivity').textContent = avgProd.toFixed(2) + '%';\n    document.getElementById('summaryQuality').textContent = avgQual.toFixed(2) + '%';\n}\n\nfunction changePage(dir) {\n    const max = Math.ceil(filtered().length \/ PER_PAGE) || 1;\n    currentPage = Math.max(1, Math.min(currentPage + dir, max));\n    renderCurrentPage();\n}\n\n\/\/ \u2500\u2500 TABLE \u2500\u2500\nfunction renderTableHead() {\n    document.getElementById('tableHead').innerHTML =\n        '<tr>' + TABS[currentTab].columns.map(c => `<th>${c}<\/th>`).join('') + '<\/tr>';\n}\n\nfunction renderTableBody(rows) {\n    const cfg  = TABS[currentTab];\n    const body = document.getElementById('tableBody');\n    if (!rows.length) {\n        body.innerHTML = `<tr class=\"state-row\"><td colspan=\"${cfg.columns.length}\">No data found<\/td><\/tr>`;\n        return;\n    }\n    body.innerHTML = rows.map(row => '<tr>' + cfg.fields.map(f => {\n        let val = row[f] != null ? row[f] : '\u2014';\n        if (f === 'productivity_percentage' || f === 'quality_percentage')\n            val = parseFloat(val || 0).toFixed(2) + '%';\n        if (f === 'productivity' && currentTab === 'project')\n            val = parseFloat(val || 0).toFixed(2);\n        return `<td>${val}<\/td>`;\n    }).join('') + '<\/tr>').join('');\n}\n\nfunction showLoading() {\n    const cfg = TABS[currentTab];\n    document.getElementById('tableBody').innerHTML =\n        `<tr class=\"state-row\"><td colspan=\"${cfg.columns.length}\">Loading...<\/td><\/tr>`;\n}\n\n\/\/ \u2500\u2500 PAGINATION \u2500\u2500\nfunction renderPagination(total) {\n    const max   = Math.ceil(total \/ PER_PAGE) || 1;\n    const start = total === 0 ? 0 : (currentPage - 1) * PER_PAGE + 1;\n    const end   = Math.min(currentPage * PER_PAGE, total);\n\n    document.getElementById('entryInfo').textContent = `Showing ${start} to ${end} of ${total} entries`;\n    document.getElementById('prevBtn').classList.toggle('disabled', currentPage === 1);\n    document.getElementById('nextBtn').classList.toggle('disabled', currentPage >= max);\n\n    const cont = document.getElementById('pageNumbers');\n    cont.innerHTML = '';\n    let s = Math.max(1, currentPage - 2), e = Math.min(max, s + 4);\n    if (e - s < 4) s = Math.max(1, e - 4);\n    for (let i = s; i <= e; i++) {\n        const span = document.createElement('span');\n        span.className = 'page-num' + (i === currentPage ? ' active' : '');\n        span.textContent = i;\n        const pg = i;\n        span.onclick = () => { currentPage = pg; renderCurrentPage(); };\n        cont.appendChild(span);\n    }\n}\n\n\/\/ \u2500\u2500 CSV (PHP parity: productivity *-export APIs) \u2500\u2500\nasync function downloadCSV() {\n    const btn = document.querySelector('.btn-csv');\n    const origText = btn ? btn.textContent : 'Download CSV';\n    if (btn) {\n        btn.disabled = true;\n        btn.textContent = 'Downloading...';\n    }\n\n    try {\n        const token = localStorage.getItem('TOKEN');\n        if (!token) {\n            alert('You are not logged in. Please login again.');\n            return;\n        }\n\n        const res = await fetch(buildExportUrl(), {\n            method: 'GET',\n            headers: { Authorization: 'Bearer ' + token }\n        });\n\n        if (!res.ok) {\n            let message = 'Failed to download CSV.';\n            try {\n                const errJson = await res.json();\n                message = errJson.message || (errJson.messages && errJson.messages[0]) || message;\n            } catch (_) {\n                message = message + ' (HTTP ' + res.status + ')';\n            }\n            alert(message);\n            return;\n        }\n\n        const blob = await res.blob();\n        if (!blob || blob.size === 0) {\n            alert('No data available to export for the selected filters.');\n            return;\n        }\n\n        let filename = `${currentTab}_productivity_${Date.now()}.csv`;\n        const disposition = res.headers.get('Content-Disposition');\n        if (disposition) {\n            const match = disposition.match(\/filename=\"?([^\";\\n]+)\"?\/i);\n            if (match && match[1]) filename = match[1].trim();\n        }\n\n        const objectUrl = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = objectUrl;\n        a.download = filename;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(objectUrl);\n    } catch (err) {\n        console.error('CSV Download Error:', err);\n        alert('Failed to download CSV. Please try again.');\n    } finally {\n        if (btn) {\n            btn.disabled = false;\n            btn.textContent = origText;\n        }\n    }\n}\n\n\/\/ \u2500\u2500 SWITCH TAB \u2500\u2500\nfunction switchTab(tab) {\n    currentTab  = tab;\n    currentPage = 1;\n    allData     = [];\n\n    document.querySelectorAll('.tab-btn').forEach((btn, i) => {\n        btn.classList.toggle('active',\n            ['executive','qc','data','teamlead','project'][i] === tab);\n    });\n\n    const cfg = TABS[tab];\n    document.getElementById('pageTitle').textContent   = cfg.title;\n    document.getElementById('summaryCards').style.display = cfg.showCards ? 'flex' : 'none';\n\n    renderFilterBar();\n    renderTableHead();\n    renderTableBody([]);\n    renderPagination(0);\n    fetchData();\n}\n\n\/\/ \u2500\u2500 INIT \u2500\u2500\nasync function init() {\n    await loadAuxLists();\n    renderFilterBar();\n    renderTableHead();\n    fetchData();\n}\n\ninit();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>EXECUTIVE WISE PRODUCTIVITY AND QUALITY SUMMARY Productivity % 0.00% Quality % 0.00% Executive Wise QC Executive Wise Data Team Wise Team Lead Wise Project Wise \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-147","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/147","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=147"}],"version-history":[{"count":10,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/147\/revisions"}],"predecessor-version":[{"id":1012,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/147\/revisions\/1012"}],"wp:attachment":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}