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