|
|
|
@ -11,6 +11,7 @@ |
|
|
|
<!-- DataTables CSS --> |
|
|
|
<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/fixedcolumns/4.2.2/css/fixedColumns.dataTables.min.css" rel="stylesheet" /> |
|
|
|
|
|
|
|
<style> |
|
|
|
body { |
|
|
|
@ -62,7 +63,7 @@ |
|
|
|
color: #0043a8; |
|
|
|
} |
|
|
|
|
|
|
|
.table-responsive { |
|
|
|
.report-container { |
|
|
|
background: #fff; |
|
|
|
padding: 15px; |
|
|
|
border-radius: 8px; |
|
|
|
@ -70,18 +71,71 @@ |
|
|
|
} |
|
|
|
|
|
|
|
/* 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 { |
|
|
|
height: 8px; |
|
|
|
width: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.dataTables_scrollBody::-webkit-scrollbar-track { |
|
|
|
background: #f1f1f1; |
|
|
|
border-radius: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.dataTables_scrollBody::-webkit-scrollbar-thumb { |
|
|
|
background: #c1c1c1; |
|
|
|
border-radius: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.dataTables_scrollBody::-webkit-scrollbar-thumb:hover { |
|
|
|
background: #a8a8a8; |
|
|
|
} |
|
|
|
@ -101,6 +155,40 @@ |
|
|
|
#detailModal .btn-close { |
|
|
|
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> |
|
|
|
</head> |
|
|
|
|
|
|
|
@ -122,14 +210,16 @@ |
|
|
|
</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> |
|
|
|
<tr id="header-row-1"> |
|
|
|
<th rowspan="3" style="vertical-align: middle;">Zone</th> |
|
|
|
<th rowspan="3" |
|
|
|
style="vertical-align: middle; white-space: normal; min-width: 250px; max-width: 350px;"> |
|
|
|
Kitchen Name / Address</th> |
|
|
|
<th rowspan="3" style="vertical-align: middle;">Shift</th> |
|
|
|
<!-- Category Headers will be appended here via JS --> |
|
|
|
</tr> |
|
|
|
<tr id="header-row-2"> |
|
|
|
@ -149,14 +239,14 @@ |
|
|
|
<!-- Details Modal --> |
|
|
|
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true" |
|
|
|
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-header"> |
|
|
|
<h5 class="modal-title" id="detailModalLabel">Compliance Details</h5> |
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|
|
|
</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"> |
|
|
|
<tr> |
|
|
|
<th class="text-center">ID</th> |
|
|
|
@ -187,7 +277,8 @@ |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 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-content"> |
|
|
|
<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://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.datatables.net/fixedcolumns/4.2.2/js/dataTables.fixedColumns.min.js"></script> |
|
|
|
|
|
|
|
<!-- SweetAlert --> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
|
|
|
@ -221,9 +313,36 @@ |
|
|
|
var modalTable = null; |
|
|
|
|
|
|
|
$(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 |
|
|
|
modalTable = $('#modalDataTable').DataTable({ |
|
|
|
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, |
|
|
|
scrollX: true, |
|
|
|
scrollY: "50vh", |
|
|
|
@ -260,6 +379,7 @@ |
|
|
|
$('#ComplianceAbstractTable tbody').on('click', '.clickable-cell', function () { |
|
|
|
var filterDate = $('#filterDate').val() || ''; |
|
|
|
var hubId = $(this).data('hubid'); |
|
|
|
var shiftId = $(this).data('shiftid'); |
|
|
|
var qid = $(this).data('qid'); |
|
|
|
var statusFilter = $(this).data('status'); |
|
|
|
|
|
|
|
@ -274,6 +394,7 @@ |
|
|
|
data: { |
|
|
|
date: filterDate, |
|
|
|
hubId: hubId, |
|
|
|
shiftId: shiftId, |
|
|
|
qid: qid, |
|
|
|
statusFilter: statusFilter |
|
|
|
}, |
|
|
|
@ -282,6 +403,7 @@ |
|
|
|
if (res.status === 'success') { |
|
|
|
populateModal(res.data); |
|
|
|
$('#detailModal').modal('show'); |
|
|
|
|
|
|
|
} else { |
|
|
|
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() { |
|
|
|
@ -309,13 +436,16 @@ |
|
|
|
categoryMap.groupedCategories = groupedCategories; |
|
|
|
|
|
|
|
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) { |
|
|
|
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() { |
|
|
|
@ -373,11 +503,16 @@ |
|
|
|
info: true, |
|
|
|
scrollX: true, |
|
|
|
scrollY: "60vh", |
|
|
|
scrollCollapse: true |
|
|
|
scrollCollapse: true, |
|
|
|
autoWidth: false, |
|
|
|
fixedColumns: { |
|
|
|
leftColumns: 3 |
|
|
|
} |
|
|
|
}); |
|
|
|
setTimeout(function () { |
|
|
|
enableDragScroll(); |
|
|
|
}, 200); |
|
|
|
// bindScrollBehavior(); |
|
|
|
table.columns.adjust().draw(); |
|
|
|
}, 300); |
|
|
|
} else { |
|
|
|
Swal.fire('Error', res.message || 'Failed to fetch data.', 'error'); |
|
|
|
} |
|
|
|
@ -390,55 +525,105 @@ |
|
|
|
} |
|
|
|
|
|
|
|
function renderAbstractBody(data) { |
|
|
|
var kitchenMap = {}; |
|
|
|
var hubGroups = {}; |
|
|
|
var questionTotals = {}; |
|
|
|
var grandTotal = 0; |
|
|
|
|
|
|
|
data.forEach(function (row) { |
|
|
|
var kId = row.hub_id; |
|
|
|
if (!kitchenMap[kId]) { |
|
|
|
kitchenMap[kId] = { |
|
|
|
if (!hubGroups[kId]) { |
|
|
|
hubGroups[kId] = { |
|
|
|
hub_id: row.hub_id, |
|
|
|
zone: row.zone || '', |
|
|
|
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: {} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
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 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>'); |
|
|
|
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 |
|
|
|
var totalIssues = 0; |
|
|
|
|
|
|
|
Object.values(groupedCategories).forEach(function (cat) { |
|
|
|
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(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); |
|
|
|
|
|
|
|
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) { |
|
|
|
@ -483,8 +668,7 @@ |
|
|
|
modalTable.draw(); |
|
|
|
setTimeout(function () { |
|
|
|
modalTable.columns.adjust().draw(); |
|
|
|
enableDragScroll(); |
|
|
|
}, 200); |
|
|
|
}, 300); |
|
|
|
} |
|
|
|
|
|
|
|
function showMedia(url, isVideo) { |
|
|
|
@ -501,41 +685,132 @@ |
|
|
|
$('#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; |
|
|
|
$(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; |
|
|
|
$(newSlider).css('cursor', 'grab'); |
|
|
|
$(this).css('cursor', ''); // Let CSS handle it (grab) |
|
|
|
}); |
|
|
|
$(newSlider).on('mouseup', function() { |
|
|
|
|
|
|
|
$(document).on('mouseup', '.dataTables_scrollBody', function () { |
|
|
|
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; |
|
|
|
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> |
|
|
|
|
|
|
|
</body> |
|
|
|
|