{"id":387,"date":"2026-04-27T05:06:10","date_gmt":"2026-04-27T05:06:10","guid":{"rendered":"https:\/\/ph-taskportal.zyneventures.com\/?page_id=387"},"modified":"2026-05-06T10:42:55","modified_gmt":"2026-05-06T10:42:55","slug":"assigned-hours","status":"publish","type":"page","link":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/assigned-hours\/","title":{"rendered":"Assigned Hours"},"content":{"rendered":"<style>.kadence-column387_86bfa0-5c > .kt-inside-inner-col,.kadence-column387_86bfa0-5c > .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-column387_86bfa0-5c > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column387_86bfa0-5c > .kt-inside-inner-col{flex-direction:column;}.kadence-column387_86bfa0-5c > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column387_86bfa0-5c > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column387_86bfa0-5c{position:relative;}@media all and (max-width: 1024px){.kadence-column387_86bfa0-5c > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column387_86bfa0-5c > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column387_86bfa0-5c\"><div class=\"kt-inside-inner-col\"><style>.kb-row-layout-id387_2979b0-98 > .kt-row-column-wrap{align-content:start;}:where(.kb-row-layout-id387_2979b0-98 > .kt-row-column-wrap) > .wp-block-kadence-column{justify-content:start;}.kb-row-layout-id387_2979b0-98 > .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-id387_2979b0-98 > .kt-row-layout-overlay{opacity:0.30;}@media all and (max-width: 1024px){.kb-row-layout-id387_2979b0-98 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}@media all and (max-width: 767px){.kb-row-layout-id387_2979b0-98 > .kt-row-column-wrap{grid-template-columns:minmax(0, 1fr);}}<\/style><div class=\"kb-row-layout-wrap kb-row-layout-id387_2979b0-98 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-column387_bdfbd5-27 > .kt-inside-inner-col,.kadence-column387_bdfbd5-27 > .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-column387_bdfbd5-27 > .kt-inside-inner-col{column-gap:var(--global-kb-gap-sm, 1rem);}.kadence-column387_bdfbd5-27 > .kt-inside-inner-col{flex-direction:column;}.kadence-column387_bdfbd5-27 > .kt-inside-inner-col > .aligncenter{width:100%;}.kadence-column387_bdfbd5-27 > .kt-inside-inner-col:before{opacity:0.3;}.kadence-column387_bdfbd5-27{position:relative;}@media all and (max-width: 1024px){.kadence-column387_bdfbd5-27 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}@media all and (max-width: 767px){.kadence-column387_bdfbd5-27 > .kt-inside-inner-col{flex-direction:column;justify-content:center;}}<\/style>\n<div class=\"wp-block-kadence-column kadence-column387_bdfbd5-27\"><div class=\"kt-inside-inner-col\">\n<style>\n    @import url(\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap-icons@1.11.3\/font\/bootstrap-icons.min.css\");\n    :root {\n        --primary-blue: #0180FF;\n        --hover-blue: #3499FF;\n        --text-dark: #212529;\n        --text-muted: #8D8C9C;\n        --border-color: #EFEFEF;\n        --bg-form: #F5F5F5;\n    }\n\n    \/* Header Layout *\/\n    .header-container {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-bottom: 30px;\n        gap: 15px;\n        flex-wrap: wrap;\n    }\n\n    .header-container h2 {\n        margin: 0;\n        font-size: 16px;\n        font-weight: 500;\n        text-transform: uppercase;\n        letter-spacing: 0.5px;\n        color: #333;\n    }\n\n    .btn {\n        padding: 8px 25px;\n        border-radius: 6px;\n        font-size: 12px;\n        font-weight: 500;\n        cursor: pointer;\n        border: none;\n        transition: 0.2s;\n        font-family: 'Sora', sans-serif;\n    }\n\n    .btn-primary {\n        background: var(--primary-blue);\n        color: white;\n    }\n\n    .btn-primary:hover {\n        background: var(--hover-blue);\n    }\n\n    \/* Table Design *\/\n    .table-container {\n        width: 100%;\n        overflow-x: auto;\n        border: 1px solid var(--border-color);\n        border-radius: 8px;\n        -webkit-overflow-scrolling: touch;\n    }\n\n    table {\n        width: 100%;\n        border-collapse: collapse;\n        min-width: 800px;\n    }\n\n    th {\n        text-align: left;\n        padding: 15px;\n        font-size: 11px;\n        text-transform: uppercase;\n        color: #333;\n        font-weight: 600;\n        background: #F5F5F5;\n        letter-spacing: 0.5px;\n        white-space: nowrap;\n    }\n\n    td {\n        padding: 15px;\n        font-size: 12px;\n        border-bottom: 1px solid var(--border-color);\n        color: #666;\n    }\n\n    .action-icon {\n        color: #A0A0A0;\n        cursor: pointer;\n        transition: color 0.2s;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n\n    .action-icon:hover {\n        color: var(--primary-blue);\n    }\n\n    \/* Pagination *\/\n    .pagination-container {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n        margin-top: 30px;\n        padding-bottom: 40px;\n        gap: 20px;\n        flex-wrap: wrap;\n    }\n\n    .pagination-controls {\n        display: flex;\n        align-items: center;\n        gap: 5px;\n        flex-wrap: nowrap;\n    }\n\n    .page-item {\n        width: 32px;\n        height: 32px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        cursor: pointer;\n        border-radius: 50%;\n        font-size: 12px;\n        color: var(--text-muted);\n        transition: background 0.2s;\n    }\n\n    .page-item.active {\n        background-color: var(--primary-blue);\n        color: white;\n    }\n\n    .nav-link {\n        cursor: pointer;\n        color: var(--text-dark);\n        font-size: 12px;\n        padding: 5px 8px;\n        user-select: none;\n    }\n    .nav-link.disabled { color: #ccc; pointer-events: none; }\n\n    \/* Responsive *\/\n    @media (max-width: 1024px) {\n        body { padding-left: 20px; padding-right: 20px; }\n    }\n\n    @media (max-width: 768px) {\n        body { padding-left: 15px; padding-right: 15px; }\n        .header-container { flex-direction: column; align-items: stretch; }\n        .header-container h2 { font-size: 14px; text-align: left; }\n        #search_form { width: 100%; }\n        #search_form > div { width: 100%; flex-direction: column; align-items: stretch !important; }\n        .search-wrap { width: 100%; }\n        .search-wrap input { width: 100% !important; }\n        .pagination-container { justify-content: center; flex-direction: column-reverse; text-align: center; }\n        th, td { padding: 12px 10px; font-size: 11px; }\n    }\n\n    @media (max-width: 480px) {\n        body { padding-left: 10px; padding-right: 10px; }\n        .header-container h2 { font-size: 13px; }\n        .btn { font-size: 11px; padding: 8px 16px; }\n        .pagination-controls { flex-wrap: wrap; justify-content: center; }\n        .page-item { width: 28px; height: 28px; font-size: 11px; }\n    }\n<\/style>\n\n<div class=\"header-container\">\n    <h2>TOTAL HOURS ASSIGNED ASSIGNMENT<\/h2>\n    <form method=\"get\" action id=\"search_form\" name=\"search_form\" style=\"margin: 0;\">\n        <div style=\"display: flex; gap: 15px; align-items: center;\">\n            <div class=\"search-wrap\">\n                <input type=\"date\" name=\"date\" id=\"search_date\" class=\"form-control\" value=\"\" style=\"padding: 10px 15px; border: 1px solid #EFEFEF; border-radius: 6px; font-size: 13px; color: #555; outline: none; box-sizing: border-box; width: 280px; font-family: inherit;\">\n            <\/div>\n            <div>\n                <button class=\"btn btn-primary\" type=\"submit\" style=\"padding: 10px 25px; font-size: 13px; border-radius: 6px; font-weight: 400;\">Search<\/button>\n            <\/div>\n        <\/div>\n    <\/form>\n<\/div>\n\n<div class=\"table-container\">\n    <table>\n        <thead>\n            <tr>\n                <th>ASSIGNED TO<\/th>\n                <th>Total Assigned Hrs<\/th>\n                <th>Total Submitted Hrs<\/th>\n                <th>Pending Hrs<\/th>\n                <th>ASSIGNED DATE<\/th>\n                <th style=\"text-align: center;\">Detail<\/th>\n            <\/tr>\n        <\/thead>\n        <tbody id=\"tableBody\">\n            <!-- Rows will be populated by JS -->\n        <\/tbody>\n    <\/table>\n<\/div>\n\n<div class=\"pagination-container\" id=\"paginationContainer\" style=\"display:none;\">\n    <div class=\"pagination-controls\">\n        <span class=\"nav-link\" id=\"prevBtn\">\u00ab Previous<\/span>\n        <div id=\"pageNumbers\" style=\"display:flex; gap: 4px;\"><\/div>\n        <span class=\"nav-link\" id=\"nextBtn\">Next \u00bb<\/span>\n    <\/div>\n    <div id=\"entryInfo\" style=\"font-size: 12px; color: var(--text-muted);\">\n        Showing 0 to 0 of 0 entries\n    <\/div>\n<\/div>\n\n<script>\n    \/\/ ============================================================\n    \/\/ API ENDPOINTS\n    \/\/ ============================================================\n    const BASE_URL = \"https:\/\/api-ph-portal.zyneventures.com\/api\/v1\";\n    const ENDPOINTS = {\n        TOTAL_HOURS: `${BASE_URL}\/reports\/total-hours-data`,\n        USER_HOURS_DETAIL: `${BASE_URL}\/reports\/user-assigned-hours-detail`\n    };\n    \/\/ ============================================================\n\n    const tbody = document.getElementById('tableBody');\n    const paginationContainer = document.getElementById('paginationContainer');\n\n    \/\/ Pagination state (client-side)\n    let masterData = [];\n    let currentPage = 1;\n    const perPage = 10;\n\n    \/\/ Track the currently selected date for use in detail navigation\n    let selectedDate = '';\n\n    const getHeaders = () => {\n        let headers = { 'Content-Type': 'application\/json' };\n        if (window.phPortal && typeof window.phPortal.authHeaders === 'function') {\n            headers = window.phPortal.authHeaders();\n        } else {\n            const token = localStorage.getItem('TOKEN') || (document.cookie.match(\/(?:^|;\\s*)TOKEN=([^;]+)\/) || [])[1];\n            if (token) headers['Authorization'] = `Bearer ${token}`;\n        }\n        return headers;\n    };\n\n    const eyeIcon = (userId) => `\n        <span class=\"action-icon\" onclick=\"handleDetailAction(${userId})\" style=\"cursor: pointer;\">\n            <i class=\"bi bi-eye mx-1\"><\/i>\n        <\/span>\n    `;\n\n    \/\/ Onclick handler for detail icon: navigates to user-assigned page with user id and selected date\n    async function handleDetailAction(userId) {\n        try {\n            await fetch(`${ENDPOINTS.USER_HOURS_DETAIL}?user_id=${userId}`, { headers: getHeaders() });\n            window.location.href = `https:\/\/ph-portal-v2.zyneventures.com\/index.php\/user-assigned\/?id=${userId}&date=${selectedDate}`;\n        } catch (error) {\n            console.error('Detail Error:', error);\n            window.location.href = `https:\/\/ph-portal-v2.zyneventures.com\/index.php\/user-assigned\/?id=${userId}&date=${selectedDate}`;\n        }\n    }\n\n    async function fetchTotalHours(date = '') {\n        if (!date) {\n            tbody.innerHTML = '<tr><td colspan=\"6\" style=\"text-align: center; padding: 30px; color: #666;\">Please select a date to view data.<\/td><\/tr>';\n            paginationContainer.style.display = 'none';\n            return;\n        }\n\n        tbody.innerHTML = '<tr><td colspan=\"6\" style=\"text-align: center; padding: 40px;\"> <div class=\"spinner-border text-primary\" role=\"status\"><span class=\"visually-hidden\">Loading...<\/span><\/div><br>Loading Report Data...<\/td><\/tr>';\n        paginationContainer.style.display = 'none';\n\n        try {\n            const url = `${ENDPOINTS.TOTAL_HOURS}?date=${date}`;\n            const response = await fetch(url, { headers: getHeaders() });\n            const result = await response.json();\n\n            console.log('Total Hours Data Result:', result);\n\n            if (result.status === 'success' && result.data) {\n                if (result.data.length > 0) {\n                    masterData = result.data;\n                    currentPage = 1;\n                    renderPaginatedTable();\n                } else {\n                    tbody.innerHTML = '<tr><td colspan=\"6\" style=\"text-align: center; padding: 30px;\">No data available for the selected criteria.<\/td><\/tr>';\n                    paginationContainer.style.display = 'none';\n                }\n            } else {\n                tbody.innerHTML = `<tr><td colspan=\"6\" style=\"text-align: center; padding: 30px; color: #ff4d4d;\">${result.errors || 'Failed to fetch data.'}<\/td><\/tr>`;\n                paginationContainer.style.display = 'none';\n            }\n        } catch (error) {\n            console.error('Error fetching total hours:', error);\n            tbody.innerHTML = '<tr><td colspan=\"6\" style=\"text-align: center; padding: 30px; color: #ff4d4d;\">A server error occurred. Please try again later.<\/td><\/tr>';\n            paginationContainer.style.display = 'none';\n        }\n    }\n\n    function renderPaginatedTable() {\n        const total = masterData.length;\n        const totalPages = Math.ceil(total \/ perPage);\n        const start = (currentPage - 1) * perPage;\n        const end = start + perPage;\n        const pageData = masterData.slice(start, end);\n\n        renderTable(pageData);\n        updatePagination({ total, page: currentPage, perPage, totalPages });\n        paginationContainer.style.display = total > 0 ? 'flex' : 'none';\n    }\n\n    function renderTable(items) {\n        let rowsHTML = '';\n        items.forEach(item => {\n            const userId = item.user_id || item.id;\n            rowsHTML += `\n                <tr>\n                    <td style=\"font-weight: 500; color: #333;\">${item.pesudo_name || 'N\/A'}<\/td>\n                    <td style=\"color: #0180FF; font-weight: 500;\">${item.total_assigned_hours ? Number(item.total_assigned_hours).toFixed(2) : '0.00'}<\/td>\n                    <td style=\"color: #1E8E3E;\">${item.total_submitted_hours ? Number(item.total_submitted_hours).toFixed(2) : '0.00'}<\/td>\n                    <td style=\"color: ${Number(item.pending_hours) > 0 ? '#D93025' : '#8D8C9C'};\">${item.pending_hours ? Number(item.pending_hours).toFixed(2) : '0.00'}<\/td>\n                    <td>${item.assigned_date || 'N\/A'}<\/td>\n                    <td style=\"text-align: center;\">${eyeIcon(userId)}<\/td>\n                <\/tr>\n            `;\n        });\n        tbody.innerHTML = rowsHTML;\n    }\n\n    function updatePagination(meta) {\n        const start = meta.total === 0 ? 0 : ((meta.page - 1) * meta.perPage) + 1;\n        const end = Math.min(meta.page * meta.perPage, meta.total);\n        document.getElementById(\"entryInfo\").textContent = `Showing ${start} to ${end} of ${meta.total} entries`;\n\n        const pageNumbers = document.getElementById(\"pageNumbers\");\n        pageNumbers.innerHTML = \"\";\n\n        const totalPages = meta.totalPages;\n        const maxVisible = 5;\n\n        let startPage = Math.max(1, meta.page - Math.floor(maxVisible \/ 2));\n        let endPage = Math.min(totalPages, startPage + maxVisible - 1);\n        if (endPage - startPage + 1 < maxVisible) {\n            startPage = Math.max(1, endPage - maxVisible + 1);\n        }\n\n        if (startPage > 1) {\n            appendPageItem(pageNumbers, 1, meta.page);\n            if (startPage > 2) appendEllipsis(pageNumbers);\n        }\n\n        for (let i = startPage; i <= endPage; i++) {\n            appendPageItem(pageNumbers, i, meta.page);\n        }\n\n        if (endPage < totalPages) {\n            if (endPage < totalPages - 1) appendEllipsis(pageNumbers);\n            appendPageItem(pageNumbers, totalPages, meta.page);\n        }\n\n        document.getElementById(\"prevBtn\").className = `nav-link ${meta.page === 1 ? 'disabled' : ''}`;\n        document.getElementById(\"nextBtn\").className = `nav-link ${meta.page === totalPages || totalPages === 0 ? 'disabled' : ''}`;\n\n        document.getElementById(\"prevBtn\").onclick = () => { if (currentPage > 1) { currentPage--; renderPaginatedTable(); } };\n        document.getElementById(\"nextBtn\").onclick = () => { if (currentPage < totalPages) { currentPage++; renderPaginatedTable(); } };\n    }\n\n    function appendPageItem(container, pageNum, activePage) {\n        const p = document.createElement(\"div\");\n        p.className = `page-item ${pageNum === activePage ? 'active' : ''}`;\n        p.textContent = pageNum;\n        p.onclick = () => { currentPage = pageNum; renderPaginatedTable(); };\n        container.appendChild(p);\n    }\n\n    function appendEllipsis(container) {\n        const el = document.createElement(\"div\");\n        el.className = \"page-item\";\n        el.textContent = \"...\";\n        el.style.cursor = \"default\";\n        container.appendChild(el);\n    }\n\n    \/\/ Search Form Handler \u2014 reads single date input and stores in selectedDate\n    document.getElementById('search_form').addEventListener('submit', function(e) {\n        e.preventDefault();\n        selectedDate = document.getElementById('search_date').value;\n        fetchTotalHours(selectedDate);\n    });\n\n    \/\/ Initial Fetch - wait for portal to be ready\n    if (document.readyState === 'loading') {\n        document.addEventListener('DOMContentLoaded', () => fetchTotalHours());\n    } else {\n        fetchTotalHours();\n    }\n<\/script>\n<\/div><\/div>\n\n<\/div><\/div><\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>TOTAL HOURS ASSIGNED ASSIGNMENT Search ASSIGNED TO Total Assigned Hrs Total Submitted Hrs Pending Hrs ASSIGNED DATE Detail \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":"","_kad_post_title":"","_kad_post_layout":"fullwidth","_kad_post_sidebar_id":"","_kad_post_content_style":"default","_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-387","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/387","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=387"}],"version-history":[{"count":24,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/387\/revisions"}],"predecessor-version":[{"id":731,"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/pages\/387\/revisions\/731"}],"wp:attachment":[{"href":"https:\/\/ph-portal-v2.zyneventures.com\/index.php\/wp-json\/wp\/v2\/media?parent=387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}