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.

1024 lines
44 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/modules/base/head :: head}">
  4. <style>
  5. </style>
  6. </head>
  7. <body>
  8. <div class="wrapper">
  9. <!-- header top menu Start -->
  10. <header th:replace="~{fragments/modules/base/header :: header}"></header>
  11. <!-- header top menu end -->
  12. <div class="page-wrap">
  13. <!--=============== Left Menu side Start ================-->
  14. <div th:replace="~{fragments/modules/base/menu :: menu}"></div>
  15. <!--=============== Left side End ================-->
  16. <div class="main-content">
  17. <div class="container-fluid">
  18. <div class="page-header mb-2">
  19. <div class="row align-items-end">
  20. <div class="col-lg-8">
  21. <div class="page-header-title">
  22. <!-- <i class="far fa-calendar-plus bg-blue"></i> -->
  23. <div class="d-inline">
  24. <h5 >Hall Absents</h5>
  25. <span>Reports for Hall Absents</span>
  26. </div>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. <div class="row">
  32. <div class="col-md-12">
  33. <div class="card">
  34. <div class="card-header">
  35. Filters
  36. </div>
  37. <div class="card-body">
  38. <div class="row">
  39. <div class="col-sm-4">
  40. <label for="attendance">Select Training</label>
  41. <select id="attendance" class="form-control">
  42. <option value="">Select</option>
  43. <option value="1" >First Training Attendance</option>
  44. <option value="2">Second Training Attendance</option>
  45. <option value="3">Second Repeat Training Attendance</option>
  46. <option value="4" selected="selected">Third Training Attendance</option>
  47. </select>
  48. </div>
  49. <div class="col-sm-4">
  50. <label for="att_type">Select Report Type</label>
  51. <select id="att_type" class="form-control">
  52. <option value="">Select</option>
  53. <option value="abstract">Abstract</option>
  54. <option value="summary">Summary</option>
  55. </select>
  56. </div>
  57. <div class="col-sm-4">
  58. <label for="tr_center">Select Training center</label>
  59. <select id="tr_center" class="form-control">
  60. <option value="">Select</option>
  61. </select>
  62. </div>
  63. </div>
  64. <div class="row mt-4">
  65. <div class="col-12 d-flex justify-content-center">
  66. <button type="submit" id="searchBtn"
  67. class="btn btn-primary mx-2">Search</button>
  68. <button class="btn btn-danger mx-2" id="resetBtn"
  69. name="reset" type="reset">Reset</button>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <div class="row">
  77. <div class="col-md-12">
  78. <div class="card" id="reportDiv" style="display: none;">
  79. <div class="card-body">
  80. <div class="table-responsive" id="summaryDiv" style="display: none;">
  81. <table id="summaryreportTable"
  82. class="table table-striped table-bordered table-hover dataTable"
  83. role="grid">
  84. <thead class="thead-dark">
  85. <!-- Main Header Row -->
  86. <tr role="row">
  87. <th rowspan="2" class="text-center">HALL NO</th>
  88. <th colspan="5" class="text-center">CATEGORY</th>
  89. <th rowspan="2" class="text-center">Total</th>
  90. </tr>
  91. <!-- Sub Header 1 -->
  92. <tr>
  93. <th class="text-center">PO</th>
  94. <th class="text-center">PO1</th>
  95. <th class="text-center">PO2</th>
  96. <th class="text-center">PO3</th>
  97. <th class="text-center">PO4</th>
  98. </tr>
  99. </thead>
  100. <tbody>
  101. <!-- Rows will be dynamically inserted via JS/DataTable -->
  102. </tbody>
  103. </table>
  104. </div>
  105. <div class="table-responsive" id="abstractDiv" style="display: none;">
  106. <table id="abstractreportTable"
  107. class="table table-striped table-bordered table-hover dataTable"
  108. role="grid">
  109. <thead class="thead-dark">
  110. <!-- Main Header Row -->
  111. <tr role="row">
  112. <th colspan="5" class="text-center">PO</th>
  113. <th colspan="5" class="text-center">PO1</th>
  114. <th colspan="5" class="text-center">PO2</th>
  115. <th colspan="5" class="text-center">PO3</th>
  116. <th colspan="5" class="text-center">PO4</th>
  117. <th colspan="5" class="text-center">TOTAL</th>
  118. </tr>
  119. <!-- Sub Header 1 -->
  120. <tr>
  121. <th class="text-center">TOTAL</th>
  122. <th class="text-center">ABSENT</th>
  123. <th class="text-center">PRESENT</th>
  124. <th class="text-center">% of ABSENT</th>
  125. <th class="text-center">% of PRESENT</th>
  126. <th class="text-center">TOTAL</th>
  127. <th class="text-center">ABSENT</th>
  128. <th class="text-center">PRESENT</th>
  129. <th class="text-center">% of ABSENT</th>
  130. <th class="text-center">% of PRESENT</th>
  131. <th class="text-center">TOTAL</th>
  132. <th class="text-center">ABSENT</th>
  133. <th class="text-center">PRESENT</th>
  134. <th class="text-center">% of ABSENT</th>
  135. <th class="text-center">% of PRESENT</th>
  136. <th class="text-center">TOTAL</th>
  137. <th class="text-center">ABSENT</th>
  138. <th class="text-center">PRESENT</th>
  139. <th class="text-center">% of ABSENT</th>
  140. <th class="text-center">% of PRESENT</th>
  141. <th class="text-center">TOTAL</th>
  142. <th class="text-center">ABSENT</th>
  143. <th class="text-center">PRESENT</th>
  144. <th class="text-center">% of ABSENT</th>
  145. <th class="text-center">% of PRESENT</th>
  146. <th class="text-center">TOTAL</th>
  147. <th class="text-center">ABSENT</th>
  148. <th class="text-center">PRESENT</th>
  149. <th class="text-center">% of ABSENT</th>
  150. <th class="text-center">% of PRESENT</th>
  151. </tr>
  152. </thead>
  153. <tbody>
  154. <!-- Rows will be dynamically inserted via JS/DataTable -->
  155. </tbody>
  156. </table>
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. <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;">
  165. <div style="position: relative; top: 50%; transform: translateY(-50%);">
  166. <div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
  167. <span class="sr-only">Loading...</span>
  168. </div>
  169. <!-- Add the 'Please wait' text below the spinner -->
  170. <p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p>
  171. </div>
  172. </div>
  173. <div th:replace="~{fragments/modules/base/app_module_menu_box :: div}"></div>
  174. <footer th:replace="~{fragments/modules/base/footer :: footer}"></footer>
  175. </div>
  176. </div>
  177. <script th:replace="~{fragments/modules/base/script :: script}"></script>
  178. <script type="text/javascript">
  179. document.getElementById('resetBtn').addEventListener('click', function () {
  180. event.preventDefault();
  181. location.reload();
  182. });
  183. $(document).ready(function () {
  184. $.ajax({
  185. url: "/election/api/dataentryadmin/report/Attendance/trainingconstituencies",
  186. method: "GET",
  187. success: function (data) {
  188. let dropdown = $("#tr_center");
  189. dropdown.empty();
  190. dropdown.append('<option value="">-- Select --</option>');
  191. data.forEach(function (row) {
  192. dropdown.append(
  193. `<option value="${row.c_code}">
  194. ${row.display_name}
  195. </option>`
  196. );
  197. });
  198. },
  199. error: function () {
  200. alert("Failed to load training constituencies");
  201. }
  202. });
  203. $('#summaryDiv').hide();
  204. $('#abstractDiv').hide();
  205. $('#reportDiv').hide();
  206. });
  207. document.getElementById('searchBtn').addEventListener('click', function (e) {
  208. let attendance=$('#attendance').val();
  209. let att_type=$('#att_type').val();
  210. let tr_center=$('#tr_center').val();
  211. let tr_center_text = $('#tr_center option:selected').text();
  212. let trainingTitle = "";
  213. if (attendance === "1") {
  214. trainingTitle = "1st DAY REVISED POLLING PERSONAL TRAINING ATTENDANCE - 28.03.2026";
  215. } else if (attendance === "2") {
  216. trainingTitle = "2nd DAY POLLING PERSONAL TRAINING ATTENDANCE - 11.04.2026";
  217. } else if (attendance === "3") {
  218. trainingTitle = "2nd REPEAT POLLING PERSONAL TRAINING ATTENDANCE - 19.04.2026";
  219. }
  220. else if (attendance === "4") {
  221. trainingTitle = "3rd DAY POLLING PERSONAL TRAINING ATTENDANCE - 22.04.2026";
  222. }
  223. if(!attendance){
  224. Swal.fire({
  225. icon: 'warning',
  226. title: 'Training Required',
  227. text: 'Please Select Training'
  228. });
  229. return;
  230. }
  231. if(!att_type){
  232. Swal.fire({
  233. icon: 'warning',
  234. title: 'Attendance Type Required',
  235. text: 'Please Select Type'
  236. });
  237. return;
  238. }
  239. if(!tr_center){
  240. Swal.fire({
  241. icon: 'warning',
  242. title: 'Training center Required',
  243. text: 'Please Select Training center'
  244. });
  245. return;
  246. }
  247. console.log("attendance="+attendance);
  248. console.log("att_type="+att_type);
  249. console.log("tr_center="+tr_center);
  250. $('#pageLoader').show();
  251. // Step 1: Check data first
  252. $.ajax({
  253. url: '/election/api/dataentryadmin/attendance/getAbsentees',
  254. type: 'GET',
  255. data: { attendance: attendance,att_type:att_type,tr_center:tr_center },
  256. success: function (res) {
  257. $('#pageLoader').hide();
  258. console.log(res.data);
  259. if (res.status==="Success") {
  260. $('#reportDiv').show();
  261. if(res.report==="abstract"){
  262. $('#summaryDiv').hide();
  263. $('#abstractDiv').show();
  264. let table = $('#abstractreportTable').DataTable({
  265. destroy: true,
  266. searching: true,
  267. autoWidth: true,
  268. paging: true,
  269. info: true,
  270. data: res.data,
  271. responsive: false,
  272. scrollX: true,
  273. columns: [
  274. { data: 'PO_TOTAL', defaultContent: '-', className: 'text-center' },
  275. { data: 'PO_ABSENT', defaultContent: '-', className: 'text-center' },
  276. { data: 'PO_PRESENT', defaultContent: '-', className: 'text-center' },
  277. { data: 'PO_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  278. { data: 'PO_PRESENT_PER', defaultContent: '-', className: 'text-center' },
  279. { data: 'PO1_TOTAL', defaultContent: '-', className: 'text-center' },
  280. { data: 'PO1_ABSENT', defaultContent: '-', className: 'text-center' },
  281. { data: 'PO1_PRESENT', defaultContent: '-', className: 'text-center' },
  282. {data: 'PO1_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  283. {data: 'PO1_PRESENT_PER', defaultContent: '-', className: 'text-center' },
  284. { data: 'PO2_TOTAL', defaultContent: '-', className: 'text-center' },
  285. { data: 'PO2_ABSENT', defaultContent: '-', className: 'text-center' },
  286. { data: 'PO2_PRESENT', defaultContent: '-', className: 'text-center' },
  287. {data: 'PO2_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  288. {data: 'PO2_PRESENT_PER', defaultContent: '-', className: 'text-center' },
  289. { data: 'PO3_TOTAL', defaultContent: '-', className: 'text-center' },
  290. { data: 'PO3_ABSENT', defaultContent: '-', className: 'text-center' },
  291. { data: 'PO3_PRESENT', defaultContent: '-', className: 'text-center' },
  292. {data: 'PO3_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  293. {data: 'PO3_PRESENT_PER', defaultContent: '-', className: 'text-center' },
  294. { data: 'PO4_TOTAL', defaultContent: '-', className: 'text-center' },
  295. { data: 'PO4_ABSENT', defaultContent: '-', className: 'text-center' },
  296. { data: 'PO4_PRESENT', defaultContent: '-', className: 'text-center' },
  297. {data: 'PO4_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  298. {data: 'PO4_PRESENT_PER', defaultContent: '-', className: 'text-center' },
  299. { data: 'TOTAL_TOTAL', defaultContent: '-', className: 'text-center' },
  300. { data: 'TOTAL_ABSENT', defaultContent: '-', className: 'text-center' },
  301. { data: 'TOTAL_PRESENT', defaultContent: '-', className: 'text-center' },
  302. {data: 'TOTAL_ABSENT_PER', defaultContent: '-', className: 'text-center' },
  303. {data: 'TOTAL_PRESENT_PER', defaultContent: '-', className: 'text-center' }
  304. ],
  305. dom: 'Bfrtip',
  306. buttons: [
  307. {
  308. extend: 'pageLength',
  309. className: 'btn btn-warning'
  310. },
  311. {
  312. title: 'Data export',
  313. extend: 'copyHtml5',
  314. text: 'Copy',
  315. className: 'btn btn-secondary',
  316. id: 'copyBtn'
  317. },
  318. {
  319. extend: 'excelHtml5',
  320. text: 'Excel',
  321. title: null,
  322. className: 'btn btn-secondary',
  323. id: 'excelBtn',
  324. filename: function () {
  325. return tr_center+'_AttendanceAbsReport_' + getTimestamp();
  326. },
  327. customize: function (xlsx) {
  328. var sheet = xlsx.xl.worksheets['sheet1.xml'];
  329. var sheetData = $('sheetData', sheet);
  330. let tr_center_text = $('#tr_center option:selected').text();
  331. // 🔹 1. SHIFT EXISTING ROWS DOWN BY 3
  332. $('row', sheetData).each(function () {
  333. var r = parseInt($(this).attr('r'));
  334. $(this).attr('r', r + 4);
  335. $(this).find('c').each(function () {
  336. var cellRef = $(this).attr('r');
  337. var col = cellRef.replace(/[0-9]/g, '');
  338. var row = parseInt(cellRef.replace(/[A-Z]/g, ''));
  339. $(this).attr('r', col + (row + 4));
  340. });
  341. });
  342. // 🔹 2. ADD HEADER ROWS
  343. sheetData.prepend(`
  344. <row r="1">
  345. <c t="inlineStr" r="A1"><is><t>${tr_center_text}</t></is></c>
  346. </row>
  347. <row r="2">
  348. <c t="inlineStr" r="A2"><is><t>{trainingTitle}</t></is></c>
  349. </row>
  350. <row r="3">
  351. <c t="inlineStr" r="A3"><is><t>ABSTRACT</t></is></c>
  352. </row>
  353. `);
  354. sheetData.prepend(`
  355. <row r="4">
  356. <c t="inlineStr" r="A4"><is><t>PO</t></is></c>
  357. <c t="inlineStr" r="F4"><is><t>PO1</t></is></c>
  358. <c t="inlineStr" r="K4"><is><t>PO2</t></is></c>
  359. <c t="inlineStr" r="P4"><is><t>PO3</t></is></c>
  360. <c t="inlineStr" r="U4"><is><t>PO4</t></is></c>
  361. <c t="inlineStr" r="Z4"><is><t>TOTAL</t></is></c>
  362. </row>
  363. `);
  364. // 🔹 3. MERGE CELLS (FULL WIDTH)
  365. var mergeCells = $('mergeCells', sheet);
  366. if (mergeCells.length === 0) {
  367. mergeCells = $('<mergeCells count="0"/>').appendTo(sheet);
  368. }
  369. mergeCells.append(`
  370. <mergeCell ref="A1:AD1"/>
  371. <mergeCell ref="A2:AD2"/>
  372. <mergeCell ref="A3:AD3"/>
  373. <mergeCell ref="A4:E4"/>
  374. <mergeCell ref="F4:J4"/>
  375. <mergeCell ref="K4:O4"/>
  376. <mergeCell ref="P4:T4"/>
  377. <mergeCell ref="U4:Y4"/>
  378. <mergeCell ref="Z4:AD4"/>
  379. `);
  380. // 🔹 4. UPDATE MERGE COUNT
  381. mergeCells.attr('count', mergeCells.find('mergeCell').length);
  382. // 5. CENTER ALIGN TITLE ROWS (ADD HERE)
  383. var styles = xlsx.xl['styles.xml'];
  384. $('cellXfs', styles).append(
  385. '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">' +
  386. '<alignment horizontal="center"/>' +
  387. '</xf>'
  388. );
  389. var styleIndex = $('cellXfs xf', styles).length - 1;
  390. // Apply style to title rows
  391. $('c[r="A1"]', sheet).attr('s', styleIndex);
  392. $('c[r="A2"]', sheet).attr('s', styleIndex);
  393. $('c[r="A3"]', sheet).attr('s', styleIndex);
  394. ['A4','F4','K4','P4','U4','Z4'].forEach(function(cell){
  395. $('c[r="' + cell + '"]', sheet).attr('s', styleIndex);
  396. });
  397. }
  398. },
  399. {
  400. extend: 'csvHtml5',
  401. text: 'CSV',
  402. className: 'btn btn-secondary',
  403. id: 'csvBtn',
  404. filename: function () {
  405. return tr_center+'_AttendanceAbsReport_' + getTimestamp();
  406. }
  407. },
  408. {
  409. extend: 'pdfHtml5',
  410. text: 'PDF',
  411. title: null,
  412. className: 'btn btn-secondary',
  413. id: 'pdfBtn',
  414. filename: function () {
  415. return tr_center+'_AttendanceAbsReport_' + getTimestamp();
  416. },
  417. orientation: 'landscape',
  418. pageSize: 'A3',
  419. customize: function (doc) {
  420. let tr_center_text = $('#tr_center option:selected').text();
  421. // 🔹 1. ADD HEADINGS
  422. doc.content.unshift(
  423. {
  424. text: tr_center_text,
  425. alignment: 'center',
  426. bold: true,
  427. fontSize: 14,
  428. margin: [0, 0, 0, 5]
  429. },
  430. {
  431. text: trainingTitle,
  432. alignment: 'center',
  433. bold: true,
  434. fontSize: 11,
  435. margin: [0, 0, 0, 5]
  436. },
  437. {
  438. text: 'ABSTRACT',
  439. alignment: 'center',
  440. bold: true,
  441. fontSize: 12,
  442. margin: [0, 0, 0, 10]
  443. }
  444. );
  445. // 🔹 2. PAGE SETTINGS
  446. doc.pageOrientation = 'landscape';
  447. doc.pageMargins = [10, 10, 10, 10];
  448. doc.defaultStyle.fontSize = 8;
  449. doc.styles.tableHeader.fontSize = 8;
  450. doc.styles.tableHeader.alignment = 'center';
  451. doc.styles.tableHeader.bold = true;
  452. doc.styles.tableHeader.fillColor = null;
  453. doc.styles.tableHeader.color = '#000';
  454. doc.styles.tableBodyEven.fillColor = null;
  455. doc.styles.tableBodyOdd.fillColor = null;
  456. // 🔹 3. FIND TABLE (IMPORTANT)
  457. var tableNode;
  458. doc.content.forEach(function (item) {
  459. if (item.table) {
  460. tableNode = item;
  461. }
  462. });
  463. if (!tableNode) return; // safety
  464. let tableBody = tableNode.table.body;
  465. // 🔹 4. ADD GROUP HEADER ROW
  466. tableBody.unshift([
  467. { text: 'PO', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
  468. { text: 'PO1', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
  469. { text: 'PO2', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
  470. { text: 'PO3', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
  471. { text: 'PO4', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {},
  472. { text: 'TOTAL', colSpan: 5, alignment: 'center', bold: true, fillColor: null, color: '#000' }, {}, {}, {}, {}
  473. ]);
  474. // 🔹 5. HEADER ROW COUNT
  475. tableNode.table.headerRows = 2;
  476. // 🔹 6. FULL WIDTH FIX
  477. tableNode.table.widths = Array(30).fill(35);
  478. // 🔹 7. TABLE STYLE
  479. tableNode.layout = {
  480. hLineWidth: () => 0.3,
  481. vLineWidth: () => 0.3,
  482. hLineColor: () => '#000',
  483. vLineColor: () => '#000',
  484. paddingLeft: () => 2,
  485. paddingRight: () => 2,
  486. paddingTop: () => 2,
  487. paddingBottom: () => 2
  488. };
  489. // CENTER ALL CELLS
  490. tableNode.table.body = tableNode.table.body.map(function (row) {
  491. return row.map(function (cell) {
  492. // 🔹 Case 1: valid object with text
  493. if (typeof cell === 'object' && cell !== null && ('text' in cell || 'colSpan' in cell)) {
  494. return {
  495. ...cell,
  496. fillColor: null,
  497. alignment: 'center'
  498. };
  499. }
  500. // 🔹 Case 2: empty placeholder {} → KEEP AS IS
  501. if (typeof cell === 'object' && cell !== null) {
  502. return cell;
  503. }
  504. // 🔹 Case 3: normal value
  505. return {
  506. text: cell !== null ? cell.toString() : '',
  507. fillColor: null,
  508. alignment: 'center'
  509. };
  510. });
  511. });
  512. }
  513. }
  514. ],
  515. columnDefs: [
  516. { targets: 'no-sort', orderable: false }
  517. ],
  518. lengthMenu: [
  519. [ 100, 200, 500, -1],
  520. ['100 rows', '200 rows', 'Show all']
  521. ],
  522. scrollY: '40vh',
  523. searching: true,
  524. });
  525. }
  526. else{
  527. $('#abstractDiv').hide();
  528. $('#summaryDiv').show();
  529. let table = $('#summaryreportTable').DataTable({
  530. destroy: true,
  531. searching: true,
  532. autoWidth: false,
  533. paging: true,
  534. info: true,
  535. ordering: false,
  536. data: res.data.sort((a, b) => a.sort_order - b.sort_order),
  537. responsive: false,
  538. scrollX: true,
  539. columns: [
  540. { data: 'hall_no', defaultContent: '', className: 'text-center' },
  541. { data: 'PO', defaultContent: '', className: 'text-center' },
  542. { data: 'PO1', defaultContent: '', className: 'text-center' },
  543. { data: 'PO2', defaultContent: '', className: 'text-center' },
  544. { data: 'PO3', defaultContent: '', className: 'text-center' },
  545. { data: 'PO4', defaultContent: '', className: 'text-center' },
  546. { data: 'Total', defaultContent: '', className: 'text-center' }
  547. ],
  548. rowCallback: function (row, data) {
  549. if (data.hall_no === 'Grand Total') {
  550. $(row).css({
  551. 'font-weight': 'bold',
  552. 'background-color': '#f2f2f2'
  553. });
  554. }
  555. },
  556. dom: 'Bfrtip',
  557. buttons: [
  558. {
  559. extend: 'pageLength',
  560. className: 'btn btn-warning'
  561. },
  562. {
  563. title: 'Data export',
  564. extend: 'copyHtml5',
  565. text: 'Copy',
  566. className: 'btn btn-secondary',
  567. id: 'copyBtn'
  568. },
  569. {
  570. extend: 'excelHtml5',
  571. text: 'Excel',
  572. title: null,
  573. className: 'btn btn-secondary',
  574. id: 'excelBtn',
  575. filename: function () {
  576. return tr_center+'_AttendanceSumReport_' + getTimestamp();
  577. },
  578. customize: function (xlsx) {
  579. var sheet = xlsx.xl.worksheets['sheet1.xml'];
  580. var sheetData = $('sheetData', sheet);
  581. let tr_center_text = $('#tr_center option:selected').text();
  582. // 🔹 1. SHIFT EXISTING ROWS DOWN BY 2 (IMPORTANT CHANGE)
  583. $('row', sheetData).each(function () {
  584. var r = parseInt($(this).attr('r'));
  585. $(this).attr('r', r + 2);
  586. $(this).find('c').each(function () {
  587. var cellRef = $(this).attr('r');
  588. var col = cellRef.replace(/[0-9]/g, '');
  589. var row = parseInt(cellRef.replace(/[A-Z]/g, ''));
  590. $(this).attr('r', col + (row + 2));
  591. });
  592. });
  593. // 🔹 2. ADD ONLY 2 HEADER ROWS
  594. sheetData.prepend(`
  595. <row r="1">
  596. <c t="inlineStr" r="A1"><is><t>${tr_center_text}</t></is></c>
  597. </row>
  598. <row r="2">
  599. <c t="inlineStr" r="A2"><is><t>HALLWISE ABSENTEES LIST</t></is></c>
  600. </row>
  601. `);
  602. // 🔹 3. MERGE CELLS (ONLY 2 ROWS)
  603. var mergeCells = $('mergeCells', sheet);
  604. if (mergeCells.length === 0) {
  605. mergeCells = $('<mergeCells count="0"/>').appendTo(sheet);
  606. }
  607. mergeCells.append(`
  608. <mergeCell ref="A1:AD1"/>
  609. <mergeCell ref="A2:AD2"/>
  610. `);
  611. mergeCells.attr('count', mergeCells.find('mergeCell').length);
  612. // 🔹 4. CENTER ALIGN
  613. var styles = xlsx.xl['styles.xml'];
  614. $('cellXfs', styles).append(
  615. '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">' +
  616. '<alignment horizontal="center"/>' +
  617. '</xf>'
  618. );
  619. var styleIndex = $('cellXfs xf', styles).length - 1;
  620. $('c[r="A1"]', sheet).attr('s', styleIndex);
  621. $('c[r="A2"]', sheet).attr('s', styleIndex);
  622. }
  623. },
  624. {
  625. extend: 'csvHtml5',
  626. text: 'CSV',
  627. className: 'btn btn-secondary',
  628. id: 'csvBtn',
  629. filename: function () {
  630. return tr_center+'_AttendanceSumReport_' + getTimestamp();
  631. }
  632. },
  633. {
  634. extend: 'pdfHtml5',
  635. text: 'PDF',
  636. title: null,
  637. className: 'btn btn-secondary',
  638. id: 'pdfBtn',
  639. filename: function () {
  640. return tr_center+'_AttendanceSumReport_' + getTimestamp();
  641. },
  642. orientation: 'portrait',
  643. pageSize: 'A3',
  644. customize: function (doc) {
  645. doc.pageOrientation = 'portrait';
  646. doc.pageSize = 'A4';
  647. doc.pageMargins = [20, 20, 20, 20];
  648. let tr_center_text = $('#tr_center option:selected').text();
  649. // Remove default title
  650. if (doc.content[0].text) {
  651. doc.content.splice(0, 1);
  652. }
  653. // Add headings
  654. doc.content.unshift(
  655. {
  656. text: tr_center_text,
  657. alignment: 'center',
  658. bold: true,
  659. fontSize: 14,
  660. margin: [0, 0, 0, 5]
  661. },
  662. {
  663. text: 'HALLWISE ABSENTEES LIST',
  664. alignment: 'center',
  665. bold: true,
  666. fontSize: 12,
  667. margin: [0, 0, 0, 10]
  668. }
  669. );
  670. // Find table
  671. var tableNode = doc.content.find(item => item.table);
  672. if (!tableNode) return;
  673. // Remove only actual header rows safely
  674. if (tableNode.table.body.length > 0) {
  675. tableNode.table.body.shift(); // remove first header row
  676. // If second row is also header (PO, PO1...), remove it
  677. if (tableNode.table.body.length > 0 &&
  678. tableNode.table.body[0].some(cell =>
  679. (cell.text || cell).toString().toUpperCase().includes('PO')
  680. )
  681. ) {
  682. tableNode.table.body.shift();
  683. }
  684. }
  685. // 🔥 STEP 2: ADD CORRECT HEADER WITH CATEGORY
  686. tableNode.table.body.unshift(
  687. [
  688. { text: 'HALL NO', rowSpan: 2, bold: true, alignment: 'center' },
  689. { text: 'CATEGORY', colSpan: 5, bold: true, alignment: 'center' }, {}, {}, {}, {},
  690. { text: 'Total', rowSpan: 2, bold: true, alignment: 'center' }
  691. ],
  692. [
  693. {},
  694. { text: 'PO', bold: true, alignment: 'center' },
  695. { text: 'PO1', bold: true, alignment: 'center' },
  696. { text: 'PO2', bold: true, alignment: 'center' },
  697. { text: 'PO3', bold: true, alignment: 'center' },
  698. { text: 'PO4', bold: true, alignment: 'center' },
  699. {}
  700. ]
  701. );
  702. // Header rows count
  703. tableNode.table.headerRows = 2;
  704. for (let i = 0; i < tableNode.table.headerRows; i++) {
  705. let row = tableNode.table.body[i];
  706. for (let j = 0; j < row.length; j++) {
  707. let cell = row[j];
  708. // 🔹 CASE 1: Proper object cell
  709. if (typeof cell === 'object' && cell !== null && ('text' in cell || 'colSpan' in cell || 'rowSpan' in cell)) {
  710. cell.text = cell.text || ''; // ✅ ensure text exists
  711. cell.bold = true;
  712. cell.alignment = 'center';
  713. cell.fillColor = null;
  714. if (cell.rowSpan) {
  715. cell.margin = [0, 5, 0, 5];
  716. }
  717. }
  718. // 🔹 CASE 2: Empty placeholder (colspan/rowspan filler)
  719. else if (cell === null || cell === undefined || cell === '') {
  720. row[j] = {
  721. text: '',
  722. bold: true,
  723. alignment: 'center',
  724. fillColor: null
  725. };
  726. }
  727. // 🔹 CASE 3: Plain value
  728. else {
  729. row[j] = {
  730. text: cell.toString(),
  731. bold: true,
  732. alignment: 'center',
  733. fillColor: null
  734. };
  735. }
  736. }
  737. }
  738. // Widths
  739. tableNode.table.widths = ['*','*','*','*','*','*','*'];
  740. // Center table
  741. tableNode.alignment = 'center';
  742. tableNode.margin = [30, 5, 30, 0];
  743. // Remove colors
  744. doc.styles.tableHeader.fillColor = null;
  745. doc.styles.tableHeader.color = '#000';
  746. doc.styles.tableBodyEven.fillColor = null;
  747. doc.styles.tableBodyOdd.fillColor = null;
  748. doc.styles.tableHeader.alignment = 'center';
  749. doc.styles.tableHeader.bold = true;
  750. doc.defaultStyle.fontSize = 8;
  751. doc.styles.tableHeader.fontSize = 9;
  752. // Compact layout
  753. tableNode.layout = {
  754. hLineWidth: () => 0.5,
  755. vLineWidth: () => 0.5,
  756. hLineColor: () => '#000',
  757. vLineColor: () => '#000',
  758. paddingLeft: () => 4,
  759. paddingRight: () => 4,
  760. paddingTop: () => 5,
  761. paddingBottom: () => 5
  762. };
  763. // Center all cells
  764. tableNode.table.body = tableNode.table.body.map(function (row) {
  765. return row.map(function (cell) {
  766. if (typeof cell === 'object' && cell !== null && ('text' in cell || 'rowSpan' in cell || 'colSpan' in cell)) {
  767. return {
  768. ...cell,
  769. alignment: 'center',
  770. fillColor: null
  771. };
  772. }
  773. if (typeof cell === 'object' && cell !== null) {
  774. return cell;
  775. }
  776. return {
  777. text: cell !== null ? cell.toString() : '',
  778. alignment: 'center',
  779. fillColor: null
  780. };
  781. });
  782. });
  783. // 🔹 MAKE "Grand Total" ROW BOLD
  784. tableNode.table.body.forEach(function (row) {
  785. let firstCell = row[0];
  786. let text = '';
  787. if (typeof firstCell === 'object' && firstCell !== null) {
  788. text = (firstCell.text || '').toString().toUpperCase();
  789. } else {
  790. text = (firstCell || '').toString().toUpperCase();
  791. }
  792. if (text.includes('GRAND TOTAL')) {
  793. row.forEach(function (cell, index) {
  794. if (typeof cell === 'object' && cell !== null) {
  795. cell.bold = true;
  796. } else {
  797. row[index] = {
  798. text: cell != null ? cell.toString() : '',
  799. bold: true,
  800. alignment: 'center'
  801. };
  802. }
  803. });
  804. }
  805. });
  806. // Page break spacing
  807. doc.pageBreakBefore = function (currentNode) {
  808. return currentNode.y > 760;
  809. };
  810. }
  811. }
  812. ],
  813. columnDefs: [
  814. { targets: 'no-sort', orderable: false }
  815. ],
  816. lengthMenu: [
  817. [ 100, 200, 500, -1],
  818. ['100 rows', '200 rows', 'Show all']
  819. ],
  820. scrollY: '40vh',
  821. searching: true,
  822. });
  823. }
  824. } else {
  825. Swal.fire({
  826. icon: 'warning',
  827. title: res.message
  828. });
  829. }
  830. },
  831. error: function () {
  832. $('#pageLoader').hide();
  833. Swal.fire({
  834. icon: 'error',
  835. title: 'Error',
  836. text: 'Issue in Fetching Attendance Data'
  837. });
  838. }
  839. });
  840. });
  841. function getTimestamp() {
  842. const now = new Date();
  843. const yyyy = now.getFullYear();
  844. const mm = String(now.getMonth() + 1).padStart(2, '0');
  845. const dd = String(now.getDate()).padStart(2, '0');
  846. const hh = String(now.getHours()).padStart(2, '0');
  847. const mi = String(now.getMinutes()).padStart(2, '0');
  848. const ss = String(now.getSeconds()).padStart(2, '0');
  849. return `${yyyy}${mm}${dd}_${hh}${mi}${ss}`;
  850. }
  851. </script>
  852. </body>
  853. </html>