|
|
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org" lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GCC IT Cell Outsourced Manpower Details</title>
<!-- CSRF Tokens --> <meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" /> <meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/body-form-styles.css}"> <link rel="stylesheet" type="text/css" th:href="@{/assets/css/home-styles.css}">
<th:block th:replace="~{fragments/header :: header}"></th:block>
<style> .spreadsheet-container { background: #ffffff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); padding: 25px; margin: 20px auto; max-width: 100%; }
.sheet-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 2px solid #eef2f5; padding-bottom: 15px; }
.sheet-title { color: #0f5c78; font-weight: 800; font-size: 1.8rem; margin: 0; display: flex; align-items: center; gap: 12px; }
.sheet-date-badge { background: #eef7fa; color: #0f5c78; font-weight: 700; padding: 6px 14px; border-radius: 20px; font-size: 0.9rem; border: 1px solid #d4e7ee; }
.table-responsive { overflow-x: auto; border-radius: 8px; border: 1px solid #dcdfe3; }
.sheet-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; text-align: left; background: #fff; }
.sheet-table th { background-color: #0f5c78; color: #ffffff; font-weight: 700; padding: 12px 10px; border: 1px solid #0b4960; text-align: center; vertical-align: middle; text-transform: uppercase; font-size: 0.85rem; letter-spacing: 0.5px; }
.sheet-table td { padding: 8px 10px; border: 1px solid #e1e4e8; vertical-align: middle; color: #2c3e50; }
.sheet-table tbody tr:nth-child(even) { background-color: #f9fbfb; }
.sheet-table tbody tr:hover { background-color: #f1f6f8; }
.col-center { text-align: center; }
.col-right { text-align: right; }
/* Inputs */ .sheet-input-number { width: 75px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; text-align: center; font-weight: 600; color: #0f5c78; transition: border-color 0.2s, box-shadow 0.2s; }
.sheet-input-number:focus { border-color: #1e82a6; box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15); outline: none; }
.sheet-input-text { width: 90%; min-width: 150px; padding: 5px 10px; border: 1px solid #ccc; border-radius: 4px; font-weight: 500; transition: border-color 0.2s, box-shadow 0.2s; }
.sheet-input-text:focus { border-color: #1e82a6; box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15); outline: none; }
/* Totals Row */ .totals-row { background-color: #e8eff2 !important; font-weight: 800; border-top: 2px solid #0f5c78; }
.totals-row td { color: #0f5c78 !important; font-size: 0.98rem; border-top: 2px solid #0f5c78; }
/* Action Buttons */ .action-bar { display: flex; justify-content: flex-end; margin-top: 20px; gap: 15px; }
.save-btn { background-color: #10b981; color: white; border: none; padding: 12px 28px; font-size: 1rem; font-weight: 700; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 6px rgba(16, 185, 129, 0.2); transition: background-color 0.2s, transform 0.1s; }
.save-btn:hover { background-color: #059669; }
.save-btn:active { transform: scale(0.98); }
/* Remarks styling */ .vacant-highlight { color: #e74c3c; font-weight: 700; }
.no-vacant { color: #27ae60; font-weight: 700; }
/* Custom Dashboard Tabs */ .dashboard-tabs { display: flex; gap: 15px; margin-bottom: 25px; border-bottom: 2px solid #eef2f5; padding-bottom: 10px; }
.dashboard-tab { padding: 10px 22px; font-size: 1.05rem; font-weight: 700; color: #7f8c8d; background: none; border: none; border-radius: 8px 8px 0 0; cursor: pointer; position: relative; transition: all 0.3s ease; }
.dashboard-tab:hover { color: #0f5c78; background-color: rgba(15, 92, 120, 0.05); }
.dashboard-tab.active { color: #0f5c78; }
.dashboard-tab.active::after { content: ''; position: absolute; bottom: -12px; left: 0; width: 100%; height: 4px; background-color: #0f5c78; border-radius: 2px; }
/* Vendor Directory Styles */ .vendor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px; margin-bottom: 30px; }
.vendor-card { background: #ffffff; border: 1px solid #eef2f6; border-radius: 12px; padding: 22px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02); transition: all 0.3s ease; display: flex; flex-direction: column; justify-content: space-between; position: relative; overflow: hidden; }
.vendor-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; background-color: #0f5c78; opacity: 0.8; }
.vendor-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(15, 92, 120, 0.08); border-color: #d4e7ee; }
.vendor-card-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px; }
.vendor-card-title { font-size: 1.15rem; font-weight: 800; color: #2c3e50; margin: 0; text-transform: capitalize; line-height: 1.3; }
.vendor-card-desc { font-size: 0.9rem; color: #7f8c8d; line-height: 1.4; margin-bottom: 20px; flex-grow: 1; }
.vendor-card-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 15px; border-top: 1px dashed #eef2f6; }
.vendor-badge { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; padding: 4px 10px; border-radius: 12px; background-color: #eef7fa; color: #0f5c78; }
/* Employee Count Interactive Badge */ .emp-count-badge { background: linear-gradient(135deg, #0f5c78, #1e82a6); color: #ffffff; font-weight: 800; font-size: 1.05rem; padding: 8px 18px; border-radius: 30px; cursor: pointer; border: none; box-shadow: 0 4px 10px rgba(30, 130, 166, 0.25); transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; }
.emp-count-badge:hover { transform: scale(1.08); box-shadow: 0 6px 15px rgba(30, 130, 166, 0.4); background: linear-gradient(135deg, #1e82a6, #249cc8); color: #ffffff; }
.emp-count-badge:active { transform: scale(0.96); }
/* Modal styling overrides */ .modal-custom-header { background-color: #0f5c78; color: white; border-top-left-radius: 14px; border-top-right-radius: 14px; padding: 20px; display: flex; justify-content: space-between; align-items: center; }
.modal-custom-title { font-weight: 800; font-size: 1.35rem; display: flex; align-items: center; gap: 10px; margin: 0; }
.modal-search-box { position: relative; margin-bottom: 20px; flex-grow: 1; }
.modal-search-box input { padding: 10px 15px 10px 40px; border: 1px solid #dcdfe3; border-radius: 8px; width: 100%; font-size: 0.95rem; transition: all 0.3s ease; }
.modal-search-box input:focus { border-color: #0f5c78; box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15); outline: none; }
.modal-search-box i { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: #7f8c8d; }
.modal-table { font-size: 0.9rem; }
.modal-table th { background-color: #eef7fa !important; color: #0f5c78 !important; font-weight: 700; text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.5px; border-bottom: 2px solid #0f5c78 !important; }
.modal-table td { vertical-align: middle; }
.modal-body { padding: 25px; }
.export-btn { background-color: #27ae60; color: white; border: none; padding: 8px 16px; font-size: 0.9rem; font-weight: 700; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.2s ease; }
.export-btn:hover { background-color: #219653; color: white; } </style></head>
<body> <th:block th:replace="~{fragments/navbar :: navbar(curPage=${curPage}, isAdmin=${isAdmin})}"></th:block>
<div class="container" style="max-width: 95%;"> <div class="spreadsheet-container">
<!-- Dashboard Tabs --> <div class="dashboard-tabs"> <button class="dashboard-tab active" id="btn-vendor-tab" onclick="switchTab('vendor-tab')"> <i class="fa-solid fa-users-gear"></i> Vendor Directory & Counts </button> <button class="dashboard-tab" id="btn-manpower-tab" onclick="switchTab('manpower-tab')"> <i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics </button> </div>
<!-- Tab 1: Vendor Directory & Counts --> <div id="vendor-tab-content" class="tab-content"> <div class="sheet-header"> <h2 class="sheet-title"> <i class="fa-solid fa-cubes text-primary"></i> Vendor Directory & Employees </h2> <span class="sheet-date-badge">Overall Count Wise Summary</span> </div>
<div class="vendor-grid"> <div class="vendor-card" th:each="vendor : ${vendorDetails}"> <div class="vendor-card-header"> <h3 class="vendor-card-title" th:text="${vendor.vendor_name}">Vendor Name</h3> <span class="vendor-badge" th:text="'Order: ' + ${vendor.orderby}">Order</span> </div> <p class="vendor-card-desc" th:text="${vendor.description != null ? vendor.description : 'No description provided.'}"> Description</p> <div class="vendor-card-footer"> <span style="font-weight: 600; color: #7f8c8d; font-size: 0.9rem;"> Employees Count: </span> <button class="emp-count-badge" th:attr="onclick=|fetchAndShowEmployees('${vendor.vendor_name}')|"> <i class="fa-solid fa-users"></i> <span th:text="${vendor.actual_employee_count}">0</span> </button> </div> </div> </div> </div>
<!-- Tab 2: Manpower Statistics (Existing Spreadsheet) --> <div id="manpower-tab-content" class="tab-content" style="display: none;"> <div class="sheet-header"> <h2 class="sheet-title"> <i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics Report </h2> <span class="sheet-date-badge" id="current-date-badge">As on 21.05.2026</span> </div>
<div class="table-responsive"> <table class="sheet-table" id="manpower-sheet-table"> <thead> <tr> <th style="width: 50px;">S.No</th> <th>Description (Designation)</th> <th>Vendor Name</th> <th style="width: 120px;">Manpower Count<br>as per Tender</th> <th style="width: 120px;">Available<br>Manpower Count</th> <th>Working Location</th> <th style="width: 110px;">Registered<br>in App</th> <th>Vacant / Remarks</th> </tr> </thead> <tbody> <tr th:each="entry, stat : ${manpowerEntries}"> <td class="col-center" th:text="${stat.count}">1</td> <td th:text="${entry.description}" style="font-weight: 600;">Description</td> <td th:text="${entry.vendor_name}">Vendor Name</td>
<!-- Read-only Tender Count --> <td class="col-center tender-cell" th:text="${entry.tender_count}" style="font-weight: 700;">0</td>
<!-- Editable Available Count --> <td class="col-center"> <input type="number" min="0" class="sheet-input-number available-input" th:value="${entry.available_count}" th:data-id="${entry.id}" oninput="recalculateTotals()"> </td>
<td th:text="${entry.working_location}">Location</td> <td class="col-center" th:text="${entry.registered_count}">0</td>
<!-- Editable Remarks --> <td> <input type="text" class="sheet-input-text remarks-input" th:value="${entry.vacant_remarks}" th:data-id="${entry.id}" oninput="recalculateTotals()"> </td> </tr> </tbody> <tfoot> <tr class="totals-row"> <td colspan="3" style="text-align: right; padding-right: 20px;">Total No</td> <td class="col-center" id="total-tender">319</td> <td class="col-center" id="total-available">292</td> <td></td> <td></td> <td id="total-vacant" style="font-weight: 800; padding-left: 20px;">30</td> </tr> </tfoot> </table> </div>
<div class="action-bar"> <button class="save-btn" onclick="saveManpowerData()"> <i class="fa-solid fa-floppy-disk"></i> Save Changes </button> </div> </div>
</div> </div>
<!-- Employee Details Modal --> <div class="modal fade" id="employeeDetailsModal" tabindex="-1" aria-labelledby="employeeDetailsModalLabel" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-centered"> <div class="modal-content" style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);"> <div class="modal-custom-header"> <h5 class="modal-custom-title" id="employeeDetailsModalLabel"> <i class="fa-solid fa-users-viewfinder"></i> Employee Details - <span id="modal-vendor-name" style="text-decoration: underline;">Vendor</span> </h5> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <!-- Search and Export Toolbar --> <div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-3 mb-4"> <div class="modal-search-box flex-grow-1 m-0"> <i class="fa-solid fa-magnifying-glass"></i> <input type="text" id="employee-search-input" placeholder="Search employees by name, designation, location, code..." oninput="filterEmployees()"> </div> <div class="d-flex align-items-center gap-2 w-100 w-md-auto justify-content-md-end"> <!-- Work Order PDF Link --> <a id="modal-workorder-file-link" href="#" target="_blank" class="btn btn-danger text-nowrap align-items-center justify-content-center gap-2" style="height: 44px; border-radius: 8px; font-weight: 600; display: none; padding: 0 15px; background-color: #c0392b; border-color: #c0392b; color: white;"> <i class="fa-solid fa-file-pdf"></i> Work Order File </a>
<select id="modal-designation-filter" class="form-select form-select-sm" style="height: 44px; border-radius: 8px; border: 1px solid #cbd5e1; padding: 0 35px 0 15px; font-weight: 600; min-width: 200px;" onchange="filterEmployees()"> <option value="">- All Designations -</option> </select> <button class="export-btn text-nowrap" onclick="exportEmployeesToCSV()"> <i class="fa-solid fa-file-csv"></i> Export to CSV </button> </div> </div>
<!-- Table of Employees --> <div class="table-responsive" style="max-height: 450px; border: 1px solid #eef2f6;"> <table class="table table-hover modal-table" id="modal-employee-table"> <thead> <tr> <th style="width: 60px;" class="text-center">S.No</th> <th>Emp Code</th> <th>Name</th> <th>Email</th> <th>Mobile</th> <th>User Type</th> <th>Designation</th> <th class="text-center" style="width: 150px;">Last Updated On</th> <th>Location</th> <th>Vendor Name</th> </tr> </thead> <tbody id="modal-employee-tbody"> <!-- Dynamically populated --> </tbody> </table> <div id="no-employees-found" class="text-center text-muted py-5" style="display: none;"> <i class="fa-solid fa-user-slash fs-1 mb-3 text-secondary"></i> <p class="fw-bold mb-0">No matching employees found for this vendor.</p> </div> </div> </div> </div> </div> </div>
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<!-- Inline Spreadsheet Script --> <script th:inline="javascript"> const contextPath = /*[[@{/}]]*/ ''; const vendorDetailsData = /*[[${vendorDetails}]]*/[]; const allDesignations = /*[[${designations}]]*/[];
// Globals to store current modal state let currentVendorName = ""; let currentVendorEmployees = []; let filteredVendorEmployees = [];
// Handle switching between dynamic dashboard and spreadsheet function switchTab(tabId) { const tabs = document.querySelectorAll(".dashboard-tab"); const tabContents = document.querySelectorAll(".tab-content");
tabs.forEach(t => t.classList.remove("active")); tabContents.forEach(c => c.style.display = "none");
if (tabId === 'vendor-tab') { document.getElementById("btn-vendor-tab").classList.add("active"); document.getElementById("vendor-tab-content").style.display = "block"; } else if (tabId === 'manpower-tab') { document.getElementById("btn-manpower-tab").classList.add("active"); document.getElementById("manpower-tab-content").style.display = "block"; } }
// Fetch employee details from API dynamically and render in Modal function fetchAndShowEmployees(vendorName, designationFilter = null) { currentVendorName = vendorName;
let displayTitle = vendorName; document.getElementById("modal-vendor-name").textContent = displayTitle;
// Populate modal designation dropdown based on selected vendor ID const modalDesigFilter = document.getElementById("modal-designation-filter"); modalDesigFilter.innerHTML = '<option value="">- All Designations -</option>';
const vendor = vendorDetailsData.find(v => v.vendor_name === vendorName); if (vendor && vendor.designationDetails) { vendor.designationDetails.forEach(des => { const opt = document.createElement("option"); opt.value = des.designation; let displayText = des.designation; const fileNo = des.File_no || des.file_no; if (fileNo && fileNo !== '-' && fileNo !== '') { displayText += " - " + fileNo; } opt.textContent = displayText; modalDesigFilter.appendChild(opt); }); }
if (designationFilter) { modalDesigFilter.value = designationFilter; } else { modalDesigFilter.value = ""; }
// Show Loading state inside tbody const tbody = document.getElementById("modal-employee-tbody"); tbody.innerHTML = ` <tr> <td colspan="10" class="text-center py-5"> <div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"> <span class="visually-hidden">Loading...</span> </div> <p class="mt-3 text-secondary fw-semibold">Fetching employee records from database...</p> </td> </tr> `; document.getElementById("no-employees-found").style.display = "none"; document.getElementById("employee-search-input").value = "";
// Show Modal using Bootstrap const modalElement = document.getElementById("employeeDetailsModal"); const modal = bootstrap.Modal.getOrCreateInstance(modalElement); modal.show();
// Fetch from REST API fetch(contextPath + 'api/vendor-employees?vendorName=' + encodeURIComponent(vendorName)) .then(res => res.json()) .then(data => { currentVendorEmployees = data; filterEmployees(); }) .catch(err => { console.error("Error fetching employees:", err); tbody.innerHTML = ` <tr> <td colspan="10" class="text-center py-5 text-danger fw-bold"> <i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i> <p>Failed to connect to server and fetch database records. Please try again.</p> </td> </tr> `; }); }
// Render the employee list inside the modal table function renderEmployeeTable(employees) { const tbody = document.getElementById("modal-employee-tbody"); const noEmployeesEl = document.getElementById("no-employees-found"); tbody.innerHTML = "";
if (employees.length === 0) { noEmployeesEl.style.display = "block"; return; }
noEmployeesEl.style.display = "none";
employees.forEach((emp, index) => { const tr = document.createElement("tr"); tr.innerHTML = ` <td class="text-center fw-bold text-secondary">${index + 1}</td> <td class="fw-semibold text-primary">${escapeHtml(emp.emp_code || '-')}</td> <td class="fw-bold text-dark">${escapeHtml(emp.name || '-')}</td> <td>${escapeHtml(emp.email || '-')}</td> <td>${escapeHtml(emp.mobile || '-')}</td> <td><span class="badge bg-secondary">${escapeHtml(emp.user_type || '-')}</span></td> <td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td> <td class="text-center fw-semibold text-primary">${escapeHtml(formatDateTime(emp.updated_at || emp.cdate))}</td> <td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td> <td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td> `; tbody.appendChild(tr); }); }
// Instant fuzzy client-side search inside the modal function filterEmployees() { const query = document.getElementById("employee-search-input").value.toLowerCase().trim(); const selectedDesignation = document.getElementById("modal-designation-filter").value.toLowerCase().trim();
let list = currentVendorEmployees;
// Apply designation filter if (selectedDesignation !== "") { list = list.filter(emp => emp.designation && emp.designation.trim().toLowerCase() === selectedDesignation); }
if (query === "") { filteredVendorEmployees = list; } else { filteredVendorEmployees = list.filter(emp => { return (emp.name && emp.name.toLowerCase().includes(query)) || (emp.emp_code && emp.emp_code.toLowerCase().includes(query)) || (emp.designation && emp.designation.toLowerCase().includes(query)) || (emp.location && emp.location.toLowerCase().includes(query)) || (emp.email && emp.email.toLowerCase().includes(query)) || (emp.mobile && emp.mobile.toLowerCase().includes(query)) || (emp.vendor_name && emp.vendor_name.toLowerCase().includes(query)) || (emp.user_type && emp.user_type.toLowerCase().includes(query)); }); }
renderEmployeeTable(filteredVendorEmployees); updateWorkOrderFileLink(); }
function updateWorkOrderFileLink() { const selectEl = document.getElementById("modal-designation-filter"); const fileLink = document.getElementById("modal-workorder-file-link"); if (!selectEl || !fileLink) return;
const selectedVal = selectEl.value.trim().toLowerCase(); if (selectedVal === "") { fileLink.classList.add("d-none"); fileLink.href = "#"; return; }
let filePath = null;
if (currentVendorName === "All Active Employees") { // Find matching designation in allDesignations const matchedDes = allDesignations.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal); filePath = matchedDes ? matchedDes.file_path : null; } else { // Find matching designation details for current vendor const vendor = vendorDetailsData.find(v => v.vendor_name === currentVendorName); if (vendor && vendor.designationDetails) { const matchedDes = vendor.designationDetails.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal); filePath = matchedDes ? matchedDes.file_path : null; } }
if (filePath) { filePath = filePath.trim(); } if (filePath && filePath !== '-' && filePath !== '') { if (!filePath.toLowerCase().endsWith('.pdf')) { filePath += '.pdf'; } const normalizedPath = filePath.startsWith('/') ? filePath.substring(1) : filePath; fileLink.href = contextPath + normalizedPath; fileLink.classList.remove("d-none"); fileLink.classList.add("d-inline-flex"); return; }
fileLink.classList.add("d-none"); fileLink.href = "#"; }
// Premium CSV Exporter function exportEmployeesToCSV() { if (filteredVendorEmployees.length === 0) { alert("No employee records to export!"); return; }
let csvContent = "data:text/csv;charset=utf-8,"; csvContent += "S.No,Employee Code,Name,Email,Mobile,User Type,Designation,Location,Vendor Name\n";
filteredVendorEmployees.forEach((emp, index) => { const row = [ index + 1, `"${(emp.emp_code || '-').replace(/"/g, '""')}"`, `"${(emp.name || '-').replace(/"/g, '""')}"`, `"${(emp.email || '-').replace(/"/g, '""')}"`, `"${(emp.mobile || '-').replace(/"/g, '""')}"`, `"${(emp.user_type || '-').replace(/"/g, '""')}"`, `"${(emp.designation || '-').replace(/"/g, '""')}"`, `"${(emp.location || '-').replace(/"/g, '""')}"`, `"${(emp.vendor_name || '-').replace(/"/g, '""')}"` ]; csvContent += row.join(",") + "\n"; });
const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri);
// Filename format: [vendor_name]_employees.csv const sanitizedVendorName = currentVendorName.toLowerCase().replace(/[^a-z0-9]/g, "_"); link.setAttribute("download", `${sanitizedVendorName}_employees.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }
// Helper function to safely escape HTML to prevent XSS function escapeHtml(str) { if (!str) return ''; return String(str) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }
// Helper function to format timestamp to dd/mm/yyyy HH:MM:ss function formatDateTime(dateTimeStr) { if (!dateTimeStr || dateTimeStr === '-' || dateTimeStr.trim() === '') return '-'; dateTimeStr = dateTimeStr.trim(); let dateObj = new Date(dateTimeStr.replace('T', ' ')); if (isNaN(dateObj.getTime())) { dateObj = new Date(dateTimeStr); } if (isNaN(dateObj.getTime())) return dateTimeStr; const day = String(dateObj.getDate()).padStart(2, '0'); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const year = dateObj.getFullYear(); const hours = String(dateObj.getHours()).padStart(2, '0'); const minutes = String(dateObj.getMinutes()).padStart(2, '0'); const seconds = String(dateObj.getSeconds()).padStart(2, '0'); return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`; }
// Dynamic calculation when page loads document.addEventListener("DOMContentLoaded", () => { // Update the date badge dynamically to current date format const d = new Date(); const day = String(d.getDate()).padStart(2, '0'); const month = String(d.getMonth() + 1).padStart(2, '0'); const year = d.getFullYear(); document.getElementById("current-date-badge").textContent = `As on ${day}.${month}.${year}`;
recalculateTotals(); });
// Recalculates Tender, Available, and Vacant counts dynamically in the footer function recalculateTotals() { const table = document.getElementById("manpower-sheet-table"); const rows = table.querySelectorAll("tbody tr");
let tenderSum = 0; let availableSum = 0; let vacantSum = 0;
rows.forEach(row => { // Tender sum const tenderVal = parseInt(row.querySelector(".tender-cell").textContent) || 0; tenderSum += tenderVal;
// Available sum const availableInput = row.querySelector(".available-input"); const availableVal = parseInt(availableInput.value) || 0; availableSum += availableVal;
// Vacant Sum parsing: Extract leading digit or number from vacant input const remarksInput = row.querySelector(".remarks-input"); const remarksVal = remarksInput.value.trim();
// Extract the first number found in the remarks input const numMatch = remarksVal.match(/^-?\d+/); if (numMatch) { vacantSum += parseInt(numMatch[0]); } });
document.getElementById("total-tender").textContent = tenderSum; document.getElementById("total-available").textContent = availableSum; document.getElementById("total-vacant").textContent = vacantSum; }
// Save dynamic spreadsheet updates back to database function saveManpowerData() { const table = document.getElementById("manpower-sheet-table"); const rows = table.querySelectorAll("tbody tr"); const payload = [];
rows.forEach(row => { const availableInput = row.querySelector(".available-input"); const remarksInput = row.querySelector(".remarks-input");
payload.push({ id: parseInt(availableInput.dataset.id), availableCount: parseInt(availableInput.value) || 0, vacantRemarks: remarksInput.value }); });
// CSRF tokens const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content"); const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
const headers = { 'Content-Type': 'application/json' };
if (csrfToken && csrfHeader) { headers[csrfHeader] = csrfToken; }
// Save to API fetch(contextPath + 'api/save-manpower-stats', { method: 'POST', headers: headers, body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => { if (data.success) { alert("Changes saved successfully to MySQL database!"); window.location.reload(); } else { alert("Failed to save changes: " + (data.error || "Unknown error")); } }) .catch(err => { console.error("Error saving data:", err); alert("Something went wrong. Failed to communicate with server."); }); } </script></body>
</html>
|