{"id":138,"date":"2026-03-31T06:54:42","date_gmt":"2026-03-31T06:54:42","guid":{"rendered":"https:\/\/ph-taskportal.zyneventures.com\/?page_id=138"},"modified":"2026-07-09T09:30:33","modified_gmt":"2026-07-09T09:30:33","slug":"user-adjustment","status":"publish","type":"page","link":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/user-adjustment\/","title":{"rendered":"User Adjustment"},"content":{"rendered":"<style>.kadence-column138_6767dd-40 > .kt-inside-inner-col,.kadence-column138_6767dd-40 > .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-column138_6767dd-40 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column138_6767dd-40 > .kt-inside-inner-col{flex-direction:column;}.kadence-column138_6767dd-40 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column138_6767dd-40 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column138_6767dd-40{position:relative;}@media all and (max-width: 1024px){.kadence-column138_6767dd-40 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column138_6767dd-40 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column138_6767dd-40\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id138_6b3421-9d > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id138_6b3421-9d > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id138_6b3421-9d > .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-id138_6b3421-9d > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id138_6b3421-9d > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id138_6b3421-9d > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id138_6b3421-9d 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-column138_492b9f-2b > .kt-inside-inner-col,.kadence-column138_492b9f-2b > .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-column138_492b9f-2b > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column138_492b9f-2b > .kt-inside-inner-col{flex-direction:column;}.kadence-column138_492b9f-2b > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column138_492b9f-2b > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column138_492b9f-2b{position:relative;}@media all and (max-width: 1024px){.kadence-column138_492b9f-2b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column138_492b9f-2b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column138_492b9f-2b\"><div class=\"kt-inside-inner-col\">\n<style>\n    :root {\n        --primary-blue: #0180FF;\n        --hover-blue: #3499FF;\n        --text-dark: #212529;\n        --text-muted: #8D8C9C;\n        --border-color: #EFEFEF;\n        --error-red: #D93025;\n    }\n\n    body {\n        font-family: 'Sora', sans-serif;\n        color: var(--text-dark);\n    }\n\n    h2 {\n        font-size: 21px;\n        font-weight: 500;\n        text-transform: uppercase;\n        letter-spacing: 0.5px;\n        margin-bottom: 30px;\n    }\n\n    .filter-container {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n        gap: 20px;\n        align-items: end;\n        margin-bottom: 25px;\n    }\n\n    .filter-group {\n        display: flex;\n        flex-direction: column;\n        gap: 8px;\n    }\n\n    .filter-group label {\n        font-size: 14px;\n        font-weight: 400;\n    }\n\n    .form-control {\n        padding: 8px 15px;\n        border: 1px solid #8D8C9C;\n        border-radius: 6px;\n        font-family: 'Sora';\n        font-size: 12px;\n        font-weight: 400;\n        outline: none;\n        height: 42px;\n        box-sizing: border-box;\n    }\n\n    .btn-row {\n        display: flex;\n        gap: 15px;\n        margin-bottom: 40px;\n        flex-wrap: wrap;\n    }\n\n    .btn {\n        padding: 8px 24px;\n        border-radius: 6px;\n        font-size: 12px;\n        font-weight: 500;\n        cursor: pointer;\n        border: none;\n        transition: 0.2s;\n        height: 42px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .btn-primary {\n        background: var(--primary-blue);\n        color: white;\n        min-width: 140px;\n    }\n\n    .btn-primary:hover {\n        background: var(--hover-blue);\n    }\n\n    .btn-clear {\n        background: #EBF2FF;\n        color: var(--primary-blue);\n        min-width: 120px;\n    }\n\n    .btn-secondary {\n        background: var(--primary-blue);\n        color: white;\n        width: auto;\n    }\n\n    .table-wrapper {\n        width: 100%;\n        overflow-x: auto;\n        border-radius: 6px;\n        border: 1px solid var(--border-color);\n    }\n\n    table {\n        width: 100%;\n        border-collapse: collapse;\n        text-align: left;\n        min-width: 1000px;\n    }\n\n    thead tr {\n        background-color: #F5F5F5;\n    }\n\n    th {\n        padding: 12px 15px;\n        font-size: 12px;\n        font-weight: 500;\n        color: #212529;\n        border-bottom: 1px solid var(--border-color);\n    }\n\n    td {\n        padding: 18px 20px;\n        font-size: 12px;\n        color: #8D8C9C;\n        border-bottom: 1px solid var(--border-color);\n    }\n\n    .action-link {\n        color: var(--error-red);\n        text-decoration: none;\n        cursor: pointer;\n        font-weight: 400;\n    }\n\n    \/* Custom Select Dropdown *\/\n    .custom-select-wrapper {\n        position: relative;\n        width: 100%;\n    }\n\n    .custom-select-trigger {\n        height: 42px;\n        padding: 0 36px 0 15px;\n        border: 1px solid #8D8C9C;\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: pointer;\n        display: flex;\n        align-items: center;\n        box-sizing: border-box;\n        width: 100%;\n        user-select: none;\n        position: relative;\n        transition: all 0.2s;\n        overflow: hidden;\n        white-space: nowrap;\n    }\n\n    .custom-select-trigger:hover {\n        border-color: var(--primary-blue);\n    }\n\n    .custom-select-wrapper.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\n    .custom-select-trigger.placeholder-shown {\n        color: #70757a;\n    }\n\n    .custom-select-trigger .cs-arrow {\n        position: absolute;\n        right: 15px;\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\n    .custom-select-wrapper.open .custom-select-trigger .cs-arrow {\n        transform: translateY(-50%) rotate(180deg);\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\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\n    .cs-label::-webkit-scrollbar {\n        display: block;\n        height: 4px;\n    }\n\n    .cs-label::-webkit-scrollbar-thumb {\n        background: #ccc;\n        border-radius: 4px;\n    }\n\n    .custom-select-wrapper.open .custom-select-dropdown {\n        display: block;\n    }\n\n    \/* Chips\/Badges styling *\/\n    .cs-chips {\n        display: flex;\n        align-items: center;\n        gap: 4px;\n    }\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\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\n    .cs-chip-remove:hover {\n        background: #dadce0;\n        color: #202124;\n    }\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\n    .custom-select-option:hover {\n        background: #f0f6ff;\n        color: var(--primary-blue);\n    }\n\n    .custom-select-option.selected {\n        background: #e8f1ff;\n        color: var(--primary-blue);\n        font-weight: 500;\n    }\n\n    .custom-select-option.highlighted {\n        background: #f0f6ff;\n        color: var(--primary-blue);\n    }\n\n    .custom-select-option .cs-checkbox {\n        width: 16px;\n        height: 16px;\n        border: 1px solid #8D8C9C;\n        border-radius: 3px;\n        background: #fff;\n        position: relative;\n        flex-shrink: 0;\n        display: none;\n    }\n\n    .custom-select-wrapper.multi .custom-select-option .cs-checkbox {\n        display: block;\n    }\n\n    .custom-select-option.selected .cs-checkbox {\n        background: var(--primary-blue);\n        border-color: var(--primary-blue);\n    }\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\n    .custom-select-option.placeholder-option {\n        color: #999;\n    }\n\n    \/* Integrated search input inside the 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\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\n    .cs-search-input::placeholder {\n        color: #70757a;\n    }\n\n    .custom-select-trigger:has(.cs-search-input) {\n        cursor: text;\n    }\n\n    .custom-select-search {\n        padding: 8px;\n        border-bottom: 1px solid #EFEFEF;\n        position: sticky;\n        top: 0;\n        background: #fff;\n        z-index: 10;\n    }\n\n    .custom-select-search input {\n        width: 100%;\n        height: 30px;\n        padding: 0 10px;\n        border: 1px solid #8D8C9C;\n        border-radius: 4px;\n        font-family: 'Sora', sans-serif;\n        font-size: 11px;\n        outline: none;\n        box-sizing: border-box;\n    }\n\n    .custom-select-search input:focus {\n        border-color: var(--primary-blue);\n    }\n\n    \/* \u2500\u2500 Pagination (Project.html style with ellipsis) \u2500\u2500 *\/\n    .pagination-footer {\n        margin-top: 50px;\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        color: var(--text-muted);\n        font-size: 12px;\n        flex-wrap: wrap;\n        gap: 12px;\n    }\n\n    .pagination-controls {\n        display: flex;\n        align-items: center;\n        gap: 8px;\n        flex-wrap: wrap;\n    }\n\n    .page-num {\n        cursor: pointer;\n        width: 35px;\n        height: 35px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border-radius: 50%;\n        font-weight: 400;\n        border: 1px solid transparent;\n        font-size: 12px;\n        flex-shrink: 0;\n    }\n\n    .page-num.active {\n        background: var(--primary-blue);\n        color: white;\n    }\n\n    .page-num:not(.active):hover {\n        background: #f1f3f4;\n    }\n\n    .nav-btn {\n        cursor: pointer;\n        color: var(--text-dark);\n        font-weight: 400;\n        white-space: nowrap;\n        font-size: 12px;\n    }\n\n    .nav-btn.disabled {\n        color: #ccc;\n        pointer-events: none;\n    }\n\n    \/* \u2500\u2500 Responsive \u2500\u2500 *\/\n    @media (max-width: 1024px) {\n        body {\n            padding-left: 40px;\n        }\n\n        .filter-container {\n            grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n        }\n    }\n\n    @media (max-width: 768px) {\n        body {\n            padding-left: 20px;\n            padding-right: 20px;\n        }\n\n        .filter-container {\n            grid-template-columns: 1fr 1fr;\n        }\n\n        .btn-row {\n            gap: 10px;\n        }\n\n        .pagination-footer {\n            flex-direction: column;\n            align-items: flex-start;\n            gap: 16px;\n        }\n\n        .pagination-controls {\n            gap: 6px;\n        }\n\n        h2 {\n            font-size: 17px;\n            margin-bottom: 20px;\n        }\n    }\n\n    @media (max-width: 480px) {\n        body {\n            padding-left: 20px;\n            padding-right: 16px;\n        }\n\n        .filter-container {\n            grid-template-columns: 1fr;\n        }\n\n        .btn-row {\n            flex-direction: column;\n        }\n\n        .btn {\n            width: 100%;\n        }\n\n        .page-num {\n            width: 30px;\n            height: 30px;\n            font-size: 11px;\n        }\n    }\n<\/style>\n\n\n<h2>User Adjustments<\/h2><br>\n\n<div class=\"filter-container\">\n    <div class=\"filter-group\" id=\"assignedToFilterContainer\">\n        <label>Assigned to<\/label>\n        <select id=\"assignedToFilter\" class=\"form-control\">\n            <option value=\"\"> <\/option>\n        <\/select>\n    <\/div>\n    <div class=\"filter-group\">\n        <label>Month<\/label>\n        <select id=\"monthFilter\" class=\"form-control\">\n            <option value=\"\">Select Month<\/option>\n            <option value=\"1\">January<\/option>\n            <option value=\"2\">February<\/option>\n            <option value=\"3\">March<\/option>\n            <option value=\"4\">April<\/option>\n            <option value=\"5\">May<\/option>\n            <option value=\"6\">June<\/option>\n            <option value=\"7\">July<\/option>\n            <option value=\"8\">August<\/option>\n            <option value=\"9\">September<\/option>\n            <option value=\"10\">October<\/option>\n            <option value=\"11\">November<\/option>\n            <option value=\"12\">December<\/option>\n        <\/select>\n    <\/div>\n    <div class=\"filter-group\">\n        <label>Year<\/label>\n        <select id=\"yearFilter\" class=\"form-control\">\n            <option value=\"\">Select Year<\/option>\n            <option value=\"2024\">2024<\/option>\n            <option value=\"2025\">2025<\/option>\n            <option value=\"2026\">2026<\/option>\n        <\/select>\n    <\/div>\n    <button class=\"btn btn-clear\" onclick=\"clearFilters()\">Clear<\/button>\n    <button class=\"btn btn-primary\" onclick=\"applyFilters()\">Filter<\/button>\n<\/div>\n\n<div class=\"btn-row\">\n    <button class=\"btn btn-secondary\" onclick=\"openAddAdjustment()\">+ Add User Adjustment<\/button>\n    <button class=\"btn btn-secondary\" onclick=\"downloadCSV()\">Download CSV<\/button>\n<\/div>\n\n<div class=\"table-wrapper\">\n    <table>\n        <thead>\n            <tr>\n                <th>User<\/th>\n                <th>Description<\/th>\n                <th>Type<\/th>\n                <th>Hours<\/th>\n                <th>Month<\/th>\n                <th>Year<\/th>\n                <th>Created By<\/th>\n                <th>Action<\/th>\n            <\/tr>\n        <\/thead>\n        <tbody id=\"adjustmentTableBody\">\n            <tr>\n                <td colspan=\"8\" style=\"text-align:center;\">Loading data&#8230;<\/td>\n            <\/tr>\n        <\/tbody>\n    <\/table>\n<\/div>\n\n<div class=\"pagination-footer\">\n    <div class=\"pagination-controls\">\n        <span id=\"prevBtn\" class=\"nav-btn\">\u00ab Previous<\/span>\n        <div id=\"pageNumbers\" style=\"display:flex; gap:6px; flex-wrap:wrap;\"><\/div>\n        <span id=\"nextBtn\" class=\"nav-btn\">Next \u00bb<\/span>\n    <\/div>\n    <div id=\"entryInfo\">Showing 0 to 0 of 0 entries<\/div>\n<\/div>\n\n<script>\n    \/* CONFIGURATION *\/\n    const BASE_URL = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\n    const GET_ADJUSTMENTS_ENDPOINT = `${BASE_URL}\/schedule\/adjust-user-hours`;\n    const GET_USERS_ENDPOINT = `${BASE_URL}\/user\/table\/list`;\n    const DELETE_ADJUSTMENT_ENDPOINT = `${BASE_URL}\/schedule\/adjust-user-hours\/delete\/`;\n    const ADD_ADJUSTMENT_URL = 'https:\/\/ph-portal-v2.zyneventures.com\/index.php\/adjust-user-hours\/';\n\n    const TOKEN = localStorage.getItem(\"TOKEN\");\n\n    const getHeaders = () => ({\n        'Content-Type': 'application\/json',\n        'Authorization': `Bearer ${TOKEN}`\n    });\n\n    const extractListData = (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\n    const extractPaginationInfo = (payload, fallbackLimit) => {\n        const directPages = Number(\n            payload?.pagination?.pages ||\n            payload?.data?.pagination?.pages ||\n            payload?.last_page ||\n            payload?.data?.last_page ||\n            0\n        );\n\n        const limit = Number(\n            payload?.pagination?.limit ||\n            payload?.data?.pagination?.limit ||\n            payload?.per_page ||\n            payload?.data?.per_page ||\n            payload?.data?.perPage ||\n            fallbackLimit\n        );\n\n        if (Number.isFinite(directPages) && directPages > 0) {\n            return {\n                pages: directPages,\n                limit: Number.isFinite(limit) && limit > 0 ? limit : fallbackLimit\n            };\n        }\n\n        const total = Number(\n            payload?.pagination?.total ||\n            payload?.data?.pagination?.total ||\n            payload?.total ||\n            payload?.data?.total ||\n            0\n        );\n\n        const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : fallbackLimit;\n        const derivedPages = total > 0 ? Math.ceil(total \/ safeLimit) : 1;\n\n        return { pages: Math.max(1, derivedPages), limit: safeLimit, total };\n    };\n\n    function renderStateRow(message, color = 'inherit') {\n        document.getElementById('adjustmentTableBody').innerHTML =\n            `<tr><td colspan=\"8\" style=\"text-align:center; color:${color};\">${message}<\/td><\/tr>`;\n    }\n\n    function safeText(value, fallback = 'N\/A') {\n        if (value === null || value === undefined || value === '') return fallback;\n        return String(value)\n            .replace(\/&\/g, '&amp;')\n            .replace(\/<\/g, '&lt;')\n            .replace(\/>\/g, '&gt;')\n            .replace(\/\"\/g, '&quot;')\n            .replace(\/'\/g, '&#39;');\n    }\n\n    function normalizeMonthDisplay(monthValue) {\n        const months = [\"\", \"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\n        const rawValue = String(monthValue || '').trim();\n        const numericMonth = Number(rawValue);\n\n        if (Number.isFinite(numericMonth) && numericMonth >= 1 && numericMonth <= 12) {\n            return months[numericMonth];\n        }\n\n        return rawValue || 'N\/A';\n    }\n\n    async function fetchAllUsers() {\n        const defaultLimit = 500;\n        const buildUrl = (page, limit = defaultLimit) => {\n            const url = new URL(GET_USERS_ENDPOINT);\n            url.searchParams.set('page', String(page));\n            url.searchParams.set('limit', String(limit));\n            return url.toString();\n        };\n\n        const firstResponse = await fetch(buildUrl(1, defaultLimit), { headers: getHeaders() });\n        const firstResult = await firstResponse.json();\n\n        let users = extractListData(firstResult);\n        const { pages, limit } = extractPaginationInfo(firstResult, defaultLimit);\n\n        if (pages > 1) {\n            const requests = [];\n            for (let page = 2; page <= pages; page++) {\n                requests.push(fetch(buildUrl(page, limit), { headers: getHeaders() }).then(res => res.json()));\n            }\n\n            const pageResults = await Promise.all(requests);\n            pageResults.forEach(result => {\n                users = users.concat(extractListData(result));\n            });\n        }\n\n        return users;\n    }\n\n    let adjustmentData = [];\n    let currentPage = 1;\n    let totalPages = 1;\n    let totalEntries = 0;\n    const rowsPerPage = 10;\n\n    window.onload = () => {\n        if (!TOKEN) {\n            alert(\"No authorization token found. Please log in.\");\n            return;\n        }\n        renderStateRow('Loading data...');\n        fetchUsersDropdown();\n        fetchAdjustments();\n    };\n\n    \/** \u2500\u2500 RENDER SEARCHABLE SELECT (Integrated Search Field) \u2500\u2500 **\/\n    function renderSelect(label, selectId, data, labelKey, valueKey = 'id', multi = false) {\n        const optionsData = (data || []).map(item => ({\n            _val: item[valueKey] ?? '',\n            _label: item[labelKey] || item['name'] || item['user_name'] || `User ${item[valueKey]}`\n        }));\n\n        const optionsHtml = optionsData.map((o, i) =>\n            `<div class=\"custom-select-option\" data-value=\"${o._val}\" onclick=\"csSelect('${selectId}', '${String(o._val).replace(\/'\/g, \"\\\\'\")}', this, ${multi})\">\n                        <div class=\"cs-checkbox\"><\/div>\n                        <span class=\"opt-label\">${o._label}<\/span>\n                    <\/div>`\n        ).join('');\n\n        const phText = `Select ${label}`;\n\n        return `\n                <label>${label}<\/label>\n                <div class=\"custom-select-wrapper${multi ? ' multi' : ''}\" id=\"csWrapper_${selectId}\" data-placeholder=\"${phText}\">\n                    <input type=\"hidden\" id=\"${selectId}\" value=\"\">\n                    <div class=\"custom-select-trigger placeholder-shown\" onclick=\"csTriggerClick('${selectId}', event)\">\n                        <div class=\"cs-label\">\n                            <input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${phText}\" autocomplete=\"off\" oninput=\"csIntegratedFilter('${selectId}', this.value)\" onkeydown=\"csKeyDown(event, '${selectId}')\" onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\" onfocus=\"csOpenDropdown('${selectId}')\">\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_${selectId}\">\n                        <div class=\"custom-select-options\">\n                            ${optionsHtml}\n                        <\/div>\n                    <\/div>\n                <\/div>\n            `;\n    }\n\n    function 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('.custom-select-wrapper.open').forEach(w => {\n            if (w !== wrapper) w.classList.remove('open');\n        });\n        if (isOpen) {\n            wrapper.classList.remove('open');\n        } else {\n            wrapper.classList.add('open');\n            \/\/ Reset filter state and focus the integrated search input if present\n            const searchInput = document.getElementById('csInput_' + selectId);\n            if (searchInput) {\n                searchInput.value = '';\n                csIntegratedFilter(selectId, '');\n                setTimeout(() => searchInput.focus(), 50);\n            } else {\n                csIntegratedFilter(selectId, '');\n            }\n            csHighlightFirst(selectId);\n        }\n    }\n\n    function csTriggerClick(selectId, e) {\n        e.stopPropagation();\n        csToggle(selectId, e);\n    }\n\n    function csOpenDropdown(selectId) {\n        const wrapper = document.getElementById('csWrapper_' + selectId);\n        if (!wrapper || wrapper.classList.contains('open')) return;\n        document.querySelectorAll('.custom-select-wrapper.open').forEach(w => {\n            if (w !== wrapper) w.classList.remove('open');\n        });\n        wrapper.classList.add('open');\n        csHighlightFirst(selectId);\n    }\n\n    function csFilter(selectId, query) {\n        csIntegratedFilter(selectId, query);\n    }\n\n    function 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            const text = opt.textContent.toLowerCase();\n            if (text.includes(q) || opt.classList.contains('placeholder-option')) {\n                opt.style.display = '';\n            } else {\n                opt.style.display = 'none';\n            }\n        });\n        \/\/ Open dropdown automatically whenever the user types in the field\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    function 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) {\n            visible[0].classList.add('highlighted');\n        }\n    }\n\n    function 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 = wrapper.classList.contains('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) {\n                csToggle(selectId, e);\n                return;\n            }\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) {\n                csToggle(selectId, e);\n                return;\n            }\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\n    function csSelect(selectId, value, optionEl, multi = false) {\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        const phText = wrapper.dataset.placeholder || '';\n\n        if (value === '') {\n            input.value = '';\n            labelEl.innerHTML = `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${phText}\" autocomplete=\"off\" oninput=\"csIntegratedFilter('${selectId}', this.value)\" onkeydown=\"csKeyDown(event, '${selectId}')\" onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\" onfocus=\"csOpenDropdown('${selectId}')\">`;\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 = `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${selectedText}\" autocomplete=\"off\" oninput=\"csIntegratedFilter('${selectId}', this.value)\" onkeydown=\"csKeyDown(event, '${selectId}')\" onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\" onfocus=\"csOpenDropdown('${selectId}')\">`;\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(value);\n            if (idx > -1) {\n                values.splice(idx, 1);\n                optionEl.classList.remove('selected');\n            } else {\n                values.push(value);\n                optionEl.classList.add('selected');\n            }\n            input.value = values.join(',');\n\n            if (values.length === 0) {\n                labelEl.innerHTML = `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"${phText}\" autocomplete=\"off\" oninput=\"csIntegratedFilter('${selectId}', this.value)\" onkeydown=\"csKeyDown(event, '${selectId}')\" onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\" onfocus=\"csOpenDropdown('${selectId}')\">`;\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(opt => ({\n                        val: opt.dataset.value,\n                        label: opt.querySelector('.opt-label').textContent.trim()\n                    }));\n\n                const chipsHtml = selectedLabels.map(item => `\n                        <div class=\"cs-chip\">\n                            <span>${item.label}<\/span>\n                            <span class=\"cs-chip-remove\" onclick=\"csRemoveChip(event, '${selectId}', '${item.val}')\">&times;<\/span>\n                        <\/div>\n                    `).join('');\n\n                labelEl.innerHTML = chipsHtml + `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_${selectId}\" placeholder=\"\" autocomplete=\"off\" oninput=\"csIntegratedFilter('${selectId}', this.value)\" onkeydown=\"csKeyDown(event, '${selectId}')\" onclick=\"csOpenDropdown('${selectId}'); event.stopPropagation();\" onfocus=\"csOpenDropdown('${selectId}')\">`;\n            }\n\n            \/\/ Keep dropdown open and refocus the integrated input so user can continue searching\n            if (wrapper.classList.contains('open')) {\n                const newInput = document.getElementById('csInput_' + selectId);\n                if (newInput) setTimeout(() => newInput.focus(), 0);\n            }\n        }\n    }\n\n    function 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) {\n            csSelect(selectId, value, optionEl, true);\n        }\n    }\n\n    document.addEventListener('click', function (e) {\n        if (!e.target.closest('.custom-select-wrapper')) {\n            document.querySelectorAll('.custom-select-wrapper.open').forEach(w => w.classList.remove('open'));\n        }\n    });\n\n    async function fetchUsersDropdown() {\n        try {\n            const users = await fetchAllUsers();\n            const container = document.getElementById('assignedToFilterContainer');\n            container.innerHTML = renderSelect('Assigned to', 'assignedToFilter', users, 'pesudo_name', 'id', true);\n        } catch (error) {\n            console.error(\"Error fetching users:\", error);\n        }\n    }\n\n    async function fetchAdjustments() {\n        const user_id = document.getElementById('assignedToFilter').value;\n        const month = document.getElementById('monthFilter').value;\n        const year = document.getElementById('yearFilter').value;\n\n        let queryParams = `?page=${currentPage}&limit=${rowsPerPage}`;\n        if (user_id) queryParams += `&user_id=${user_id}`;\n        if (month) queryParams += `&month=${String(month).padStart(2, '0')}`;\n        if (year) queryParams += `&year=${year}`;\n\n        try {\n            renderStateRow('Loading data...');\n            const response = await fetch(`${GET_ADJUSTMENTS_ENDPOINT}${queryParams}`, { headers: getHeaders() });\n            const result = await response.json();\n\n            if (response.ok && (result.status === \"success\" || result.code === 200 || result.data)) {\n                adjustmentData = extractListData(result);\n                const pagination = extractPaginationInfo(result, rowsPerPage);\n                totalEntries = Number(\n                    result?.data?.pagination?.total ||\n                    result?.pagination?.total ||\n                    adjustmentData.length ||\n                    0\n                );\n                totalPages = pagination.pages || 1;\n                renderTable();\n            } else {\n                adjustmentData = [];\n                totalEntries = 0;\n                totalPages = 1;\n                renderStateRow('No data found.');\n                renderPagination();\n            }\n        } catch (error) {\n            console.error(\"Error fetching adjustments:\", error);\n            adjustmentData = [];\n            totalEntries = 0;\n            totalPages = 1;\n            renderStateRow('Failed to load data. Please check your connection.', 'red');\n            renderPagination();\n        }\n    }\n\n    \/* DELETE \u2014 GET \/schedule\/adjust-user-hours\/delete\/:id *\/\n    async function deleteEntry(id) {\n        if (confirm(\"Are you sure you want to delete this entry?\")) {\n            try {\n                const response = await fetch(`${DELETE_ADJUSTMENT_ENDPOINT}${id}`, {\n                    method: 'GET',\n                    headers: getHeaders()\n                });\n                const result = await response.json();\n                if (response.ok && (result.status === \"success\" || result.code === 200)) {\n                    fetchAdjustments();\n                } else {\n                    alert(result.message || \"Failed to delete entry.\");\n                }\n            } catch (error) {\n                console.error(\"Error deleting entry:\", error);\n                alert(\"An error occurred while deleting. Please try again.\");\n            }\n        }\n    }\n\n    function renderTable() {\n        const tbody = document.getElementById('adjustmentTableBody');\n        tbody.innerHTML = '';\n\n        if (adjustmentData.length === 0) {\n            renderStateRow('No records found.');\n            renderPagination();\n            return;\n        }\n\n        adjustmentData.forEach((item) => {\n            const tr = document.createElement('tr');\n            tr.innerHTML = `\n                    <td>${safeText(item.user_name)}<\/td>\n                    <td>${safeText(item.description, '')}<\/td>\n                    <td>${safeText(item.type, '')}<\/td>\n                    <td>${safeText(item.hours, '0')}<\/td>\n                    <td>${safeText(normalizeMonthDisplay(item.month))}<\/td>\n                    <td>${safeText(item.year)}<\/td>\n                    <td>${safeText(item.created_by_name)}<\/td>\n                    <td><a class=\"action-link\" onclick=\"deleteEntry(${item.id})\">Delete<\/a><\/td>\n                `;\n            tbody.appendChild(tr);\n        });\n\n        renderPagination();\n    }\n\n    \/* \u2500\u2500 Ellipsis pagination logic (Project.html pattern) \u2500\u2500 *\/\n    function buildPageList(current, total) {\n        if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);\n        const pages = [];\n        if (current <= 3) {\n            pages.push(1, 2, 3, 4, '...', total);\n        } else if (current >= total - 2) {\n            pages.push(1, '...', total - 3, total - 2, total - 1, total);\n        } else {\n            pages.push(1, '...', current - 1, current, current + 1, '...', total);\n        }\n        return pages;\n    }\n\n    function renderPagination() {\n        const startEntry = totalEntries === 0 ? 0 : ((currentPage - 1) * rowsPerPage) + 1;\n        const endEntry = Math.min(currentPage * rowsPerPage, totalEntries);\n        document.getElementById('entryInfo').textContent = `Showing ${startEntry} to ${endEntry} of ${totalEntries} entries`;\n\n        const container = document.getElementById('pageNumbers');\n        container.innerHTML = '';\n\n        const pageList = buildPageList(currentPage, totalPages);\n        pageList.forEach(p => {\n            const span = document.createElement('span');\n            if (p === '...') {\n                span.className = 'page-num';\n                span.textContent = '\u2026';\n                span.style.cursor = 'default';\n            } else {\n                span.className = p === currentPage ? 'page-num active' : 'page-num';\n                span.textContent = p;\n                span.onclick = () => { currentPage = p; fetchAdjustments(); };\n            }\n            container.appendChild(span);\n        });\n\n        const prevBtn = document.getElementById('prevBtn');\n        const nextBtn = document.getElementById('nextBtn');\n\n        prevBtn.className = currentPage === 1 ? 'nav-btn disabled' : 'nav-btn';\n        prevBtn.onclick = () => { if (currentPage > 1) { currentPage--; fetchAdjustments(); } };\n\n        nextBtn.className = (currentPage === totalPages || totalPages === 0) ? 'nav-btn disabled' : 'nav-btn';\n        nextBtn.onclick = () => { if (currentPage < totalPages) { currentPage++; fetchAdjustments(); } };\n    }\n\n    function applyFilters() {\n        currentPage = 1;\n        fetchAdjustments();\n    }\n\n    function clearFilters() {\n        const assignedInput = document.getElementById('assignedToFilter');\n        if (assignedInput) {\n            assignedInput.value = '';\n            const wrapper = document.getElementById('csWrapper_assignedToFilter');\n            if (wrapper) {\n                const trigger = wrapper.querySelector('.custom-select-trigger');\n                const labelEl = trigger.querySelector('.cs-label');\n                const phText = wrapper.dataset.placeholder || '';\n                labelEl.innerHTML = `<input type=\"text\" class=\"cs-search-input\" id=\"csInput_assignedToFilter\" placeholder=\"${phText}\" autocomplete=\"off\" oninput=\"csIntegratedFilter('assignedToFilter', this.value)\" onkeydown=\"csKeyDown(event, 'assignedToFilter')\" onclick=\"csOpenDropdown('assignedToFilter'); event.stopPropagation();\" onfocus=\"csOpenDropdown('assignedToFilter')\">`;\n                trigger.classList.add('placeholder-shown');\n                wrapper.querySelectorAll('.custom-select-option').forEach(o => o.classList.remove('selected'));\n            }\n        }\n        document.getElementById('monthFilter').value = '';\n        document.getElementById('yearFilter').value = '';\n        currentPage = 1;\n        fetchAdjustments();\n    }\n\n    function openAddAdjustment() { window.location.href = ADD_ADJUSTMENT_URL; }\n\n    async function downloadCSV() {\n        const user_id = document.getElementById('assignedToFilter').value;\n        const month = document.getElementById('monthFilter').value;\n        const year = document.getElementById('yearFilter').value;\n\n        const buildQuery = (page) => {\n            let q = `?page=${page}&limit=500`;\n            if (user_id) q += `&user_id=${user_id}`;\n            if (month) q += `&month=${month}`;\n            if (year) q += `&year=${year}`;\n            return q;\n        };\n\n        try {\n            \/* \u2500\u2500 Fetch page 1 to get total pages \u2500\u2500 *\/\n            const firstRes = await fetch(`${GET_ADJUSTMENTS_ENDPOINT}${buildQuery(1)}`, { headers: getHeaders() });\n            const firstResult = await firstRes.json();\n\n            let allData = extractListData(firstResult);\n            const { pages } = extractPaginationInfo(firstResult, 500);\n\n            \/* \u2500\u2500 Fetch remaining pages in parallel \u2500\u2500 *\/\n            if (pages > 1) {\n                const reqs = [];\n                for (let p = 2; p <= pages; p++) {\n                    reqs.push(\n                        fetch(`${GET_ADJUSTMENTS_ENDPOINT}${buildQuery(p)}`, { headers: getHeaders() })\n                            .then(r => r.json())\n                    );\n                }\n                const results = await Promise.all(reqs);\n                results.forEach(r => { allData = allData.concat(extractListData(r)); });\n            }\n\n            if (!allData.length) {\n                alert(\"No data available to download.\");\n                return;\n            }\n\n            \/* \u2500\u2500 Build CSV \u2500\u2500 *\/\n            const csvSafe = (val) => {\n                if (val === null || val === undefined) return '';\n                const str = String(val).replace(\/\"\/g, '\"\"');\n                return str.includes(',') || str.includes('\"') || str.includes('\\n') ? `\"${str}\"` : str;\n            };\n\n            const headers = ['User', 'Description', 'Type', 'Hours', 'Month', 'Year', 'Created By'];\n            const rows = allData.map(item => [\n                csvSafe(item.user_name),\n                csvSafe(item.description),\n                csvSafe(item.type),\n                csvSafe(item.hours ?? 0),\n                csvSafe(normalizeMonthDisplay(item.month)),\n                csvSafe(item.year),\n                csvSafe(item.created_by_name)\n            ].join(','));\n\n            const csvContent = [headers.join(','), ...rows].join('\\n');\n            const blob = new Blob([csvContent], { type: 'text\/csv;charset=utf-8;' });\n            const url = URL.createObjectURL(blob);\n            const a = document.createElement('a');\n            a.href = url;\n            a.download = `user_adjustments${year ? '_' + year : ''}${month ? '_m' + month : ''}.csv`;\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n\n        } catch (error) {\n            console.error(\"CSV download error:\", error);\n            alert(\"Failed to download CSV. Please try again.\");\n        }\n    }\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>User Adjustments Assigned to Month Select MonthJanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember Year Select Year202420252026 Clear Filter + Add User Adjustment Download CSV User Description Type Hours Month Year Created By Action Loading data&#8230; \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":"disable","_kad_post_title":"hide","_kad_post_layout":"fullwidth","_kad_post_sidebar_id":"","_kad_post_content_style":"unboxed","_kad_post_vertical_padding":"hide","_kad_post_feature":"hide","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-138","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/138","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=138"}],"version-history":[{"count":6,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/138\/revisions"}],"predecessor-version":[{"id":1239,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/138\/revisions\/1239"}],"wp:attachment":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=138"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}