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
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>
|