6 changed files with 1755 additions and 4 deletions
-
10src/main/java/com/gcc/commissionerreview/controller/FlagpolesApiController.java
-
20src/main/java/com/gcc/commissionerreview/controller/MainController.java
-
27src/main/java/com/gcc/commissionerreview/service/FlagpolesService.java
-
354src/main/resources/templates/modules/flagpoles/homepage.html
-
672src/main/resources/templates/modules/flagpoles/permanent_request.html
-
676src/main/resources/templates/modules/flagpoles/temporary_request.html
@ -0,0 +1,354 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org"> |
||||
|
|
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<title>Flagpoles Abstract Report</title> |
||||
|
|
||||
|
<!-- Bootstrap 5 CSS --> |
||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.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> |
||||
|
table.dataTable th, |
||||
|
table.dataTable td { |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
border: 1px solid #000; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th, |
||||
|
.table-dark th { |
||||
|
background-color: #003366 !important; |
||||
|
color: white; |
||||
|
text-align: center !important; |
||||
|
} |
||||
|
|
||||
|
.page-header-title h4 { |
||||
|
margin: 20px 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div class="page-header-title"> |
||||
|
<h4>Flagpoles Home Page:</h4> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container mt-4"> |
||||
|
<div class="card p-3 shadow-sm"> |
||||
|
|
||||
|
<div class="row g-3 align-items-end mb-3 justify-content-center"> |
||||
|
|
||||
|
<div class="col-auto"> |
||||
|
<button type="button" class="btn btn-primary" id="temporarybtn" |
||||
|
onclick="location.href='/commissionerreview/flagpoles/temporary'">Temporary Request</button> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<button type="button" class="btn btn-primary" id="permanentbtn" |
||||
|
onclick="location.href='/commissionerreview/flagpoles/permanent'">Permanent Request</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="pageLoader" |
||||
|
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 9999; text-align: center;"> |
||||
|
<div style="position: relative; top: 50%; transform: translateY(-50%);"> |
||||
|
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"> |
||||
|
<span class="sr-only"></span> |
||||
|
</div> |
||||
|
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Details Modal --> |
||||
|
|
||||
|
|
||||
|
<!-- 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://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script> |
||||
|
|
||||
|
<script> |
||||
|
|
||||
|
|
||||
|
let dataTable; |
||||
|
|
||||
|
function initDataTable() { |
||||
|
dataTable = $('#reportTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
paging: false, |
||||
|
searching: false, |
||||
|
ordering: false, // Let SQL handle ordering by Zone |
||||
|
info: false, |
||||
|
buttons: [ |
||||
|
{ |
||||
|
extend: 'excelHtml5', |
||||
|
title: 'Flagpoles_Abstract_Report', |
||||
|
text: 'Download Excel', |
||||
|
className: 'btn btn-success btn-sm' |
||||
|
}, |
||||
|
{ |
||||
|
extend: 'pdfHtml5', |
||||
|
title: 'Flagpoles_Abstract_Report', |
||||
|
text: 'Download PDF', |
||||
|
className: 'btn btn-danger btn-sm', |
||||
|
orientation: 'portrait', |
||||
|
pageSize: 'A4' |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
let currentType = 'OVERALL'; |
||||
|
|
||||
|
function loadReport(type) { |
||||
|
if (type) { |
||||
|
currentType = type; |
||||
|
} else { |
||||
|
if (!currentType) currentType = 'OVERALL'; |
||||
|
} |
||||
|
|
||||
|
const fromDate = document.getElementById("fromDate").value; |
||||
|
const toDate = document.getElementById("toDate").value; |
||||
|
|
||||
|
// Update UI to show selected filter (optional visual cue) |
||||
|
// Reset colors |
||||
|
$('.card-body .col-md-4').removeClass('bg-light'); |
||||
|
// Highlight selected |
||||
|
if (currentType === 'OVERALL') $('.card-body .col-md-4:eq(0)').addClass('bg-light'); |
||||
|
if (currentType === 'TEMPORARY') $('.card-body .col-md-4:eq(1)').addClass('bg-light'); |
||||
|
if (currentType === 'PERMANENT') $('.card-body .col-md-4:eq(2)').addClass('bg-light'); |
||||
|
|
||||
|
$('#pageLoader').show(); |
||||
|
|
||||
|
let url = `/commissionerreview/api/flagpoles/abstract?fromDate=${fromDate}&toDate=${toDate}`; |
||||
|
if (currentType && currentType !== 'OVERALL') { |
||||
|
url += `&applicationType=${currentType}`; |
||||
|
} |
||||
|
|
||||
|
fetch(url) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
$('#pageLoader').hide(); |
||||
|
if (json.status === "success") { |
||||
|
populateTable(json.data); |
||||
|
if (json.summary) { |
||||
|
$('#tempCount').text(json.summary.TEMPORARY || 0); |
||||
|
$('#permCount').text(json.summary.PERMANENT || 0); |
||||
|
$('#overallCount').text(json.summary.OVERALL || 0); |
||||
|
} else { |
||||
|
if (!type) { // Only zero out if not maintaining state, but here we keep summary if just filtering |
||||
|
// actually backend returns summary always so update it matches current date range |
||||
|
$('#tempCount').text(0); |
||||
|
$('#permCount').text(0); |
||||
|
$('#overallCount').text(0); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
alert("Error: " + json.message); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
$('#pageLoader').hide(); |
||||
|
console.error("Fetch Error:", err); |
||||
|
alert("Failed to load data"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function populateTable(data) { |
||||
|
dataTable.clear(); |
||||
|
|
||||
|
data.forEach(row => { |
||||
|
dataTable.row.add([ |
||||
|
row.zone, |
||||
|
`<a href="#" onclick="viewDetails('${row.zone}', 'Total')">${row.total_requests}</a>`, |
||||
|
`<a href="#" onclick="viewDetails('${row.zone}', 'Approved')">${row.approved_count}</a>`, |
||||
|
`<a href="#" onclick="viewDetails('${row.zone}', 'Rejected')">${row.rejected_count}</a>` |
||||
|
]); |
||||
|
}); |
||||
|
|
||||
|
dataTable.draw(); |
||||
|
} |
||||
|
|
||||
|
function viewDetails(zone, status) { |
||||
|
const fromDate = document.getElementById("fromDate").value; |
||||
|
const toDate = document.getElementById("toDate").value; |
||||
|
|
||||
|
$('#detailsModalLabel').text(`${status} Requests for zone: ${zone}`); |
||||
|
$('#detailsPageLoader').show(); |
||||
|
// Clear previous table data |
||||
|
if ($.fn.DataTable.isDataTable('#detailsTable')) { |
||||
|
$('#detailsTable').DataTable().destroy(); |
||||
|
} |
||||
|
$('#detailsTableBody').empty(); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('detailsModal')).show(); |
||||
|
|
||||
|
let url = `/commissionerreview/api/flagpoles/details?zone=${zone}&status=${status}&fromDate=${fromDate}&toDate=${toDate}`; |
||||
|
if (currentType && currentType !== 'OVERALL') { |
||||
|
url += `&applicationType=${currentType}`; |
||||
|
} |
||||
|
|
||||
|
fetch(url) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
$('#detailsPageLoader').hide(); |
||||
|
if (json.status === "success") { |
||||
|
populateDetailsTable(json.data); |
||||
|
} else { |
||||
|
alert("Error: " + json.message); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
$('#detailsPageLoader').hide(); |
||||
|
console.error("Fetch Error:", err); |
||||
|
alert("Failed to load details"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function formatValue(val) { |
||||
|
if (val === null || val === undefined || val === 'null' || val === '') { |
||||
|
return '-'; |
||||
|
} |
||||
|
return val; |
||||
|
} |
||||
|
|
||||
|
function populateDetailsTable(data) { |
||||
|
const tbody = $('#detailsTableBody'); |
||||
|
tbody.empty(); |
||||
|
// <td>${formatValue(row.application_option)}</td> |
||||
|
// <td>${formatValue(row.installation_type)}</td> |
||||
|
data.forEach((row, index) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.refid)}</td> |
||||
|
<td>${formatValue(row.applicant_name)}</td> |
||||
|
<td>${formatValue(row.mobile_number)}</td> |
||||
|
<td>${formatValue(row.application_type)}</td> |
||||
|
|
||||
|
<td>${formatValue(row.event_desc)}</td> |
||||
|
<td>${formatValue(row.event_date)}</td> |
||||
|
<td>${formatValue(row.final_status)}</td> |
||||
|
<td> |
||||
|
<button class="btn btn-info btn-sm" onclick="viewStreetDetails(${row.id}, '${row.refid}')"> |
||||
|
View Streets |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
var table = $('#detailsTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
scrollX: true, |
||||
|
buttons: [ |
||||
|
'excelHtml5', |
||||
|
'pdfHtml5' |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
// Adjust columns after small delay to ensure modal transition ends |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
} |
||||
|
|
||||
|
function viewStreetDetails(requestId, refId) { |
||||
|
$('#streetModalLabel').text(`Street Details for Request: ${refId}`); |
||||
|
|
||||
|
// Show modal |
||||
|
const modal = new bootstrap.Modal(document.getElementById('streetModal')); |
||||
|
modal.show(); |
||||
|
|
||||
|
// Destroy existing DataTable if it exists |
||||
|
if ($.fn.DataTable.isDataTable('#streetTable')) { |
||||
|
$('#streetTable').DataTable().destroy(); |
||||
|
} |
||||
|
|
||||
|
const tbody = $('#streetTableBody'); |
||||
|
tbody.empty(); |
||||
|
tbody.html('<tr><td colspan="6" class="text-center">Loading...</td></tr>'); |
||||
|
|
||||
|
fetch(`/commissionerreview/api/flagpoles/street-details?requestId=${requestId}`) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
tbody.empty(); |
||||
|
if (json.status === "success" && json.data.length > 0) { |
||||
|
json.data.forEach((row, index) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.zone)}</td> |
||||
|
<td>${formatValue(row.ward)}</td> |
||||
|
<td>${formatValue(row.street_name)}</td> |
||||
|
<td>${formatValue(row.no_of_poles)}</td> |
||||
|
<td>${formatValue(row.no_of_statue)}</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
// Initialize DataTable |
||||
|
var table = $('#streetTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
scrollX: true, |
||||
|
buttons: [ |
||||
|
'excelHtml5', |
||||
|
'pdfHtml5' |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
// Adjust columns |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
} else { |
||||
|
tbody.html('<tr><td colspan="6" class="text-center">No street details found</td></tr>'); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Fetch Error:", err); |
||||
|
tbody.html('<tr><td colspan="6" class="text-center text-danger">Failed to load data</td></tr>'); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
$(document).ready(function () { |
||||
|
console.log("Document ready, initializing..."); |
||||
|
initDataTable(); |
||||
|
|
||||
|
// Bind search button click |
||||
|
$('#searchBtn').click(function (e) { |
||||
|
e.preventDefault(); |
||||
|
loadReport(); |
||||
|
}); |
||||
|
|
||||
|
// Initial load |
||||
|
loadReport(); |
||||
|
|
||||
|
// Fix DataTable alignment in modals |
||||
|
$('#detailsModal, #streetModal').on('shown.bs.modal', function () { |
||||
|
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); |
||||
|
}); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,672 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org"> |
||||
|
|
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<title>Permanent Flagpole Request Report</title> |
||||
|
|
||||
|
<!-- Bootstrap 5 CSS --> |
||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.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> |
||||
|
table.dataTable th, |
||||
|
table.dataTable td { |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
border: 1px solid #000; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th, |
||||
|
.table-dark th { |
||||
|
background-color: #003366 !important; |
||||
|
color: white; |
||||
|
text-align: center !important; |
||||
|
} |
||||
|
|
||||
|
.page-header-title h4 { |
||||
|
margin: 20px 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.clickable-card { |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.2s; |
||||
|
} |
||||
|
|
||||
|
.clickable-card:hover { |
||||
|
background-color: #e2e6ea; |
||||
|
} |
||||
|
|
||||
|
.card-active { |
||||
|
background-color: #e2e6ea; |
||||
|
border: 2px solid #0d6efd !important; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
|
||||
|
|
||||
|
<div class="container mt-4"> |
||||
|
<div class="card p-3 shadow-sm"> |
||||
|
<div class="text-center mb-4"> |
||||
|
<h4 class="fw-bold text-primary">Permanent Flagpole Request Report</h4> |
||||
|
<div class="d-flex justify-content-center align-items-center gap-2 mt-2"> |
||||
|
<label class="fw-bold">Application Type:</label> |
||||
|
<select class="form-select form-select-sm" style="width: auto;" |
||||
|
onchange="if(this.value) window.location.href=this.value;"> |
||||
|
<option value="/commissionerreview/flagpolereport">Temporary</option> |
||||
|
<option value="/commissionerreview/flagpoles/permanent" selected>Permanent</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Responsive Filter Section --> |
||||
|
<div class="row g-2 mb-3 justify-content-center justify-content-md-end align-items-end"> |
||||
|
<div class="col-6 col-md-auto"> |
||||
|
<label class="form-label fw-bold small">From Date</label> |
||||
|
<input type="date" id="fromDate" class="form-control form-control-sm"> |
||||
|
</div> |
||||
|
<div class="col-6 col-md-auto"> |
||||
|
<label class="form-label fw-bold small">To Date</label> |
||||
|
<input type="date" id="toDate" class="form-control form-control-sm"> |
||||
|
</div> |
||||
|
<div class="col-12 col-md-auto"> |
||||
|
<div class="d-flex gap-2"> |
||||
|
<button class="btn btn-primary btn-sm flex-grow-1 flex-md-grow-0" |
||||
|
onclick="filterByDate()">Search</button> |
||||
|
<button class="btn btn-outline-secondary btn-sm flex-grow-1 flex-md-grow-0" |
||||
|
onclick="resetDateFilter()">Reset</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Summary Cards --> |
||||
|
<div class="card mb-3 shadow-sm border-primary"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row text-center"> |
||||
|
<div id="card-all" class="col-6 col-md-3 border-end clickable-card card-active" |
||||
|
onclick="filterReport('ALL')"> |
||||
|
<h6 class="text-primary fw-bold">Total Requests</h6> |
||||
|
<h3 id="totalRequests">0</h3> |
||||
|
</div> |
||||
|
<div id="card-approved" class="col-6 col-md-3 border-end clickable-card" |
||||
|
onclick="filterReport('APPROVED')"> |
||||
|
<h6 class="text-success fw-bold">Approved</h6> |
||||
|
<h3 id="totalApproved">0</h3> |
||||
|
</div> |
||||
|
<div id="card-rejected" class="col-6 col-md-3 border-end clickable-card" |
||||
|
onclick="filterReport('REJECTED')"> |
||||
|
<h6 class="text-danger fw-bold">Rejected</h6> |
||||
|
<h3 id="totalRejected">0</h3> |
||||
|
</div> |
||||
|
<div class="col-6 col-md-3"> |
||||
|
<h6 class="text-warning fw-bold">Total Amount Earned</h6> |
||||
|
<h3 id="totalAmount">0</h3> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="table-responsive"> |
||||
|
<table id="reportTable" class="table table-bordered table-hover mt-3 text-center"> |
||||
|
<thead class="table-primary"> |
||||
|
<tr> |
||||
|
<th class="text-center">Zone</th> |
||||
|
<th class="text-center">Total Requests</th> |
||||
|
<th class="text-center">Approved</th> |
||||
|
<th class="text-center">Rejected</th> |
||||
|
<th class="text-center">Total Amount</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="reportTableBody"> |
||||
|
<!-- Data will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="pageLoader" |
||||
|
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 9999; text-align: center;"> |
||||
|
<div style="position: relative; top: 50%; transform: translateY(-50%);"> |
||||
|
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"> |
||||
|
<span class="sr-only"></span> |
||||
|
</div> |
||||
|
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Details Modal --> |
||||
|
<div class="modal fade" id="detailsModal" tabindex="-1" aria-labelledby="detailsModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-xl"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="detailsModalLabel">Request Details</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table id="detailsTable" class="table table-bordered table-striped" style="width:100%"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Ref ID</th> |
||||
|
<th>Applicant Name</th> |
||||
|
<th>Mobile</th> |
||||
|
<th>Application Type</th> |
||||
|
<th>Event Desc</th> |
||||
|
<th>Event Date</th> |
||||
|
<th>Total Cost</th> |
||||
|
<th>Status</th> |
||||
|
<th>History</th> |
||||
|
<th>Uploaded Images</th> |
||||
|
<th>Action</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="detailsTableBody"> |
||||
|
<!-- Details will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Street Details Modal --> |
||||
|
<div class="modal fade" id="streetModal" tabindex="-1" aria-labelledby="streetModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="streetModalLabel">Street Details</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table id="streetTable" class="table table-bordered table-striped" width="100%"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Zone</th> |
||||
|
<th>Ward</th> |
||||
|
<th>Street Name</th> |
||||
|
<th>Poles</th> |
||||
|
<th>Statues</th> |
||||
|
<th>Street Cost</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="streetTableBody"> |
||||
|
<!-- Streets will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- History Modal --> |
||||
|
<div class="modal fade" id="historyModal" tabindex="-1" aria-labelledby="historyModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="historyModalLabel">Application History</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table class="table table-bordered table-striped"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Status</th> |
||||
|
<th>Remarks</th> |
||||
|
<th>Action Date</th> |
||||
|
<th>Updated By</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="historyTableBody"> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Documents Modal --> |
||||
|
<div class="modal fade" id="documentsModal" tabindex="-1" aria-labelledby="documentsModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="documentsModalLabel">Uploaded Documents</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body" id="documentsModalBody"> |
||||
|
<!-- Images/Links will be populated here --> |
||||
|
</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://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script> |
||||
|
|
||||
|
<script> |
||||
|
let dataTable; |
||||
|
let allRequests = []; // Store fetched data |
||||
|
const APPLICATION_TYPE_ID = 2; // 1 for Temporary, 2 for Permanent |
||||
|
|
||||
|
function initDataTable() { |
||||
|
dataTable = $('#reportTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
paging: false, |
||||
|
searching: false, |
||||
|
ordering: true, |
||||
|
info: false, |
||||
|
buttons: [ |
||||
|
{ extend: 'excelHtml5', title: 'Permanent_Flagpole_Report', text: 'Download Excel', className: 'btn btn-success btn-sm' }, |
||||
|
{ extend: 'pdfHtml5', title: 'Permanent_Flagpole_Report', text: 'Download PDF', className: 'btn btn-danger btn-sm', orientation: 'portrait', pageSize: 'A4' } |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function loadReport(startDate = '', endDate = '') { |
||||
|
$('#pageLoader').show(); |
||||
|
|
||||
|
let url = '/commissionerreview/api/flagpoles/admin-reports'; |
||||
|
if (startDate && endDate) { |
||||
|
url += `?startDate=${startDate}&endDate=${endDate}`; |
||||
|
} |
||||
|
|
||||
|
fetch(url) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
$('#pageLoader').hide(); |
||||
|
if (json.status === "success") { |
||||
|
if (json.count === 0) { |
||||
|
allRequests = []; |
||||
|
processAndPopulate([]); |
||||
|
return; |
||||
|
} |
||||
|
if (json.data) { |
||||
|
// Filter for Permanent Requests |
||||
|
allRequests = json.data.filter(req => req.application_type_id === APPLICATION_TYPE_ID); |
||||
|
processAndPopulate(allRequests); |
||||
|
} |
||||
|
} else { |
||||
|
alert("Error: " + json.message); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
$('#pageLoader').hide(); |
||||
|
console.error("Fetch Error:", err); |
||||
|
alert("Failed to load data"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function filterByDate() { |
||||
|
const fromDateStr = $('#fromDate').val(); |
||||
|
const toDateStr = $('#toDate').val(); |
||||
|
|
||||
|
if (!fromDateStr || !toDateStr) { |
||||
|
alert("Please select both From and To dates"); |
||||
|
return; |
||||
|
} |
||||
|
loadReport(fromDateStr, toDateStr); |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
function resetDateFilter() { |
||||
|
$('#fromDate').val(''); |
||||
|
$('#toDate').val(''); |
||||
|
loadReport(); |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
function filterReport(type) { |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
if (type === 'ALL') { |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} else if (type === 'APPROVED') { |
||||
|
$('#card-approved').addClass('card-active'); |
||||
|
} else if (type === 'REJECTED') { |
||||
|
$('#card-rejected').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
let filtered = []; |
||||
|
if (type === 'ALL') { |
||||
|
filtered = allRequests; |
||||
|
} else if (type === 'APPROVED') { |
||||
|
filtered = allRequests.filter(req => determineStatus(req) === 'APPROVED'); |
||||
|
} else if (type === 'REJECTED') { |
||||
|
filtered = allRequests.filter(req => determineStatus(req) === 'REJECTED'); |
||||
|
} |
||||
|
processAndPopulate(filtered, false); |
||||
|
} |
||||
|
|
||||
|
function processAndPopulate(requests, updateSummary = true) { |
||||
|
// Aggregate data by Zone |
||||
|
const zoneMap = new Map(); |
||||
|
|
||||
|
// Initialize for all 15 zones |
||||
|
for (let i = 1; i <= 15; i++) { |
||||
|
let zoneKey = i.toString().padStart(2, '0'); |
||||
|
zoneMap.set(zoneKey, { |
||||
|
zone: zoneKey, |
||||
|
total_requests: 0, |
||||
|
approved_count: 0, |
||||
|
rejected_count: 0, |
||||
|
total_amount: 0, |
||||
|
requests: [] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
let totalReq = 0, totalApp = 0, totalRej = 0, totalAmt = 0; |
||||
|
|
||||
|
requests.forEach(req => { |
||||
|
// Determine zone from streets array (taking first street's zone as primary) |
||||
|
let zone = "Unknown"; |
||||
|
if (req.streets && req.streets.length > 0 && req.streets[0].zone) { |
||||
|
zone = req.streets[0].zone; |
||||
|
// Normalize to 2 digits string if numeric |
||||
|
if (/^\d+$/.test(zone)) { |
||||
|
zone = parseInt(zone).toString().padStart(2, '0'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!zoneMap.has(zone)) { |
||||
|
// Start with 0 values for unknown/new zones |
||||
|
zoneMap.set(zone, { |
||||
|
zone: zone, |
||||
|
total_requests: 0, |
||||
|
approved_count: 0, |
||||
|
rejected_count: 0, |
||||
|
total_amount: 0, |
||||
|
requests: [] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
let isApproved = false; |
||||
|
let isRejected = false; |
||||
|
|
||||
|
let status = determineStatus(req); |
||||
|
|
||||
|
if (status === 'APPROVED') isApproved = true; |
||||
|
if (status === 'REJECTED') isRejected = true; |
||||
|
|
||||
|
let entry = zoneMap.get(zone); |
||||
|
entry.total_requests++; |
||||
|
entry.total_amount += (req.total_cost || 0); |
||||
|
entry.requests.push(req); // Store request for drilldown |
||||
|
|
||||
|
if (isApproved) entry.approved_count++; |
||||
|
if (isRejected) entry.rejected_count++; |
||||
|
|
||||
|
// Global totals |
||||
|
totalReq++; |
||||
|
totalAmt += (req.total_cost || 0); |
||||
|
if (isApproved) totalApp++; |
||||
|
if (isRejected) totalRej++; |
||||
|
}); |
||||
|
|
||||
|
if (updateSummary) { |
||||
|
// Update Summary Cards |
||||
|
$('#totalRequests').text(totalReq); |
||||
|
$('#totalApproved').text(totalApp); |
||||
|
$('#totalRejected').text(totalRej); |
||||
|
$('#totalAmount').text(totalAmt.toFixed(2)); |
||||
|
} |
||||
|
|
||||
|
// Populate Main Table |
||||
|
dataTable.clear(); |
||||
|
|
||||
|
// Convert map to array. |
||||
|
const reportData = Array.from(zoneMap.values()); |
||||
|
|
||||
|
// Optional: Sort by Zone ID if numeric |
||||
|
reportData.sort((a, b) => { |
||||
|
return (parseInt(a.zone) || 0) - (parseInt(b.zone) || 0); |
||||
|
}); |
||||
|
|
||||
|
reportData.forEach(row => { |
||||
|
dataTable.row.add([ |
||||
|
row.zone, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Total')">${row.total_requests}</a>`, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Approved')">${row.approved_count}</a>`, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Rejected')">${row.rejected_count}</a>`, |
||||
|
row.total_amount.toFixed(2) |
||||
|
]); |
||||
|
}); |
||||
|
dataTable.draw(); |
||||
|
} |
||||
|
|
||||
|
function determineStatus(req) { |
||||
|
// Logic to determine status from request object |
||||
|
// Check history for latest status |
||||
|
if (req.history && req.history.length > 0) { |
||||
|
const lastHistory = req.history[req.history.length - 1]; |
||||
|
return lastHistory.status; // "APPROVED" / "REJECTED" |
||||
|
} |
||||
|
return "PENDING"; |
||||
|
} |
||||
|
|
||||
|
function viewDetails(zone, type) { |
||||
|
$('#detailsPageLoader').show(); |
||||
|
|
||||
|
const filtered = allRequests.filter(req => { |
||||
|
let reqZone = (req.streets && req.streets.length > 0) ? req.streets[0].zone : "Unknown"; |
||||
|
// Normalize |
||||
|
if (/^\d+$/.test(reqZone)) { |
||||
|
reqZone = parseInt(reqZone).toString().padStart(2, '0'); |
||||
|
} |
||||
|
|
||||
|
if (reqZone !== zone) return false; |
||||
|
|
||||
|
let status = determineStatus(req); |
||||
|
if (type === 'Approved' && status !== 'APPROVED') return false; |
||||
|
if (type === 'Rejected' && status !== 'REJECTED') return false; |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
|
||||
|
$('#detailsModalLabel').text(`${type} Requests for Zone: ${zone}`); |
||||
|
|
||||
|
// Populate Modal Table |
||||
|
const tbody = $('#detailsTableBody'); |
||||
|
|
||||
|
if ($.fn.DataTable.isDataTable('#detailsTable')) { |
||||
|
$('#detailsTable').DataTable().destroy(); |
||||
|
} |
||||
|
tbody.empty(); |
||||
|
|
||||
|
filtered.forEach((row, index) => { |
||||
|
let status = determineStatus(row); |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.refid)}</td> |
||||
|
<td>${formatValue(row.applicant_name)}</td> |
||||
|
<td>${formatValue(row.mobile_number)}</td> |
||||
|
<td>${formatValue(row.application_type_name)}</td> |
||||
|
<td>${formatValue(row.event_desc)}</td> |
||||
|
<td>${formatValue(row.event_date ? row.event_date.split('T')[0] : '')}</td> |
||||
|
<td>${formatValue(row.total_cost)}</td> |
||||
|
<td>${status}</td> |
||||
|
<td> |
||||
|
<button class="btn btn-secondary btn-sm" onclick="viewHistory('${row.refid}')">View History</button> |
||||
|
</td> |
||||
|
<td> |
||||
|
<button class="btn btn-primary btn-sm" onclick="viewDocuments('${row.refid}')">View Documents</button> |
||||
|
</td> |
||||
|
<td> |
||||
|
<button class="btn btn-info btn-sm" onclick="viewStreetDetails('${row.refid}')"> |
||||
|
View Streets |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('detailsModal')).show(); |
||||
|
|
||||
|
var table = $('#detailsTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
buttons: ['excelHtml5', 'pdfHtml5'] |
||||
|
}); |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
|
||||
|
$('#detailsPageLoader').hide(); |
||||
|
} |
||||
|
|
||||
|
function viewStreetDetails(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); // refid might be string/int |
||||
|
if (!req) return; |
||||
|
|
||||
|
$('#streetModalLabel').text(`Street Details for Request: ${refId}`); |
||||
|
|
||||
|
const tbody = $('#streetTableBody'); |
||||
|
|
||||
|
if ($.fn.DataTable.isDataTable('#streetTable')) { |
||||
|
$('#streetTable').DataTable().destroy(); |
||||
|
} |
||||
|
tbody.empty(); |
||||
|
|
||||
|
if (req.streets && req.streets.length > 0) { |
||||
|
req.streets.forEach((row, index) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.zone)}</td> |
||||
|
<td>${formatValue(row.ward)}</td> |
||||
|
<td>${formatValue(row.street_name)}</td> |
||||
|
<td>${formatValue(row.no_of_poles)}</td> |
||||
|
<td>${formatValue(row.no_of_statue)}</td> |
||||
|
<td>${formatValue(row.street_cost)}</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
} else { |
||||
|
tbody.html('<tr><td colspan="7" class="text-center">No street details found</td></tr>'); |
||||
|
} |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('streetModal')).show(); |
||||
|
|
||||
|
var table = $('#streetTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
buttons: ['excelHtml5', 'pdfHtml5'] |
||||
|
}); |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
} |
||||
|
|
||||
|
function viewHistory(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); |
||||
|
if (!req || !req.history) { |
||||
|
alert("No history found"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
$('#historyModalLabel').text(`History for Request: ${refId}`); |
||||
|
const tbody = $('#historyTableBody'); |
||||
|
tbody.empty(); |
||||
|
|
||||
|
req.history.forEach((h, i) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${i + 1}</td> |
||||
|
<td>${h.status || '-'}</td> |
||||
|
<td>${h.remarks || '-'}</td> |
||||
|
<td>${h.action_date ? h.action_date.replace('T', ' ') : '-'}</td> |
||||
|
<td>${h.department_name || h.updated_by || '-'}</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('historyModal')).show(); |
||||
|
} |
||||
|
|
||||
|
function viewDocuments(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); |
||||
|
if (!req) return; |
||||
|
|
||||
|
$('#documentsModalLabel').text(`Documents for Request: ${refId}`); |
||||
|
const body = $('#documentsModalBody'); |
||||
|
body.empty(); |
||||
|
|
||||
|
let found = false; |
||||
|
// For Permanent, show all potential image fields |
||||
|
// Heuristic: key contains 'path', 'url', 'image', 'photo' and value looks like URL or path |
||||
|
Object.keys(req).forEach(key => { |
||||
|
const val = req[key]; |
||||
|
// Filter for string values that look like file paths or params |
||||
|
if (val && typeof val === 'string' && (val.startsWith('http') || val.match(/\.(jpg|jpeg|png|pdf)$/i) || key.includes('path') || key.includes('url') || key.includes('image'))) { |
||||
|
|
||||
|
// Skip some obviously non-image URL fields if any (like 'api_url' etc, but assume data fields) |
||||
|
// Also skip short strings that might be misidentified |
||||
|
if (val.length < 5) return; |
||||
|
|
||||
|
found = true; |
||||
|
body.append(` |
||||
|
<div class="mb-3"> |
||||
|
<h6>${key.replace(/_/g, ' ').toUpperCase()}</h6> |
||||
|
<a href="${val}" target="_blank" class="btn btn-outline-primary">View Document</a> |
||||
|
</div> |
||||
|
`); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!found) { |
||||
|
body.html('<p class="text-center text-muted">No documents found.</p>'); |
||||
|
} |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('documentsModal')).show(); |
||||
|
} |
||||
|
|
||||
|
function formatValue(val) { |
||||
|
if (val === null || val === undefined || val === 'null' || val === '') { |
||||
|
return '-'; |
||||
|
} |
||||
|
return val; |
||||
|
} |
||||
|
|
||||
|
$(document).ready(function () { |
||||
|
initDataTable(); |
||||
|
loadReport(); |
||||
|
|
||||
|
$('#detailsModal, #streetModal').on('shown.bs.modal', function () { |
||||
|
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); |
||||
|
}); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,676 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org"> |
||||
|
|
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<title>Temporary Flagpole Request Report</title> |
||||
|
|
||||
|
<!-- Bootstrap 5 CSS --> |
||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.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> |
||||
|
table.dataTable th, |
||||
|
table.dataTable td { |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
border: 1px solid #000; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th, |
||||
|
.table-dark th { |
||||
|
background-color: #003366 !important; |
||||
|
color: white; |
||||
|
text-align: center !important; |
||||
|
} |
||||
|
|
||||
|
.page-header-title h4 { |
||||
|
margin: 20px 0; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.clickable-card { |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.2s; |
||||
|
} |
||||
|
|
||||
|
.clickable-card:hover { |
||||
|
background-color: #e2e6ea; |
||||
|
} |
||||
|
|
||||
|
.card-active { |
||||
|
background-color: #e2e6ea; |
||||
|
border: 2px solid #0d6efd !important; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
|
||||
|
|
||||
|
<div class="container mt-4"> |
||||
|
<div class="card p-3 shadow-sm"> |
||||
|
<div class="text-center mb-4"> |
||||
|
<h4 class="fw-bold text-primary">Temporary Flagpole Request Report</h4> |
||||
|
<div class="d-flex justify-content-center align-items-center gap-2 mt-2"> |
||||
|
<label class="fw-bold">Application Type:</label> |
||||
|
<select class="form-select form-select-sm" style="width: auto;" |
||||
|
onchange="if(this.value) window.location.href=this.value;"> |
||||
|
<option value="/commissionerreview/flagpolereport" selected>Temporary</option> |
||||
|
<option value="/commissionerreview/flagpoles/permanent">Permanent</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- Responsive Filter Section --> |
||||
|
<div class="row g-2 mb-3 justify-content-center justify-content-md-end align-items-end"> |
||||
|
<div class="col-6 col-md-auto"> |
||||
|
<label class="form-label fw-bold small">From Date</label> |
||||
|
<input type="date" id="fromDate" class="form-control form-control-sm"> |
||||
|
</div> |
||||
|
<div class="col-6 col-md-auto"> |
||||
|
<label class="form-label fw-bold small">To Date</label> |
||||
|
<input type="date" id="toDate" class="form-control form-control-sm"> |
||||
|
</div> |
||||
|
<div class="col-12 col-md-auto"> |
||||
|
<div class="d-flex gap-2"> |
||||
|
<button class="btn btn-primary btn-sm flex-grow-1 flex-md-grow-0" |
||||
|
onclick="filterByDate()">Search</button> |
||||
|
<button class="btn btn-outline-secondary btn-sm flex-grow-1 flex-md-grow-0" |
||||
|
onclick="resetDateFilter()">Reset</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Summary Cards --> |
||||
|
<div class="card mb-3 shadow-sm border-primary"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row text-center"> |
||||
|
<div id="card-all" class="col-6 col-md-3 border-end clickable-card card-active" |
||||
|
onclick="filterReport('ALL')"> |
||||
|
<h6 class="text-primary fw-bold">Total Requests</h6> |
||||
|
<h3 id="totalRequests">0</h3> |
||||
|
</div> |
||||
|
<div id="card-approved" class="col-6 col-md-3 border-end clickable-card" |
||||
|
onclick="filterReport('APPROVED')"> |
||||
|
<h6 class="text-success fw-bold">Approved</h6> |
||||
|
<h3 id="totalApproved">0</h3> |
||||
|
</div> |
||||
|
<div id="card-rejected" class="col-6 col-md-3 border-end clickable-card" |
||||
|
onclick="filterReport('REJECTED')"> |
||||
|
<h6 class="text-danger fw-bold">Rejected</h6> |
||||
|
<h3 id="totalRejected">0</h3> |
||||
|
</div> |
||||
|
<div class="col-6 col-md-3"> |
||||
|
<h6 class="text-warning fw-bold">Total Amount Earned</h6> |
||||
|
<h3 id="totalAmount">0</h3> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="table-responsive"> |
||||
|
<table id="reportTable" class="table table-bordered table-hover mt-3 text-center"> |
||||
|
<thead class="table-primary"> |
||||
|
<tr> |
||||
|
<th class="text-center">Zone</th> |
||||
|
<th class="text-center">Total Requests</th> |
||||
|
<th class="text-center">Approved</th> |
||||
|
<th class="text-center">Rejected</th> |
||||
|
<th class="text-center">Total Amount</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="reportTableBody"> |
||||
|
<!-- Data will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="pageLoader" |
||||
|
style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 9999; text-align: center;"> |
||||
|
<div style="position: relative; top: 50%; transform: translateY(-50%);"> |
||||
|
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"> |
||||
|
<span class="sr-only"></span> |
||||
|
</div> |
||||
|
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Details Modal --> |
||||
|
<div class="modal fade" id="detailsModal" tabindex="-1" aria-labelledby="detailsModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-xl"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="detailsModalLabel">Request Details</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table id="detailsTable" class="table table-bordered table-striped" style="width:100%"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Ref ID</th> |
||||
|
<th>Applicant Name</th> |
||||
|
<th>Mobile</th> |
||||
|
<th>Application Type</th> |
||||
|
<th>Event Desc</th> |
||||
|
<th>Event Date</th> |
||||
|
<th>Total Cost</th> |
||||
|
<th>Status</th> |
||||
|
<th>History</th> |
||||
|
<th>Uploaded Images</th> |
||||
|
<th>Action</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="detailsTableBody"> |
||||
|
<!-- Details will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Street Details Modal --> |
||||
|
<div class="modal fade" id="streetModal" tabindex="-1" aria-labelledby="streetModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="streetModalLabel">Street Details</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table id="streetTable" class="table table-bordered table-striped" width="100%"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Zone</th> |
||||
|
<th>Ward</th> |
||||
|
<th>Street Name</th> |
||||
|
<th>Poles</th> |
||||
|
<th>Statues</th> |
||||
|
<th>Street Cost</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="streetTableBody"> |
||||
|
<!-- Streets will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- History Modal --> |
||||
|
<div class="modal fade" id="historyModal" tabindex="-1" aria-labelledby="historyModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="historyModalLabel">Application History</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<div class="table-responsive"> |
||||
|
<table class="table table-bordered table-striped"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>S.No</th> |
||||
|
<th>Status</th> |
||||
|
<th>Remarks</th> |
||||
|
<th>Action Date</th> |
||||
|
<th>Updated By</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="historyTableBody"> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Documents Modal --> |
||||
|
<div class="modal fade" id="documentsModal" tabindex="-1" aria-labelledby="documentsModalLabel" aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-lg"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="documentsModalLabel">Uploaded Documents</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body" id="documentsModalBody"> |
||||
|
<!-- Images/Links will be populated here --> |
||||
|
</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://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script> |
||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script> |
||||
|
|
||||
|
<script> |
||||
|
let dataTable; |
||||
|
let allRequests = []; // Store fetched data |
||||
|
const APPLICATION_TYPE_ID = 1; // 1 for Temporary, 2 for Permanent |
||||
|
|
||||
|
function initDataTable() { |
||||
|
dataTable = $('#reportTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
paging: false, |
||||
|
searching: false, |
||||
|
ordering: true, |
||||
|
info: false, |
||||
|
buttons: [ |
||||
|
{ extend: 'excelHtml5', title: 'Temporary_Flagpole_Report', text: 'Download Excel', className: 'btn btn-success btn-sm' }, |
||||
|
{ extend: 'pdfHtml5', title: 'Temporary_Flagpole_Report', text: 'Download PDF', className: 'btn btn-danger btn-sm', orientation: 'portrait', pageSize: 'A4' } |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function loadReport(startDate = '', endDate = '') { |
||||
|
$('#pageLoader').show(); |
||||
|
|
||||
|
let url = '/commissionerreview/api/flagpoles/admin-reports'; |
||||
|
if (startDate && endDate) { |
||||
|
url += `?startDate=${startDate}&endDate=${endDate}`; |
||||
|
} |
||||
|
|
||||
|
fetch(url) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
$('#pageLoader').hide(); |
||||
|
if (json.status === "success") { |
||||
|
if (json.count === 0) { |
||||
|
allRequests = []; |
||||
|
processAndPopulate([]); |
||||
|
return; |
||||
|
} |
||||
|
if (json.data) { |
||||
|
// Filter for Temporary Requests |
||||
|
allRequests = json.data.filter(req => req.application_type_id === APPLICATION_TYPE_ID); |
||||
|
processAndPopulate(allRequests); |
||||
|
} |
||||
|
} else { |
||||
|
alert("Error: " + json.message); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
$('#pageLoader').hide(); |
||||
|
console.error("Fetch Error:", err); |
||||
|
alert("Failed to load data"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function filterByDate() { |
||||
|
const fromDateStr = $('#fromDate').val(); |
||||
|
const toDateStr = $('#toDate').val(); |
||||
|
|
||||
|
if (!fromDateStr || !toDateStr) { |
||||
|
alert("Please select both From and To dates"); |
||||
|
return; |
||||
|
} |
||||
|
loadReport(fromDateStr, toDateStr); |
||||
|
// Reset active status card to ALL |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
function resetDateFilter() { |
||||
|
$('#fromDate').val(''); |
||||
|
$('#toDate').val(''); |
||||
|
loadReport(); |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
function filterReport(type) { |
||||
|
$('.clickable-card').removeClass('card-active'); |
||||
|
if (type === 'ALL') { |
||||
|
$('#card-all').addClass('card-active'); |
||||
|
} else if (type === 'APPROVED') { |
||||
|
$('#card-approved').addClass('card-active'); |
||||
|
} else if (type === 'REJECTED') { |
||||
|
$('#card-rejected').addClass('card-active'); |
||||
|
} |
||||
|
|
||||
|
let filtered = []; |
||||
|
if (type === 'ALL') { |
||||
|
filtered = allRequests; |
||||
|
} else if (type === 'APPROVED') { |
||||
|
filtered = allRequests.filter(req => determineStatus(req) === 'APPROVED'); |
||||
|
} else if (type === 'REJECTED') { |
||||
|
filtered = allRequests.filter(req => determineStatus(req) === 'REJECTED'); |
||||
|
} |
||||
|
processAndPopulate(filtered, false); |
||||
|
} |
||||
|
|
||||
|
function processAndPopulate(requests, updateSummary = true) { |
||||
|
// Aggregate data by Zone |
||||
|
const zoneMap = new Map(); |
||||
|
|
||||
|
// Initialize for all 15 zones |
||||
|
for (let i = 1; i <= 15; i++) { |
||||
|
let zoneKey = i.toString().padStart(2, '0'); |
||||
|
zoneMap.set(zoneKey, { |
||||
|
zone: zoneKey, |
||||
|
total_requests: 0, |
||||
|
approved_count: 0, |
||||
|
rejected_count: 0, |
||||
|
total_amount: 0, |
||||
|
requests: [] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
let totalReq = 0, totalApp = 0, totalRej = 0, totalAmt = 0; |
||||
|
|
||||
|
requests.forEach(req => { |
||||
|
// Determine zone from streets array (taking first street's zone as primary) |
||||
|
let zone = "Unknown"; |
||||
|
if (req.streets && req.streets.length > 0 && req.streets[0].zone) { |
||||
|
zone = req.streets[0].zone; |
||||
|
// Normalize to 2 digits string if numeric |
||||
|
if (/^\d+$/.test(zone)) { |
||||
|
zone = parseInt(zone).toString().padStart(2, '0'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!zoneMap.has(zone)) { |
||||
|
// Start with 0 values for unknown/new zones |
||||
|
zoneMap.set(zone, { |
||||
|
zone: zone, |
||||
|
total_requests: 0, |
||||
|
approved_count: 0, |
||||
|
rejected_count: 0, |
||||
|
total_amount: 0, |
||||
|
requests: [] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
let isApproved = false; |
||||
|
let isRejected = false; |
||||
|
|
||||
|
// Using a helper to determine status from request object |
||||
|
let status = determineStatus(req); |
||||
|
|
||||
|
if (status === 'APPROVED') isApproved = true; |
||||
|
if (status === 'REJECTED') isRejected = true; |
||||
|
|
||||
|
let entry = zoneMap.get(zone); |
||||
|
entry.total_requests++; |
||||
|
entry.total_amount += (req.total_cost || 0); |
||||
|
entry.requests.push(req); // Store request for drilldown |
||||
|
|
||||
|
if (isApproved) entry.approved_count++; |
||||
|
if (isRejected) entry.rejected_count++; |
||||
|
|
||||
|
// Global totals |
||||
|
totalReq++; |
||||
|
totalAmt += (req.total_cost || 0); |
||||
|
if (isApproved) totalApp++; |
||||
|
if (isRejected) totalRej++; |
||||
|
}); |
||||
|
|
||||
|
if (updateSummary) { |
||||
|
// Update Summary Cards |
||||
|
$('#totalRequests').text(totalReq); |
||||
|
$('#totalApproved').text(totalApp); |
||||
|
$('#totalRejected').text(totalRej); |
||||
|
$('#totalAmount').text(totalAmt.toFixed(2)); |
||||
|
} |
||||
|
|
||||
|
// Populate Main Table |
||||
|
dataTable.clear(); |
||||
|
// Sort by Zone ? API does numeric zones usually. |
||||
|
// Convert map to array. |
||||
|
const reportData = Array.from(zoneMap.values()); |
||||
|
|
||||
|
// Optional: Sort by Zone ID if numeric |
||||
|
reportData.sort((a, b) => { |
||||
|
return (parseInt(a.zone) || 0) - (parseInt(b.zone) || 0); |
||||
|
}); |
||||
|
|
||||
|
reportData.forEach(row => { |
||||
|
dataTable.row.add([ |
||||
|
row.zone, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Total')">${row.total_requests}</a>`, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Approved')">${row.approved_count}</a>`, |
||||
|
`<a href="javascript:void(0)" onclick="viewDetails('${row.zone}', 'Rejected')">${row.rejected_count}</a>`, |
||||
|
row.total_amount.toFixed(2) |
||||
|
]); |
||||
|
}); |
||||
|
dataTable.draw(); |
||||
|
} |
||||
|
|
||||
|
function determineStatus(req) { |
||||
|
// Logic to determine status from request object |
||||
|
// Check history for latest status |
||||
|
if (req.history && req.history.length > 0) { |
||||
|
// Get latest history |
||||
|
// Assuming history is ordered? or sort by action_date if needed. |
||||
|
// Sample shows 14-02-2026 then 15-02-2026. |
||||
|
// Let's assume last in array is latest or check logic. |
||||
|
const lastHistory = req.history[req.history.length - 1]; |
||||
|
return lastHistory.status; // "APPROVED" / "REJECTED" |
||||
|
} |
||||
|
return "PENDING"; |
||||
|
} |
||||
|
|
||||
|
function viewDetails(zone, type) { |
||||
|
$('#detailsPageLoader').show(); |
||||
|
// Filter requests from allRequests or zoneMap |
||||
|
// I'll re-filter from allRequests to be safe or store in zoneMap. |
||||
|
// Using zoneMap data would be faster but I need to access it. AllRequests is global. |
||||
|
|
||||
|
const filtered = allRequests.filter(req => { |
||||
|
let reqZone = (req.streets && req.streets.length > 0) ? req.streets[0].zone : "Unknown"; |
||||
|
// Normalize |
||||
|
if (/^\d+$/.test(reqZone)) { |
||||
|
reqZone = parseInt(reqZone).toString().padStart(2, '0'); |
||||
|
} |
||||
|
|
||||
|
if (reqZone !== zone) return false; |
||||
|
|
||||
|
let status = determineStatus(req); |
||||
|
if (type === 'Approved' && status !== 'APPROVED') return false; |
||||
|
if (type === 'Rejected' && status !== 'REJECTED') return false; |
||||
|
|
||||
|
return true; |
||||
|
}); |
||||
|
|
||||
|
$('#detailsModalLabel').text(`${type} Requests for Zone: ${zone}`); |
||||
|
|
||||
|
// Populate Modal Table |
||||
|
const tbody = $('#detailsTableBody'); |
||||
|
|
||||
|
if ($.fn.DataTable.isDataTable('#detailsTable')) { |
||||
|
$('#detailsTable').DataTable().destroy(); |
||||
|
} |
||||
|
tbody.empty(); |
||||
|
|
||||
|
filtered.forEach((row, index) => { |
||||
|
let status = determineStatus(row); |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.refid)}</td> |
||||
|
<td>${formatValue(row.applicant_name)}</td> |
||||
|
<td>${formatValue(row.mobile_number)}</td> |
||||
|
<td>${formatValue(row.application_type_name)}</td> |
||||
|
<td>${formatValue(row.event_desc)}</td> |
||||
|
<td>${formatValue(row.event_date ? row.event_date.split('T')[0] : '')}</td> |
||||
|
<td>${formatValue(row.total_cost)}</td> |
||||
|
<td>${status}</td> |
||||
|
<td> |
||||
|
<button class="btn btn-secondary btn-sm" onclick="viewHistory('${row.refid}')">View History</button> |
||||
|
</td> |
||||
|
<td> |
||||
|
<button class="btn btn-primary btn-sm" onclick="viewDocuments('${row.refid}')">View Documents</button> |
||||
|
</td> |
||||
|
<td> |
||||
|
<button class="btn btn-info btn-sm" onclick="viewStreetDetails('${row.refid}')"> |
||||
|
View Streets |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('detailsModal')).show(); |
||||
|
|
||||
|
var table = $('#detailsTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
buttons: ['excelHtml5', 'pdfHtml5'] |
||||
|
}); |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
|
||||
|
$('#detailsPageLoader').hide(); |
||||
|
} |
||||
|
|
||||
|
function viewStreetDetails(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); // refid might be string/int |
||||
|
if (!req) return; |
||||
|
|
||||
|
$('#streetModalLabel').text(`Street Details for Request: ${refId}`); |
||||
|
|
||||
|
const tbody = $('#streetTableBody'); |
||||
|
|
||||
|
if ($.fn.DataTable.isDataTable('#streetTable')) { |
||||
|
$('#streetTable').DataTable().destroy(); |
||||
|
} |
||||
|
tbody.empty(); |
||||
|
|
||||
|
if (req.streets && req.streets.length > 0) { |
||||
|
req.streets.forEach((row, index) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${index + 1}</td> |
||||
|
<td>${formatValue(row.zone)}</td> |
||||
|
<td>${formatValue(row.ward)}</td> |
||||
|
<td>${formatValue(row.street_name)}</td> |
||||
|
<td>${formatValue(row.no_of_poles)}</td> |
||||
|
<td>${formatValue(row.no_of_statue)}</td> |
||||
|
<td>${formatValue(row.street_cost)}</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
} else { |
||||
|
tbody.html('<tr><td colspan="7" class="text-center">No street details found</td></tr>'); |
||||
|
} |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('streetModal')).show(); |
||||
|
|
||||
|
var table = $('#streetTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
buttons: ['excelHtml5', 'pdfHtml5'] |
||||
|
}); |
||||
|
setTimeout(function () { |
||||
|
table.columns.adjust().draw(); |
||||
|
}, 200); |
||||
|
} |
||||
|
|
||||
|
function viewHistory(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); |
||||
|
if (!req || !req.history) { |
||||
|
alert("No history found"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
$('#historyModalLabel').text(`History for Request: ${refId}`); |
||||
|
const tbody = $('#historyTableBody'); |
||||
|
tbody.empty(); |
||||
|
|
||||
|
req.history.forEach((h, i) => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<td>${i + 1}</td> |
||||
|
<td>${h.status || '-'}</td> |
||||
|
<td>${h.remarks || '-'}</td> |
||||
|
<td>${h.action_date ? h.action_date.replace('T', ' ') : '-'}</td> |
||||
|
<td>${h.department_name || h.updated_by || '-'}</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('historyModal')).show(); |
||||
|
} |
||||
|
|
||||
|
function viewDocuments(refId) { |
||||
|
const req = allRequests.find(r => r.refid == refId); |
||||
|
if (!req) return; |
||||
|
|
||||
|
$('#documentsModalLabel').text(`Documents for Request: ${refId}`); |
||||
|
const body = $('#documentsModalBody'); |
||||
|
body.empty(); |
||||
|
|
||||
|
let found = false; |
||||
|
// For Temporary, look for keys containing 'aadhaar' |
||||
|
Object.keys(req).forEach(key => { |
||||
|
if (key.toLowerCase().includes('aadhaar')) { |
||||
|
const val = req[key]; |
||||
|
if (val && (val.startsWith('http') || val.match(/\.(jpg|jpeg|png|pdf)$/i))) { |
||||
|
found = true; |
||||
|
body.append(` |
||||
|
<div class="mb-3"> |
||||
|
<h6>${key.replace(/_/g, ' ').toUpperCase()}</h6> |
||||
|
<a href="${val}" target="_blank" class="btn btn-outline-primary">View Document</a> |
||||
|
</div> |
||||
|
`); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!found) { |
||||
|
body.html('<p class="text-center text-muted">No Aadhaar documents found.</p>'); |
||||
|
} |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('documentsModal')).show(); |
||||
|
} |
||||
|
|
||||
|
function formatValue(val) { |
||||
|
if (val === null || val === undefined || val === 'null' || val === '') { |
||||
|
return '-'; |
||||
|
} |
||||
|
return val; |
||||
|
} |
||||
|
|
||||
|
$(document).ready(function () { |
||||
|
initDataTable(); |
||||
|
loadReport(); |
||||
|
|
||||
|
$('#detailsModal, #streetModal').on('shown.bs.modal', function () { |
||||
|
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); |
||||
|
}); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue