You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1035 lines
45 KiB

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/modules/base/head :: head}">
<style>
</style>
</head>
<body>
<div class="wrapper">
<!-- header top menu Start -->
<header th:replace="~{fragments/modules/base/header :: header}"></header>
<!-- header top menu end -->
<div class="page-wrap">
<!--=============== Left Menu side Start ================-->
<div th:replace="~{fragments/modules/base/menu :: menu}"></div>
<!--=============== Left side End ================-->
<div class="main-content">
<div class="container-fluid">
<div class="page-header mb-2">
<div class="row align-items-end">
<div class="col-lg-7">
<div class="page-header-title">
<!-- <i class="far fa-calendar-plus bg-blue"></i> -->
<div class="d-inline">
<h5 >Hall Absents</h5>
<span>Reports for Attendance</span>
</div>
</div>
</div>
<div class="col-lg-5">
<span id="constituencySpan"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
Filters
</div>
<div class="card-body">
<span th:text="${mapped_ac}" style="display:none;" id="mapped_ac"></span>
<div class="row">
<div class="col-sm-4">
<label for="attendance">Select Training</label>
<select id="attendance" class="form-control">
<option value="">Select</option>
<option value="1" >First Training Attendance</option>
<option value="2">Second Training Attendance</option>
<option value="3" >Second Repeat Training Attendance</option>
<option value="4" selected="selected">Third Training Attendance</option>
</select>
</div>
<div class="col-sm-4">
<label for="att_type">Select Report Type</label>
<select id="att_type" class="form-control">
<option value="">Select</option>
<option value="abstract">Abstract</option>
<option value="summary">Summary</option>
</select>
</div>
<!-- <div class="col-sm-4">
<label for="tr_center">Select Training center</label>
<select id="tr_center" class="form-control">
<option value="">Select</option>
</select>
</div> -->
</div>
<div class="row mt-4">
<div class="col-12 d-flex justify-content-center">
<button type="submit" id="searchBtn"
class="btn btn-primary mx-2">Search</button>
<button class="btn btn-danger mx-2" id="resetBtn"
name="reset" type="reset">Reset</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card" id="reportDiv" style="display: none;">
<div class="card-body">
<div class="table-responsive" id="summaryDiv" style="display: none;">
<table id="summaryreportTable"
class="table table-striped table-bordered table-hover dataTable"
role="grid">
<thead class="thead-dark">
<!-- Main Header Row -->
<tr role="row">
<th rowspan="2" class="text-center">HALL NO</th>
<th colspan="5" class="text-center">CATEGORY</th>
<th rowspan="2" class="text-center">Total</th>
</tr>
<!-- Sub Header 1 -->
<tr>
<th class="text-center">PO</th>
<th class="text-center">PO1</th>
<th class="text-center">PO2</th>
<th class="text-center">PO3</th>
<th class="text-center">PO4</th>
</tr>
</thead>
<tbody>
<!-- Rows will be dynamically inserted via JS/DataTable -->
</tbody>
</table>
</div>
<div class="table-responsive" id="abstractDiv" style="display: none;">
<table id="abstractreportTable"
class="table table-striped table-bordered table-hover dataTable"
role="grid">
<thead class="thead-dark">
<!-- Main Header Row -->
<tr role="row">
<th colspan="5" class="text-center">PO</th>
<th colspan="5" class="text-center">PO1</th>
<th colspan="5" class="text-center">PO2</th>
<th colspan="5" class="text-center">PO3</th>
<th colspan="5" class="text-center">PO4</th>
<th colspan="5" class="text-center">TOTAL</th>
</tr>
<!-- Sub Header 1 -->
<tr>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
<th class="text-center">TOTAL</th>
<th class="text-center">ABSENT</th>
<th class="text-center">PRESENT</th>
<th class="text-center">% of ABSENT</th>
<th class="text-center">% of PRESENT</th>
</tr>
</thead>
<tbody>
<!-- Rows will be dynamically inserted via JS/DataTable -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="pageLoader" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 9999; text-align: center;">
<div style="position: relative; top: 50%; transform: translateY(-50%);">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>
<!-- Add the 'Please wait' text below the spinner -->
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p>
</div>
</div>
<div th:replace="~{fragments/modules/base/app_module_menu_box :: div}"></div>
<footer th:replace="~{fragments/modules/base/footer :: footer}"></footer>
</div>
</div>
<script th:replace="~{fragments/modules/base/script :: script}"></script>
<script type="text/javascript">
let mapped_ac = document.getElementById('mapped_ac').textContent;
let spanValue = "";
document.getElementById('resetBtn').addEventListener('click', function () {
event.preventDefault();
location.reload();
});
$(document).ready(function () {
$.ajax({
url: "/election/api/dataentryadmin/attendance/trainingconstituencies",
method: "GET",
data:{c_code:mapped_ac},
success: function (data) {
console.log("Response:", data);
let spanText = "";
if (data && data.length > 0) {
$.each(data, function (index, item) {
spanText += item.display_name;
// if multiple records → add comma or line break
if (index !== data.length - 1) {
spanText += ", ";
// OR use "<br>" for new line
}
});
} else {
spanText = "No Constituency Found";
}
$('#constituencySpan').html(spanText);
spanValue = $('#constituencySpan').text();
console.log("Global spanValue:", spanValue);
},
error: function () {
alert("Failed to fetch allocated training constituency");
}
});
$('#summaryDiv').hide();
$('#abstractDiv').hide();
$('#reportDiv').hide();
});
document.getElementById('searchBtn').addEventListener('click', function (e) {
let attendance=$('#attendance').val();
let att_type=$('#att_type').val();
let tr_center=mapped_ac;
let trainingTitle = "";
if (attendance === "1") {
trainingTitle = "1st DAY REVISED POLLING PERSONAL TRAINING ATTENDANCE - 28.03.2026";
} else if (attendance === "2") {
trainingTitle = "2nd DAY POLLING PERSONAL TRAINING ATTENDANCE - 11.04.2026";
} else if (attendance === "3") {
trainingTitle = "2nd REPEAT POLLING PERSONAL TRAINING ATTENDANCE - 19.04.2026";
}
else if (attendance === "4") {
trainingTitle = "3rd DAY POLLING PERSONAL TRAINING ATTENDANCE - 22.04.2026";
}
if(!attendance){
Swal.fire({
icon: 'warning',
title: 'Training Required',
text: 'Please Select Training'
});
return;
}
if(!att_type){
Swal.fire({
icon: 'warning',
title: 'Attendance Type Required',
text: 'Please Select Type'
});
return;
}
/* console.log("attendance="+attendance);
console.log("att_type="+att_type);
console.log("tr_center="+tr_center);
console.log("Using global spanValue:", spanValue); */
$('#pageLoader').show();
// Step 1: Check data first
$.ajax({
url: '/election/api/dataentryadmin/attendance/getAbsentees',
type: 'GET',
data: { attendance: attendance,att_type:att_type,tr_center:tr_center },
success: function (res) {
$('#pageLoader').hide();
console.log(res.data);
if (res.status==="Success") {
$('#reportDiv').show();
if(res.report==="abstract"){
$('#summaryDiv').hide();
$('#abstractDiv').show();
let table = $('#abstractreportTable').DataTable({
destroy: true,
searching: true,
autoWidth: true,
paging: true,
info: true,
data: res.data,
responsive: false,
scrollX: true,
columns: [
{ data: 'PO_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'PO_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO_PRESENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'PO_PRESENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'PO1_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'PO1_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO1_PRESENT', defaultContent: '-', className: 'text-center' },
{data: 'PO1_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{data: 'PO1_PRESENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'PO2_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'PO2_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO2_PRESENT', defaultContent: '-', className: 'text-center' },
{data: 'PO2_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{data: 'PO2_PRESENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'PO3_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'PO3_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO3_PRESENT', defaultContent: '-', className: 'text-center' },
{data: 'PO3_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{data: 'PO3_PRESENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'PO4_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'PO4_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'PO4_PRESENT', defaultContent: '-', className: 'text-center' },
{data: 'PO4_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{data: 'PO4_PRESENT_PER', defaultContent: '-', className: 'text-center' },
{ data: 'TOTAL_TOTAL', defaultContent: '-', className: 'text-center' },
{ data: 'TOTAL_ABSENT', defaultContent: '-', className: 'text-center' },
{ data: 'TOTAL_PRESENT', defaultContent: '-', className: 'text-center' },
{data: 'TOTAL_ABSENT_PER', defaultContent: '-', className: 'text-center' },
{data: 'TOTAL_PRESENT_PER', defaultContent: '-', className: 'text-center' }
],
dom: 'Bfrtip',
buttons: [
{
extend: 'pageLength',
className: 'btn btn-warning'
},
{
title: 'Data export',
extend: 'copyHtml5',
text: 'Copy',
className: 'btn btn-secondary',
id: 'copyBtn'
},
{
extend: 'excelHtml5',
text: 'Excel',
title: null,
className: 'btn btn-secondary',
id: 'excelBtn',
filename: function () {
return tr_center+'_AttendanceAbsReport_' + getTimestamp();
},
customize: function (xlsx) {
var sheet = xlsx.xl.worksheets['sheet1.xml'];
var sheetData = $('sheetData', sheet);
let tr_center_text = spanValue;
// 🔹 1. SHIFT EXISTING ROWS DOWN BY 3
$('row', sheetData).each(function () {
var r = parseInt($(this).attr('r'));
$(this).attr('r', r + 4);
$(this).find('c').each(function () {
var cellRef = $(this).attr('r');
var col = cellRef.replace(/[0-9]/g, '');
var row = parseInt(cellRef.replace(/[A-Z]/g, ''));
$(this).attr('r', col + (row + 4));
});
});
// 🔹 2. ADD HEADER ROWS
sheetData.prepend(`
<row r="1">
<c t="inlineStr" r="A1"><is><t>${tr_center_text}</t></is></c>
</row>
<row r="2">
<c t="inlineStr" r="A2"><is><t>${trainingTitle}</t></is></c>
</row>
<row r="3">
<c t="inlineStr" r="A3"><is><t>ABSTRACT</t></is></c>
</row>
`);
sheetData.prepend(`
<row r="4">
<c t="inlineStr" r="A4"><is><t>PO</t></is></c>
<c t="inlineStr" r="F4"><is><t>PO1</t></is></c>
<c t="inlineStr" r="K4"><is><t>PO2</t></is></c>
<c t="inlineStr" r="P4"><is><t>PO3</t></is></c>
<c t="inlineStr" r="U4"><is><t>PO4</t></is></c>
<c t="inlineStr" r="Z4"><is><t>TOTAL</t></is></c>
</row>
`);
// 🔹 3. MERGE CELLS (FULL WIDTH)
var mergeCells = $('mergeCells', sheet);
if (mergeCells.length === 0) {
mergeCells = $('<mergeCells count="0"/>').appendTo(sheet);
}
mergeCells.append(`
<mergeCell ref="A1:AD1"/>
<mergeCell ref="A2:AD2"/>
<mergeCell ref="A3:AD3"/>
<mergeCell ref="A4:E4"/>
<mergeCell ref="F4:J4"/>
<mergeCell ref="K4:O4"/>
<mergeCell ref="P4:T4"/>
<mergeCell ref="U4:Y4"/>
<mergeCell ref="Z4:AD4"/>
`);
// 🔹 4. UPDATE MERGE COUNT
mergeCells.attr('count', mergeCells.find('mergeCell').length);
// 5. CENTER ALIGN TITLE ROWS (ADD HERE)
var styles = xlsx.xl['styles.xml'];
$('cellXfs', styles).append(
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">' +
'<alignment horizontal="center"/>' +
'</xf>'
);
var styleIndex = $('cellXfs xf', styles).length - 1;
// Apply style to title rows
$('c[r="A1"]', sheet).attr('s', styleIndex);
$('c[r="A2"]', sheet).attr('s', styleIndex);
$('c[r="A3"]', sheet).attr('s', styleIndex);
['A4','F4','K4','P4','U4','Z4'].forEach(function(cell){
$('c[r="' + cell + '"]', sheet).attr('s', styleIndex);
});
}
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'btn btn-secondary',
id: 'csvBtn',
filename: function () {
return tr_center+'_AttendanceAbsReport_' + getTimestamp();
}
},
{
extend: 'pdfHtml5',
text: 'PDF',
title: null,
className: 'btn btn-secondary',
id: 'pdfBtn',
filename: function () {
return tr_center+'_AttendanceAbsReport_' + getTimestamp();
},
orientation: 'landscape',
pageSize: 'A3',
customize: function (doc) {
let tr_center_text = spanValue;
// 🔹 1. ADD HEADINGS
doc.content.unshift(
{
text: tr_center_text,
alignment: 'center',
bold: true,
fontSize: 14,
margin: [0, 0, 0, 5]
},
{
text: trainingTitle,
alignment: 'center',
bold: true,
fontSize: 11,
margin: [0, 0, 0, 5]
},
{
text: 'ABSTRACT',
alignment: 'center',
bold: true,
fontSize: 12,
margin: [0, 0, 0, 10]
}
);
// 🔹 2. PAGE SETTINGS
doc.pageOrientation = 'landscape';
doc.pageMargins = [10, 10, 10, 10];
doc.defaultStyle.fontSize = 8;
doc.styles.tableHeader.fontSize = 8;
doc.styles.tableHeader.alignment = 'center';
doc.styles.tableHeader.bold = true;
doc.styles.tableHeader.fillColor = null;
doc.styles.tableHeader.color = '#000';
doc.styles.tableBodyEven.fillColor = null;
doc.styles.tableBodyOdd.fillColor = null;
// 🔹 3. FIND TABLE (IMPORTANT)
var tableNode;
doc.content.forEach(function (item) {
if (item.table) {
tableNode = item;
}
});
if (!tableNode) return; // safety
let tableBody = tableNode.table.body;
// 🔹 4. ADD GROUP HEADER ROW
tableBody.unshift([
{ text: 'PO', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
{ text: 'PO1', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
{ text: 'PO2', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
{ text: 'PO3', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
{ text: 'PO4', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
{ text: 'TOTAL', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {}
]);
// 🔹 5. HEADER ROW COUNT
tableNode.table.headerRows = 2;
// 🔹 6. FULL WIDTH FIX
tableNode.table.widths = Array(30).fill(35);
// 🔹 7. TABLE STYLE
tableNode.layout = {
hLineWidth: () => 0.3,
vLineWidth: () => 0.3,
hLineColor: () => '#000',
vLineColor: () => '#000',
paddingLeft: () => 2,
paddingRight: () => 2,
paddingTop: () => 2,
paddingBottom: () => 2
};
// CENTER ALL CELLS
tableNode.table.body = tableNode.table.body.map(function (row) {
return row.map(function (cell) {
// 🔹 Case 1: valid object with text
if (typeof cell === 'object' && cell !== null && ('text' in cell || 'colSpan' in cell)) {
return {
...cell,
fillColor: null,
alignment: 'center'
};
}
// 🔹 Case 2: empty placeholder {} → KEEP AS IS
if (typeof cell === 'object' && cell !== null) {
return cell;
}
// 🔹 Case 3: normal value
return {
text: cell !== null ? cell.toString() : '',
fillColor: null,
alignment: 'center'
};
});
});
}
}
],
columnDefs: [
{ targets: 'no-sort', orderable: false }
],
lengthMenu: [
[ 100, 200, 500, -1],
['100 rows', '200 rows', 'Show all']
],
scrollY: '40vh',
searching: true,
});
}
else{
$('#abstractDiv').hide();
$('#summaryDiv').show();
let table = $('#summaryreportTable').DataTable({
destroy: true,
searching: true,
autoWidth: false,
paging: true,
info: true,
ordering: false,
data: res.data.sort((a, b) => a.sort_order - b.sort_order),
responsive: false,
scrollX: true,
columns: [
{ data: 'hall_no', defaultContent: '', className: 'text-center' },
{ data: 'PO', defaultContent: '', className: 'text-center' },
{ data: 'PO1', defaultContent: '', className: 'text-center' },
{ data: 'PO2', defaultContent: '', className: 'text-center' },
{ data: 'PO3', defaultContent: '', className: 'text-center' },
{ data: 'PO4', defaultContent: '', className: 'text-center' },
{ data: 'Total', defaultContent: '', className: 'text-center' }
],
rowCallback: function (row, data) {
if (data.hall_no === 'Grand Total') {
$(row).css({
'font-weight': 'bold',
'background-color': '#f2f2f2'
});
}
},
dom: 'Bfrtip',
buttons: [
{
extend: 'pageLength',
className: 'btn btn-warning'
},
{
title: 'Data export',
extend: 'copyHtml5',
text: 'Copy',
className: 'btn btn-secondary',
id: 'copyBtn'
},
{
extend: 'excelHtml5',
text: 'Excel',
title: null,
className: 'btn btn-secondary',
id: 'excelBtn',
filename: function () {
return tr_center+'_AttendanceSumReport_' + getTimestamp();
},
customize: function (xlsx) {
var sheet = xlsx.xl.worksheets['sheet1.xml'];
var sheetData = $('sheetData', sheet);
let tr_center_text = spanValue;
// 🔹 1. SHIFT EXISTING ROWS DOWN BY 2 (IMPORTANT CHANGE)
$('row', sheetData).each(function () {
var r = parseInt($(this).attr('r'));
$(this).attr('r', r + 2);
$(this).find('c').each(function () {
var cellRef = $(this).attr('r');
var col = cellRef.replace(/[0-9]/g, '');
var row = parseInt(cellRef.replace(/[A-Z]/g, ''));
$(this).attr('r', col + (row + 2));
});
});
// 🔹 2. ADD ONLY 2 HEADER ROWS
sheetData.prepend(`
<row r="1">
<c t="inlineStr" r="A1"><is><t>${tr_center_text}</t></is></c>
</row>
<row r="2">
<c t="inlineStr" r="A2"><is><t>HALLWISE ABSENTEES LIST</t></is></c>
</row>
`);
// 🔹 3. MERGE CELLS (ONLY 2 ROWS)
var mergeCells = $('mergeCells', sheet);
if (mergeCells.length === 0) {
mergeCells = $('<mergeCells count="0"/>').appendTo(sheet);
}
mergeCells.append(`
<mergeCell ref="A1:AD1"/>
<mergeCell ref="A2:AD2"/>
`);
mergeCells.attr('count', mergeCells.find('mergeCell').length);
// 🔹 4. CENTER ALIGN
var styles = xlsx.xl['styles.xml'];
$('cellXfs', styles).append(
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">' +
'<alignment horizontal="center"/>' +
'</xf>'
);
var styleIndex = $('cellXfs xf', styles).length - 1;
$('c[r="A1"]', sheet).attr('s', styleIndex);
$('c[r="A2"]', sheet).attr('s', styleIndex);
}
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'btn btn-secondary',
id: 'csvBtn',
filename: function () {
return tr_center+'_AttendanceSumReport_' + getTimestamp();
}
},
{
extend: 'pdfHtml5',
text: 'PDF',
title: null,
className: 'btn btn-secondary',
id: 'pdfBtn',
filename: function () {
return tr_center+'_AttendanceSumReport_' + getTimestamp();
},
orientation: 'portrait',
pageSize: 'A3',
customize: function (doc) {
doc.pageOrientation = 'portrait';
doc.pageSize = 'A4';
doc.pageMargins = [20, 20, 20, 20];
let tr_center_text = spanValue;
// Remove default title
if (doc.content[0].text) {
doc.content.splice(0, 1);
}
// Add headings
doc.content.unshift(
{
text: tr_center_text,
alignment: 'center',
bold: true,
fontSize: 14,
margin: [0, 0, 0, 5]
},
{
text: 'HALLWISE ABSENTEES LIST',
alignment: 'center',
bold: true,
fontSize: 12,
margin: [0, 0, 0, 10]
}
);
// Find table
var tableNode = doc.content.find(item => item.table);
if (!tableNode) return;
// Remove only actual header rows safely
if (tableNode.table.body.length > 0) {
tableNode.table.body.shift(); // remove first header row
// If second row is also header (PO, PO1...), remove it
if (tableNode.table.body.length > 0 &&
tableNode.table.body[0].some(cell =>
(cell.text || cell).toString().toUpperCase().includes('PO')
)
) {
tableNode.table.body.shift();
}
}
// 🔥 STEP 2: ADD CORRECT HEADER WITH CATEGORY
tableNode.table.body.unshift(
[
{ text: 'HALL NO', rowSpan: 2, bold: true, alignment: 'center' },
{ text: 'CATEGORY', colSpan: 5, bold: true, alignment: 'center' }, {}, {}, {}, {},
{ text: 'Total', rowSpan: 2, bold: true, alignment: 'center' }
],
[
{},
{ text: 'PO', bold: true, alignment: 'center' },
{ text: 'PO1', bold: true, alignment: 'center' },
{ text: 'PO2', bold: true, alignment: 'center' },
{ text: 'PO3', bold: true, alignment: 'center' },
{ text: 'PO4', bold: true, alignment: 'center' },
{}
]
);
// Header rows count
tableNode.table.headerRows = 2;
for (let i = 0; i < tableNode.table.headerRows; i++) {
let row = tableNode.table.body[i];
for (let j = 0; j < row.length; j++) {
let cell = row[j];
// 🔹 CASE 1: Proper object cell
if (typeof cell === 'object' && cell !== null && ('text' in cell || 'colSpan' in cell || 'rowSpan' in cell)) {
cell.text = cell.text || ''; // ✅ ensure text exists
cell.bold = true;
cell.alignment = 'center';
cell.fillColor = null;
if (cell.rowSpan) {
cell.margin = [0, 5, 0, 5];
}
}
// 🔹 CASE 2: Empty placeholder (colspan/rowspan filler)
else if (cell === null || cell === undefined || cell === '') {
row[j] = {
text: '',
bold: true,
alignment: 'center',
fillColor: null
};
}
// 🔹 CASE 3: Plain value
else {
row[j] = {
text: cell.toString(),
bold: true,
alignment: 'center',
fillColor: null
};
}
}
}
// Widths
tableNode.table.widths = ['*','*','*','*','*','*','*'];
// Center table
tableNode.alignment = 'center';
tableNode.margin = [30, 5, 30, 0];
// Remove colors
doc.styles.tableHeader.fillColor = null;
doc.styles.tableHeader.color = '#000';
doc.styles.tableBodyEven.fillColor = null;
doc.styles.tableBodyOdd.fillColor = null;
doc.styles.tableHeader.alignment = 'center';
doc.styles.tableHeader.bold = true;
doc.defaultStyle.fontSize = 8;
doc.styles.tableHeader.fontSize = 9;
// Compact layout
tableNode.layout = {
hLineWidth: () => 0.5,
vLineWidth: () => 0.5,
hLineColor: () => '#000',
vLineColor: () => '#000',
paddingLeft: () => 4,
paddingRight: () => 4,
paddingTop: () => 5,
paddingBottom: () => 5
};
// Center all cells
tableNode.table.body = tableNode.table.body.map(function (row) {
return row.map(function (cell) {
if (typeof cell === 'object' && cell !== null && ('text' in cell || 'rowSpan' in cell || 'colSpan' in cell)) {
return {
...cell,
alignment: 'center',
fillColor: null
};
}
if (typeof cell === 'object' && cell !== null) {
return cell;
}
return {
text: cell !== null ? cell.toString() : '',
alignment: 'center',
fillColor: null
};
});
});
// 🔹 MAKE "Grand Total" ROW BOLD
tableNode.table.body.forEach(function (row) {
let firstCell = row[0];
let text = '';
if (typeof firstCell === 'object' && firstCell !== null) {
text = (firstCell.text || '').toString().toUpperCase();
} else {
text = (firstCell || '').toString().toUpperCase();
}
if (text.includes('GRAND TOTAL')) {
row.forEach(function (cell, index) {
if (typeof cell === 'object' && cell !== null) {
cell.bold = true;
} else {
row[index] = {
text: cell != null ? cell.toString() : '',
bold: true,
alignment: 'center'
};
}
});
}
});
// Page break spacing
doc.pageBreakBefore = function (currentNode) {
return currentNode.y > 760;
};
}
}
],
columnDefs: [
{ targets: 'no-sort', orderable: false }
],
lengthMenu: [
[ 100, 200, 500, -1],
['100 rows', '200 rows', 'Show all']
],
scrollY: '40vh',
searching: true,
});
}
} else {
Swal.fire({
icon: 'warning',
title: res.message
});
}
},
error: function () {
$('#pageLoader').hide();
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Issue in Fetching Attendance Data'
});
}
});
});
function getTimestamp() {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mi = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
return `${yyyy}${mm}${dd}_${hh}${mi}${ss}`;
}
</script>
</body>
</html>