{"id":96,"date":"2026-03-31T05:27:01","date_gmt":"2026-03-31T05:27:01","guid":{"rendered":"https:\/\/ph-taskportal.zyneventures.com\/?page_id=96"},"modified":"2026-07-09T10:07:29","modified_gmt":"2026-07-09T10:07:29","slug":"categories","status":"publish","type":"page","link":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/categories\/","title":{"rendered":"Categories"},"content":{"rendered":"<style>.kadence-column96_acf1b2-12 > .kt-inside-inner-col,.kadence-column96_acf1b2-12 > .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-column96_acf1b2-12 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column96_acf1b2-12 > .kt-inside-inner-col{flex-direction:column;}.kadence-column96_acf1b2-12 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column96_acf1b2-12 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column96_acf1b2-12{position:relative;}@media all and (max-width: 1024px){.kadence-column96_acf1b2-12 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column96_acf1b2-12 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column96_acf1b2-12\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id96_613a85-72 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id96_613a85-72 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id96_613a85-72 > .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-id96_613a85-72 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id96_613a85-72 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id96_613a85-72 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id96_613a85-72 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-column96_b6f8ca-7b > .kt-inside-inner-col,.kadence-column96_b6f8ca-7b > .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-column96_b6f8ca-7b > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column96_b6f8ca-7b > .kt-inside-inner-col{flex-direction:column;}.kadence-column96_b6f8ca-7b > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column96_b6f8ca-7b > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column96_b6f8ca-7b{position:relative;}@media all and (max-width: 1024px){.kadence-column96_b6f8ca-7b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column96_b6f8ca-7b > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column96_b6f8ca-7b\"><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    }\n\n    body { \n        margin: 0; \n        font-family: 'Sora', sans-serif; \n        color: var(--text-dark); \n        background-color: #fff;\n    }\n    \n    \/* Header Section *\/\n    .header-container { \n        display: flex; \n        justify-content: space-between; \n        align-items: center; \n        margin-bottom: 30px;\n        flex-wrap: wrap;\n        gap: 12px;\n    }\n    .title-group { \n        display: flex; \n        align-items: center; \n        gap: 10px; \n    }\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    .count { \n        color: var(--text-muted); \n        font-size: 16px; \n        font-weight: 400; \n    }\n    \n    .action-group { \n        display: flex; \n        gap: 12px; \n        align-items: center;\n        flex-wrap: wrap;\n    }\n\n    \/* Search Bar *\/\n    .search-wrapper { \n        background: #F1F3F4; \n        border-radius: 6px; \n        padding: 8px 15px; \n        display: flex; \n        align-items: center; \n        width: 240px; \n        gap: 12px;\n    }\n    .search-icon { \n        width: 18px; \n        height: 18px; \n        color: #8D8C9C; \n        flex-shrink: 0;\n    }\n    .search-wrapper input { \n        border: none; \n        background: transparent; \n        outline: none; \n        width: 100%; \n        font-size: 12px; \n        font-family: 'Sora'; \n        color: #212529;\n    }\n\n    \/* Filter Button*\/\n    .btn-filter {\n        padding: 0;\n        width: 36px;\n        height: 36px;\n        border-radius: 6px;\n        border: 1px solid #DADCE0;\n        background: #fff;\n        cursor: pointer;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        color: #5f6368;\n        transition: background 0.2s;\n        flex-shrink: 0;\n    }\n    .btn-filter:hover { background: #F1F3F4; }\n\n    .btn { \n        padding: 8px 16px; \n        border-radius: 6px; \n        font-size: 12px; \n        font-weight: 400; \n        cursor: pointer; \n        border: none; \n        transition: 0.2s; \n        display: flex; \n        align-items: center; \n        justify-content: center;\n        gap: 6px;\n        white-space: nowrap;\n    }\n    .btn-primary { background: var(--primary-blue); color: white; }\n    .btn-primary:hover { background: var(--hover-blue); }\n\n    \/* Grid *\/\n    .category-grid { \n        display: grid; \n        grid-template-columns: repeat(3, 1fr); \n        gap: 16px; \n        margin-top: 10px; \n    }\n\n    @media (max-width: 1100px) {\n        .category-grid { grid-template-columns: repeat(2, 1fr); }\n    }\n    @media (max-width: 700px) {\n        .category-grid { grid-template-columns: 1fr; }\n        body { padding: 20px 16px; }\n        .search-wrapper { width: 100%; }\n        .action-group { width: 100%; }\n    }\n\n    .category-card { \n        border: 1px solid var(--border-color); \n        border-radius: 12px; \n        padding: 20px 24px; \n        background: #fff;\n        transition: box-shadow 0.3s;\n    }\n    .category-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.05); }\n    \n    .card-header { \n        display: flex; \n        justify-content: space-between; \n        align-items: center; \n        margin-bottom: 16px; \n    }\n    .card-title { \n        font-weight: 500; \n        font-size: 18px; \n        color: var(--text-dark); \n    }\n    \n    .badge-container { display: flex; align-items: center; gap: 8px; }\n    \n    .status-badge { \n        padding: 3px 10px; \n        border-radius: 20px; \n        font-size: 12px; \n        font-weight: 400; \n        display: flex; \n        align-items: center; \n        justify-content: center;\n        gap: 5px; \n        height: 22px;\n        box-sizing: border-box;\n        white-space: nowrap;\n    }\n    .status-active { background: #E6F4EA; color: var(--success-green); }\n    .status-active::before { content: '\u25cf'; font-size: 7px; }\n    .status-inactive { background: #FCE8E6; color: var(--error-red); }\n    .status-inactive::before { content: '\u25cf'; font-size: 7px; }\n    \n    .card-body { }\n    .info-label { color: var(--text-muted); font-size: 13px; }\n\n    \/* Dropdown Menu *\/\n    .menu-container { \n        position: relative; \n        display: flex; \n        align-items: center; \n    }\n    .menu-dots { \n        cursor: pointer; \n        color: #212529; \n        font-size: 20px; \n        line-height: 1; \n        padding: 2px 4px; \n        border-radius: 4px;\n    }\n    .menu-dots:hover { background: #F1F3F4; }\n    .dropdown-content { \n        display: none; \n        position: absolute; \n        right: 0; \n        top: 28px; \n        background: white; \n        min-width: 120px; \n        box-shadow: 0 8px 16px rgba(0,0,0,0.1); \n        z-index: 100; \n        border-radius: 8px; \n        border: 1px solid #eee; \n        overflow: hidden;\n    }\n    .dropdown-content.show { display: block; }\n    .dropdown-item { \n        padding: 10px 16px; \n        font-size: 12px; \n        cursor: pointer; \n        color: #444; \n    }\n    .dropdown-item:hover { background-color: #f8f9fa; color: var(--primary-blue); }\n\n    \/* Modal Overlay *\/\n    .modal-overlay { \n        display: none; \n        position: fixed; \n        top: 0; \n        left: 0; \n        width: 100%; \n        height: 100%; \n        background: rgba(0,0,0,0.4); \n        z-index: 1000; \n        justify-content: center; \n        align-items: center; \n    }\n    .modal-content { \n        background: white; \n        padding: 40px; \n        border-radius: 12px; \n        width: 500px;\n        max-width: calc(100vw - 40px);\n        box-shadow: 0 20px 40px rgba(0,0,0,0.2);\n    }\n    .modal-content h3 { margin: 0 0 30px 0; font-size: 24px; font-weight: 500; }\n    \n    .form-group { margin-bottom: 25px; }\n    .form-group label { \n        display: block; \n        font-size: 12px; \n        font-weight: 500; \n        margin-bottom: 12px; \n    }\n    .form-group label span { color: var(--error-red); }\n    .form-control { \n        width: 100%; \n        padding: 14px; \n        border: 1px solid #E0E0E0; \n        border-radius: 8px; \n        box-sizing: border-box; \n        font-family: 'Sora'; \n        font-size: 12px;\n    }\n\n    .btn-close-modal { \n        background: transparent; \n        color: #212529; \n        padding: 10px 20px; \n        font-size: 12px;\n        font-weight: 400;\n        border: 1px solid #E0E0E0;\n        border-radius: 6px;\n        cursor: pointer;\n    }\n    .btn-close-modal:hover { background: #F1F3F4; color: #212529;}\n    \n    \/* Toggle Switch *\/\n    .toggle-row { \n        display: flex; \n        justify-content: space-between; \n        align-items: center; \n        margin-top: 10px; \n    }\n    .switch { \n        position: relative; \n        display: inline-block; \n        width: 44px; \n        height: 22px; \n    }\n    .switch input { opacity: 0; width: 0; height: 0; }\n    .slider { \n        position: absolute; \n        cursor: pointer; \n        top: 0; \n        left: 0; \n        right: 0; \n        bottom: 0; \n        background-color: #ccc; \n        transition: .4s; \n        border-radius: 34px; \n    }\n    .slider:before { \n        position: absolute; \n        content: \"\"; \n        height: 16px; \n        width: 16px; \n        left: 3px; \n        bottom: 3px; \n        background-color: white; \n        transition: .4s; \n        border-radius: 50%; \n    }\n    input:checked + .slider { background-color: var(--primary-blue); }\n    input:checked + .slider:before { transform: translateX(22px); }\n\n    \/* Pagination Footer *\/\n    .pagination-footer { \n        margin-top: 40px; \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    .pagination-controls { display: flex; align-items: center; gap: 8px; }\n    .page-num { \n        cursor: pointer; \n        width: 32px; \n        height: 32px; \n        display: flex; \n        align-items: center; \n        justify-content: center; \n        border-radius: 50%; \n        font-weight: 400; \n        font-size: 12px;\n    }\n    .page-num.active { background: var(--primary-blue); color: white; }\n    .page-num:not(.active):hover { background: #F1F3F4; }\n    .nav-btn { \n        cursor: pointer; \n        color: var(--text-dark); \n        font-weight: 500; \n        font-size: 13px;\n    }\n    .nav-btn.disabled { color: #ccc; pointer-events: none; }\n\n    \/* Mobile: entry info on separate row *\/\n    @media (max-width: 600px) {\n        .pagination-footer {\n            flex-direction: column;\n            align-items: flex-start;\n        }\n    }\n<\/style>\n\n\n<div class=\"header-container\">\n<div class=\"title-group\">\n    <h2>CATEGORIES<\/h2>\n    <span class=\"count\" id=\"categoryCount\">(0)<\/span>\n<\/div>\n<div class=\"action-group\">\n    <div class=\"search-wrapper\">\n        <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n            <circle cx=\"11\" cy=\"11\" r=\"8\"><\/circle>\n            <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"><\/line>\n        <\/svg>\n        <input type=\"text\" id=\"searchInput\" placeholder=\"Search Category...\" oninput=\"handleSearch()\">\n    <\/div>\n    <!-- Filter button: white bg, border, thin icon \u2014 matches original -->\n    <button class=\"btn-filter\" title=\"Filter\">\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n            <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"><\/line>\n            <line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"><\/line>\n            <line x1=\"11\" y1=\"18\" x2=\"13\" y2=\"18\"><\/line>\n        <\/svg>\n    <\/button>\n    <button class=\"btn btn-primary\" onclick=\"openModal('add')\">+ Add New Category<\/button>\n    <button class=\"btn btn-primary\" onclick=\"downloadCSV()\">Download CSV<\/button>\n<\/div>\n<\/div>\n\n<div class=\"category-grid\" id=\"categoryContainer\"><\/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:4px;\"><\/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<div class=\"modal-overlay\" id=\"categoryModal\">\n<div class=\"modal-content\">\n    <h3 id=\"modalTitle\">New Category<\/h3>\n    <form id=\"categoryForm\">\n        <input type=\"hidden\" id=\"editCategoryId\">\n        <div class=\"form-group\">\n            <label>Category Name <span>*<\/span><\/label>\n            <input type=\"text\" id=\"categoryName\" class=\"form-control\" placeholder=\"Enter Category Name\" required=\"\">\n        <\/div>\n        \n        <div class=\"toggle-row\">\n            <label style=\"font-size:12px; font-weight:500;\">Category Active <span style=\"color:var(--error-red)\">*<\/span><\/label>\n            <label class=\"switch\">\n                <input type=\"checkbox\" id=\"categoryActive\" checked=\"\">\n                <span class=\"slider\"><\/span>\n            <\/label>\n        <\/div>\n\n        <div style=\"display:flex; justify-content:flex-end; gap:15px; margin-top:40px;\">\n            <button type=\"button\" class=\"btn-close-modal\" onclick=\"closeModal()\">Close<\/button>\n            <button type=\"submit\" id=\"saveBtn\" class=\"btn btn-primary\" style=\"padding: 10px 40px;\">Save<\/button>\n        <\/div>\n    <\/form>\n<\/div>\n<\/div>\n\n<script>\n\/** ENDPOINTS **\/\nconst BASE_URL = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\nconst ENDPOINTS = {\n    ALL: `${BASE_URL}\/category\/all`,\n    DETAIL: `${BASE_URL}\/category\/detail`,\n    ADD: `${BASE_URL}\/category\/add`,\n    UPDATE: `${BASE_URL}\/category\/update`,\n    DELETE: `${BASE_URL}\/category\/delete`\n};\n\nlet allCategories = [];\nlet filteredCategories = [];\nlet currentPage = 1;\nconst perPage = 10;\n\nconst getHeaders = () => ({\n    'Content-Type': 'application\/json',\n    'Authorization': `Bearer ${localStorage.getItem(\"TOKEN\")}`\n});\n\n\/** FETCH ALL PAGES (for global search + client-side pagination) **\/\nasync function fetchAllCategories() {\n    try {\n        const limit = 500;\n        const firstRes = await fetch(\n            `${ENDPOINTS.ALL}?page=1&limit=${limit}`,\n            { headers: getHeaders() }\n        );\n        const firstResult = await firstRes.json();\n        if (firstResult.code !== 200) return;\n\n        let items = firstResult.data.data || [];\n        const lastPage = firstResult.data.last_page || 1;\n\n        if (lastPage > 1) {\n            const pageRequests = [];\n            for (let p = 2; p <= lastPage; p++) {\n                pageRequests.push(\n                    fetch(`${ENDPOINTS.ALL}?page=${p}&#038;limit=${limit}`, { headers: getHeaders() })\n                        .then(r => r.json())\n                        .catch(() => null)\n                );\n            }\n            const otherPages = await Promise.all(pageRequests);\n            otherPages.forEach(result => {\n                if (result?.code === 200 && Array.isArray(result.data?.data)) {\n                    items = items.concat(result.data.data);\n                }\n            });\n        }\n\n        allCategories = items;\n        applySearchAndRender();\n    } catch (err) { console.error(\"Fetch Error:\", err); }\n}\n\nfunction applySearchAndRender() {\n    const q = document.getElementById('searchInput').value.toLowerCase().trim();\n    if (!q) {\n        filteredCategories = [...allCategories];\n    } else {\n        filteredCategories = allCategories.filter(c =>\n            (c.category || '').toLowerCase().includes(q) ||\n            String(c.id || '').includes(q)\n        );\n    }\n    currentPage = 1;\n    renderCategories();\n    renderPagination();\n}\n\n\/** RENDER CARDS **\/\nfunction renderCategories() {\n    const container = document.getElementById('categoryContainer');\n    const total = filteredCategories.length;\n    const totalPages = Math.max(1, Math.ceil(total \/ perPage));\n    if (currentPage > totalPages) currentPage = totalPages;\n\n    document.getElementById('categoryCount').textContent = `(${allCategories.length})`;\n\n    const startIdx = total === 0 ? 0 : (currentPage - 1) * perPage;\n    const endIdx = Math.min(startIdx + perPage, total);\n    const list = filteredCategories.slice(startIdx, endIdx);\n\n    const displayStart = total === 0 ? 0 : startIdx + 1;\n    document.getElementById('entryInfo').textContent =\n        `Showing ${displayStart} to ${endIdx} of ${total} entries`;\n\n    container.innerHTML = '';\n    if (!list.length) {\n        container.innerHTML = '<div style=\"grid-column:1\/-1;text-align:center;padding:40px;color:var(--text-muted);font-size:14px;\">No categories found.<\/div>';\n        return;\n    }\n\n    list.forEach(cat => {\n        const isActive = cat.is_active;\n        const card = document.createElement('div');\n        card.className = 'category-card';\n        card.innerHTML = `\n            <div class=\"card-header\">\n                <span class=\"card-title\">${cat.category || 'N\/A'}<\/span>\n                <div class=\"badge-container\">\n                    <span class=\"status-badge ${isActive ? 'status-active' : 'status-inactive'}\">\n                        ${isActive ? 'Active' : 'Inactive'}\n                    <\/span>\n                    <div class=\"menu-container\">\n                        <span class=\"menu-dots\" onclick=\"toggleMenu(event, ${cat.id})\">\u22ee<\/span>\n                        <div class=\"dropdown-content\" id=\"dropdown-${cat.id}\">\n                            <div class=\"dropdown-item\" onclick=\"openModal('edit', ${cat.id})\">Edit<\/div>\n                            <div class=\"dropdown-item\" style=\"color:var(--error-red)\" onclick=\"deleteCategory(${cat.id})\">Delete<\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n            <div class=\"card-body\">\n                <div class=\"info-label\">Category ID: <strong>${cat.id}<\/strong><\/div>\n            <\/div>`;\n        container.appendChild(card);\n    });\n}\n\n\/** MODAL LOGIC (ADD\/EDIT) \u2014 fixed: fetch detail then populate **\/\nasync function openModal(mode, id = null) {\n    const modal = document.getElementById('categoryModal');\n    const form = document.getElementById('categoryForm');\n    form.reset();\n    document.getElementById('editCategoryId').value = '';\n    document.getElementById('categoryActive').checked = true;\n\n    if (mode === 'edit' && id) {\n        document.getElementById('modalTitle').textContent = 'Update Category';\n        try {\n            const res = await fetch(ENDPOINTS.DETAIL, { \n                method: 'POST', \n                headers: getHeaders(), \n                body: JSON.stringify({ id: id }) \n            });\n            const result = await res.json();\n            if (result.code === 200 && result.data) {\n                const data = result.data;\n                document.getElementById('editCategoryId').value = data.id;\n                document.getElementById('categoryName').value = data.category || '';\n                document.getElementById('categoryActive').checked = !!data.is_active;\n            }\n        } catch (err) { console.error('Detail fetch error:', err); }\n        modal.style.display = 'flex';\n    } else {\n        document.getElementById('modalTitle').textContent = 'New Category';\n        modal.style.display = 'flex';\n    }\n}\n\nfunction closeModal() { document.getElementById('categoryModal').style.display = 'none'; }\n\n\/** SAVE LOGIC (ADD\/UPDATE) **\/\ndocument.getElementById('categoryForm').onsubmit = async (e) => {\n    e.preventDefault();\n    const id = document.getElementById('editCategoryId').value;\n    const payload = {\n        category: document.getElementById('categoryName').value,\n        is_active: document.getElementById('categoryActive').checked\n    };\n    \n    const url = id ? ENDPOINTS.UPDATE : ENDPOINTS.ADD;\n    if (id) payload.id = parseInt(id);\n\n    try {\n        const res = await fetch(url, {\n            method: 'POST', \n            headers: getHeaders(), \n            body: JSON.stringify(payload)\n        });\n        const result = await res.json();\n        if (result.code === 200) { \n            closeModal(); \n            fetchAllCategories(); \n        }\n    } catch (err) { console.error(err); }\n};\n\n\/** PAGINATION **\/\nfunction goToPage(page) {\n    currentPage = page;\n    renderCategories();\n    renderPagination();\n}\n\nfunction renderPagination() {\n    const container = document.getElementById('pageNumbers');\n    container.innerHTML = '';\n\n    const total = Math.max(1, Math.ceil(filteredCategories.length \/ perPage));\n    const cur = currentPage;\n\n    const prevBtn = document.getElementById('prevBtn');\n    prevBtn.onclick = () => { if (cur > 1) goToPage(cur - 1); };\n    prevBtn.className = cur === 1 ? 'nav-btn disabled' : 'nav-btn';\n    let pages = [];\n\n    if (total <= 5) {\n        for (let i = 1; i <= total; i++) pages.push(i);\n    } else {\n        pages = [1];\n        if (cur > 3) pages.push('...');\n        for (let i = Math.max(2, cur - 1); i <= Math.min(total - 1, cur + 1); i++) pages.push(i);\n        if (cur < total - 2) pages.push('...');\n        pages.push(total);\n    }\n\n    pages.forEach(p => {\n        const span = document.createElement('span');\n        if (p === '...') {\n            span.textContent = '...';\n            span.style.cssText = 'display:flex;align-items:center;justify-content:center;width:32px;height:32px;font-size:13px;color:var(--text-muted)';\n        } else {\n            span.className = p === cur ? 'page-num active' : 'page-num';\n            span.textContent = p;\n            span.onclick = () => goToPage(p);\n        }\n        container.appendChild(span);\n    });\n    \n    const nextBtn = document.getElementById('nextBtn');\n    nextBtn.onclick = () => { if (cur < total) goToPage(cur + 1); };\n    nextBtn.className = cur === total ? 'nav-btn disabled' : 'nav-btn';\n}\n\n\/** DELETE CATEGORY **\/\nasync function deleteCategory(id) {\n    if (!confirm('Are you sure you want to delete this category?')) return;\n    try {\n        const res = await fetch(ENDPOINTS.DELETE, {\n            method: 'POST',\n            headers: getHeaders(),\n            body: JSON.stringify({ id: id })\n        });\n        const result = await res.json();\n        if (result.code === 200) {\n            fetchAllCategories();\n        }\n    } catch (err) { console.error('Delete Error:', err); }\n}\n\n\/** SEARCH (across all loaded categories) **\/\nfunction handleSearch() {\n    applySearchAndRender();\n}\n\n\/** DROPDOWN **\/\nfunction toggleMenu(e, id) {\n    e.stopPropagation();\n    document.querySelectorAll('.dropdown-content').forEach(d => d.classList.remove('show'));\n    document.getElementById(`dropdown-${id}`).classList.add('show');\n}\nwindow.onclick = () => document.querySelectorAll('.dropdown-content').forEach(d => d.classList.remove('show'));\n\n\/** DOWNLOAD CSV **\/\nfunction downloadCSV() {\n    const exportList = filteredCategories.length ? filteredCategories : allCategories;\n    if (!exportList.length) return;\n    const headers = ['ID', 'Category Name', 'Status'];\n    const rows = exportList.map(cat => [\n        cat.id,\n        `\"${(cat.category || '').replace(\/\"\/g, '\"\"')}\"`,\n        cat.is_active ? 'Active' : 'Inactive'\n    ]);\n    const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\\n');\n    const blob = new Blob([csv], { 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 = 'categories.csv';\n    a.click();\n    URL.revokeObjectURL(url);\n}\n\n\/\/ Initial load\n    fetchAllCategories();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>CATEGORIES (0) + Add New Category Download CSV \u00ab Previous Next \u00bb Showing 0 to 0 of 0 entries New Category Category Name * Category Active * Close Save<\/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-96","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/96","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=96"}],"version-history":[{"count":2,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/96\/revisions"}],"predecessor-version":[{"id":1244,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/96\/revisions\/1244"}],"wp:attachment":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=96"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}