{"id":43,"date":"2026-03-30T12:30:08","date_gmt":"2026-03-30T12:30:08","guid":{"rendered":"https:\/\/ph-portal.zyneventures.com\/?page_id=43"},"modified":"2026-03-30T12:30:08","modified_gmt":"2026-03-30T12:30:08","slug":"roles-right","status":"publish","type":"page","link":"https:\/\/ph-portal.zyneventures.com\/index.php\/roles-right\/","title":{"rendered":"Roles Right"},"content":{"rendered":"<style>.kadence-column43_f5c277-ad > .kt-inside-inner-col,.kadence-column43_f5c277-ad > .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-column43_f5c277-ad > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column43_f5c277-ad > .kt-inside-inner-col{flex-direction:column;}.kadence-column43_f5c277-ad > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column43_f5c277-ad > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column43_f5c277-ad{position:relative;}@media all and (max-width: 1024px){.kadence-column43_f5c277-ad > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column43_f5c277-ad > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column43_f5c277-ad\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id43_b52ff5-35 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id43_b52ff5-35 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id43_b52ff5-35 > .kt-row-column-wrap{column-gap:var(--global-kb-gap-md, 2rem);row-gap:var(--global-kb-gap-md, 2rem);padding-top:var(--global-kb-spacing-sm, 1.5rem);padding-bottom:var(--global-kb-spacing-sm, 1.5rem);grid-template-columns:minmax(0, 1fr);}.kb-row-layout-id43_b52ff5-35 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id43_b52ff5-35 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id43_b52ff5-35 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id43_b52ff5-35 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-column43_fd6743-18 > .kt-inside-inner-col,.kadence-column43_fd6743-18 > .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-column43_fd6743-18 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column43_fd6743-18 > .kt-inside-inner-col{flex-direction:column;}.kadence-column43_fd6743-18 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column43_fd6743-18 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column43_fd6743-18{position:relative;}@media all and (max-width: 1024px){.kadence-column43_fd6743-18 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column43_fd6743-18 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column43_fd6743-18\"><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: #1E8E3E;\n        --error-red: #D93025;\n        --bg-light: #F1F3F4;\n    }\n\n    body {\n      font-family: 'Sora', -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n      background: #fff;\n      margin: 0;\n      padding-left: 60px;\n      color: var(--text-dark);\n    }\n    .container {\n      max-width: 1400px;\n      margin: 0 auto;\n    }\n    \/* \u2500\u2500 Header \u2500\u2500 *\/\n        .header-container {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n        }\n        .title-group { display: flex; align-items: center; gap: 10px; }\n        .title-group h2 {\n            margin: 0;\n            font-size: 21px;\n            font-weight: 500;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n    .zyne-matrix-wrapper {\n      margin: 0px 0;\n      border-radius: 6px;\n      background: #ffffff;\n      overflow: hidden;\n    }\n    .zyne-scroll-container {\n      overflow-x: auto;\n      width: 100%;\n    }\n    .kb-table-block {\n      width: 100%;\n      border-collapse: collapse;\n      min-width: 600px;\n    }\n    .kb-table-block th {\n      background: transparent;\n      color: var(--text-dark);\n      padding: 10px 20px;\n      font-size: 12px;\n      font-weight: 500;\n      text-transform: uppercase;\n      letter-spacing: 0.025em;\n      border-bottom: 1px solid var(--border-color);\n      text-align: left;\n    }\n    .kb-table-block td {\n        font-size: 12px;\n        font-weight: 400;\n        color: var(--text-muted);\n        padding: 10px 20px;\n        \/* border-bottom: 1px solid #f1f5f9; *\/\n    }\n    .role-header-content {\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n    .role-checkbox {\n      width: 14px;\n      height: 14px;\n      cursor: pointer;\n      accent-color: #3b82f6;\n    }\n    .controls {\n      display: flex;\n      align-items: center;\n      gap: 16px;\n      padding: 20px;\n      background: #ffffff;\n      border-top: 1px solid var(--border-color);\n    }\n    button {\n      padding: 8px 24px;\n      background: #3b82f6;\n      color: white;\n      border: none;\n      border-radius: 6px;\n      font-size: 12px;\n      font-weight: 400;\n      cursor: pointer;\n      transition: all 0.2s ease;\n    }\n    button:hover:not(:disabled) {\n      background: #2563eb;\n      transform: translateY(-1px);\n    }\n    button:disabled {\n      background: #cbd5e1;\n      cursor: not-allowed;\n    }\n    .status {\n      font-size: 14px;\n      font-weight: 500;\n    }\n    .status.success { color: #16a34a; }\n    .status.error   { color: #dc2626; }\n    .status.loading { color: #64748b; }\n\n    \/* Custom Scrollbar *\/\n    .zyne-scroll-container::-webkit-scrollbar { height: 8px; }\n    .zyne-scroll-container::-webkit-scrollbar-track { background: #f1f5f9; }\n    .zyne-scroll-container::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }\n  <\/style>\n\n<!-- Header -->\n<div class=\"header-container\">\n    <div class=\"title-group\">\n        <h2>Roles Rights<\/h2>\n    <\/div>\n<\/div><br>\n\n<div class=\"container\">\n  <div class=\"zyne-matrix-wrapper\">\n    <div class=\"zyne-scroll-container\">\n      <table id=\"roles-matrix\" class=\"kb-table-block\">\n        <thead>\n          <tr id=\"matrix-header\">\n            <\/tr>\n        <\/thead>\n        <tbody id=\"matrix-body\">\n          <\/tbody>\n      <\/table>\n    <\/div>\n\n    <div class=\"controls\">\n      <button id=\"save-btn\" disabled>Save<\/button>\n      <span id=\"status\" class=\"status\"><\/span>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function() {\n  const BASE_URL = \"https:\/\/api-ph-portal.zyneventures.com\/api\/v1\";\n  const GET_URL  = `${BASE_URL}\/role\/getall`;\n  const POST_URL = `${BASE_URL}\/role\/add-role-rights`;\n\n  const headerRow = document.getElementById(\"matrix-header\");\n  const tableBody = document.getElementById(\"matrix-body\");\n  const saveBtn   = document.getElementById(\"save-btn\");\n  const statusEl  = document.getElementById(\"status\");\n\n  \/**\n   * Corrected Auth Logic: Consistent with login_form.html\n   *\/\n  function getAuthHeader() {\n    const token = localStorage.getItem(\"TOKEN\"); \n    return token ? { \"Authorization\": `Bearer ${token}` } : {};\n  }\n\n  function setStatus(msg, type = \"loading\") {\n    statusEl.textContent = msg;\n    statusEl.className = `status ${type}`;\n  }\n\n  \/**\n   * Toggles all checkboxes in a specific column\n   *\/\n  window.toggleColumn = function(roleId, isChecked) {\n    const columnCheckboxes = document.querySelectorAll(`.role-checkbox[data-role-id=\"${roleId}\"]`);\n    columnCheckboxes.forEach(chk => chk.checked = isChecked);\n  };\n\n  \/**\n   * Updated Fetch: Removed first column and updated structure\n   *\/\n  async function fetchRoles() {\n    headerRow.innerHTML = \"\"; \/\/ Cleared \"Permissions \/ Rights\" column\n    tableBody.innerHTML = \"\";\n    setStatus(\"Syncing matrix...\", \"loading\");\n    saveBtn.disabled = true;\n\n    try {\n      const res = await fetch(GET_URL, {\n        method: \"GET\",\n        headers: { \"Accept\": \"application\/json\", ...getAuthHeader() }\n      });\n\n      if (res.status === 401) throw new Error(\"Session expired. Please log in again.\");\n      const json = await res.json();\n      const rolesData = json.data || [];\n\n      if (rolesData.length === 0) return setStatus(\"No data found.\", \"error\");\n\n      \/\/ 1. Render Headers (Select All Checkbox + Role Names) starting from the first column\n      rolesData.forEach(role => {\n        const th = document.createElement(\"th\");\n        th.innerHTML = `\n          <div class=\"role-header-content\">\n            <input type=\"checkbox\" class=\"header-toggle\" \n                   onchange=\"toggleColumn(${role.id}, this.checked)\"\n                   title=\"Select all for ${role.name}\">\n            <span>${role.name}<\/span>\n          <\/div>\n        `;\n        headerRow.appendChild(th);\n      });\n\n      \/\/ 2. Render Rows (Checkbox + Rights text)\n      const rightsTemplate = rolesData[0].rights || [];\n      rightsTemplate.forEach((_, index) => {\n        const row = document.createElement(\"tr\");\n        \n        rolesData.forEach(role => {\n          const td = document.createElement(\"td\");\n          const currentRight = role.rights[index];\n          \n          td.innerHTML = `\n            <div class=\"role-header-content\">\n              <input type=\"checkbox\" class=\"role-checkbox\" \n                     data-role-id=\"${role.id}\" \n                     data-right-id=\"${currentRight.id}\"\n                     ${currentRight.is_checked ? \"checked\" : \"\"}>\n              <span style=\"font-size: 12px; color: #64748b;\">${currentRight.name}<\/span>\n            <\/div>\n          `;\n          row.appendChild(td);\n        });\n        tableBody.appendChild(row);\n      });\n\n      setStatus(\"Data loaded successfully\", \"success\");\n      saveBtn.disabled = false;\n\n    } catch (err) {\n      setStatus(err.message, \"error\");\n    }\n  }\n\n  \/**\n   * Updated Payload structure\n   *\/\n  async function saveRoles() {\n    const checkBoxes = document.querySelectorAll(\".role-checkbox:checked\");\n    const roleIdMap = {};\n\n    checkBoxes.forEach(chk => {\n      const roleId = chk.dataset.roleId;\n      const rightId = parseInt(chk.dataset.rightId);\n\n      if (!roleIdMap[roleId]) {\n        roleIdMap[roleId] = [];\n      }\n      roleIdMap[roleId].push(rightId);\n    });\n\n    const payload = { role_id: roleIdMap };\n\n    setStatus(\"Saving changes...\", \"loading\");\n    saveBtn.disabled = true;\n\n    try {\n      const res = await fetch(POST_URL, {\n        method: \"POST\",\n        headers: { \n          \"Content-Type\": \"application\/json\", \n          \"Accept\": \"application\/json\",\n          ...getAuthHeader() \n        },\n        body: JSON.stringify(payload)\n      });\n\n      if (!res.ok) throw new Error(\"Failed to save changes.\");\n\n      setStatus(\"Save successful!\", \"success\");\n      await fetchRoles(); \n\n    } catch (err) {\n      setStatus(err.message, \"error\");\n      saveBtn.disabled = false;\n    }\n  }\n\n  saveBtn.addEventListener(\"click\", saveRoles);\n  window.addEventListener('load', fetchRoles);\n})();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Roles Rights Save<\/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":"hide","_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":true,"_kad_post_classname":"","footnotes":""},"class_list":["post-43","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/43","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=43"}],"version-history":[{"count":1,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/43\/revisions"}],"predecessor-version":[{"id":44,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/43\/revisions\/44"}],"wp:attachment":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=43"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}