@ -6,6 +6,7 @@
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Attendance Dashboard< / title >
< link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel = "stylesheet" >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js" crossorigin = "anonymous" > < / script >
< style >
.spinner-container {
display: flex;
@ -75,11 +76,23 @@
< h4 > Zone Summary< / h4 >
< div class = "zone-summary-row" >
< div class = "zone-summary-card" >
< h5 > Park< / h5 >
< div class = "d-flex justify-content-between align-items-center mb-2" >
< h5 > Park< / h5 >
< button class = "btn btn-sm btn-primary"
onclick="downloadExcelFromTable(document.getElementById('zoneSummaryPark'), 'park_summary.xlsx')">
< i class = "bi bi-download" > < / i > Download Excel
< / button >
< / div >
< div id = "zoneSummaryPark" > < / div >
< / div >
< div class = "zone-summary-card" >
< h5 > NULM< / h5 >
< div class = "d-flex justify-content-between align-items-center mb-2" >
< h5 > NULM< / h5 >
< button class = "btn btn-sm btn-primary"
onclick="downloadExcelFromTable(document.getElementById('zoneSummaryNulm'), 'nulm_summary.xlsx')">
< i class = "bi bi-download" > < / i > Download Excel
< / button >
< / div >
< div id = "zoneSummaryNulm" > < / div >
< / div >
< / div >
@ -155,7 +168,16 @@
if (!Array.isArray(data)) { target.innerText = JSON.stringify(data); return; }
let html = '< div class = "row" > ';
data.forEach(item => {
html += `< div class = "col-md-3" > < div class = "card p-2 mb-2" > < strong > ${item.emp_type}< / strong > < div > ${item.totalCount}< / div > < / div > < / div > `;
const facialRegistered = item.facialRegistered || 0;
const facialNotRegistered = item.facialNotRegistered || 0;
html += `< div class = "col-md-3" > < div class = "card p-3 mb-2" >
< strong > ${item.emp_type}< / strong >
< div class = "mt-2" >
< div class = "small" > Total: < strong > ${item.totalCount}< / strong > < / div >
< div class = "small" > Facial Registered: < strong > ${facialRegistered}< / strong > < / div >
< div class = "small" > Facial Not Registered: < strong > ${facialNotRegistered}< / strong > < / div >
< / div >
< / div > < / div > `;
});
html += '< / div > ';
target.innerHTML = html;
@ -166,27 +188,53 @@
return '< div class = "spinner-container" > < div class = "spinner-border" role = "status" > < span class = "visually-hidden" > Loading...< / span > < / div > < / div > ';
}
function downloadExcelFromTable(tableElement, fileName) {
if (typeof XLSX === 'undefined') {
alert('Excel library is not loaded. Please refresh the page and try again.');
console.error('XLSX library failed to load from CDN');
return;
}
if (!tableElement) {
alert('Table not found');
return;
}
const table = tableElement.querySelector('table');
if (!table) {
alert('No table to download');
return;
}
const workbook = XLSX.utils.table_to_book(table);
XLSX.writeFile(workbook, fileName);
}
// Expose to global scope for onclick handlers
window.downloadExcelFromTable = downloadExcelFromTable;
function renderTable(target, data, empType) {
if (!Array.isArray(data)) { target.innerText = JSON.stringify(data); return; }
if (data.length === 0) { target.innerHTML = '< div class = "alert alert-info" > No records< / div > '; return; }
const rows = data.slice().sort((a, b) => String(a.zone || '').localeCompare(String(b.zone || ''), undefined, { numeric: true }));
let html = '< table class = "table table-sm table-striped" > < thead > < tr > < th > Zone< / th > < th > Total< / th > < th > Present< / th > < th > OD< / th > < th > Leave< / th > < / tr > < / thead > < tbody > ';
let html = '< table class = "table table-sm table-striped" > < thead > < tr > < th > Zone< / th > < th class = "text-center" > Total< / th > < th class = "text-center" > Facial Registered< / th > < th class = "text-center" > Facial Not Registered< / th > < th class = "text-center" > Present< / th > < th class = "text-center" > OD< / th > < th class = "text-center" > Leave< / th > < / tr > < / thead > < tbody > ';
rows.forEach(r => {
const zone = r.zone;
const total = Number(r.TotalCount) || 0;
const facialRegistered = Number(r.facialRegistered) || 0;
const facialNotRegistered = Number(r.facialNotRegistered) || 0;
const present = Number(r.AttendanceMarked) || 0;
const od = Number(r.ODCount) || 0;
const leave = Number(r.LeaveCount) || 0;
html += `< tr > < td > ${zone || '-'}< / td > `;
html += `< td > ${total}< / td > `;
html += `< td > ` + (present > 0
html += `< td class = "text-center" > ${total}< / td > `;
html += `< td class = "text-center" > ${facialRegistered}< / td > `;
html += `< td class = "text-center" > ${facialNotRegistered}< / td > `;
html += `< td class = "text-center" > ` + (present > 0
? `< a href = "#" class = "zone-count" data-zone = "${zone}" data-emptype = "${empType}" data-category = "present" > ${present}< / a > `
: `< span class = "disabled-count" > ${present}< / span > `) + `< / td > `;
html += `< td > ` + (od > 0
html += `< td class = "text-center" > ` + (od > 0
? `< a href = "#" class = "zone-count" data-zone = "${zone}" data-emptype = "${empType}" data-category = "od" > ${od}< / a > `
: `< span class = "disabled-count" > ${od}< / span > `) + `< / td > `;
html += `< td > ` + (leave > 0
html += `< td class = "text-center" > ` + (leave > 0
? `< a href = "#" class = "zone-count" data-zone = "${zone}" data-emptype = "${empType}" data-category = "leave" > ${leave}< / a > `
: `< span class = "disabled-count" > ${leave}< / span > `) + `< / td > `;
html += '< / tr > ';
@ -278,20 +326,24 @@
if (data.length === 0) { modalBody.innerHTML = '< div class = "alert alert-info" > No park-wise records< / div > '; return; }
let html = '< div class = "table-responsive" > < table class = "table table-sm table-striped" > < thead > < tr > ';
html += '< th > Park Name< / th > < th > Total< / th > < th > Present< / th > < th > OD< / th > < th > Leave< / th > ';
html += '< th > Park Name< / th > < th class = "text-center" > Total< / th > < th class = "text-center" > Facial Registered< / th > < th class = "text-center" > Facial Not Registered< / th > < th class = "text-center" > Present< / th > < th class = "text-center" > OD< / th > < th class = "text-center" > Leave< / th > ';
html += '< / tr > < / thead > < tbody > ';
data.forEach(r => {
const park = r.park_name || '-';
const total = Number(r.TotalCount) || 0;
const facialRegistered = Number(r.facialRegistered) || 0;
const facialNotRegistered = Number(r.facialNotRegistered) || 0;
const present = Number(r.AttendanceMarked) || 0;
const od = Number(r.ODCount) || 0;
const leave = Number(r.LeaveCount) || 0;
html += '< tr > ';
html += `< td > ${park}< / td > `;
html += `< td > ${total}< / td > `;
html += `< td > ` + (present > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "present" > ${present}< / a > ` : `< span class = "disabled-count" > ${present}< / span > `) + `< / td > `;
html += `< td > ` + (od > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "od" > ${od}< / a > ` : `< span class = "disabled-count" > ${od}< / span > `) + `< / td > `;
html += `< td > ` + (leave > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "leave" > ${leave}< / a > ` : `< span class = "disabled-count" > ${leave}< / span > `) + `< / td > `;
html += `< td class = "text-center" > ${total}< / td > `;
html += `< td class = "text-center" > ${facialRegistered}< / td > `;
html += `< td class = "text-center" > ${facialNotRegistered}< / td > `;
html += `< td class = "text-center" > ` + (present > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "present" > ${present}< / a > ` : `< span class = "disabled-count" > ${present}< / span > `) + `< / td > `;
html += `< td class = "text-center" > ` + (od > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "od" > ${od}< / a > ` : `< span class = "disabled-count" > ${od}< / span > `) + `< / td > `;
html += `< td class = "text-center" > ` + (leave > 0 ? `< a href = "#" class = "park-employee-count" data-zone = "${zone}" data-park = "${park}" data-category = "leave" > ${leave}< / a > ` : `< span class = "disabled-count" > ${leave}< / span > `) + `< / td > `;
html += '< / tr > ';
});
html += '< / tbody > < / table > < / div > ';
@ -301,230 +353,231 @@
}
}
async function loadParkDetails(zone, type) {
const modalEl = document.getElementById('detailModal');
const modalBody = document.getElementById('detailModalBody');
const modalTitle = document.getElementById('detailModalLabel');
modalTitle.innerText = `Parks — Zone ${zone} — ${type}`;
modalBody.innerHTML = createLoader();
new bootstrap.Modal(modalEl).show();
try {
const url = `/commissionerreview/api/parks/getParkDataByZone?zone=${encodeURIComponent(zone)}&type=${encodeURIComponent(type)}`;
const res = await fetch(url);
if (!res.ok) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error ${res.status}< / div > `;
return;
}
const data = await res.json();
if (!Array.isArray(data)) { modalBody.innerText = JSON.stringify(data); return; }
if (data.length === 0) { modalBody.innerHTML = '< div class = "alert alert-info" > No records< / div > '; return; }
// Build table from keys of first row
const cols = Object.keys(data[0]);
let html = '< div class = "table-responsive" > < table class = "table table-sm table-bordered" > < thead > < tr > < th > S.No< / th > ';
cols.forEach(c => html += `< th > ${c.replace(/_/g, ' ')}< / th > `);
html += '< / tr > < / thead > < tbody > ';
data.forEach((row, idx) => {
html += '< tr > ';
html += `< td > ${idx + 1}< / td > `;
cols.forEach(c => html += `< td > ${row[c] != null ? row[c] : '-'}< / td > `);
html += '< / tr > ';
});
html += '< / tbody > < / table > < / div > ';
modalBody.innerHTML = html;
} catch (e) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error: ${e}< / div > `;
}
}
// loadDetailed may be called with explicit empType/category/zone (when clicking a zone count)
async function loadDetailed(empType, category, zoneVal, parkName) {
// show details in modal
const rawFrom = document.getElementById('fromDate').value;
const rawTo = document.getElementById('toDate').value;
const fromDate = toApiDdMmYyyy(rawFrom);
const toDate = toApiDdMmYyyy(rawTo);
const modalEl = document.getElementById('detailModal');
const modalBody = document.getElementById('detailModalBody');
const modalTitle = document.getElementById('detailModalLabel');
if (!empType || !category || !zoneVal) {
modalTitle.innerText = 'Details';
modalBody.innerHTML = '< div class = "text-warning" > Missing parameters. Click a count to view details.< / div > ';
async function loadParkDetails(zone, type) {
const modalEl = document.getElementById('detailModal');
const modalBody = document.getElementById('detailModalBody');
const modalTitle = document.getElementById('detailModalLabel');
modalTitle.innerText = `Parks — Zone ${zone} — ${type}`;
modalBody.innerHTML = createLoader();
new bootstrap.Modal(modalEl).show();
return;
}
try {
const url = `/commissionerreview/api/parks/getParkDataByZone?zone=${encodeURIComponent(zone)}&type=${encodeURIComponent(type)}`;
const res = await fetch(url);
if (!res.ok) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error ${res.status}< / div > `;
return;
}
const data = await res.json();
if (!Array.isArray(data)) { modalBody.innerText = JSON.stringify(data); return; }
if (data.length === 0) { modalBody.innerHTML = '< div class = "alert alert-info" > No records< / div > '; return; }
let titleSuffix = parkName ? ` — Park ${parkName}` : '';
modalTitle.innerText = `${empType} — Zone ${zoneVal}${titleSuffix} — ${category.toUpperCase()} (${rawFrom} → ${rawTo})`;
modalBody.innerHTML = createLoader();
// Show modal immediately with loader
new bootstrap.Modal(modalEl).show();
try {
let url = `${apiBase}/detailed?empType=${encodeURIComponent(empType)}&category=${encodeURIComponent(category)}&fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`;
url += `&zone=${encodeURIComponent(zoneVal)}`;
if (parkName) {
url += `&parkName=${encodeURIComponent(parkName)}`;
// Build table from keys of first row
const cols = Object.keys(data[0]);
let html = '< div class = "table-responsive" > < table class = "table table-sm table-bordered" > < thead > < tr > < th > S.No< / th > ';
cols.forEach(c => html += `< th > ${c.replace(/_/g, ' ')}< / th > `);
html += '< / tr > < / thead > < tbody > ';
data.forEach((row, idx) => {
html += '< tr > ';
html += `< td > ${idx + 1}< / td > `;
cols.forEach(c => html += `< td > ${row[c] != null ? row[c] : '-'}< / td > `);
html += '< / tr > ';
});
html += '< / tbody > < / table > < / div > ';
modalBody.innerHTML = html;
} catch (e) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error: ${e}< / div > `;
}
const res = await fetch(url);
if (!res.ok) {
const text = await res.text().catch(() => '');
modalBody.innerHTML = `< div class = "alert alert-danger" > Error ${res.status}: ${text}< / div > `;
}
// loadDetailed may be called with explicit empType/category/zone (when clicking a zone count)
async function loadDetailed(empType, category, zoneVal, parkName) {
// show details in modal
const rawFrom = document.getElementById('fromDate').value;
const rawTo = document.getElementById('toDate').value;
const fromDate = toApiDdMmYyyy(rawFrom);
const toDate = toApiDdMmYyyy(rawTo);
const modalEl = document.getElementById('detailModal');
const modalBody = document.getElementById('detailModalBody');
const modalTitle = document.getElementById('detailModalLabel');
if (!empType || !category || !zoneVal) {
modalTitle.innerText = 'Details';
modalBody.innerHTML = '< div class = "text-warning" > Missing parameters. Click a count to view details.< / div > ';
new bootstrap.Modal(modalEl).show();
return;
}
const data = await res.json();
if (!Array.isArray(data)) { modalBody.innerText = JSON.stringify(data); return; }
if (data.length === 0) { modalBody.innerHTML = '< div class = "alert alert-info" > No records< / div > '; return; }
// Define columns to display based on category
let displayColumns = [];
const catLower = (category || 'present').toLowerCase();
if (catLower === 'present') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('intime', 'inphoto', 'outtime', 'outphoto');
} else if (catLower === 'leave') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('leavedatetime', 'leaveby');
} else if (catLower === 'od') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('odtime', 'odby');
} else {
displayColumns = Object.keys(data[0]);
}
// Filter data to include only displayColumns, handle case-insensitive matching
const filteredData = data.map(row => {
const filtered = {};
displayColumns.forEach(col => {
// Try exact match first, then case-insensitive
const key = Object.keys(row).find(k => k === col || k.toLowerCase() === col.toLowerCase());
if (key) {
filtered[col] = row[key];
}
let titleSuffix = parkName ? ` — Park ${parkName}` : '';
modalTitle.innerText = `${empType} — Zone ${zoneVal}${titleSuffix} — ${category.toUpperCase()} (${rawFrom} → ${rawTo})`;
modalBody.innerHTML = createLoader();
// Show modal immediately with loader
new bootstrap.Modal(modalEl).show();
try {
let url = `${apiBase}/detailed?empType=${encodeURIComponent(empType)}&category=${encodeURIComponent(category)}&fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`;
url += `&zone=${encodeURIComponent(zoneVal)}`;
if (parkName) {
url += `&parkName=${encodeURIComponent(parkName)}`;
}
const res = await fetch(url);
if (!res.ok) {
const text = await res.text().catch(() => '');
modalBody.innerHTML = `< div class = "alert alert-danger" > Error ${res.status}: ${text}< / div > `;
return;
}
const data = await res.json();
if (!Array.isArray(data)) { modalBody.innerText = JSON.stringify(data); return; }
if (data.length === 0) { modalBody.innerHTML = '< div class = "alert alert-info" > No records< / div > '; return; }
// Define columns to display based on category
let displayColumns = [];
const catLower = (category || 'present').toLowerCase();
if (catLower === 'present') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('intime', 'inphoto', 'outtime', 'outphoto');
} else if (catLower === 'leave') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('leavedatetime', 'leaveby');
} else if (catLower === 'od') {
displayColumns = ['enrollment_id', 'name', 'designation', 'department'];
if (empType === 'Park') displayColumns.push('park_name');
displayColumns.push('odtime', 'odby');
} else {
displayColumns = Object.keys(data[0]);
}
// Filter data to include only displayColumns, handle case-insensitive matching
const filteredData = data.map(row => {
const filtered = {};
displayColumns.forEach(col => {
// Try exact match first, then case-insensitive
const key = Object.keys(row).find(k => k === col || k.toLowerCase() === col.toLowerCase());
if (key) {
filtered[col] = row[key];
}
});
return filtered;
});
return filtered;
});
function prettyName(key) {
const map = {
enrollment_id: 'Enrollment ID',
name: 'Name',
designation: 'Designation',
department: 'Department',
park_name: 'Park Name',
intime: 'In Time',
inphoto: 'In Photo',
outtime: 'Out Time',
outphoto: 'Out Photo',
leavedatetime: 'Leave DateTime',
leaveby: 'Leave By',
odtime: 'OD DateTime',
odby: 'OD By',
};
if (map[key]) return map[key];
return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}
function prettyName(key) {
const map = {
enrollment_id: 'Enrollment ID',
name: 'Name',
designation: 'Designation',
department: 'Department',
park_name: 'Park Name',
intime: 'In Time',
inphoto: 'In Photo',
outtime: 'Out Time',
outphoto: 'Out Photo',
leavedatetime: 'Leave DateTime',
leaveby: 'Leave By',
odtime: 'OD DateTime',
odby: 'OD By',
};
if (map[key]) return map[key];
return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}
let html = '< div class = "table-responsive" > < table class = "table table-sm table-bordered" > < thead > < tr > ';
// add serial number header
html += '< th > S.No< / th > ';
displayColumns.forEach(k => {
html += `< th > ${prettyName(k)}< / th > `;
});
html += '< / tr > < / thead > < tbody > ';
filteredData.forEach((row, idx) => {
html += '< tr > ';
// serial number cell
html += `< td > ${idx + 1}< / td > `;
let html = '< div class = "table-responsive" > < table class = "table table-sm table-bordered" > < thead > < tr > ';
// add serial number header
html += '< th > S.No< / th > ';
displayColumns.forEach(k => {
const val = row[k];
// Handle photo columns - show as thumbnail if it's an image
if ((k === 'inphoto' || k === 'outphoto') & & val) {
html += `< td > < img src = "${val}" alt = "photo" style = "max-width: 100px; max-height: 100px; cursor: pointer;" onclick = "this.style.maxWidth='500px'; this.style.maxHeight='500px';" > < / td > `;
} else {
html += `< td > ${val != null ? val : '-'}< / td > `;
}
html += `< th > ${prettyName(k)}< / th > `;
});
html += '< / tr > ';
});
html += '< / tbody > < / table > < / div > ';
modalBody.innerHTML = html;
} catch (e) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error: ${e}< / div > `;
html += '< / tr > < / thead > < tbody > ';
filteredData.forEach((row, idx) => {
html += '< tr > ';
// serial number cell
html += `< td > ${idx + 1}< / td > `;
displayColumns.forEach(k => {
const val = row[k];
// Handle photo columns - show as thumbnail if it's an image
if ((k === 'inphoto' || k === 'outphoto') & & val) {
html += `< td > < img src = "${val}" alt = "photo" style = "max-width: 100px; max-height: 100px; cursor: pointer;" onclick = "this.style.maxWidth='500px'; this.style.maxHeight='500px';" > < / td > `;
} else {
html += `< td > ${val != null ? val : '-'}< / td > `;
}
});
html += '< / tr > ';
});
html += '< / tbody > < / table > < / div > ';
modalBody.innerHTML = html;
} catch (e) {
modalBody.innerHTML = `< div class = "alert alert-danger" > Error: ${e}< / div > `;
}
}
}
document.addEventListener('DOMContentLoaded', () => {
setDefaults();
document.getElementById('loadSummaryBtn').addEventListener('click', () => {
const rawFrom = document.getElementById('fromDate').value;
const rawTo = document.getElementById('toDate').value;
if (rawFrom & & rawTo & & rawTo < rawFrom ) {
alert('To date must be the same as or later than From date');
return;
}
loadAbstractCounts(); loadZoneSummary();
});
// delegate clicks: zone links -> park-wise summary, zone counts -> detailed, park counts -> park-details
document.addEventListener('click', (ev) => {
const zoneLink = ev.target.closest & & ev.target.closest('.zone-link');
if (zoneLink) {
ev.preventDefault();
const zone = zoneLink.getAttribute('data-zone');
loadParkWiseSummary(zone);
return;
}
document.addEventListener('DOMContentLoaded', () => {
setDefaults();
document.getElementById('loadSummaryBtn').addEventListener('click', () => {
const rawFrom = document.getElementById('fromDate').value;
const rawTo = document.getElementById('toDate').value;
if (rawFrom & & rawTo & & rawTo < rawFrom ) {
alert('To date must be the same as or later than From date');
return;
}
loadAbstractCounts(); loadZoneSummary();
});
// delegate clicks: zone links -> park-wise summary, zone counts -> detailed, park counts -> park-details
document.addEventListener('click', (ev) => {
const zoneLink = ev.target.closest & & ev.target.closest('.zone-link');
if (zoneLink) {
ev.preventDefault();
const zone = zoneLink.getAttribute('data-zone');
loadParkWiseSummary(zone);
return;
}
const zoneAnchor = ev.target.closest & & ev.target.closest('.zone-count');
if (zoneAnchor) {
ev.preventDefault();
const zone = zoneAnchor.getAttribute('data-zone');
const empType = zoneAnchor.getAttribute('data-emptype');
const category = zoneAnchor.getAttribute('data-category');
// For Park: show park-wise summary; For NULM: show employee details directly
if (empType === 'Park') {
loadParkWiseSummary(zone, category);
} else {
loadDetailed(empType, category, zone);
const zoneAnchor = ev.target.closest & & ev.target.closest('.zone-count');
if (zoneAnchor) {
ev.preventDefault();
const zone = zoneAnchor.getAttribute('data-zone');
const empType = zoneAnchor.getAttribute('data-emptype');
const category = zoneAnchor.getAttribute('data-category');
// For Park: show park-wise summary; For NULM: show employee details directly
if (empType === 'Park') {
loadParkWiseSummary(zone, category);
} else {
loadDetailed(empType, category, zone);
}
return;
}
return;
}
const parkAnchor = ev.target.closest & & ev.target.closest('.park-count');
if (parkAnchor) {
ev.preventDefault();
const zone = parkAnchor.getAttribute('data-zone');
const type = parkAnchor.getAttribute('data-type');
loadParkDetails(zone, type);
return;
}
const parkAnchor = ev.target.closest & & ev.target.closest('.park-count');
if (parkAnchor) {
ev.preventDefault();
const zone = parkAnchor.getAttribute('data-zone');
const type = parkAnchor.getAttribute('data-type');
loadParkDetails(zone, type);
return;
}
const parkEmployeeAnchor = ev.target.closest & & ev.target.closest('.park-employee-count');
if (parkEmployeeAnchor) {
ev.preventDefault();
const zone = parkEmployeeAnchor.getAttribute('data-zone');
const park = parkEmployeeAnchor.getAttribute('data-park');
const category = parkEmployeeAnchor.getAttribute('data-category');
loadDetailed('Park', category, zone, park);
return;
}
const parkEmployeeAnchor = ev.target.closest & & ev.target.closest('.park-employee-count');
if (parkEmployeeAnchor) {
ev.preventDefault();
const zone = parkEmployeeAnchor.getAttribute('data-zone');
const park = parkEmployeeAnchor.getAttribute('data-park');
const category = parkEmployeeAnchor.getAttribute('data-category');
loadDetailed('Park', category, zone, park);
return;
}
});
// set date constraints and load initial
setDateLimits();
// load initial
loadAbstractCounts();
loadZoneSummary();
});
// set date constraints and load initial
setDateLimits();
// load initial
loadAbstractCounts();
loadZoneSummary();
});
}) ();
})();
< / script >
< script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" > < / script >
<!-- Park - wise Summary Modal -->
< div class = "modal fade" id = "parkWiseSummaryModal" tabindex = "-1" aria-labelledby = "parkWiseSummaryModalLabel" aria-hidden = "true" >
< div class = "modal fade" id = "parkWiseSummaryModal" tabindex = "-1" aria-labelledby = "parkWiseSummaryModalLabel"
aria-hidden="true">
< div class = "modal-dialog modal-lg modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
@ -535,6 +588,10 @@
Loading...
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-primary"
onclick="downloadExcelFromTable(document.getElementById('parkWiseSummaryModal'), 'park_wise_summary.xlsx')">
< i class = "bi bi-download" > < / i > Download Excel
< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Close< / button >
< / div >
< / div >
@ -553,6 +610,10 @@
Loading...
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-primary"
onclick="downloadExcelFromTable(document.getElementById('detailModal'), 'employee_details.xlsx')">
< i class = "bi bi-download" > < / i > Download Excel
< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Close< / button >
< / div >
< / div >