Browse Source

Newly added food distrubution report controller, service, and page

master
yoganandhan 6 months ago
parent
commit
9510d37b72
  1. 70
      src/main/java/com/gcc/commissionerreview/controller/FoodDistrubutionReportApiController.java
  2. 5
      src/main/java/com/gcc/commissionerreview/controller/MainController.java
  3. 107
      src/main/java/com/gcc/commissionerreview/service/Fooddistributionservice.java
  4. 543
      src/main/resources/templates/modules/fooddistribution/fooddistributionreport.html

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

@ -0,0 +1,70 @@
package com.gcc.commissionerreview.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.gcc.commissionerreview.service.Fooddistributionservice;
@RestController
@RequestMapping("/commissionerreview/api/fooddistribution")
public class FoodDistrubutionReportApiController {
@Autowired
private Fooddistributionservice fooddistributionservice;
@GetMapping("/categories")
public ResponseEntity<Map<String, Object>> getQuestions() {
Map<String, Object> response = new HashMap<>();
try {
List<Map<String, Object>> questions = fooddistributionservice.getQuestions();
response.put("status", "success");
response.put("data", questions);
} catch (Exception e) {
response.put("status", "error");
response.put("message", e.getMessage());
}
return ResponseEntity.ok(response);
}
@GetMapping("/complianceAbstract")
public ResponseEntity<Map<String, Object>> getComplianceAbstractReport(
@RequestParam(value = "date", required = false) String date) {
Map<String, Object> response = new HashMap<>();
try {
List<Map<String, Object>> reportData = fooddistributionservice.getComplianceAbstractReport(date);
response.put("status", "success");
response.put("data", reportData);
} catch (Exception e) {
response.put("status", "error");
response.put("message", e.getMessage());
}
return ResponseEntity.ok(response);
}
@GetMapping("/complianceDetails")
public ResponseEntity<Map<String, Object>> getComplianceDetailedReport(
@RequestParam(value = "date", required = false) String date,
@RequestParam("hubId") Integer hubId,
@RequestParam("qid") Integer qid,
@RequestParam("statusFilter") String statusFilter) {
Map<String, Object> response = new HashMap<>();
try {
List<Map<String, Object>> detailedData = fooddistributionservice.getComplianceDetailedReport(date, hubId,
qid, statusFilter);
response.put("status", "success");
response.put("data", detailedData);
} catch (Exception e) {
response.put("status", "error");
response.put("message", e.getMessage());
}
return ResponseEntity.ok(response);
}
}

5
src/main/java/com/gcc/commissionerreview/controller/MainController.java

@ -301,4 +301,9 @@ public class MainController {
return "modules/bulkwaste/bulkwastereport";
}
@GetMapping("/fooddistributionreport")
public String fooddistributionreport(Model model) {
return "modules/fooddistribution/fooddistributionreport";
}
}

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

@ -0,0 +1,107 @@
package com.gcc.commissionerreview.service;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class Fooddistributionservice {
@Autowired
private JdbcTemplate jdbcTemplate;
@Value("${fileBaseUrl:}")
private String fileBaseUrl;
@Autowired
public void setDataSource(@Qualifier("mysqlFoodDistributionDataSource") DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getQuestions() {
String sql = "SELECT qcm.qcm_id, qcm.audit_category, pqm.qid, pqm.q_english " +
"FROM pmc_questions_master pqm " +
"JOIN questions_category_master qcm ON pqm.qcm_id = qcm.qcm_id " +
"WHERE pqm.isactive = 1 AND pqm.isdelete = 0 AND qcm.isactive = 1 AND qcm.isdelete = 0 " +
"ORDER BY qcm.orderby, pqm.orderby";
return jdbcTemplate.queryForList(sql);
}
public List<Map<String, Object>> getComplianceAbstractReport(String date) {
String dateFilter = "";
if (date != null && !date.trim().isEmpty()) {
dateFilter = " AND DATE(pa.audit_date) = ? ";
}
String sql = "SELECT " +
"hm.id AS hub_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 AS answer_status, " +
"COUNT(pf.id) AS issue_count " +
"FROM hub_master hm " +
"LEFT JOIN pmc_audit pa ON pa.hub_id = hm.id 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_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 " +
"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";
if (date != null && !date.trim().isEmpty()) {
return jdbcTemplate.queryForList(sql, date);
} else {
return jdbcTemplate.queryForList(sql);
}
}
public List<Map<String, Object>> getComplianceDetailedReport(String date, Integer hubId, Integer qid, String status) {
String dateFilter = "";
if (date != null && !date.trim().isEmpty()) {
dateFilter = " AND DATE(pa.audit_date) = ? ";
}
String sql = "SELECT " +
"pf.id, " +
"DATE_FORMAT(pa.audit_date, '%d/%m/%y %H:%i') AS auditDate, " +
"pa.shiftid, " + // Assuming shiftid is used; update if shift_master exists
"hm.zone, " +
"hm.ward, " +
"pa.address, " +
"qcm.audit_category, " +
"pqm.q_english, " +
"pam.english_name, " +
"hm.si_name AS name, " +
"dl.name AS raisedBy, " +
"pf.remarks AS pmcRemarks, " +
"CONCAT('" + fileBaseUrl + "/gccofficialapp/files', pf.image) AS videoLink, " +
"pf.food_swing_sts AS fsStatus, " +
"dl2.name AS fsChallengedBy, " +
"pf.fs_remarks AS fsRemarks, " +
"CONCAT('" + fileBaseUrl + "/gccofficialapp/files', pf.fs_image) AS fsVideoLink " +
"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 hub_master hm ON hm.id = pa.hub_id AND hm.id = ? " +
"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 pmc_answer_master pam ON pam.aid = pf.answer AND pam.english_name = ? " +
"LEFT JOIN driver_login dl ON pa.cby = dl.loginid " +
"LEFT JOIN driver_login dl2 ON pf.fs_cby = dl2.loginid " +
"WHERE pf.isactive = 1 AND pf.isdelete = 0 " +
"ORDER BY pf.id DESC";
if (date != null && !date.trim().isEmpty()) {
return jdbcTemplate.queryForList(sql, date, hubId, qid, status);
} else {
return jdbcTemplate.queryForList(sql, hubId, qid, status);
}
}
}

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

@ -0,0 +1,543 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Food Distribution FSSAI Compliance Report</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- FontAwesome CSS for icons -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- 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" />
<style>
body {
background-color: #f8f9fa;
}
.page-header-title h4 {
margin: 20px 0;
text-align: center;
font-weight: bold;
color: #003366;
text-transform: uppercase;
}
.filter-section {
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
table.dataTable th,
table.dataTable td {
text-align: center;
vertical-align: middle;
border: 1px solid #dee2e6 !important;
}
thead th {
background-color: #003366 !important;
color: white !important;
}
.sub-header th {
background-color: #e9ecef !important;
color: #495057 !important;
font-weight: 600;
}
.clickable-cell {
cursor: pointer;
color: #0d6efd;
font-weight: bold;
text-decoration: underline;
}
.clickable-cell:hover {
color: #0043a8;
}
.table-responsive {
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
/* Custom Scrollbar for modern UX */
.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;
}
/* Modal tweaks */
#detailModal .modal-content {
border-radius: 8px;
}
#detailModal .modal-header {
background-color: #003366;
color: #fff;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
#detailModal .btn-close {
filter: invert(1);
}
</style>
</head>
<body>
<div class="container-fluid py-4">
<div class="page-header-title">
<h4>Food Distribution FSSAI Compliance Report</h4>
</div>
<!-- Filter Section -->
<div class="filter-section d-flex justify-content-center align-items-end gap-3 flex-wrap">
<div>
<label for="filterDate" class="form-label fw-bold">Select Date</label>
<input type="date" id="filterDate" class="form-control">
</div>
<div>
<button id="btnSearch" class="btn btn-primary px-4"><i class="fa-solid fa-search"></i> Search</button>
</div>
</div>
<div class="table-responsive">
<table id="ComplianceAbstractTable" class="table table-striped table-bordered display nowrap w-100">
<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>
<!-- Category Headers will be appended here via JS -->
</tr>
<tr id="header-row-2">
<!-- Questions sub-headers will be appended here via JS -->
</tr>
<tr id="header-row-3" class="sub-header">
<!-- Issue/No Issue sub-headers will be appended here via JS -->
</tr>
</thead>
<tbody>
<!-- Populated via JS -->
</tbody>
</table>
</div>
</div>
<!-- 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-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">
<thead class="table-dark">
<tr>
<th class="text-center">ID</th>
<th class="text-center">Audit Date</th>
<th class="text-center">Shift</th>
<th class="text-center">Zone</th>
<th class="text-center">Ward</th>
<th class="text-center">Address</th>
<th class="text-center">Audit Category</th>
<th class="text-center">Question</th>
<th class="text-center">Answer</th>
<th class="text-center">Kitchen SI Name</th>
<th class="text-center">Raised By</th>
<th class="text-center">PMC Remarks</th>
<th class="text-center">PMC Image/Video Viewer</th>
<th class="text-center">FS Status</th>
<th class="text-center">FS Challenged By</th>
<th class="text-center">FS Remarks</th>
<th class="text-center">FS Image/Video Viewer</th>
</tr>
</thead>
<tbody id="modalTableBody">
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Media Modal -->
<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">
<h5 class="modal-title" id="mediaModalLabel">Media Viewer</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center" id="mediaModalBody">
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<!-- DataTables JS -->
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.3.6/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.html5.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://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- SweetAlert -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script th:inline="none">
var categoryMap = [];
var table = null;
var modalTable = null;
$(document).ready(function () {
// Init modal DT just once
modalTable = $('#modalDataTable').DataTable({
autoWidth: false,
responsive: false,
scrollX: true,
scrollY: "50vh",
scrollCollapse: true,
searching: true,
paging: true,
info: true,
ordering: false
});
// 1) Fetch categories first
$.ajax({
url: '/commissionerreview/api/fooddistribution/categories',
method: 'GET',
success: function (res) {
if (res.status === 'success') {
categoryMap = res.data;
buildTableHeaders();
loadAbstractReport();
} else {
Swal.fire('Error', 'Failed to load categories', 'error');
}
},
error: function () {
Swal.fire('Error', 'API error loading categories', 'error');
}
});
$('#btnSearch').click(function () {
loadAbstractReport();
});
// Delegate click for modal details
$('#ComplianceAbstractTable tbody').on('click', '.clickable-cell', function () {
var filterDate = $('#filterDate').val() || '';
var hubId = $(this).data('hubid');
var qid = $(this).data('qid');
var statusFilter = $(this).data('status');
if (!hubId || !qid || !statusFilter) return;
// Show loading...
Swal.fire({ title: 'Loading...', allowOutsideClick: false, didOpen: function () { Swal.showLoading(); } });
$.ajax({
url: '/commissionerreview/api/fooddistribution/complianceDetails',
method: 'GET',
data: {
date: filterDate,
hubId: hubId,
qid: qid,
statusFilter: statusFilter
},
success: function (res) {
Swal.close();
if (res.status === 'success') {
populateModal(res.data);
$('#detailModal').modal('show');
} else {
Swal.fire('Error', 'Failed to load detailed data.', 'error');
}
},
error: function () {
Swal.fire('Error', 'API error loading detailed data.', 'error');
}
});
});
});
function buildTableHeaders() {
var hr1 = $('#header-row-1');
var hr2 = $('#header-row-2');
var hr3 = $('#header-row-3');
var groupedCategories = {};
categoryMap.forEach(function (q) {
if (!groupedCategories[q.qcm_id]) {
groupedCategories[q.qcm_id] = { name: q.audit_category, questions: [] };
}
groupedCategories[q.qcm_id].questions.push(q);
});
categoryMap.groupedCategories = groupedCategories;
Object.values(groupedCategories).forEach(function (cat) {
hr1.append('<th colspan="' + (cat.questions.length * 2) + '">' + 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>');
});
});
}
function loadAbstractReport() {
var filterDate = $('#filterDate').val() || '';
// Show loading
Swal.fire({ title: 'Fetching Data...', allowOutsideClick: false, didOpen: function () { Swal.showLoading(); } });
$.ajax({
url: '/commissionerreview/api/fooddistribution/complianceAbstract',
method: 'GET',
data: { date: filterDate },
success: function (res) {
Swal.close();
if (res.status === 'success') {
if (table !== null) {
table.destroy();
$('#ComplianceAbstractTable tbody').empty();
}
renderAbstractBody(res.data);
table = $('#ComplianceAbstractTable').DataTable({
dom: 'Bfrtip',
buttons: [
'copy',
'csv',
{
text: 'Excel',
className: 'btn-excel',
action: function (e, dt, node, config) {
// Save currently selected page length
var currentLen = dt.page.len();
// Show all data to ensure we capture the whole table
dt.page.len(-1).draw(false);
// Get the raw DOM table element
var tableEl = document.getElementById('ComplianceAbstractTable');
// Use SheetJS to convert it purely visually exactly as the screen merges it
var wb = XLSX.utils.table_to_book(tableEl, { sheet: "FSSAI Report", raw: true });
// Trigger download
XLSX.writeFile(wb, 'Food_Distribution_Compliance.xlsx');
// Restore old page length
dt.page.len(currentLen).draw(false);
}
},
'print'
],
lengthMenu: [[50, 100, -1], [50, 100, "All"]],
paging: true,
ordering: false,
info: true,
scrollX: true,
scrollY: "60vh",
scrollCollapse: true
});
setTimeout(function(){
enableDragScroll();
}, 200);
} else {
Swal.fire('Error', res.message || 'Failed to fetch data.', 'error');
}
},
error: function () {
Swal.close();
Swal.fire('Error', 'API request failed.', 'error');
}
});
}
function renderAbstractBody(data) {
var kitchenMap = {};
data.forEach(function (row) {
var kId = row.hub_id;
if (!kitchenMap[kId]) {
kitchenMap[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 : ''),
counts: {}
};
}
var qId = row.qid;
if (!kitchenMap[kId].counts[qId]) {
kitchenMap[kId].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;
}
});
var tbody = $('#ComplianceAbstractTable tbody');
var groupedCategories = categoryMap.groupedCategories;
Object.values(kitchenMap).forEach(function (kitchen) {
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>');
// Loop in the same order as headers
Object.values(groupedCategories).forEach(function (cat) {
cat.questions.forEach(function (q) {
var counts = kitchen.counts[q.qid] || { 'Issue Found': 0, 'No Issue': 0 };
var issueCount = counts['Issue Found'];
var noIssueCount = counts['No Issue'];
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>';
tr.append(tdIssue);
tr.append(tdNoIssue);
});
});
tbody.append(tr);
});
}
function populateModal(data) {
modalTable.clear();
data.forEach(function (item) {
var ext = item.videoLink ? item.videoLink.split('.').pop().toLowerCase() : '';
var isVideo = ['mp4', 'mov', 'avi', 'webm', 'mkv'].includes(ext);
var imgBtn = '-';
if (item.videoLink && item.videoLink.indexOf('null') === -1) {
imgBtn = '<button onclick="showMedia(\'' + item.videoLink + '\', ' + isVideo + ')" class="btn btn-sm btn-info text-white"><i class="fa fa-' + (isVideo ? 'video' : 'image') + '"></i> View</button>';
}
var fsExt = item.fsVideoLink ? item.fsVideoLink.split('.').pop().toLowerCase() : '';
var isFsVideo = ['mp4', 'mov', 'avi', 'webm', 'mkv'].includes(fsExt);
var fsImgBtn = '-';
if (item.fsVideoLink && item.fsVideoLink.indexOf('null') === -1) {
fsImgBtn = '<button onclick="showMedia(\'' + item.fsVideoLink + '\', ' + isFsVideo + ')" class="btn btn-sm btn-info text-white"><i class="fa fa-' + (isFsVideo ? 'video' : 'image') + '"></i> View</button>';
}
modalTable.row.add([
item.id,
item.auditDate || '-',
item.shiftid || '-',
item.zone || '-',
item.ward || '-',
item.address || '-',
item.audit_category || '-',
item.q_english || '-',
item.english_name || '-',
item.name || '-',
item.raisedBy || '-',
item.pmcRemarks || '-',
imgBtn,
item.fsStatus || '-',
item.fsChallengedBy || '-',
item.fsRemarks || '-',
fsImgBtn
]);
});
modalTable.draw();
setTimeout(function () {
modalTable.columns.adjust().draw();
enableDragScroll();
}, 200);
}
function showMedia(url, isVideo) {
var mediaHtml = '';
if (isVideo) {
mediaHtml = '<video width="100%" controls controlsList="nodownload" disablePictureInPicture><source src="' + url + '">Your browser does not support the video tag.</video>';
} else {
mediaHtml = '<img src="' + url + '" style="max-width: 100%; height: auto; pointer-events: none; user-select: none;" oncontextmenu="return false;" />';
}
$('#mediaModalBody').html(mediaHtml);
$('#mediaModal').modal('show');
$('#mediaModal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
$('#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) {
isDown = true;
$(newSlider).css('cursor', 'grabbing');
startX = e.pageX - newSlider.offsetLeft;
scrollLeft = newSlider.scrollLeft;
});
$(newSlider).on('mouseleave', function() {
isDown = false;
$(newSlider).css('cursor', 'grab');
});
$(newSlider).on('mouseup', function() {
isDown = false;
$(newSlider).css('cursor', 'grab');
});
$(newSlider).on('mousemove', function(e) {
if (!isDown) return;
e.preventDefault();
var x = e.pageX - newSlider.offsetLeft;
var walk = (x - startX) * 1.5;
newSlider.scrollLeft = scrollLeft - walk;
});
}
});
}
</script>
</body>
</html>
Loading…
Cancel
Save