Browse Source

changes in food distrubution report both service, controller and pages

master
yoganandhan 6 months ago
parent
commit
9615b9157c
  1. 3
      src/main/java/com/gcc/commissionerreview/controller/FoodDistrubutionReportApiController.java
  2. 44
      src/main/java/com/gcc/commissionerreview/service/Fooddistributionservice.java
  3. 385
      src/main/resources/templates/modules/fooddistribution/fooddistributionreport.html

3
src/main/java/com/gcc/commissionerreview/controller/FoodDistrubutionReportApiController.java

@ -53,12 +53,13 @@ public class FoodDistrubutionReportApiController {
public ResponseEntity<Map<String, Object>> getComplianceDetailedReport( public ResponseEntity<Map<String, Object>> getComplianceDetailedReport(
@RequestParam(value = "date", required = false) String date, @RequestParam(value = "date", required = false) String date,
@RequestParam("hubId") Integer hubId, @RequestParam("hubId") Integer hubId,
@RequestParam(value = "shiftId", required = false) Integer shiftId,
@RequestParam("qid") Integer qid, @RequestParam("qid") Integer qid,
@RequestParam("statusFilter") String statusFilter) { @RequestParam("statusFilter") String statusFilter) {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
List<Map<String, Object>> detailedData = fooddistributionservice.getComplianceDetailedReport(date, hubId, List<Map<String, Object>> detailedData = fooddistributionservice.getComplianceDetailedReport(date, hubId,
qid, statusFilter);
shiftId, qid, statusFilter);
response.put("status", "success"); response.put("status", "success");
response.put("data", detailedData); response.put("data", detailedData);
} catch (Exception e) { } catch (Exception e) {

44
src/main/java/com/gcc/commissionerreview/service/Fooddistributionservice.java

@ -42,19 +42,25 @@ public class Fooddistributionservice {
String sql = "SELECT " + String sql = "SELECT " +
"hm.id AS hub_id, hm.zone, hm.ward, hm.permanent_location, hm.si_name, " + "hm.id AS hub_id, hm.zone, hm.ward, hm.permanent_location, hm.si_name, " +
"sm.shiftid, sm.name AS shift_name, " +
"qcm.qcm_id, qcm.audit_category, " + "qcm.qcm_id, qcm.audit_category, " +
"pqm.qid, pqm.q_english, " + "pqm.qid, pqm.q_english, " +
"pam.english_name AS answer_status, " + "pam.english_name AS answer_status, " +
"COUNT(pf.id) AS issue_count " + "COUNT(pf.id) AS issue_count " +
"FROM hub_master hm " + "FROM hub_master hm " +
"LEFT JOIN pmc_audit pa ON pa.hub_id = hm.id AND pa.isactive = 1 AND pa.isdelete = 0 " + dateFilter +
"CROSS JOIN shift_master sm " +
"LEFT JOIN pmc_audit pa ON pa.hub_id = hm.id AND pa.shiftid = sm.shiftid AND pa.isactive = 1 AND pa.isdelete = 0 "
+ dateFilter +
"LEFT JOIN pmc_feedback pf ON pf.pmc_audit_id = pa.id AND pf.isactive = 1 AND pf.isdelete = 0 " + "LEFT JOIN pmc_feedback pf ON pf.pmc_audit_id = pa.id AND pf.isactive = 1 AND pf.isdelete = 0 " +
"LEFT JOIN pmc_questions_master pqm ON pqm.qid = pf.questions AND pqm.isactive = 1 AND pqm.isdelete = 0 " +
"LEFT JOIN questions_category_master qcm ON qcm.qcm_id = pqm.qcm_id AND qcm.isactive = 1 AND qcm.isdelete = 0 " +
"LEFT JOIN pmc_questions_master pqm ON pqm.qid = pf.questions AND pqm.isactive = 1 AND pqm.isdelete = 0 "
+
"LEFT JOIN questions_category_master qcm ON qcm.qcm_id = pqm.qcm_id AND qcm.isactive = 1 AND qcm.isdelete = 0 "
+
"LEFT JOIN pmc_answer_master pam ON pam.aid = pf.answer AND pam.isactive = 1 AND pam.isdelete = 0 " + "LEFT JOIN pmc_answer_master pam ON pam.aid = pf.answer AND pam.isactive = 1 AND pam.isdelete = 0 " +
"WHERE hm.is_active = 1 AND hm.is_delete = 0 " +
"GROUP BY hm.id, hm.zone, hm.ward, hm.permanent_location, hm.si_name, qcm.qcm_id, qcm.audit_category, pqm.qid, pqm.q_english, pam.english_name " +
"ORDER BY hm.id, qcm.orderby, pqm.orderby";
"WHERE hm.is_active = 1 AND hm.is_delete = 0 AND sm.isactive = 1 AND sm.isdelete = 0 " +
"GROUP BY hm.id, hm.zone, hm.ward, hm.permanent_location, hm.si_name, sm.shiftid, sm.name, qcm.qcm_id, qcm.audit_category, pqm.qid, pqm.q_english, pam.english_name "
+
"ORDER BY hm.id, sm.orderby, qcm.orderby, pqm.orderby";
if (date != null && !date.trim().isEmpty()) { if (date != null && !date.trim().isEmpty()) {
return jdbcTemplate.queryForList(sql, date); return jdbcTemplate.queryForList(sql, date);
@ -63,16 +69,21 @@ public class Fooddistributionservice {
} }
} }
public List<Map<String, Object>> getComplianceDetailedReport(String date, Integer hubId, Integer qid, String status) {
public List<Map<String, Object>> getComplianceDetailedReport(String date, Integer hubId, Integer shiftId,
Integer qid, String status) {
String dateFilter = ""; String dateFilter = "";
if (date != null && !date.trim().isEmpty()) { if (date != null && !date.trim().isEmpty()) {
dateFilter = " AND DATE(pa.audit_date) = ? "; dateFilter = " AND DATE(pa.audit_date) = ? ";
} }
String shiftFilter = "";
if (shiftId != null && shiftId > 0) {
shiftFilter = " AND pa.shiftid = ? ";
}
String sql = "SELECT " + String sql = "SELECT " +
"pf.id, " + "pf.id, " +
"DATE_FORMAT(pa.audit_date, '%d/%m/%y %H:%i') AS auditDate, " + "DATE_FORMAT(pa.audit_date, '%d/%m/%y %H:%i') AS auditDate, " +
"pa.shiftid, " + // Assuming shiftid is used; update if shift_master exists
"sm.name AS shift, " +
"hm.zone, " + "hm.zone, " +
"hm.ward, " + "hm.ward, " +
"pa.address, " + "pa.address, " +
@ -88,8 +99,10 @@ public class Fooddistributionservice {
"pf.fs_remarks AS fsRemarks, " + "pf.fs_remarks AS fsRemarks, " +
"CONCAT('" + fileBaseUrl + "/gccofficialapp/files', pf.fs_image) AS fsVideoLink " + "CONCAT('" + fileBaseUrl + "/gccofficialapp/files', pf.fs_image) AS fsVideoLink " +
"FROM pmc_feedback pf " + "FROM pmc_feedback pf " +
"JOIN pmc_audit pa ON pa.id = pf.pmc_audit_id AND pa.isactive = 1 AND pa.isdelete = 0 " + dateFilter +
"JOIN pmc_audit pa ON pa.id = pf.pmc_audit_id AND pa.isactive = 1 AND pa.isdelete = 0 " + dateFilter
+ shiftFilter +
"JOIN hub_master hm ON hm.id = pa.hub_id AND hm.id = ? " + "JOIN hub_master hm ON hm.id = pa.hub_id AND hm.id = ? " +
"LEFT JOIN shift_master sm ON sm.shiftid = pa.shiftid AND sm.isactive = 1 AND sm.isdelete = 0 " +
"JOIN pmc_questions_master pqm ON pqm.qid = pf.questions AND pqm.qid = ? " + "JOIN pmc_questions_master pqm ON pqm.qid = pf.questions AND pqm.qid = ? " +
"JOIN questions_category_master qcm ON qcm.qcm_id = pf.qcmid " + "JOIN questions_category_master qcm ON qcm.qcm_id = pf.qcmid " +
"JOIN pmc_answer_master pam ON pam.aid = pf.answer AND pam.english_name = ? " + "JOIN pmc_answer_master pam ON pam.aid = pf.answer AND pam.english_name = ? " +
@ -98,10 +111,17 @@ public class Fooddistributionservice {
"WHERE pf.isactive = 1 AND pf.isdelete = 0 " + "WHERE pf.isactive = 1 AND pf.isdelete = 0 " +
"ORDER BY pf.id DESC"; "ORDER BY pf.id DESC";
java.util.List<Object> params = new java.util.ArrayList<>();
if (date != null && !date.trim().isEmpty()) { if (date != null && !date.trim().isEmpty()) {
return jdbcTemplate.queryForList(sql, date, hubId, qid, status);
} else {
return jdbcTemplate.queryForList(sql, hubId, qid, status);
params.add(date);
} }
if (shiftId != null && shiftId > 0) {
params.add(shiftId);
}
params.add(hubId);
params.add(qid);
params.add(status);
return jdbcTemplate.queryForList(sql, params.toArray());
} }
} }

385
src/main/resources/templates/modules/fooddistribution/fooddistributionreport.html

@ -11,6 +11,7 @@
<!-- DataTables CSS --> <!-- DataTables CSS -->
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet" /> <link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/buttons/2.3.6/css/buttons.dataTables.min.css" rel="stylesheet" /> <link href="https://cdn.datatables.net/buttons/2.3.6/css/buttons.dataTables.min.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/fixedcolumns/4.2.2/css/fixedColumns.dataTables.min.css" rel="stylesheet" />
<style> <style>
body { body {
@ -62,7 +63,7 @@
color: #0043a8; color: #0043a8;
} }
.table-responsive {
.report-container {
background: #fff; background: #fff;
padding: 15px; padding: 15px;
border-radius: 8px; border-radius: 8px;
@ -70,18 +71,71 @@
} }
/* Custom Scrollbar for modern UX */ /* Custom Scrollbar for modern UX */
/* .dataTables_scrollBody {
overflow-x: scroll !important;
overflow-y: auto !important;
white-space: nowrap;
cursor: grab;
} */
.dataTables_scrollBody {
overflow-x: auto !important;
overflow-y: auto !important;
white-space: nowrap;
touch-action: pan-x pan-y;
/* VERY IMPORTANT */
cursor: grab;
}
.dataTables_scrollBody:active {
cursor: grabbing;
}
/* Smooth scroll for touchpad */
.dataTables_wrapper {
overflow-x: auto !important;
}
/* IMPORTANT */
.report-container table.dataTable {
/* width: max-content !important; */
width: 100% !important;
/* ✅ instead of max-content */
/* force table to expand */
}
/* Sticky columns background fix */
.report-container table.dataTable tbody tr td.dtfc-fixed-left {
background-color: #fff !important;
z-index: 1;
}
.report-container table.dataTable.table-striped tbody tr:nth-of-type(odd) td.dtfc-fixed-left {
background-color: #f2f2f2 !important;
}
.report-container table.dataTable tbody tr.grand-total-row td.dtfc-fixed-left {
background-color: #003366 !important;
color: #fff !important;
}
.dataTables_scrollBody::-webkit-scrollbar { .dataTables_scrollBody::-webkit-scrollbar {
height: 8px; height: 8px;
width: 8px; width: 8px;
} }
.dataTables_scrollBody::-webkit-scrollbar-track { .dataTables_scrollBody::-webkit-scrollbar-track {
background: #f1f1f1; background: #f1f1f1;
border-radius: 4px; border-radius: 4px;
} }
.dataTables_scrollBody::-webkit-scrollbar-thumb { .dataTables_scrollBody::-webkit-scrollbar-thumb {
background: #c1c1c1; background: #c1c1c1;
border-radius: 4px; border-radius: 4px;
} }
.dataTables_scrollBody::-webkit-scrollbar-thumb:hover { .dataTables_scrollBody::-webkit-scrollbar-thumb:hover {
background: #a8a8a8; background: #a8a8a8;
} }
@ -101,6 +155,40 @@
#detailModal .btn-close { #detailModal .btn-close {
filter: invert(1); filter: invert(1);
} }
#modalDataTable th {
white-space: normal !important;
/* allow wrapping */
word-break: break-word;
text-align: center !important;
vertical-align: middle !important;
max-width: 120px;
/* adjust width */
}
/* #modalDataTable td {
white-space: normal !important;
word-break: break-word;
text-align: center;
vertical-align: middle;
max-width: 150px;
} */
#modalDataTable th,
#modalDataTable td {
white-space: normal !important;
word-break: break-word;
text-align: center;
vertical-align: middle;
}
/* Prevent modal table columns from squishing, forces horizontal scroll */
#detailModal .dataTables_scrollHeadInner table,
#detailModal .dataTables_scrollBody table {
min-width: 2400px !important;
width: 100% !important;
}
</style> </style>
</head> </head>
@ -122,14 +210,16 @@
</div> </div>
</div> </div>
<div class="table-responsive">
<table id="ComplianceAbstractTable" class="table table-striped table-bordered display nowrap w-100">
<div class="report-container">
<div style="overflow-x:auto;">
<table id="ComplianceAbstractTable" class="table table-striped table-bordered display nowrap">
<thead> <thead>
<tr id="header-row-1"> <tr id="header-row-1">
<th rowspan="3" style="vertical-align: middle;">Zone</th> <th rowspan="3" style="vertical-align: middle;">Zone</th>
<th rowspan="3" <th rowspan="3"
style="vertical-align: middle; white-space: normal; min-width: 250px; max-width: 350px;"> style="vertical-align: middle; white-space: normal; min-width: 250px; max-width: 350px;">
Kitchen Name / Address</th> Kitchen Name / Address</th>
<th rowspan="3" style="vertical-align: middle;">Shift</th>
<!-- Category Headers will be appended here via JS --> <!-- Category Headers will be appended here via JS -->
</tr> </tr>
<tr id="header-row-2"> <tr id="header-row-2">
@ -149,14 +239,14 @@
<!-- Details Modal --> <!-- Details Modal -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true" <div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true"
data-bs-backdrop="static"> data-bs-backdrop="static">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="detailModalLabel">Compliance Details</h5> <h5 class="modal-title" id="detailModalLabel">Compliance Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body table-responsive">
<table id="modalDataTable" class="table table-bordered table-striped w-100">
<div class="modal-body">
<table id="modalDataTable" class="table table-bordered table-striped">
<thead class="table-dark"> <thead class="table-dark">
<tr> <tr>
<th class="text-center">ID</th> <th class="text-center">ID</th>
@ -187,7 +277,8 @@
</div> </div>
<!-- Media Modal --> <!-- Media Modal -->
<div class="modal fade" id="mediaModal" tabindex="-1" aria-labelledby="mediaModalLabel" aria-hidden="true" style="z-index: 1060;">
<div class="modal fade" id="mediaModal" tabindex="-1" aria-labelledby="mediaModalLabel" aria-hidden="true"
style="z-index: 1060;">
<div class="modal-dialog modal-lg modal-dialog-centered"> <div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -211,6 +302,7 @@
<script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.print.min.js"></script> <script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.print.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script src="https://cdn.datatables.net/fixedcolumns/4.2.2/js/dataTables.fixedColumns.min.js"></script>
<!-- SweetAlert --> <!-- SweetAlert -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
@ -221,9 +313,36 @@
var modalTable = null; var modalTable = null;
$(document).ready(function () { $(document).ready(function () {
// Set default date to yesterday
var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var yyyy = yesterday.getFullYear();
var mm = String(yesterday.getMonth() + 1).padStart(2, '0');
var dd = String(yesterday.getDate()).padStart(2, '0');
$('#filterDate').val(yyyy + '-' + mm + '-' + dd);
// Init modal DT just once // Init modal DT just once
modalTable = $('#modalDataTable').DataTable({ modalTable = $('#modalDataTable').DataTable({
autoWidth: false, autoWidth: false,
columnDefs: [
{ width: "60px", targets: 0 }, // ID
{ width: "120px", targets: 1 }, // Date
{ width: "60px", targets: 2 }, // Shift
{ width: "60px", targets: 3 }, // Zone
{ width: "60px", targets: 4 }, // Ward
{ width: "200px", targets: 5 }, // Address
{ width: "180px", targets: 6 }, // Category
{ width: "180px", targets: 7 }, // Question
{ width: "120px", targets: 8 }, // Answer
{ width: "180px", targets: 9 }, // Kitchen SI
{ width: "150px", targets: 10 }, // Raised By
{ width: "220px", targets: 11 }, // PMC Remarks
{ width: "150px", targets: 12 },
{ width: "120px", targets: 13 },
{ width: "150px", targets: 14 },
{ width: "220px", targets: 15 }, // FS Remarks
{ width: "150px", targets: 16 }
],
responsive: false, responsive: false,
scrollX: true, scrollX: true,
scrollY: "50vh", scrollY: "50vh",
@ -260,6 +379,7 @@
$('#ComplianceAbstractTable tbody').on('click', '.clickable-cell', function () { $('#ComplianceAbstractTable tbody').on('click', '.clickable-cell', function () {
var filterDate = $('#filterDate').val() || ''; var filterDate = $('#filterDate').val() || '';
var hubId = $(this).data('hubid'); var hubId = $(this).data('hubid');
var shiftId = $(this).data('shiftid');
var qid = $(this).data('qid'); var qid = $(this).data('qid');
var statusFilter = $(this).data('status'); var statusFilter = $(this).data('status');
@ -274,6 +394,7 @@
data: { data: {
date: filterDate, date: filterDate,
hubId: hubId, hubId: hubId,
shiftId: shiftId,
qid: qid, qid: qid,
statusFilter: statusFilter statusFilter: statusFilter
}, },
@ -282,6 +403,7 @@
if (res.status === 'success') { if (res.status === 'success') {
populateModal(res.data); populateModal(res.data);
$('#detailModal').modal('show'); $('#detailModal').modal('show');
} else { } else {
Swal.fire('Error', 'Failed to load detailed data.', 'error'); Swal.fire('Error', 'Failed to load detailed data.', 'error');
} }
@ -291,6 +413,11 @@
} }
}); });
}); });
$('#detailModal').on('shown.bs.modal', function () {
if (modalTable !== null) {
modalTable.columns.adjust().draw();
}
});
}); });
function buildTableHeaders() { function buildTableHeaders() {
@ -309,13 +436,16 @@
categoryMap.groupedCategories = groupedCategories; categoryMap.groupedCategories = groupedCategories;
Object.values(groupedCategories).forEach(function (cat) { Object.values(groupedCategories).forEach(function (cat) {
hr1.append('<th colspan="' + (cat.questions.length * 2) + '">' + cat.name + '</th>');
hr1.append('<th colspan="' + cat.questions.length + '">' + cat.name + '</th>');
cat.questions.forEach(function (q) { cat.questions.forEach(function (q) {
hr2.append('<th colspan="2">' + q.q_english + '</th>');
hr3.append('<th>Issue Found</th><th>No Issue</th>');
hr2.append('<th>' + q.q_english + '</th>');
hr3.append('<th>Issue Found</th>');
}); });
}); });
// Add Total column header
hr1.append('<th rowspan="3" style="vertical-align: middle; background-color: #003366 !important; color: white !important; min-width: 120px;">Total Issue Found</th>');
} }
function loadAbstractReport() { function loadAbstractReport() {
@ -373,11 +503,16 @@
info: true, info: true,
scrollX: true, scrollX: true,
scrollY: "60vh", scrollY: "60vh",
scrollCollapse: true
scrollCollapse: true,
autoWidth: false,
fixedColumns: {
leftColumns: 3
}
}); });
setTimeout(function(){
enableDragScroll();
}, 200);
setTimeout(function () {
// bindScrollBehavior();
table.columns.adjust().draw();
}, 300);
} else { } else {
Swal.fire('Error', res.message || 'Failed to fetch data.', 'error'); Swal.fire('Error', res.message || 'Failed to fetch data.', 'error');
} }
@ -390,55 +525,105 @@
} }
function renderAbstractBody(data) { function renderAbstractBody(data) {
var kitchenMap = {};
var hubGroups = {};
var questionTotals = {};
var grandTotal = 0;
data.forEach(function (row) { data.forEach(function (row) {
var kId = row.hub_id; var kId = row.hub_id;
if (!kitchenMap[kId]) {
kitchenMap[kId] = {
if (!hubGroups[kId]) {
hubGroups[kId] = {
hub_id: row.hub_id, hub_id: row.hub_id,
zone: row.zone || '', zone: row.zone || '',
name_address: (row.si_name || row.permanent_location || ('Kitchen ' + row.hub_id)) + (row.permanent_location ? ' - ' + row.permanent_location : ''), name_address: (row.si_name || row.permanent_location || ('Kitchen ' + row.hub_id)) + (row.permanent_location ? ' - ' + row.permanent_location : ''),
shifts: {}
};
}
var shiftId = row.shiftid || '0';
var shiftName = row.shift_name || '-';
if (!hubGroups[kId].shifts[shiftId]) {
hubGroups[kId].shifts[shiftId] = {
shift_id: shiftId,
shift_name: shiftName,
counts: {} counts: {}
}; };
} }
var qId = row.qid; var qId = row.qid;
if (!kitchenMap[kId].counts[qId]) {
kitchenMap[kId].counts[qId] = { 'Issue Found': 0, 'No Issue': 0 };
if (qId) {
if (!hubGroups[kId].shifts[shiftId].counts[qId]) {
hubGroups[kId].shifts[shiftId].counts[qId] = { 'Issue Found': 0, 'No Issue': 0 };
} }
// Add issue / no issue count
var s = row.answer_status; var s = row.answer_status;
if (s === 'Issue Found' || s === 'No Issue') { if (s === 'Issue Found' || s === 'No Issue') {
kitchenMap[kId].counts[qId][s] += row.issue_count;
hubGroups[kId].shifts[shiftId].counts[qId][s] += row.issue_count;
}
} }
}); });
var tbody = $('#ComplianceAbstractTable tbody'); var tbody = $('#ComplianceAbstractTable tbody');
var groupedCategories = categoryMap.groupedCategories; var groupedCategories = categoryMap.groupedCategories;
Object.values(kitchenMap).forEach(function (kitchen) {
Object.values(hubGroups).forEach(function (hub) {
var shiftKeys = Object.keys(hub.shifts);
var rowSpanCount = shiftKeys.length;
shiftKeys.forEach(function (shiftId, index) {
var shift = hub.shifts[shiftId];
var tr = $('<tr></tr>'); var tr = $('<tr></tr>');
tr.append('<td class="text-center align-middle">' + kitchen.zone + '</td>');
tr.append('<td class="text-center align-middle" style="white-space: normal; min-width: 250px; max-width: 350px;">' + kitchen.name_address + '</td>');
if (index === 0) {
tr.append('<td class="text-center align-middle" rowspan="' + rowSpanCount + '">' + hub.zone + '</td>');
tr.append('<td class="text-center align-middle" rowspan="' + rowSpanCount + '" style="white-space: normal; min-width: 250px; max-width: 350px;">' + hub.name_address + '</td>');
} else {
// Maintain perfect column counts for DataTables matrix validation to completely prevent tn/18 error!
tr.append('<td style="display: none;"></td>');
tr.append('<td style="display: none;"></td>');
}
tr.append('<td class="text-center align-middle" style="white-space: nowrap; font-weight: bold; color: #003366;">' + shift.shift_name + '</td>');
// Loop in the same order as headers // Loop in the same order as headers
var totalIssues = 0;
Object.values(groupedCategories).forEach(function (cat) { Object.values(groupedCategories).forEach(function (cat) {
cat.questions.forEach(function (q) { cat.questions.forEach(function (q) {
var counts = kitchen.counts[q.qid] || { 'Issue Found': 0, 'No Issue': 0 };
var counts = shift.counts[q.qid] || { 'Issue Found': 0 };
var issueCount = counts['Issue Found'];
var noIssueCount = counts['No Issue'];
var issueCount = counts['Issue Found'] || 0;
totalIssues += issueCount;
questionTotals[q.qid] = (questionTotals[q.qid] || 0) + issueCount;
var tdIssue = '<td><span class="clickable-cell" data-hubid="' + kitchen.hub_id + '" data-qid="' + q.qid + '" data-status="Issue Found">' + (issueCount || '') + '</span></td>';
var tdNoIssue = '<td><span class="clickable-cell" data-hubid="' + kitchen.hub_id + '" data-qid="' + q.qid + '" data-status="No Issue">' + (noIssueCount || '') + '</span></td>';
var tdIssue = '<td><span class="clickable-cell" data-hubid="' + hub.hub_id + '" data-shiftid="' + shift.shift_id + '" data-qid="' + q.qid + '" data-status="Issue Found">' + (issueCount || '') + '</span></td>';
tr.append(tdIssue); tr.append(tdIssue);
tr.append(tdNoIssue);
}); });
}); });
tr.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.1em; color: #dc3545;">' + (totalIssues > 0 ? totalIssues : '') + '</td>');
tbody.append(tr); tbody.append(tr);
grandTotal += totalIssues;
});
}); });
// Append Grand Total Row at bottom
var totalRow = $('<tr class="grand-total-row"></tr>');
totalRow.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.1em; background-color: #003366; color: white;">Grand Total</td>');
totalRow.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.1em; background-color: #003366; color: white;">-</td>');
totalRow.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.1em; background-color: #003366; color: white;">-</td>');
Object.values(groupedCategories).forEach(function (cat) {
cat.questions.forEach(function (q) {
var colTotal = questionTotals[q.qid] || 0;
totalRow.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.1em; background-color: #e9ecef; color: #003366;">' + (colTotal > 0 ? colTotal : '') + '</td>');
});
});
totalRow.append('<td class="text-center align-middle" style="font-weight: bold; font-size: 1.25em; color: #dc3545; background-color: #f8d7da;">' + (grandTotal > 0 ? grandTotal : '') + '</td>');
tbody.append(totalRow);
} }
function populateModal(data) { function populateModal(data) {
@ -483,8 +668,7 @@
modalTable.draw(); modalTable.draw();
setTimeout(function () { setTimeout(function () {
modalTable.columns.adjust().draw(); modalTable.columns.adjust().draw();
enableDragScroll();
}, 200);
}, 300);
} }
function showMedia(url, isVideo) { function showMedia(url, isVideo) {
@ -501,41 +685,132 @@
$('#mediaModalBody').empty(); $('#mediaModalBody').empty();
}); });
} }
function enableDragScroll() {
$('.dataTables_scrollBody').each(function() {
var newSlider = this;
if (!$(newSlider).hasClass('drag-scroll-enabled')) {
$(newSlider).addClass('drag-scroll-enabled');
var isDown = false;
var startX;
var scrollLeft;
$(newSlider).css('cursor', 'grab');
$(newSlider).on('mousedown', function(e) {
// function bindScrollBehavior() {
// var scrollBodies = document.querySelectorAll('.dataTables_scrollBody');
// scrollBodies.forEach(function (body) {
// body.removeEventListener('wheel', handleCustomWheel);
// body.addEventListener('wheel', handleCustomWheel, { passive: false });
// });
// }
// function handleCustomWheel(e) {
// var deltaX = e.deltaX;
// var deltaY = e.deltaY;
// var el = this;
// var maxScrollLeft = el.scrollWidth - el.clientWidth;
// if (Math.abs(deltaX) > 0) {
// if ((deltaX > 0 && el.scrollLeft < maxScrollLeft) || (deltaX < 0 && el.scrollLeft > 0)) {
// el.scrollLeft += deltaX;
// e.preventDefault();
// }
// } else if (Math.abs(deltaY) > 0) {
// var canScrollY = el.scrollHeight > el.clientHeight + 2;
// var canScrollX = el.scrollWidth > el.clientWidth;
// if (!canScrollY && canScrollX) {
// if ((deltaY > 0 && el.scrollLeft < maxScrollLeft) || (deltaY < 0 && el.scrollLeft > 0)) {
// el.scrollLeft += (deltaY > 0 ? 50 : -50);
// e.preventDefault();
// }
// }
// }
// }
// document.addEventListener('wheel', function (e) {
// const scrollBody = document.querySelector('.dataTables_scrollBody');
// if (!scrollBody) return;
// const canScrollX = scrollBody.scrollWidth > scrollBody.clientWidth;
// // ✅ REAL horizontal gesture (touchpad left/right)
// if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
// scrollBody.scrollLeft += e.deltaX;
// return; // don't block
// }
// // ✅ OPTIONAL: vertical → horizontal only when no vertical scroll
// const canScrollY = scrollBody.scrollHeight > scrollBody.clientHeight;
// if (!canScrollY && canScrollX) {
// scrollBody.scrollLeft -= e.deltaY;
// e.preventDefault();
// }
// }, { passive: false });
// $(document).on('wheel', '.dataTables_scrollBody', function (e) {
// const el = this;
// const deltaX = e.originalEvent.deltaX;
// const deltaY = e.originalEvent.deltaY;
// const canScrollX = el.scrollWidth > el.clientWidth;
// // ✅ Case 1: Real horizontal gesture
// if (Math.abs(deltaX) > 0) {
// el.scrollLeft += deltaX;
// return;
// }
// // ✅ Case 2: Touchpad sends only vertical → convert smartly
// if (canScrollX && Math.abs(deltaY) > 0) {
// el.scrollLeft += deltaY;
// e.preventDefault();
// }
// });
let isDown = false;
let startX;
let scrollLeft;
$(document).on('mousedown', '.dataTables_scrollBody', function (e) {
isDown = true; isDown = true;
$(newSlider).css('cursor', 'grabbing');
startX = e.pageX - newSlider.offsetLeft;
scrollLeft = newSlider.scrollLeft;
$(this).css('cursor', 'grabbing');
startX = e.pageX - this.offsetLeft;
scrollLeft = this.scrollLeft;
}); });
$(newSlider).on('mouseleave', function() {
$(document).on('mouseleave', '.dataTables_scrollBody', function () {
isDown = false; isDown = false;
$(newSlider).css('cursor', 'grab');
$(this).css('cursor', ''); // Let CSS handle it (grab)
}); });
$(newSlider).on('mouseup', function() {
$(document).on('mouseup', '.dataTables_scrollBody', function () {
isDown = false; isDown = false;
$(newSlider).css('cursor', 'grab');
$(this).css('cursor', ''); // Let CSS handle it (grab)
}); });
$(newSlider).on('mousemove', function(e) {
$(document).on('mousemove', '.dataTables_scrollBody', function (e) {
if (!isDown) return; if (!isDown) return;
e.preventDefault(); e.preventDefault();
var x = e.pageX - newSlider.offsetLeft;
var walk = (x - startX) * 1.5;
newSlider.scrollLeft = scrollLeft - walk;
const x = e.pageX - this.offsetLeft;
const walk = (x - startX) * 1.5; // Scroll speed multiplier
this.scrollLeft = scrollLeft - walk;
}); });
// Touchpad scroll mapping support
$(document).on('wheel', '.dataTables_scrollBody', function (e) {
const el = this;
const deltaX = e.originalEvent.deltaX;
const deltaY = e.originalEvent.deltaY;
const canScrollY = el.scrollHeight > Math.ceil(el.clientHeight) + 5;
const canScrollX = el.scrollWidth > Math.ceil(el.clientWidth) + 5;
// Priority 1: Native Horizontal Touchpad Swipe
if (Math.abs(deltaX) > Math.abs(deltaY)) {
el.scrollLeft += deltaX;
e.preventDefault();
} }
});
// Priority 2: Convert Vertical swipe/wheel to Horizontal ONLY if there is no vertical scrollbar
else if (!canScrollY && canScrollX && Math.abs(deltaY) > 0) {
el.scrollLeft += deltaY;
e.preventDefault();
} }
});
</script> </script>
</body> </body>

Loading…
Cancel
Save