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.

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