{"id":159,"date":"2026-03-31T07:17:45","date_gmt":"2026-03-31T07:17:45","guid":{"rendered":"https:\/\/ph-portal.zyneventures.com\/?page_id=159"},"modified":"2026-03-31T07:19:00","modified_gmt":"2026-03-31T07:19:00","slug":"quality","status":"publish","type":"page","link":"https:\/\/ph-portal.zyneventures.com\/index.php\/quality\/","title":{"rendered":"Quality"},"content":{"rendered":"<style>.kadence-column159_3b324d-91 > .kt-inside-inner-col,.kadence-column159_3b324d-91 > .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-column159_3b324d-91 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column159_3b324d-91 > .kt-inside-inner-col{flex-direction:column;}.kadence-column159_3b324d-91 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column159_3b324d-91 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column159_3b324d-91{position:relative;}@media all and (max-width: 1024px){.kadence-column159_3b324d-91 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column159_3b324d-91 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column159_3b324d-91\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id159_40324e-86 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id159_40324e-86 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id159_40324e-86 > .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-id159_40324e-86 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id159_40324e-86 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id159_40324e-86 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id159_40324e-86 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-column159_2ec18e-88 > .kt-inside-inner-col,.kadence-column159_2ec18e-88 > .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-column159_2ec18e-88 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column159_2ec18e-88 > .kt-inside-inner-col{flex-direction:column;}.kadence-column159_2ec18e-88 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column159_2ec18e-88 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column159_2ec18e-88{position:relative;}@media all and (max-width: 1024px){.kadence-column159_2ec18e-88 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column159_2ec18e-88 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column159_2ec18e-88\"><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    --bg-light: #F8F9FA;\n  }\n  *, *::before, *::after { box-sizing: border-box;}\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 WRAPPER \u2500\u2500 *\/\n  .page-wrapper { max-width: 100%; }\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: 28px; }\n\n  \/* \u2500\u2500 FILTER CARD \u2500\u2500 *\/\n  .filter-card { \n    background: transparent; \n    border: none; \n    border-radius: 0; \n    padding: 0; \n    margin-bottom: 24px; \n  }\n\n  .filter-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n  .filter-row + .filter-row { margin-top: 14px; }\n\n  \/* custom select wrapper *\/\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 #DDE2E8; \n    border-radius: 7px;\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  \/* search input *\/\n  .search-box {\n    flex: 1 1 180px; \n    min-width: 140px; \n    max-width: 260px;\n    padding: 9px 14px; \n    border: 1px solid #DDE2E8; \n    border-radius: 7px;\n    font-family: 'Sora', sans-serif; \n    font-size: 12px; \n    color: var(--text-dark); \n    outline: none;\n  }\n  .search-box:focus { border-color: var(--primary-blue); }\n  .search-box::placeholder { color: #bbb; }\n\n  \/* buttons *\/\n  .btn { \n    padding: 9px 22px; \n    border-radius: 7px; \n    font-family: 'Sora', sans-serif; \n    font-size: 12px; \n    font-weight: 400; \n    cursor: pointer; \n    border: none; \n    transition: background 0.2s; \n    white-space: nowrap; \n  }\n  .btn-clear { background: #EBF2FF; color: var(--primary-blue); }\n  .btn-clear:hover { background: #d6e8ff; }\n  .btn-primary { background: var(--primary-blue); color: #fff; }\n  .btn-primary:hover { background: var(--hover-blue); }\n  .btn-csv { background: var(--primary-blue); color: #fff; }\n  .btn-csv:hover { background: var(--hover-blue); }\n  .btn-group { display: flex; gap: 10px; align-items: center; }\n  .btn-group-right { margin-left: auto; }\n\n  \/* \u2500\u2500 TABS \u2500\u2500 *\/\n  .tab-bar { \n    padding-top: 15px;\n    display: flex; \n    border-bottom: 2px solid var(--border-color); \n    margin-bottom: 0; \n    gap: 0; \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    position: relative; \n    white-space: nowrap; \n    user-select: none;\n  }\n  .tab-item.active { color: #212529; font-weight: 500; }\n  .tab-item.active::after {\n    content: ''; \n    position: absolute; \n    bottom: -3px; \n    left: 0; \n    right: 0; \n    height: 3px; \n    background: var(--primary-blue);\n  }\n  .tab-item:hover:not(.active) { color:  var(--text-muted) }\n\n  \/* \u2500\u2500 TABLE WRAPPER \u2500\u2500 *\/\n  .table-wrapper { \n    overflow-x: auto; \n    margin-top: 0; \n    border: 1px solid var(--border-color); \n    border-top: none; \n    border-radius: 0 0 10px 10px; \n  }\n  table { width: 100%; border-collapse: collapse; font-size: 12px; }\n  thead tr { background: #f5f5f5; }\n  thead th {\n    padding: 8px 15px; \n    font-weight: 500; \n    font-size: 12px; \n    text-transform: uppercase;\n    letter-spacing: 0.3px; \n    color: #212529; \n    border-bottom: 1px solid var(--border-color);\n    white-space: nowrap; \n    text-align: left;\n  }\n  tbody tr { border-bottom: 1px solid var(--border-color); transition: background 0.15s; }\n  tbody tr:last-child { border-bottom: none; }\n  tbody tr:hover { background: #f5f5f5; }\n  tbody td { \n    padding: 10px 10px; \n    font-weight: 400;\n    color: var(--text-muted); \n    vertical-align: middle; \n    white-space: nowrap; \n  }\n\n  \/* \u2500\u2500 COLORED BADGES \u2500\u2500 *\/\n  .badge-mi { \n    background: #FFF8E1; \n    color: #B7791F; \n    border: 1px solid #F6E05E; \n    border-radius: 6px; \n    padding: 3px 8px; \n    font-size: 12px; \n    font-weight: 400; \n    display: inline-block; \n  }\n  .badge-cr { \n    background: #FFF0F0; \n    color: #C53030; \n    border: 1px solid #FEB2B2; \n    border-radius: 6px; \n    padding: 3px 8px; \n    font-size: 12px; \n    font-weight: 400; \n    display: inline-block; \n  }\n  .badge-zte { \n    background: #FFF0F6; \n    color: #B83280; \n    border: 1px solid #FBB6CE; \n    border-radius: 6px; \n    padding: 3px 8px; \n    font-size: 12px; \n    font-weight: 400; \n    display: inline-block; \n  }\n\n  \/* name pill *\/\n  .name-pill { \n    background: #E0F7F0; \n    color: #0D6E52; \n    border-radius: 20px; \n    padding: 4px 14px; \n    font-size: 12px; \n    font-weight: 400; \n    display: inline-block; \n  }\n\n  \/* quality color *\/\n  .q-high { color: #1A7A4A; font-weight: 400; }\n  .q-mid  { color: #B7791F; font-weight: 400; }\n  .q-low  { color: #C53030; font-weight: 400; }\n\n  \/* \u2500\u2500 PAGINATION \u2500\u2500 *\/\n  .pagination-footer {\n    display: flex; \n    justify-content: space-between; \n    align-items: center;\n    padding: 16px 4px; \n    font-size: 12px; \n    color: var(--text-muted); \n    flex-wrap: wrap; gap: 10px;\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    background: transparent; \n    color: var(--text-muted); \n    user-select: none; \n    transition: background 0.2s;\n    border: 1px solid var(--border-color);\n  }\n  .nav-btn:hover:not(.disabled) { background: transparent; }\n  .nav-btn.disabled { opacity: 0.4; pointer-events: none; }\n  .page-num {\n    width: 32px; \n    height: 32px; \n    border-radius: 50%; \n    display: flex; \n    align-items: center; \n    justify-content: center;\n    cursor: pointer; \n    font-size: 12px; \n    background: transparent; \n    color: var(--text-muted);\n    border: 1px solid var(--border-color); \n    transition: background 0.2s;\n  }\n  .page-num.active { background: var(--primary-blue); color: #fff; border-color: var(--primary-blue); }\n  .page-num:hover:not(.active) { background: transparent; }\n\n  \/* loading \/ empty *\/\n  .state-row td { text-align: center; padding: 40px; color: var(--text-muted); font-size: 13px; }\n  .spinner { \n    display: inline-block; \n    width: 18px; height: 18px; \n    border: 2px solid #ddd; \n    border-top-color: var(--primary-blue); \n    border-radius: 50%; \n    animation: spin 0.7s linear infinite; \n    vertical-align: middle; \n    margin-right: 6px; \n  }\n  @keyframes spin { to { transform: rotate(360deg); } }\n\n  \/* \u2500\u2500 RESPONSIVE \u2500\u2500 *\/\n  @media (max-width: 900px) {\n    .page-wrapper { padding: 20px 16px 40px; }\n    .btn-group { margin-left: 0; }\n    .filter-row { gap: 8px; }\n    .sel-wrap { max-width: 100%; }\n    .tab-item { padding: 10px 14px; font-size: 13px; }\n  }\n  @media (max-width: 600px) {\n    .tab-bar { overflow-x: auto; }\n    .btn-group { flex-wrap: wrap; }\n  }\n<\/style>\n\n\n<div class=\"page-wrapper\">\n\n  <!-- TITLE + dynamic tab title -->\n  <div class=\"page-title\" id=\"pageTitle\">EXECUTIVE WISE-QUALITY<\/div>\n\n  <!-- FILTER CARD (dynamic content per tab) -->\n  <div class=\"filter-card\" id=\"filterCard\"><\/div>\n\n  <!-- TABS -->\n  <div class=\"tab-bar\">\n    <div class=\"tab-item active\" data-tab=\"executive\"    onclick=\"switchTab('executive')\">Executive Wise<\/div>\n    <div class=\"tab-item\"        data-tab=\"qc_executive\" onclick=\"switchTab('qc_executive')\">QC Executive Wise<\/div>\n    <div class=\"tab-item\"        data-tab=\"client\"       onclick=\"switchTab('client')\">Client Wise<\/div>\n    <div class=\"tab-item\"        data-tab=\"account\"      onclick=\"switchTab('account')\">Account Wise<\/div>\n  <\/div>\n\n  <!-- TABLE -->\n  <div class=\"table-wrapper\">\n    <table id=\"dataTable\">\n      <thead id=\"tableHead\"><\/thead>\n      <tbody id=\"tableBody\"><tr class=\"state-row\"><td colspan=\"20\"><span class=\"spinner\"><\/span>Loading\u2026<\/td><\/tr><\/tbody>\n    <\/table>\n  <\/div>\n\n  <!-- PAGINATION -->\n  <div class=\"pagination-footer\">\n    <div class=\"pagination-controls\">\n      <span class=\"nav-btn disabled\" id=\"prevBtn\" onclick=\"changePage(-1)\">\u00ab Previous<\/span>\n      <div id=\"pageNumbers\" style=\"display:flex;gap:6px;\"><\/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<\/div>\n\n<script>\n\/\/CONFIGURATION\nconst BASE_URL = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\nconst ENDPOINTS = {\n  TABLE_LIST:     `${BASE_URL}\/accountandbenchmark\/tableList`,\n  INDEX_CLIENT:   `${BASE_URL}\/quality\/index-client`,\n  INDEX_ACCOUNT:  `${BASE_URL}\/quality\/index-account`,\n  INDEX_EXECUTIVE:`${BASE_URL}\/quality\/index-executive`,\n  QC_EXECUTIVE:   `${BASE_URL}\/quality\/qc-index-executive`,\n};\n\nconst MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];\nconst CUR_YEAR = new Date().getFullYear();\nconst YEARS = Array.from({length:10},(_,i)=>CUR_YEAR-i);\n\n\/\/ STATE\nlet activeTab       = 'executive';\nlet pagination      = { page:1, limit:10, total:0, pages:0 };\nlet accountList     = [];  \/\/ from tableList\nlet executiveList   = [];  \/\/ from index-executive\nlet qcExecutiveList = [];  \/\/ from qc-index-executive\nlet filterState     = {};  \/\/ dynamic per tab\n\nfunction getDefaultFilterState(){\n  return {\n    month: '',\n    year: '',\n  };\n}\n\n\/\/ AUTH HELPER\nfunction getHeaders(){\n  const token = localStorage.getItem('TOKEN') || '';\n  return { 'Content-Type':'application\/json', 'Accept':'application\/json', 'Authorization':`Bearer ${token}` };\n}\n\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  if (Array.isArray(payload?.data?.rows)) return payload.data.rows;\n  if (Array.isArray(payload?.rows)) return payload.rows;\n  return [];\n}\n\nfunction extractPagination(payload, fallbackLimit, fallbackTotal){\n  const paginationInfo = payload?.pagination || payload?.data?.pagination || {};\n  const limitValue = Number(\n    paginationInfo.limit || payload?.limit || payload?.data?.limit || fallbackLimit || 10\n  );\n  const safeLimit = Number.isFinite(limitValue) && limitValue > 0 ? limitValue : (fallbackLimit || 10);\n\n  const totalValue = Number(\n    paginationInfo.total || payload?.total || payload?.data?.total || fallbackTotal || 0\n  );\n  const safeTotal = Number.isFinite(totalValue) && totalValue >= 0 ? totalValue : (fallbackTotal || 0);\n\n  const pagesValue = Number(\n    paginationInfo.pages || payload?.pages || payload?.data?.pages || (safeTotal > 0 ? Math.ceil(safeTotal \/ safeLimit) : 1)\n  );\n  const safePages = Number.isFinite(pagesValue) && pagesValue > 0 ? pagesValue : 1;\n\n  const pageValue = Number(\n    paginationInfo.page || payload?.page || payload?.data?.page || 1\n  );\n  const safePage = Number.isFinite(pageValue) && pageValue > 0 ? pageValue : 1;\n\n  return { page: safePage, limit: safeLimit, total: safeTotal, pages: safePages };\n}\n\nfunction hasRequestFilters(payload = {}){\n  return Object.keys(payload).some(key => !['page', 'limit'].includes(key) && payload[key] !== '' && payload[key] !== null && payload[key] !== undefined);\n}\n\nfunction hasMeaningfulResponse(payload){\n  const rows = extractRows(payload);\n  if (rows.length > 0) return true;\n  const paginationInfo = extractPagination(payload, pagination.limit, rows.length);\n  return paginationInfo.total > 0;\n}\n\nfunction requestViaXhr(method, url, payload){\n  return new Promise((resolve, reject) => {\n    const xhr = new XMLHttpRequest();\n    xhr.open(method, url, true);\n\n    Object.entries(getHeaders()).forEach(([key, value]) => {\n      xhr.setRequestHeader(key, value);\n    });\n\n    xhr.onload = () => {\n      if (xhr.status >= 200 && xhr.status < 300) {\n        try {\n          resolve(JSON.parse(xhr.responseText || '{}'));\n        } catch (error) {\n          reject(error);\n        }\n      } else {\n        reject(new Error(`HTTP ${xhr.status}`));\n      }\n    };\n\n    xhr.onerror = () => reject(new Error('Network request failed'));\n    xhr.send(payload ? JSON.stringify(payload) : null);\n  });\n}\n\nasync function requestWithBodyFallback(url, payload = {}){\n  \/\/ Some of these APIs are implemented as GET endpoints that still expect a JSON body.\n  const shouldRequireDataBeforeAccepting = hasRequestFilters(payload);\n\n  const shouldAcceptResponse = (responsePayload) => {\n    if (!shouldRequireDataBeforeAccepting) return true;\n    return hasMeaningfulResponse(responsePayload);\n  };\n\n  const buildQueryUrl = () => {\n    const params = new URLSearchParams();\n    Object.entries(payload).forEach(([k, v]) => {\n      if (v !== undefined && v !== null && v !== '') params.set(k, String(v));\n    });\n    return params.toString() ? `${url}?${params.toString()}` : url;\n  };\n\n  try {\n    const xhrPayload = await requestViaXhr('GET', url, payload);\n    if (shouldAcceptResponse(xhrPayload)) return xhrPayload;\n  } catch (e) {}\n\n  try {\n    const postRes = await fetch(url, {\n      method: 'POST',\n      headers: getHeaders(),\n      body: JSON.stringify(payload)\n    });\n    if (postRes.ok) {\n      const postPayload = await postRes.json();\n      if (shouldAcceptResponse(postPayload)) return postPayload;\n    }\n  } catch (e) {}\n\n  const getUrl = buildQueryUrl();\n  const getRes = await fetch(getUrl, { method: 'GET', headers: getHeaders() });\n  return await getRes.json();\n}\n\n\/\/ BOOT\nasync function init(){\n  await loadAccountList();\n  await loadExecutiveLists();\n  switchTab('executive');\n}\n\nasync function loadAccountList(){\n  try {\n    const r = await fetch(\n      ENDPOINTS.TABLE_LIST, { \n        method:'GET', \n        headers:getHeaders() \n      });\n    const d = await r.json();\n    accountList = extractRows(d);\n  } catch(e){ accountList = []; }\n}\n\nasync function loadExecutiveLists(){\n  try {\n    const payload = { page: 1, limit: 500 };\n    const [de, dq] = await Promise.all([\n      requestWithBodyFallback(ENDPOINTS.INDEX_EXECUTIVE, payload),\n      requestWithBodyFallback(ENDPOINTS.QC_EXECUTIVE, payload),\n    ]);\n    executiveList   = extractRows(de);\n    qcExecutiveList = extractRows(dq);\n  } catch(e){ executiveList=[]; qcExecutiveList=[]; }\n}\n\n\/\/ TAB SWITCHING\nfunction switchTab(tab){\n  activeTab = tab;\n  pagination = { page:1, limit:10, total:0, pages:0 };\n  filterState = getDefaultFilterState();\n\n  document.querySelectorAll('.tab-item').forEach(t=>t.classList.toggle('active', t.dataset.tab===tab));\n\n  const titles = {\n    executive:    'EXECUTIVE WISE-QUALITY',\n    qc_executive: 'QC EXECUTIVE WISE-QUALITY',\n    client:       'CLIENT WISE-QUALITY',\n    account:      'ACCOUNT WISE-QUALITY',\n  };\n  document.getElementById('pageTitle').textContent = titles[tab];\n\n  renderFilterForm();\n  fetchData();\n}\n\n\/\/ FILTER FORMS\nfunction monthSelHTML(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 yearSelHTML(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}\nfunction accountSelHTML(id='fAccount'){\n  const opts = accountList.map(a=>`<option value=\"${a.id}\">${a.name}<\/option>`).join('');\n  return `<div class=\"sel-wrap\" style=\"max-width:240px;\"><select id=\"${id}\">\n    <option value=\"\">Select Accounts<\/option>${opts}\n  <\/select><\/div>`;\n}\nfunction clientSelHTML(id='fClient', clients=[]){\n  const opts = clients.map(c=>`<option value=\"${c.client_id}\">${c.client_name}<\/option>`).join('');\n  return `<div class=\"sel-wrap\" style=\"max-width:240px;\"><select id=\"${id}\">\n    <option value=\"\">Select Clients<\/option>${opts}\n  <\/select><\/div>`;\n}\nfunction executiveSelHTML(id='fExecutive', list=[]){\n  const opts = list.map(e=>`<option value=\"${e.user_id}\">${e.pesudo_name}<\/option>`).join('');\n  return `<div class=\"sel-wrap\" style=\"max-width:220px;\"><select id=\"${id}\">\n    <option value=\"\">Select Executive<\/option>${opts}\n  <\/select><\/div>`;\n}\nfunction qcExecutiveSelHTML(id='fQCExecutive', list=[]){\n  const opts = list.map(e=>`<option value=\"${e.user_id}\">${e.pesudo_name}<\/option>`).join('');\n  return `<div class=\"sel-wrap\" style=\"max-width:220px;\"><select id=\"${id}\">\n    <option value=\"\">Select QC Executive<\/option>${opts}\n  <\/select><\/div>`;\n}\nfunction actionBtnsHTML(leftAlign=false){\n  const cls = leftAlign ? 'btn-group' : 'btn-group btn-group-right';\n  return `<div class=\"${cls}\">\n    <button class=\"btn btn-clear\" onclick=\"clearFilters()\">Clear<\/button>\n    <button class=\"btn btn-primary\" onclick=\"applyFilters()\">Filter<\/button>\n    <button class=\"btn btn-csv\" onclick=\"downloadCSV()\">Download CSV<\/button>\n  <\/div>`;\n}\n\nasync function renderFilterForm(){\n  const card = document.getElementById('filterCard');\n  if(activeTab==='executive'){\n    card.innerHTML = `\n      <div class=\"filter-row\">\n        ${monthSelHTML()} ${yearSelHTML()}\n        ${executiveSelHTML('fExecutive', executiveList)}\n        ${actionBtnsHTML(false)}\n      <\/div>`;\n  } else if(activeTab==='qc_executive'){\n    card.innerHTML = `\n      <div class=\"filter-row\">\n        ${monthSelHTML()} ${yearSelHTML()}\n        ${qcExecutiveSelHTML('fQCExecutive', qcExecutiveList)}\n        ${actionBtnsHTML(false)}\n      <\/div>`;\n  } else if(activeTab==='client'){\n    const clients = await fetchClientList();\n    card.innerHTML = `\n      <div class=\"filter-row\">\n        ${monthSelHTML()} ${yearSelHTML()}\n        ${clientSelHTML('fClient', clients)}\n        ${actionBtnsHTML(false)}\n      <\/div>`;\n  } else if(activeTab==='account'){\n    const clients = await fetchClientList();\n    card.innerHTML = `\n      <div class=\"filter-row\">\n        ${monthSelHTML()} ${yearSelHTML()}\n        ${accountSelHTML()} ${clientSelHTML('fClient', clients)}\n      <\/div>\n      <div class=\"filter-row\" style=\"margin-top:12px;\">\n        ${actionBtnsHTML(true)}\n      <\/div>`;\n  }\n  restoreFilterState();\n}\n\nasync function fetchClientList(){\n  try {\n    const d = await requestWithBodyFallback(ENDPOINTS.INDEX_CLIENT, { page: 1, limit: 200 });\n    const rows = extractRows(d);\n    const seen = new Set();\n    return rows.filter(r=>{ \n      if(seen.has(r.client_id)) \n      return false; seen.add(r.client_id); return true; });\n  } catch(e){ return []; }\n}\n\nfunction restoreFilterState(){\n  if(filterState.month){     const el=document.getElementById('fMonth');      if(el) el.value=filterState.month; }\n  if(filterState.year){      const el=document.getElementById('fYear');       if(el) el.value=filterState.year;  }\n  if(filterState.executive){ const el=document.getElementById('fExecutive');  if(el) el.value=filterState.executive; }\n  if(filterState.qcExec){    const el=document.getElementById('fQCExecutive');if(el) el.value=filterState.qcExec; }\n  if(filterState.client){    const el=document.getElementById('fClient');     if(el) el.value=filterState.client; }\n  if(filterState.account){   const el=document.getElementById('fAccount');    if(el) el.value=filterState.account; }\n}\nfunction readFilterState(){\n  const v = id=>{ const el=document.getElementById(id); return el?el.value:''; };\n  filterState = {\n    month:     v('fMonth'),\n    year:      v('fYear'),\n    executive: v('fExecutive'),\n    qcExec:    v('fQCExecutive'),\n    client:    v('fClient'),\n    account:   v('fAccount'),\n  };\n}\nfunction applyFilters(){ readFilterState(); pagination.page=1; fetchData(); }\nfunction clearFilters(){\n  filterState=getDefaultFilterState();\n  ['fMonth','fYear','fExecutive','fQCExecutive','fClient','fAccount'].forEach(id=>{ const el=document.getElementById(id); if(el) el.value=''; });\n  restoreFilterState();\n  pagination.page=1; fetchData();\n}\n\n\/\/ API FETCH\nasync function fetchData(){\n  setLoading();\n  try {\n    let url='', options={ method:'GET', headers:getHeaders() };\n\n    if(activeTab==='executive'){\n      url = ENDPOINTS.INDEX_EXECUTIVE;\n      const payload = { page: pagination.page, limit: pagination.limit };\n      if(filterState.month) payload.month = parseInt(filterState.month);\n      if(filterState.year) payload.year = parseInt(filterState.year);\n      if(filterState.executive) payload.user_id = parseInt(filterState.executive);\n      const d = await requestWithBodyFallback(url, payload);\n      const rows = extractRows(d);\n      const pg = extractPagination(d, pagination.limit, rows.length);\n      pagination = { ...pagination, ...pg };\n      renderTable(rows);\n      renderPagination();\n      return;\n    } else if(activeTab==='qc_executive'){\n      url = ENDPOINTS.QC_EXECUTIVE;\n      const payload = { page: pagination.page, limit: pagination.limit };\n      if(filterState.month) payload.month = parseInt(filterState.month);\n      if(filterState.year) payload.year = parseInt(filterState.year);\n      if(filterState.qcExec) payload.user_id = parseInt(filterState.qcExec);\n      const d = await requestWithBodyFallback(url, payload);\n      const rows = extractRows(d);\n      const pg = extractPagination(d, pagination.limit, rows.length);\n      pagination = { ...pagination, ...pg };\n      renderTable(rows);\n      renderPagination();\n      return;\n    } else if(activeTab==='client'){\n      url = ENDPOINTS.INDEX_CLIENT;\n      const payload = { page: pagination.page, limit: pagination.limit };\n      if(filterState.month) payload.month = parseInt(filterState.month);\n      if(filterState.year) payload.year = parseInt(filterState.year);\n      if(filterState.client) payload.client_id = parseInt(filterState.client);\n      const d = await requestWithBodyFallback(url, payload);\n      const rows = extractRows(d);\n      const pg = extractPagination(d, pagination.limit, rows.length);\n      pagination = { ...pagination, ...pg };\n      renderTable(rows);\n      renderPagination();\n      return;\n    } else if(activeTab==='account'){\n      url = ENDPOINTS.INDEX_ACCOUNT;\n      const params = new URLSearchParams();\n      if(filterState.account) params.set('account_id', filterState.account);\n      if(filterState.client)  params.set('client_id',  filterState.client);\n      if(filterState.month)   params.set('month',  filterState.month);\n      if(filterState.year)    params.set('year',   filterState.year);\n      params.set('page',  pagination.page);\n      params.set('limit', pagination.limit);\n      url += '?'+params.toString();\n      options = { method:'GET', headers:getHeaders() };\n    }\n\n    const r = await fetch(url, options);\n    const d = await r.json();\n    const rows = extractRows(d);\n    const pg = extractPagination(d, pagination.limit, rows.length);\n    pagination = { ...pagination, ...pg };\n    renderTable(rows);\n    renderPagination();\n  } catch(e){\n    console.error(e);\n    document.getElementById('tableBody').innerHTML = `<tr class=\"state-row\"><td colspan=\"20\">Error loading data. Please try again.<\/td><\/tr>`;\n  }\n}\n\nfunction setLoading(){\n  document.getElementById('tableBody').innerHTML = `<tr class=\"state-row\"><td colspan=\"20\"><span class=\"spinner\"><\/span>Loading\u2026<\/td><\/tr>`;\n}\n\n\/\/ TABLE HEADERS\nconst HEADS = {\n  executive: ['A. Manager','T. Lead','Pseudo Name','DOS Completed','QC Completed','QC %','Minor','Critical','ZTE','Total Errors','Minor WTG','Critical WTG','ZTE WTG','Total Error %','Quality'],\n  qc_executive: ['A. Manager','T. Lead','Pseudo Name','DOS Assign','DOS Completed','DOS %','Minor','Critical','ZTE','Total Errors','Minor WTG','Critical WTG','ZTE WTG','Total Error Ratio','Gernal Error Ratio','Quality'],\n  client:  ['Client','Account','DOS Completed','QC Completed','QC %','Minor','Critical','ZTE','Total Errors','Minor WTG','Critical WTG','ZTE WTG','Total Error %','Quality'],\n  account: ['Client','Account','# of DOS','QC Completed','QC %'],\n};\n\nfunction renderTable(rows){\n  const safeRows = Array.isArray(rows) ? rows : [];\n  const heads = HEADS[activeTab];\n  document.getElementById('tableHead').innerHTML = `<tr>${heads.map(h=>`<th>${h}<\/th>`).join('')}<\/tr>`;\n\n  const search = (filterState.search||'').toLowerCase();\n  const filtered = search\n    ? safeRows.filter(r=>JSON.stringify(r).toLowerCase().includes(search))\n    : safeRows;\n\n  if(!filtered.length){\n    document.getElementById('tableBody').innerHTML = `<tr class=\"state-row\"><td colspan=\"${heads.length}\">No data found.<\/td><\/tr>`;\n    updateEntryInfo(0,0,0);\n    return;\n  }\n\n  const start = (pagination.page-1)*pagination.limit+1;\n  const end   = Math.min(pagination.page*pagination.limit, pagination.total);\n  updateEntryInfo(start, end, pagination.total);\n\n  let html='';\n  filtered.forEach(row=>{\n    if(activeTab==='executive'){\n      html+=`<tr>\n        <td>${row.manager||'-'}<\/td>\n        <td>${row.team_lead||'-'}<\/td>\n        <td><span class=\"name-pill\">${row.pesudo_name||'-'}<\/span><\/td>\n        <td>${row.dos_completed??row.assigned_dos??row.dos_assign??'-'}<\/td>\n        <td>${row.qc_completed??0}<\/td>\n        <td>${fmt(row.qc_percentage)}%<\/td>\n        <td><span class=\"badge-mi\">MI (${row.minor_errors??0})<\/span><\/td>\n        <td><span class=\"badge-cr\">CR (${row.critical_errors??0})<\/span><\/td>\n        <td><span class=\"badge-zte\">ZTE(${row.zte_errors??0})<\/span><\/td>\n        <td>${row.total_errors??0}<\/td>\n        <td>${fmt(row.minor_wtg)}<\/td>\n        <td>${fmt(row.critical_wtg)}<\/td>\n        <td>${fmt(row.zte_wtg)}<\/td>\n        <td>${fmt(row.total_error_percentage??row.total_error_ratio)}%<\/td>\n        <td class=\"${qClass(row.quality_percentage)}\">${fmt(row.quality_percentage)}%<\/td>\n      <\/tr>`;\n    } else if(activeTab==='qc_executive'){\n      html+=`<tr>\n        <td>${row.manager||'-'}<\/td>\n        <td>${row.team_lead||'-'}<\/td>\n        <td><span class=\"name-pill\">${row.pesudo_name||'-'}<\/span><\/td>\n        <td>${row.dos_assign??row.assigned_dos??'-'}<\/td>\n        <td>${row.dos_completed??'-'}<\/td>\n        <td>${fmt(row.dos_percentage??0)}%<\/td>\n        <td><span class=\"badge-mi\">MI (${row.minor_errors??0})<\/span><\/td>\n        <td><span class=\"badge-cr\">CR (${row.critical_errors??0})<\/span><\/td>\n        <td><span class=\"badge-zte\">ZTE(${row.zte_errors??0})<\/span><\/td>\n        <td>${row.total_errors??0}<\/td>\n        <td>${fmt(row.minor_wtg)}<\/td>\n        <td>${fmt(row.critical_wtg)}<\/td>\n        <td>${fmt(row.zte_wtg)}<\/td>\n        <td>${fmt(row.total_error_ratio??row.total_error_percentage)}<\/td>\n        <td>${fmt(row.general_error_ratio??0)}<\/td>\n        <td class=\"${qClass(row.quality_percentage)}\">${fmt(row.quality_percentage)}%<\/td>\n      <\/tr>`;\n    } else if(activeTab==='client'){\n      html+=`<tr>\n        <td>${row.client_name||'-'}<\/td>\n        <td>${row.account_name||'Account'}<\/td>\n        <td>${row.dos_completed??row.no_of_dos??row.assigned_dos??'-'}<\/td>\n        <td>${row.qc_completed??'-'}<\/td>\n        <td>${fmt(row.qc_percentage)}%<\/td>\n        <td><span class=\"badge-mi\">Mi (${row.minor_errors??0})<\/span><\/td>\n        <td><span class=\"badge-cr\">CR (${row.critical_errors??0})<\/span><\/td>\n        <td><span class=\"badge-zte\">ZTE(${row.zte_errors??0})<\/span><\/td>\n        <td>${row.total_errors??0}<\/td>\n        <td>${fmt(row.minor_wtg)}<\/td>\n        <td>${fmt(row.critical_wtg)}<\/td>\n        <td>${fmt(row.zte_wtg)}<\/td>\n        <td>${fmt(row.total_error_percentage??row.total_error_ratio)}%<\/td>\n        <td class=\"${qClass(row.quality_percentage)}\">${fmt(row.quality_percentage)}%<\/td>\n      <\/tr>`;\n    } else if(activeTab==='account'){\n      html+=`<tr>\n        <td>${row.client_name||'-'}<\/td>\n        <td>${row.account_name||'-'}<\/td>\n        <td>${row.no_of_dos??row.dos_completed??'-'}<\/td>\n        <td>${row.qc_completed??'-'}<\/td>\n        <td>${fmt(row.qc_percentage)}%<\/td>\n      <\/tr>`;\n    }\n  });\n  document.getElementById('tableBody').innerHTML = html;\n}\n\n\/\/  PAGINATION\nfunction renderPagination(){\n  const { page, pages } = pagination;\n  const prev = document.getElementById('prevBtn');\n  const next = document.getElementById('nextBtn');\n  prev.className = page<=1 ? 'nav-btn disabled' : 'nav-btn';\n  next.className = page>=pages ? 'nav-btn disabled' : 'nav-btn';\n\n  const container = document.getElementById('pageNumbers');\n  container.innerHTML='';\n\n  \/\/ show max 7 page buttons with ellipsis\n  let nums=[];\n  if(pages<=7){ for(let i=1;i<=pages;i++) nums.push(i); }\n  else {\n    nums=[1];\n    if(page>3) nums.push('\u2026');\n    for(let i=Math.max(2,page-1);i<=Math.min(pages-1,page+1);i++) nums.push(i);\n    if(page<pages-2) nums.push('\u2026');\n    nums.push(pages);\n  }\n  nums.forEach(n=>{\n    if(n==='\u2026'){\n      const s=document.createElement('span');\n      s.textContent='\u2026'; s.style.cssText='display:flex;align-items:center;padding:0 4px;color:#aaa;';\n      container.appendChild(s);\n    } else {\n      const s=document.createElement('span');\n      s.className=n===page?'page-num active':'page-num';\n      s.textContent=n;\n      s.onclick=()=>goToPage(n);\n      container.appendChild(s);\n    }\n  });\n}\n\nfunction goToPage(n){ pagination.page=n; fetchData(); }\nfunction changePage(dir){\n  const np=pagination.page+dir;\n  if(np<1||np>pagination.pages) return;\n  pagination.page=np; fetchData();\n}\nfunction updateEntryInfo(s,e,t){\n  document.getElementById('entryInfo').textContent=`Showing ${s} to ${e} of ${t} entries`;\n}\n\n\/\/ CSV DOWNLOAD\nfunction downloadCSV(){\n  const heads = HEADS[activeTab];\n  const rows  = [...document.querySelectorAll('#tableBody tr:not(.state-row)')];\n  if(!rows.length){ alert('No data to download.'); return; }\n  let csv = heads.join(',')+'\\n';\n  rows.forEach(tr=>{\n    const cells=[...tr.querySelectorAll('td')].map(td=>'\"'+(td.textContent.trim().replace(\/\"\/g,'\"\"'))+'\"');\n    csv+=cells.join(',')+'\\n';\n  });\n  const a=document.createElement('a');\n  a.href='data:text\/csv;charset=utf-8,'+encodeURIComponent(csv);\n  a.download=`quality_${activeTab}_${Date.now()}.csv`;\n  a.click();\n}\n\n\/\/ HELPERS\nfunction fmt(v){ \n  if(v===null||v===undefined||v==='') \n  return '0.00'; const n=parseFloat(v); \n  return isNaN(n)?v:n.toFixed(2); \n}\nfunction qClass(v){ \n  const n=parseFloat(v)||0; \n  return n>=99?'q-high':n>=90?'q-mid':'q-low'; \n}\n\n\/\/ BOOT \n  init();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>EXECUTIVE WISE-QUALITY Executive Wise QC Executive Wise Client Wise Account Wise Loading\u2026 \u00ab Previous Next \u00bb Showing 0 to 0 of 0 entries<\/p>\n","protected":false},"author":1,"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-159","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/159","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/comments?post=159"}],"version-history":[{"count":2,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/159\/revisions"}],"predecessor-version":[{"id":162,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/159\/revisions\/162"}],"wp:attachment":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=159"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}