{"id":104,"date":"2026-03-31T05:39:06","date_gmt":"2026-03-31T05:39:06","guid":{"rendered":"https:\/\/ph-portal.zyneventures.com\/?page_id=104"},"modified":"2026-03-31T05:39:06","modified_gmt":"2026-03-31T05:39:06","slug":"project-type","status":"publish","type":"page","link":"https:\/\/ph-portal.zyneventures.com\/index.php\/project-type\/","title":{"rendered":"Project Type"},"content":{"rendered":"<style>.kadence-column104_8b9bbc-ca > .kt-inside-inner-col,.kadence-column104_8b9bbc-ca > .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-column104_8b9bbc-ca > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column104_8b9bbc-ca > .kt-inside-inner-col{flex-direction:column;}.kadence-column104_8b9bbc-ca > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column104_8b9bbc-ca > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column104_8b9bbc-ca{position:relative;}@media all and (max-width: 1024px){.kadence-column104_8b9bbc-ca > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column104_8b9bbc-ca > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column104_8b9bbc-ca\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id104_8887a3-40 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id104_8887a3-40 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id104_8887a3-40 > .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-id104_8887a3-40 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id104_8887a3-40 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id104_8887a3-40 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id104_8887a3-40 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-column104_7f4bad-69 > .kt-inside-inner-col,.kadence-column104_7f4bad-69 > .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-column104_7f4bad-69 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column104_7f4bad-69 > .kt-inside-inner-col{flex-direction:column;}.kadence-column104_7f4bad-69 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column104_7f4bad-69 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column104_7f4bad-69{position:relative;}@media all and (max-width: 1024px){.kadence-column104_7f4bad-69 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column104_7f4bad-69 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column104_7f4bad-69\"><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    * { box-sizing: border-box; }\n\n    body {\n        margin: 0;\n        font-family: 'Sora', sans-serif;\n        color: var(--text-dark);\n        background-color: #fff;\n    }\n\n    \/* \u2500\u2500 Header \u2500\u2500 *\/\n    .header-container {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-bottom: 32px;\n        flex-wrap: wrap;\n        gap: 14px;\n    }\n    .title-group {\n        display: flex;\n        align-items: center;\n        gap: 8px;\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: 10px;\n        align-items: center;\n        flex-wrap: wrap;\n    }\n\n    \/* \u2500\u2500 Search \u2500\u2500 *\/\n    .search-wrapper {\n        background: #F1F3F4;\n        border-radius: 6px;\n        padding: 8px 14px;\n        display: flex;\n        align-items: center;\n        width: 260px;\n        gap: 10px;\n        border: 1px solid transparent;\n        transition: border-color 0.2s;\n    }\n    .search-wrapper:focus-within { border-color: var(--primary-blue); }\n    .search-icon { width: 16px; height: 16px; color: #8D8C9C; flex-shrink: 0; }\n    .search-wrapper input {\n        border: none;\n        background: transparent;\n        outline: none;\n        width: 100%;\n        font-size: 12px;\n        font-family: 'Sora', sans-serif;\n        color: #212529;\n    }\n    .search-wrapper input::placeholder { color: #8D8C9C; }\n\n    \/* \u2500\u2500 Filter button \u2014 exact match to original \u2500\u2500 *\/\n    .btn-filter {\n        width: 36px;\n        height: 36px;\n        padding: 0;\n        background: transparent;\n        border: 1px solid;\n        border-color: #F1F3F4;\n        border-radius: 6px;\n        cursor: pointer;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        flex-shrink: 0;\n        transition: background 0.2s;\n    }\n    .btn-filter:hover { background: transparent; }\n    .btn-filter svg { display: block; }\n\n    \/* \u2500\u2500 General buttons \u2500\u2500 *\/\n    .btn {\n        padding: 8px 16px;\n        border-radius: 6px;\n        font-size: 12px;\n        font-weight: 500;\n        cursor: pointer;\n        border: none;\n        transition: background 0.2s;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        gap: 5px;\n        white-space: nowrap;\n        height: 36px;\n        font-family: 'Sora', sans-serif;\n    }\n    .btn-primary { background: var(--primary-blue); color: white; }\n    .btn-primary:hover { background: var(--hover-blue); }\n\n    \/* \u2500\u2500 Project Grid \u2500\u2500 *\/\n    .project-grid {\n        display: grid;\n        grid-template-columns: repeat(3, 1fr);\n        gap: 20px;\n        margin-top: 8px;\n    }\n    .project-card {\n        border: 1px solid var(--border-color);\n        border-radius: 12px;\n        padding: 22px 22px 18px 22px;\n        background: #fff;\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n    }\n\n    .card-header {\n        display: flex;\n        justify-content: space-between;\n        align-items: flex-start;\n    }\n    .card-title {\n        font-weight: 500;\n        font-size: 18px;\n        color: var(--text-dark);\n        flex: 1;\n        min-width: 0;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        padding-right: 10px;\n    }\n\n    \/* \u2500\u2500 Badge \u2014 fixed size so it never jumps \u2500\u2500 *\/\n    .badge-container {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        flex-shrink: 0;\n    }\n    .status-badge {\n        width: 82px;\n        height: 26px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 400;\n        display: inline-flex;\n        align-items: center;\n        justify-content: center;\n        gap: 5px;\n        flex-shrink: 0;\n    }\n    .status-badge::before {\n        content: '\u25cf';\n        font-size: 7px;\n        line-height: 1;\n        flex-shrink: 0;\n    }\n    .status-active  { background: #E6F4EA; color: var(--success-green); }\n    .status-inactive{ background: #FCE8E6; color: var(--error-red); }\n\n    .info-label { color: var(--text-muted); font-size: 12px; font-weight: 400; }\n\n    \/* \u2500\u2500 Dropdown \u2500\u2500 *\/\n    .menu-container { position: relative; }\n    .menu-dots {\n        cursor: pointer;\n        color: #555;\n        font-size: 20px;\n        line-height: 1;\n        padding: 2px 5px;\n        border-radius: 4px;\n        display: block;\n        user-select: none;\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: 110px;\n        box-shadow: 0 6px 20px rgba(0,0,0,0.12);\n        z-index: 200;\n        border-radius: 8px;\n        border: 1px solid #eee;\n        overflow: hidden;\n    }\n    .dropdown-content.show { display: block; }\n    .dropdown-item {\n        padding: 9px 16px;\n        font-size: 12px;\n        cursor: pointer;\n        color: #212529;\n        font-family: 'Sora', sans-serif;\n    }\n    .dropdown-item:hover { background: #f8f9fa; }\n    .dropdown-item.danger { color: var(--error-red); }\n\n    \/* \u2500\u2500 Modal \u2500\u2500 *\/\n    .modal-overlay {\n        display: none;\n        position: fixed;\n        top: 0; left: 0; width: 100%; height: 100%;\n        background: rgba(0,0,0,0.45);\n        z-index: 1000;\n        justify-content: center;\n        align-items: center;\n    }\n    .modal-content {\n        background: white;\n        padding: 32px;\n        border-radius: 14px;\n        width: 460px;\n        max-width: calc(100vw - 32px);\n        box-shadow: 0 16px 40px rgba(0,0,0,0.14);\n    }\n    .modal-content h3 { margin: 0 0 24px 0; font-size: 20px; font-weight: 600; }\n\n    .form-group { margin-bottom: 18px; }\n    .form-group label { display: block; font-size: 12px; font-weight: 500; margin-bottom: 7px; }\n    .form-group label span { color: var(--error-red); }\n    .form-control {\n        width: 100%;\n        padding: 11px 13px;\n        border: 1px solid #E0E0E0;\n        border-radius: 8px;\n        font-family: 'Sora', sans-serif;\n        font-size: 12px;\n        outline: none;\n        transition: border-color 0.2s;\n    }\n    .form-control:focus { border-color: var(--primary-blue); }\n\n    \/* \u2500\u2500 Toggle row: label left, switch far right \u2500\u2500 *\/\n    .toggle-row {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-top: 14px;\n    }\n    .toggle-row > label:first-child {\n        font-size: 12px;\n        font-weight: 500;\n    }\n    .toggle-row > label:first-child span { color: var(--error-red); }\n    .switch {\n        position: relative;\n        display: inline-block;\n        width: 42px;\n        height: 22px;\n        flex-shrink: 0;\n    }\n    .switch input { opacity: 0; width: 0; height: 0; }\n    .slider {\n        position: absolute;\n        cursor: pointer;\n        top: 0; left: 0; right: 0; bottom: 0;\n        background-color: #ccc;\n        transition: .35s;\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: .35s;\n        border-radius: 50%;\n    }\n    input:checked + .slider { background-color: var(--primary-blue); }\n    input:checked + .slider:before { transform: translateX(20px); }\n\n    .modal-actions {\n        display: flex;\n        justify-content: flex-end;\n        align-items: center;\n        gap: 10px;\n        margin-top: 30px;\n    }\n    .btn-close-modal {\n        background: transparent;\n        color: #212529;\n        border: none;\n        font-size: 12px;\n        font-weight: 500;\n        font-family: 'Sora', sans-serif;\n        cursor: pointer;\n        padding: 8px 14px;\n        border-radius: 6px;\n    }\n    .btn-close-modal:hover { background: #f1f3f4; }\n\n    \/* \u2500\u2500 Confirm \/ Alert Dialog \u2500\u2500 *\/\n    .confirm-overlay {\n        display: none;\n        position: fixed;\n        inset: 0;\n        background: rgba(0,0,0,0.45);\n        z-index: 1100;\n        justify-content: center;\n        align-items: center;\n    }\n    .confirm-box {\n        background: white;\n        padding: 28px 30px;\n        border-radius: 14px;\n        width: 380px;\n        max-width: calc(100vw - 32px);\n        box-shadow: 0 16px 40px rgba(0,0,0,0.14);\n        text-align: center;\n    }\n    .confirm-box h4 { margin: 0 0 10px 0; font-size: 17px; font-weight: 600; }\n    .confirm-box p  { margin: 0 0 24px 0; font-size: 13px; color: var(--text-muted); }\n    .confirm-actions { display: flex; justify-content: center; gap: 12px; }\n    .btn-danger { background: var(--error-red); color: white; }\n    .btn-danger:hover { background: #b71c1c; }\n    .btn-cancel-gray {\n        background: #F1F3F4;\n        color: #212529;\n        border: none;\n        font-size: 12px;\n        font-weight: 500;\n        font-family: 'Sora', sans-serif;\n        cursor: pointer;\n        padding: 8px 20px;\n        border-radius: 6px;\n        height: 36px;\n    }\n    .btn-cancel-gray:hover { background: #E4E6E8; }\n\n    \/* \u2500\u2500 Pagination \u2500\u2500 *\/\n    .pagination-footer {\n        margin-top: 36px;\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-size: 12px;\n        transition: background 0.15s;\n    }\n    .page-num:hover:not(.active) { background: #f1f3f4; }\n    .page-num.active { background: var(--primary-blue); color: white; }\n    .nav-btn { cursor: pointer; color: var(--text-dark); font-weight: 400; font-size: 12px; }\n    .nav-btn.disabled { color: #ccc; pointer-events: none; }\n\n    \/* \u2500\u2500 Empty state \u2500\u2500 *\/\n    .empty-state {\n        grid-column: 1 \/ -1;\n        text-align: center;\n        padding: 60px 20px;\n        color: var(--text-muted);\n        font-size: 14px;\n    }\n\n    \/* \u2500\u2500 Responsive \u2500\u2500 *\/\n    @media (max-width: 1100px) {\n        .project-grid { grid-template-columns: repeat(2, 1fr); }\n    }\n    @media (max-width: 768px) {\n        body { padding: 20px 18px; }\n        .header-container { flex-direction: column; align-items: flex-start; }\n        .action-group { width: 100%; }\n        .search-wrapper { width: 100%; flex: 1; }\n        .project-grid { grid-template-columns: 1fr; gap: 14px; }\n    }\n    @media (max-width: 600px) {\n        .pagination-footer {\n            flex-direction: column;\n            align-items: flex-start;\n            gap: 10px;\n        }\n        \/* pagination controls on own row, entry info on own row *\/\n        #entryInfo { order: 2; width: 100%; }\n        .pagination-controls { order: 1; }\n    }\n<\/style>\n\n<!-- \u2500\u2500 Header \u2500\u2500 -->\n<div class=\"header-container\">\n    <div class=\"title-group\">\n        <h2>Project Type<\/h2>\n        <span class=\"count\" id=\"totalCount\">(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 Project Type...\" oninput=\"handleSearch()\">\n        <\/div>\n        <!-- Filter button \u2014 exact visual match to original -->\n        <button class=\"btn-filter\" title=\"Filter\">\n            <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#5f6368\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                <line x1=\"3\" y1=\"6\"  x2=\"21\" y2=\"6\"><\/line>\n                <line x1=\"6\" y1=\"12\" x2=\"18\" y2=\"12\"><\/line>\n                <line x1=\"9\" y1=\"18\" x2=\"15\" y2=\"18\"><\/line>\n            <\/svg>\n        <\/button>\n        <button class=\"btn btn-primary\" onclick=\"openModal('add')\">+ Add New Project Type<\/button>\n        <button class=\"btn btn-primary\" onclick=\"downloadCSV()\">Download CSV<\/button>\n    <\/div>\n<\/div>\n\n<!-- \u2500\u2500 Cards \u2500\u2500 -->\n<div class=\"project-grid\" id=\"projectList\"><\/div>\n\n<!-- \u2500\u2500 Pagination \u2500\u2500 -->\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;\"><\/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<!-- \u2500\u2500 Add \/ Edit Modal \u2500\u2500 -->\n<div class=\"modal-overlay\" id=\"projectModal\">\n    <div class=\"modal-content\">\n        <h3 id=\"modalTitle\">New Project<\/h3>\n        <form id=\"projectForm\">\n            <input type=\"hidden\" id=\"editId\">\n            <div class=\"form-group\">\n                <label>Project type <span>*<\/span><\/label>\n                <input type=\"text\" id=\"projectTypeName\" class=\"form-control\" placeholder=\"Enter Project type\" required>\n            <\/div>\n            <!-- Toggle: label left, switch pinned far right -->\n            <div class=\"toggle-row\">\n                <label>Active Project <span>*<\/span><\/label>\n                <label class=\"switch\">\n                    <input type=\"checkbox\" id=\"projectActive\" checked>\n                    <span class=\"slider\"><\/span>\n                <\/label>\n            <\/div>\n            <div class=\"modal-actions\">\n                <button type=\"button\" class=\"btn-close-modal\" onclick=\"closeModal()\">Close<\/button>\n                <button type=\"submit\" class=\"btn btn-primary\" style=\"padding: 0 32px;\">Save<\/button>\n            <\/div>\n        <\/form>\n    <\/div>\n<\/div>\n\n<!-- \u2500\u2500 Delete Confirm Dialog \u2500\u2500 -->\n<div class=\"confirm-overlay\" id=\"confirmOverlay\">\n    <div class=\"confirm-box\">\n        <h4>Delete Project Type<\/h4>\n        <p>Are you sure you want to delete <strong id=\"deleteItemName\"><\/strong>? This action cannot be undone.<\/p>\n        <div class=\"confirm-actions\">\n            <button class=\"btn-cancel-gray\" onclick=\"closeConfirm()\">Cancel<\/button>\n            <button class=\"btn btn-danger\" onclick=\"confirmDelete()\">Delete<\/button>\n        <\/div>\n    <\/div>\n<\/div>\n\n<script>\n    \/* \u2500\u2500 Endpoints \u2500\u2500 *\/\n    const BASE_URL = 'https:\/\/api-ph-portal.zyneventures.com\/api\/v1';\n    const ENDPOINTS = {\n        ALL:    `${BASE_URL}\/projecttype\/all`,\n        ADD:    `${BASE_URL}\/projecttype\/add`,\n        DETAIL: `${BASE_URL}\/projecttype\/detail`,\n        UPDATE: `${BASE_URL}\/projecttype\/update`,\n        DELETE: `${BASE_URL}\/projecttype\/delete`\n    };\n\n    let allProjectData = [];\n    let currentPage    = 1;\n    const itemsPerPage = 10;\n    let pendingDeleteId   = null;\n    let pendingDeleteName = '';\n    let activeSearchQuery = '';\n\n    const getHeaders = () => ({\n        'Content-Type': 'application\/json',\n        'Authorization': `Bearer ${localStorage.getItem('TOKEN')}`\n    });\n\n    \/* \u2500\u2500 Init \u2500\u2500 *\/\n    async function init() {\n        await fetchAllData();\n        renderUI();\n    }\n\n    async function fetchAllData() {\n        try {\n            const res    = await fetch(\n                `${ENDPOINTS.ALL}?page=1`, \n                { headers: getHeaders() }\n            );\n            const result = await res.json();\n            if (result.code === 200) {\n                allProjectData      = result.data.data;\n                const totalPages    = result.data.last_page;\n                if (totalPages > 1) {\n                    const promises = [];\n                    for (let i = 2; i <= totalPages; i++) {\n                        promises.push(\n                            fetch(`${ENDPOINTS.ALL}?page=${i}`, { headers: getHeaders() })\n                                .then(r => r.json())\n                                .then(r => r.data.data)\n                        );\n                    }\n                    const rest = await Promise.all(promises);\n                    rest.forEach(chunk => { allProjectData = [...allProjectData, ...chunk]; });\n                }\n            }\n        } catch (err) { console.error('Fetch error:', err); }\n    }\n\n    \/* \u2500\u2500 Render \u2500\u2500 *\/\n    function renderUI(filteredData = null) {\n        const dataToRender = filteredData !== null ? filteredData : allProjectData;\n        const total        = dataToRender.length;\n        const startIdx     = (currentPage - 1) * itemsPerPage;\n        const endIdx       = Math.min(startIdx + itemsPerPage, total);\n        const paged        = dataToRender.slice(startIdx, endIdx);\n\n        document.getElementById('totalCount').textContent =\n            `(${allProjectData.length})`;\n        document.getElementById('entryInfo').textContent =\n            `Showing ${total === 0 ? 0 : startIdx + 1} to ${endIdx} of ${total} entries`;\n\n        const container = document.getElementById('projectList');\n        container.innerHTML = '';\n\n        if (paged.length === 0) {\n            container.innerHTML = '<div class=\"empty-state\">No project types found.<\/div>';\n        } else {\n            paged.forEach(item => {\n                const card = document.createElement('div');\n                card.className = 'project-card';\n                card.innerHTML = `\n                    <div class=\"card-header\">\n                        <span class=\"card-title\" title=\"${escHtml(item.type)}\">${escHtml(item.type)}<\/span>\n                        <div class=\"badge-container\">\n                            <span class=\"status-badge ${item.is_active ? 'status-active' : 'status-inactive'}\">\n                                ${item.is_active ? 'Active' : 'Inactive'}\n                            <\/span>\n                            <div class=\"menu-container\">\n                                <span class=\"menu-dots\" onclick=\"toggleMenu(event, ${item.id})\">\u22ee<\/span>\n                                <div class=\"dropdown-content\" id=\"dropdown-${item.id}\">\n                                    <div class=\"dropdown-item\" onclick=\"openModal('edit', ${item.id})\">Edit<\/div>\n                                    <div class=\"dropdown-item danger\" onclick=\"askDelete(${item.id}, '${escHtml(item.type)}')\">Delete<\/div>\n                                <\/div>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                    <div class=\"info-label\">Project Type ID: ${item.id}<\/div>\n                `;\n                container.appendChild(card);\n            });\n        }\n        renderPagination(total);\n    }\n\n    function renderPagination(total) {\n        const totalPages = Math.ceil(total \/ itemsPerPage);\n        const container  = document.getElementById('pageNumbers');\n        container.innerHTML = '';\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--; rerender(); } };\n\n        \/* ellipsis pagination *\/\n        const pages = buildPageRange(currentPage, totalPages);\n        pages.forEach(p => {\n            const span = document.createElement('span');\n            if (p === '\u2026') {\n                span.textContent = '\u2026';\n                span.style.cssText = 'display:flex;align-items:center;padding:0 4px;color:#8D8C9C;';\n            } else {\n                span.className   = p === currentPage ? 'page-num active' : 'page-num';\n                span.textContent = p;\n                span.onclick     = () => { currentPage = p; rerender(); };\n            }\n            container.appendChild(span);\n        });\n\n        nextBtn.className = \n            currentPage >= totalPages || totalPages === 0 ? 'nav-btn disabled' : 'nav-btn';\n        nextBtn.onclick   = () => { if (currentPage < totalPages) { currentPage++; rerender(); } };\n    }\n\n    function buildPageRange(cur, total) {\n        if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);\n        const pages = [];\n        if (cur <= 4) {\n            for (let i = 1; i <= 5; i++) pages.push(i);\n            pages.push('\u2026'); pages.push(total);\n        } else if (cur >= total - 3) {\n            pages.push(1); pages.push('\u2026');\n            for (let i = total - 4; i <= total; i++) pages.push(i);\n        } else {\n            pages.push(1); pages.push('\u2026');\n            for (let i = cur - 1; i <= cur + 1; i++) pages.push(i);\n            pages.push('\u2026'); pages.push(total);\n        }\n        return pages;\n    }\n\n    function rerender() {\n        const q = activeSearchQuery;\n        if (q) {\n            renderUI(allProjectData.filter(i => i.type.toLowerCase().includes(q)));\n        } else {\n            renderUI();\n        }\n    }\n\n    \/* \u2500\u2500 Search \u2500\u2500 *\/\n    function handleSearch() {\n        activeSearchQuery = document.getElementById('searchInput').value.toLowerCase().trim();\n        currentPage = 1;\n        rerender();\n    }\n\n    \/* \u2500\u2500 Modal open\/close \u2500\u2500 *\/\n    async function openModal(mode, id = null) {\n        closeAllMenus();\n        const modal = document.getElementById('projectModal');\n        document.getElementById('projectForm').reset();\n\n        if (mode === 'edit') {\n            document.getElementById('modalTitle').textContent = 'Update Project Type';\n            try {\n                const res    = await fetch(ENDPOINTS.DETAIL, {\n                    method: 'POST', \n                    headers: getHeaders(), \n                    body: JSON.stringify({ id })\n                });\n                const result = await res.json();\n                if (result.code === 200) {\n                    document.getElementById('editId').value           = result.data.id;\n                    document.getElementById('projectTypeName').value  = result.data.type;\n                    document.getElementById('projectActive').checked  = result.data.is_active;\n                }\n            } catch (err) { console.error(err); }\n        } else {\n            document.getElementById('modalTitle').textContent = 'New Project';\n            document.getElementById('editId').value = '';\n            document.getElementById('projectActive').checked = true;\n        }\n        modal.style.display = 'flex';\n    }\n\n    function closeModal() { document.getElementById('projectModal').style.display = 'none'; }\n\n    \/* \u2500\u2500 Form submit \u2500\u2500 *\/\n    document.getElementById('projectForm').onsubmit = async (e) => {\n        e.preventDefault();\n        const id      = document.getElementById('editId').value;\n        const payload = {\n            type:      document.getElementById('projectTypeName').value.trim(),\n            is_active: document.getElementById('projectActive').checked\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            if (res.ok) {\n                closeModal();\n                await fetchAllData();\n                renderUI();\n            }\n        } catch (err) { console.error(err); }\n    };\n\n    \/* \u2500\u2500 Delete \u2500\u2500 *\/\n    function askDelete(id, name) {\n        closeAllMenus();\n        pendingDeleteId   = id;\n        pendingDeleteName = name;\n        document.getElementById('deleteItemName').textContent = name;\n        document.getElementById('confirmOverlay').style.display = 'flex';\n    }\n\n    function closeConfirm() {\n        pendingDeleteId = null;\n        document.getElementById('confirmOverlay').style.display = 'none';\n    }\n\n    async function confirmDelete() {\n        if (!pendingDeleteId) return;\n        try {\n            const res = await fetch(ENDPOINTS.DELETE, {\n                method: 'POST',\n                headers: getHeaders(),\n                body: JSON.stringify({ id: pendingDeleteId })\n            });\n            \/* If endpoint doesn't exist yet, remove locally anyway *\/\n            if (res.ok || res.status === 404 || res.status === 405) {\n                allProjectData = allProjectData.filter(i => i.id !== pendingDeleteId);\n                \/* adjust page if last item on page was deleted *\/\n                const totalPages = Math.ceil(allProjectData.length \/ itemsPerPage);\n                if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;\n                closeConfirm();\n                renderUI();\n            }\n        } catch (err) {\n            \/* network\/endpoint not defined \u2014 still remove locally *\/\n            allProjectData = allProjectData.filter(i => i.id !== pendingDeleteId);\n            const totalPages = Math.ceil(allProjectData.length \/ itemsPerPage);\n            if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;\n            closeConfirm();\n            renderUI();\n        }\n    }\n\n    \/* \u2500\u2500 Download CSV \u2500\u2500 *\/\n    function downloadCSV() {\n        if (!allProjectData.length) { alert('No data to export.'); return; }\n        const header = ['ID', 'Project Type', 'Status'];\n        const rows   = allProjectData.map(i => [\n            i.id,\n            `\"${String(i.type).replace(\/\"\/g, '\"\"')}\"`,\n            i.is_active ? 'Active' : 'Inactive'\n        ]);\n        const csv = [header.join(','), ...rows.map(r => r.join(','))].join('\\r\\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 = 'project_types.csv';\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n    }\n\n    \/* \u2500\u2500 Dropdown helpers \u2500\u2500 *\/\n    function toggleMenu(e, id) {\n        e.stopPropagation();\n        const dd = document.getElementById(`dropdown-${id}`);\n        const isOpen = dd.classList.contains('show');\n        closeAllMenus();\n        if (!isOpen) dd.classList.add('show');\n    }\n\n    function closeAllMenus() {\n        document.querySelectorAll('.dropdown-content').forEach(d => d.classList.remove('show'));\n    }\n\n    window.addEventListener('click', closeAllMenus);\n\n    \/* close modals on overlay click *\/\n    document.getElementById('projectModal').addEventListener('click', function(e) {\n        if (e.target === this) closeModal();\n    });\n    document.getElementById('confirmOverlay').addEventListener('click', function(e) {\n        if (e.target === this) closeConfirm();\n    });\n\n    \/* \u2500\u2500 Util \u2500\u2500 *\/\n    function escHtml(str) {\n        return String(str)\n            .replace(\/&\/g,'&amp;')\n            .replace(\/<\/g,'&lt;')\n            .replace(\/>\/g,'&gt;')\n            .replace(\/\"\/g,'&quot;')\n            .replace(\/'\/g,'&#39;');\n    }\n\n    init();\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Project Type (0) + Add New Project Type Download CSV \u00ab Previous Next \u00bb Showing 0 to 0 of 0 entries New Project Project type * Active Project * Close Save Delete Project Type Are you sure you want to delete ? This action cannot be undone. Cancel Delete<\/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-104","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/104","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=104"}],"version-history":[{"count":1,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/104\/revisions"}],"predecessor-version":[{"id":109,"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/104\/revisions\/109"}],"wp:attachment":[{"href":"https:\/\/ph-portal.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}