5 changed files with 716 additions and 1 deletions
-
22src/main/java/com/gcc/commissionerreview/configuration/DataSourceConfig.java
-
72src/main/java/com/gcc/commissionerreview/controller/FlagpolesApiController.java
-
6src/main/java/com/gcc/commissionerreview/controller/MainController.java
-
199src/main/java/com/gcc/commissionerreview/service/FlagpolesService.java
-
416src/main/resources/templates/modules/flagpoles/abstractreport.html
@ -0,0 +1,72 @@ |
|||||
|
package com.gcc.commissionerreview.controller; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
import org.springframework.ui.Model; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.servlet.ModelAndView; |
||||
|
import com.gcc.commissionerreview.service.FlagpolesService; |
||||
|
import java.util.Map; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/commissionerreview/api") |
||||
|
public class FlagpolesApiController { |
||||
|
|
||||
|
@Autowired |
||||
|
private FlagpolesService flagpolesService; |
||||
|
|
||||
|
@GetMapping("/flagpoles/abstract") |
||||
|
public Map<String, Object> getAbstractData( |
||||
|
@RequestParam(required = false) String fromDate, |
||||
|
@RequestParam(required = false) String toDate, |
||||
|
@RequestParam(required = false) String applicationType) { |
||||
|
try { |
||||
|
List<Map<String, Object>> data = flagpolesService.getZoneWiseRequestCounts(fromDate, toDate, |
||||
|
applicationType); |
||||
|
// Only fetch summary if no type filter or type is NULL (to avoid re-fetching |
||||
|
// summary when just filtering table, but front-end might want to keep it) |
||||
|
// Actually, keep fetching summary so numbers don't disappear if specific logic |
||||
|
// changes. |
||||
|
// But usually summary counts should stay static for the date range unless date |
||||
|
// changes. |
||||
|
Map<String, Integer> summary = flagpolesService.getTypeWiseCounts(fromDate, toDate); |
||||
|
return Map.of("status", "success", "data", data, "summary", summary); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
return Map.of("status", "error", "message", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/flagpoles/details") |
||||
|
public Map<String, Object> getRequestDetails( |
||||
|
@RequestParam String zone, |
||||
|
@RequestParam(required = false) String status, |
||||
|
@RequestParam(required = false) String fromDate, |
||||
|
@RequestParam(required = false) String toDate) { |
||||
|
try { |
||||
|
List<Map<String, Object>> data = flagpolesService.getRequestDetails(zone, status, fromDate, toDate); |
||||
|
return Map.of("status", "success", "data", data); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
return Map.of("status", "error", "message", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/flagpoles/street-details") |
||||
|
public Map<String, Object> getStreetDetails(@RequestParam int requestId) { |
||||
|
try { |
||||
|
List<Map<String, Object>> data = flagpolesService.getStreetDetails(requestId); |
||||
|
return Map.of("status", "success", "data", data); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
return Map.of("status", "error", "message", e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,199 @@ |
|||||
|
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.jdbc.core.JdbcTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
@Service |
||||
|
public class FlagpolesService { |
||||
|
|
||||
|
private JdbcTemplate jdbcTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
public void setDataSource(@Qualifier("mysqlFlagPoleManagerSystemDataSource") DataSource dataSource) { |
||||
|
this.jdbcTemplate = new JdbcTemplate(dataSource); |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getZoneWiseRequestCounts(String fromDate, String toDate, String applicationType) { |
||||
|
StringBuilder sql = new StringBuilder("SELECT " + |
||||
|
" s.zone, " + |
||||
|
" COUNT(u.id) as total_requests, " + |
||||
|
" SUM(CASE WHEN u.final_status = 'Approved' THEN 1 ELSE 0 END) as approved_count, " + |
||||
|
" SUM(CASE WHEN u.final_status = 'Rejected' THEN 1 ELSE 0 END) as rejected_count " + |
||||
|
"FROM user_request_details u " + |
||||
|
"JOIN street_details s ON u.id = s.user_req_id " + |
||||
|
"LEFT JOIN application_type at ON u.application_type = at.id "); |
||||
|
|
||||
|
List<Object> params = new java.util.ArrayList<>(); |
||||
|
|
||||
|
// Helper to handle date and type filters |
||||
|
boolean whereAdded = false; |
||||
|
|
||||
|
if (fromDate != null && !fromDate.isEmpty() && toDate != null && !toDate.isEmpty()) { |
||||
|
sql.append("WHERE DATE(u.cdate) BETWEEN ? AND ? "); |
||||
|
params.add(fromDate); |
||||
|
params.add(toDate); |
||||
|
whereAdded = true; |
||||
|
} |
||||
|
|
||||
|
if (applicationType != null && !applicationType.isEmpty() && !applicationType.equalsIgnoreCase("OVERALL")) { |
||||
|
if (whereAdded) { |
||||
|
sql.append("AND at.type = ? "); |
||||
|
} else { |
||||
|
sql.append("WHERE at.type = ? "); |
||||
|
whereAdded = true; |
||||
|
} |
||||
|
params.add(applicationType); |
||||
|
} |
||||
|
|
||||
|
sql.append("GROUP BY s.zone ORDER BY s.zone"); |
||||
|
|
||||
|
List<Map<String, Object>> dbResults = jdbcTemplate.queryForList(sql.toString(), params.toArray()); |
||||
|
|
||||
|
// Parse DB results into a Map keyed by Zone Number for robust matching |
||||
|
Map<Integer, Map<String, Object>> zoneDataMap = new java.util.HashMap<>(); |
||||
|
for (Map<String, Object> row : dbResults) { |
||||
|
String zoneStr = (String) row.get("zone"); |
||||
|
if (zoneStr != null) { |
||||
|
// Extract number from "Zone 10" or "Zone 05" |
||||
|
String numberOnly = zoneStr.replaceAll("[^0-9]", ""); |
||||
|
if (!numberOnly.isEmpty()) { |
||||
|
try { |
||||
|
int zoneNum = Integer.parseInt(numberOnly); |
||||
|
// Update zone display format to "01", "02", etc. |
||||
|
row.put("zone", String.format("%02d", zoneNum)); |
||||
|
zoneDataMap.put(zoneNum, row); |
||||
|
} catch (NumberFormatException e) { |
||||
|
// ignore malformed zone names |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Generate final list for Zones 1 to 15 |
||||
|
List<Map<String, Object>> finalResults = new java.util.ArrayList<>(); |
||||
|
for (int i = 1; i <= 15; i++) { |
||||
|
if (zoneDataMap.containsKey(i)) { |
||||
|
finalResults.add(zoneDataMap.get(i)); |
||||
|
} else { |
||||
|
Map<String, Object> emptyRow = new java.util.HashMap<>(); |
||||
|
emptyRow.put("zone", String.format("%02d", i)); |
||||
|
emptyRow.put("total_requests", 0); |
||||
|
emptyRow.put("approved_count", 0); |
||||
|
emptyRow.put("rejected_count", 0); |
||||
|
finalResults.add(emptyRow); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return finalResults; |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getRequestDetails(String zone, String status, String fromDate, String toDate) { |
||||
|
StringBuilder sql = new StringBuilder("SELECT " + |
||||
|
" u.id, " + |
||||
|
" u.refid, " + |
||||
|
" u.applicant_name, " + |
||||
|
" u.mobile_number, " + |
||||
|
" u.event_desc, " + |
||||
|
" DATE_FORMAT(u.event_date, '%d-%m-%Y') as event_date, " + |
||||
|
" u.final_status, " + |
||||
|
" at.type as application_type, " + |
||||
|
" ao.option_name as application_option, " + |
||||
|
" it.installation_name as installation_type " + |
||||
|
"FROM user_request_details u " + |
||||
|
"LEFT JOIN application_type at ON u.application_type = at.id " + |
||||
|
"LEFT JOIN application_option ao ON u.application_option = ao.id " + |
||||
|
"LEFT JOIN installation_type it ON u.installation_type = it.id " + |
||||
|
"LEFT JOIN street_details s ON u.id = s.user_req_id " + |
||||
|
"WHERE 1=1 "); |
||||
|
|
||||
|
List<Object> params = new java.util.ArrayList<>(); |
||||
|
|
||||
|
if (zone != null && !zone.isEmpty()) { |
||||
|
// Handle various zone formats (01, 1, Zone 1, Zone 01) |
||||
|
String zoneNumStr = zone.replaceAll("[^0-9]", ""); |
||||
|
if (!zoneNumStr.isEmpty()) { |
||||
|
try { |
||||
|
int z = Integer.parseInt(zoneNumStr); |
||||
|
sql.append("AND (s.zone = ? OR s.zone = ? OR s.zone = ? OR s.zone = ?) "); |
||||
|
params.add("Zone " + z); |
||||
|
params.add("Zone " + String.format("%02d", z)); |
||||
|
params.add(String.valueOf(z)); |
||||
|
params.add(String.format("%02d", z)); |
||||
|
} catch (NumberFormatException e) { |
||||
|
sql.append("AND s.zone = ? "); |
||||
|
params.add(zone); |
||||
|
} |
||||
|
} else { |
||||
|
sql.append("AND s.zone = ? "); |
||||
|
params.add(zone); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (status != null && !status.isEmpty() && !status.equalsIgnoreCase("Total")) { |
||||
|
sql.append("AND u.final_status = ? "); |
||||
|
params.add(status); |
||||
|
} |
||||
|
|
||||
|
if (fromDate != null && !fromDate.isEmpty() && toDate != null && !toDate.isEmpty()) { |
||||
|
sql.append("AND DATE(u.cdate) BETWEEN ? AND ? "); |
||||
|
params.add(fromDate); |
||||
|
params.add(toDate); |
||||
|
} |
||||
|
|
||||
|
sql.append("GROUP BY u.id ORDER BY u.cdate DESC"); |
||||
|
|
||||
|
return jdbcTemplate.queryForList(sql.toString(), params.toArray()); |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getStreetDetails(int requestId) { |
||||
|
String sql = "SELECT " + |
||||
|
" s.zone, " + |
||||
|
" s.ward, " + |
||||
|
" s.street_name, " + |
||||
|
" s.no_of_poles, " + |
||||
|
" s.no_of_statue " + |
||||
|
"FROM street_details s " + |
||||
|
"WHERE s.user_req_id = ?"; |
||||
|
return jdbcTemplate.queryForList(sql, requestId); |
||||
|
} |
||||
|
|
||||
|
public Map<String, Integer> getTypeWiseCounts(String fromDate, String toDate) { |
||||
|
StringBuilder sql = new StringBuilder("SELECT " + |
||||
|
" at.type, " + |
||||
|
" COUNT(u.id) as count " + |
||||
|
"FROM user_request_details u " + |
||||
|
"JOIN application_type at ON u.application_type = at.id "); |
||||
|
|
||||
|
Object[] params = new Object[] {}; |
||||
|
|
||||
|
if (fromDate != null && !fromDate.isEmpty() && toDate != null && !toDate.isEmpty()) { |
||||
|
sql.append("WHERE DATE(u.cdate) BETWEEN ? AND ? "); |
||||
|
params = new Object[] { fromDate, toDate }; |
||||
|
} |
||||
|
|
||||
|
sql.append("GROUP BY at.type"); |
||||
|
|
||||
|
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql.toString(), params); |
||||
|
Map<String, Integer> counts = new java.util.HashMap<>(); |
||||
|
counts.put("TEMPORARY", 0); |
||||
|
counts.put("PERMANENT", 0); |
||||
|
|
||||
|
int total = 0; |
||||
|
for (Map<String, Object> row : results) { |
||||
|
String type = ((String) row.get("type")).toUpperCase(); |
||||
|
Number count = (Number) row.get("count"); |
||||
|
int val = count.intValue(); |
||||
|
counts.put(type, val); |
||||
|
total += val; |
||||
|
} |
||||
|
counts.put("OVERALL", total); |
||||
|
return counts; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,416 @@ |
|||||
|
<!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 { |
||||
|
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 Abstract Report</h4> |
||||
|
</div> |
||||
|
|
||||
|
<div class="container mt-4"> |
||||
|
<div class="card p-3 shadow-sm"> |
||||
|
<h5 class="mb-3"><strong>Report</strong></h5> |
||||
|
|
||||
|
<div class="row g-3 align-items-end mb-3"> |
||||
|
<div class="col-auto"> |
||||
|
<label for="fromDate" class="form-label">From Date</label> |
||||
|
<input type="date" id="fromDate" class="form-control"> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<label for="toDate" class="form-label">To Date</label> |
||||
|
<input type="date" id="toDate" class="form-control"> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<button class="btn btn-primary" id="searchBtn">Search</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Summary Cards --> |
||||
|
<!-- Summary Card --> |
||||
|
<!-- Summary Card --> |
||||
|
<div class="card mb-3 shadow-sm border-primary"> |
||||
|
<div class="card-body"> |
||||
|
<div class="row text-center"> |
||||
|
<div class="col-md-4 border-end" style="cursor: pointer;" onclick="loadReport('OVERALL')"> |
||||
|
<h6 class="text-primary fw-bold">Total Overall Requests</h6> |
||||
|
<h3 id="overallCount">0</h3> |
||||
|
</div> |
||||
|
<div class="col-md-4 border-end" style="cursor: pointer;" onclick="loadReport('TEMPORARY')"> |
||||
|
<h6 class="text-info fw-bold">Total Temporary Requests</h6> |
||||
|
<h3 id="tempCount">0</h3> |
||||
|
</div> |
||||
|
<div class="col-md-4" style="cursor: pointer;" onclick="loadReport('PERMANENT')"> |
||||
|
<h6 class="text-success fw-bold">Total Permanent Requests</h6> |
||||
|
<h3 id="permCount">0</h3> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<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> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="reportTableBody"> |
||||
|
<!-- Data will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</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 id="detailsPageLoader" style="display: none; text-align: center; padding: 20px;"> |
||||
|
<div class="spinner-border text-primary" role="status"> |
||||
|
<span class="sr-only"></span> |
||||
|
</div> |
||||
|
<p>Loading details...</p> |
||||
|
</div> |
||||
|
<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>App Type</th> |
||||
|
<th>App Option</th> |
||||
|
<th>Install Type</th> |
||||
|
<th>Event Desc</th> |
||||
|
<th>Event Date</th> |
||||
|
<th>Status</th> |
||||
|
<th>Action</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="detailsTableBody"> |
||||
|
<!-- Details will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</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; |
||||
|
|
||||
|
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}`); |
||||
|
$('#detailsPageLoader').show(); |
||||
|
// Clear previous table data |
||||
|
if ($.fn.DataTable.isDataTable('#detailsTable')) { |
||||
|
$('#detailsTable').DataTable().destroy(); |
||||
|
} |
||||
|
$('#detailsTableBody').empty(); |
||||
|
|
||||
|
new bootstrap.Modal(document.getElementById('detailsModal')).show(); |
||||
|
|
||||
|
fetch(`/commissionerreview/api/flagpoles/details?zone=${zone}&status=${status}&fromDate=${fromDate}&toDate=${toDate}`) |
||||
|
.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(); |
||||
|
|
||||
|
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.application_option)}</td> |
||||
|
<td>${formatValue(row.installation_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})"> |
||||
|
View Streets |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`); |
||||
|
}); |
||||
|
|
||||
|
$('#detailsTable').DataTable({ |
||||
|
dom: 'Bfrtip', |
||||
|
buttons: [ |
||||
|
'excelHtml5', |
||||
|
'pdfHtml5' |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function viewStreetDetails(requestId) { |
||||
|
$('#streetModalLabel').text(`Street Details for Request ID`); |
||||
|
$('#streetTableBody').html('<tr><td colspan="5" class="text-center">Loading...</td></tr>'); |
||||
|
new bootstrap.Modal(document.getElementById('streetModal')).show(); |
||||
|
|
||||
|
fetch(`/commissionerreview/api/flagpoles/street-details?requestId=${requestId}`) |
||||
|
.then(res => res.json()) |
||||
|
.then(json => { |
||||
|
const tbody = $('#streetTableBody'); |
||||
|
tbody.empty(); |
||||
|
if (json.status === "success" && json.data.length > 0) { |
||||
|
json.data.forEach(row => { |
||||
|
tbody.append(` |
||||
|
<tr> |
||||
|
<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> |
||||
|
`); |
||||
|
}); |
||||
|
} else { |
||||
|
tbody.html('<tr><td colspan="5" class="text-center">No street details found</td></tr>'); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Fetch Error:", err); |
||||
|
$('#streetTableBody').html('<tr><td colspan="5" 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 () { |
||||
|
loadReport(); |
||||
|
}); |
||||
|
|
||||
|
// Initial load |
||||
|
loadReport(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<!-- 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"> |
||||
|
<table class="table table-bordered table-striped" width="100%"> |
||||
|
<thead class="table-dark"> |
||||
|
<tr> |
||||
|
<th>Zone</th> |
||||
|
<th>Ward</th> |
||||
|
<th>Street Name</th> |
||||
|
<th>Poles</th> |
||||
|
<th>Statues</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="streetTableBody"> |
||||
|
<!-- Streets will be populated here --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue