4 changed files with 725 additions and 0 deletions
-
70src/main/java/com/gcc/commissionerreview/controller/FoodDistrubutionReportApiController.java
-
5src/main/java/com/gcc/commissionerreview/controller/MainController.java
-
107src/main/java/com/gcc/commissionerreview/service/Fooddistributionservice.java
-
543src/main/resources/templates/modules/fooddistribution/fooddistributionreport.html
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue