@ -1,24 +1,37 @@
<!DOCTYPE html>
< html xmlns:th = "http://www.thymeleaf.org" >
< head th:replace = "~{fragments/modules/base/head :: head}" > < / head >
< style >
.card-box {
box-shadow: rgba(0, 0, 0, 0.5) 0px 3px 8px;
}
.card{
margin-bottom: 0px !important;
}
.card-title-fixed {
min-height: 48px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
/* Thumbnail styling */
.complaint-thumb {
width: 110px;
height: 110px;
object-fit: cover;
margin: 5px;
cursor: pointer;
transition: transform 0.2s ease-in-out, box-shadow 0.2s;
}
.complaint-thumb:hover {
transform: scale(1.05);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
/* Image grid alignment */
.image-grid {
gap: 8px;
}
#pendingModal .modal-body {
min-height: 60vh;
max-height: 70vh;
overflow-y: auto;
}
< / style >
< body >
< body >
@ -47,8 +60,8 @@
< div class = "col-lg-4" >
< nav class = "breadcrumb-container" aria-label = "breadcrumb" >
< ol class = "breadcrumb" >
< li class = "breadcrumb-item" > < a href = "/gcc/index" > < i
class="ik ik-home">< / i > < / a > < / li >
< li class = "breadcrumb-item" > < a href = "/gcc/index" > < i class = "ik ik-home" > < / i > < / a >
< / li >
< li class = "breadcrumb-item" aria-current = "page" > Green Committee< / li >
< li class = "breadcrumb-item active" aria-current = "page" > Dashboard< / li >
< / ol >
@ -58,69 +71,171 @@
< / div >
< br >
< div class = "row mt-2" style = "row-gap:10px;" >
< div class = "col-xl-3 col-md-6 col-sm-12" >
< div class = "card card-box p-3 text-center h-100" >
< div class = "row mt-1" >
< div class = "col-12" >
< div class = "card" >
< div class = "card-body" >
< div class = "row" >
< div class = "col-sm-4" >
< label for = "startDate" style = "font-size: medium;" > From Date:< / label >
< input type = "date" id = "startDate" class = "form-control"
onclick="this.showPicker()">
< / div >
< div class = "col-sm-4" >
< label for = "endDate" style = "font-size: medium;" > To Date:< / label >
< input type = "date" id = "endDate" class = "form-control"
onclick="this.showPicker()">
< / div >
< div class = "card-title-fixed" >
< h6 class = "text-facebook mb-2" >
< b > Application Received< / b >
< / h6 >
< / div >
< div class = "row mt-4" >
< div class = "col-12 d-flex justify-content-center" >
< button onclick = "loadZoneReport()"
class="btn btn-primary mx-2">Search< / button >
< h4 class = "text-twitter fw-700 mt-3" th:text = "${app_count}" > < / h4 >
< button class = "btn btn-danger mx-2" id = "resetBtn" name = "reset"
type="reset">Reset< / button >
< / div >
< / div >
< div class = "col-xl-3 col-md-6 col-sm-12" >
< div class = "card card-box p-3 text-center h-100" >
< div class = "card-title-fixed" >
< h6 class = "text-facebook mb-2" >
< b > DGC Member Inspection Completed< / b >
< / h6 >
< / div >
< h4 class = "text-twitter fw-700 mt-3" th:text = "${dgc_count}" > < / h4 >
< / div >
< / div >
< / div >
< div class = "col-xl-3 col-md-6 col-sm-12" >
< div class = "card card-box p-3 text-center h-100" >
< div class = "row mt-1" >
< div class = "card-title-fixed" >
< h6 class = "text-facebook mb-2" >
< b > Inspection Pending< / b >
< / h6 >
< div class = "col-12" >
< div class = "card" >
< div class = "card-body" >
< div class = "page-header-title" >
< h5 style = "margin: 0;font-weight: bold;" > Zone-wise Abstract Data< / h5 >
< hr >
< / div >
< table id = "ZoneDatatable"
class="table table-striped table-bordered table-hover dataTable" role="grid">
< thead class = "thead-dark" >
< tr role = "row" >
< th rowspan = "2" class = "text-center" > Zones< / th >
< th rowspan = "2" class = "text-center" > Total Request< / th >
< th colspan = "3" class = "text-center" > Inspection< / th >
< th rowspan = "2" class = "text-center" > Committee Pending< / th >
< th rowspan = "2" class = "text-center" > Withdraw< / th >
< th rowspan = "2" class = "text-center" > Completed< / th >
< / tr >
< tr >
< th class = "text-center" > NGO Pending< / th >
< th class = "text-center" > GCC Pending< / th >
< th class = "text-center" > TNFD Pending< / th >
< / tr >
< / thead >
< tbody >
< / tbody >
< tfoot >
< tr class = "font-weight-bold bg-light" >
< th class = "text-center" > TOTAL< / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< / tr >
< / tfoot >
< / table >
< / div >
< / div >
< / div >
< / div >
< h4 class = "text-twitter fw-700 mt-3" th:text = "${inspec_count}" > < / h4 >
< / div >
< / div >
< div class = "col-xl-3 col-md-6 col-sm-12" >
< div class = "card card-box p-3 text-center h-100" >
< div class = "card-title-fixed" >
< h6 class = "text-facebook mb-2" >
< b > Committee Decision Pending< / b >
< / h6 >
< div class = "modal fade" id = "zoneDetailsModal" tabindex = "-1" aria-labelledby = "zoneDetailsLabel"
aria-hidden="true" data-bs-keyboard="false" data-bs-backdrop="static">
< div class = "modal-dialog modal-xl" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" id = "zoneModalTitle" > Zone Details< / h5 >
< button type = "button" class = "close" data-dismiss = "modal" aria-label = "Close" >
< span aria-hidden = "true" > × < / span >
< / button >
< / div >
< div class = "modal-body" >
< table id = "WardDatatable" class = "table table-striped table-bordered display nowrap w-100" >
< thead class = "thead-dark" >
< tr role = "row" >
< th rowspan = "2" class = "text-center" > Zones< / th >
< th rowspan = "2" class = "text-center" > Total Request< / th >
< th colspan = "3" class = "text-center" > Inspection< / th >
< th rowspan = "2" class = "text-center" > Committee Pending< / th >
< th rowspan = "2" class = "text-center" > Withdraw< / th >
< th rowspan = "2" class = "text-center" > Completed< / th >
< / tr >
< tr >
< th class = "text-center" > NGO Pending< / th >
< th class = "text-center" > GCC Pending< / th >
< th class = "text-center" > TNFD Pending< / th >
< / tr >
< / thead >
< tbody >
< / tbody >
< tfoot >
< tr class = "font-weight-bold bg-light" >
< th class = "text-center" > TOTAL< / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< th class = "text-center" > < / th >
< / tr >
< / tfoot >
< / table >
< h4 class = "text-twitter fw-700 mt-3" th:text = "${cp_count}" > < / h4 >
< / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "pendingModal" tabindex = "-1" aria-labelledby = "DetailsLabel" aria-hidden = "true"
data-bs-keyboard="false" data-bs-backdrop="static">
< div class = "modal-dialog modal-xl" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 id = "pendingModalTitle" > < / h5 >
< button type = "button" class = "close" data-dismiss = "modal" aria-label = "Close" >
< span aria-hidden = "true" > × < / span >
< / button >
< / div >
< div class = "modal-body" id = "pendingModalBody" > < / div >
< / div >
< / div >
< / div >
< div class = "modal fade" id = "imagePreviewModal" tabindex = "-1" >
< div class = "modal-dialog modal-dialog-centered modal-lg" >
< div class = "modal-content" >
< div class = "modal-body text-center p-2" >
< img id = "previewImage" src = "" class = "img-fluid rounded" >
< / 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 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" > Loading...< / span >
@ -143,9 +258,602 @@
< script type = "text/javascript" >
let CURRENT_ZONE = null;
function openImagePreview(src) {
$('#previewImage').attr('src', src);
$('#imagePreviewModal').modal('show');
}
$(document).ready(function () {
var table = $('#ZoneDatatable').DataTable({
dom: 'Bfrtip',
columnDefs: [
{ targets: 'no-sort', orderable: false }
],
lengthMenu: [
[50, 100, 200, 500, -1],
['50 rows', '100 rows', '200 rows', 'Show all']
],
scrollY: '80vh',
scrollX: true,
buttons: [
{
extend: 'pageLength',
className: 'btn btn-warning'
},
{
title: 'Data export',
extend: 'copyHtml5',
text: 'Copy',
className: 'btn btn-secondary',
id: 'copyBtn'
},
{
extend: 'excelHtml5',
text: 'Excel',
className: 'btn btn-secondary',
id: 'excelBtn'
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'btn btn-secondary',
id: 'csvBtn'
},
{
extend: 'pdfHtml5',
text: 'PDF',
className: 'btn btn-secondary',
id: 'pdfBtn'
}
],
fixedHeader: false,
autoWidth: false,
responsive: false,
searching: false,
select: false,
processing: false
});
// Set today's date as max
const today = new Date().toISOString().split('T')[0];
$('#startDate, #endDate').attr('max', today);
// When start date changes → set min for end date
$('#startDate').on('change', function () {
const startDate = $(this).val();
$('#endDate').attr('min', startDate);
// If endDate already selected but invalid → clear it
if ($('#endDate').val() & & $('#endDate').val() < startDate ) {
$('#endDate').val('');
}
});
// When end date changes → validate against start date
$('#endDate').on('change', function () {
const startDate = $('#startDate').val();
const endDate = $(this).val();
if (startDate & & endDate < startDate ) {
Swal.fire({
icon: 'warning',
title: 'Invalid Date Range',
text: 'End date cannot be before start date'
});
$(this).val('');
}
});
loadZoneReport();
$('#zoneDetailsModal').on('shown.bs.modal', function () {
if ($.fn.DataTable.isDataTable('#WardDatatable')) {
let questionDatatable = $('#WardDatatable').DataTable();
questionDatatable.columns.adjust().draw();
}
});
});
function openZoneModal(zoneCode) {
CURRENT_ZONE = zoneCode;
//console.log("Opening modal for zone:", zoneCode);
const startDate = $('#startDate').val(); // yyyy-mm-dd or ""
const endDate = $('#endDate').val();
$('#pageLoader').show();
// Set data in modal (example)
$('#zoneModalTitle').text('Zone ' + zoneCode + ' Details');
$.ajax({
url: '/gcc/api/greencommittee/report/getWardDetailsByZone',
method: 'GET',
data: {
zone: zoneCode,
startDate: startDate,
endDate: endDate
},
success: function (response) {
var res = Array.isArray(response) ? response[0] : response;
//console.log("Response:", res);
$('#pageLoader').hide();
if (res.status === "Success") {
if (Array.isArray(res.data) & & res.data.length > 0) {
// console.log("Details Array:", response);
let table = $('#WardDatatable').DataTable({
destroy: true,
autoWidth: true,
info: true,
data: res.data, // using response directly
columns: [
{ data: 'ward_name', className: 'text-center' },
{ data: 'total_requests', className: 'text-center' },
{
data: 'ngo_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'NGO', 'WARD')
},
{
data: 'gcc_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'GCC', 'WARD')
},
{
data: 'tnfd_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'TNFD', 'WARD')
},
{
data: 'committee_pending',
className: 'text-center'
},
{
data: 'withdrawn',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'WITHDRAW', 'WARD')
},
{ data: 'completed', className: 'text-center' }
],
footerCallback: footerSumCallback,
dom: 'Bfrtip',
lengthMenu: [
[50, 100, 200, 500, -1],
['50 rows', '100 rows', '200 rows', 'Show all']
],
buttons: [
{
extend: 'pageLength',
className: 'btn btn-warning'
},
{
title: 'Data export',
extend: 'copyHtml5',
text: 'Copy',
className: 'btn btn-secondary',
id: 'copyBtn'
},
{
extend: 'excelHtml5',
text: 'Excel',
className: 'btn btn-secondary',
id: 'excelBtn'
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'btn btn-secondary',
id: 'csvBtn'
},
{
extend: 'pdfHtml5',
text: 'PDF',
className: 'btn btn-secondary',
id: 'pdfBtn'
}
],
scrollY: '400px',
scrollX: true,
responsive: false,
searching: false,
ordering: false
});
$('#zoneDetailsModal').modal('show');
}
else if (Array.isArray(res.data) & & res.data.length === 0) {
$('#pageLoader').hide();
//alert("No data available.");
Swal.fire({
icon: 'info',
title: 'No Data',
text: 'No data available.',
showConfirmButton: true,
timer: 3000
});
}
else {
$('#pageLoader').hide();
//alert("Error: " + response.message);
Swal.fire({
icon: 'error',
title: 'Error',
text: res.message,
showConfirmButton: true,
timer: 3000
});
}
}
else {
$('#pageLoader').hide();
//alert("Error: " + response.message);
Swal.fire({
icon: 'error',
title: 'Error',
text: res.message,
showConfirmButton: true,
timer: 3000
});
}
},
error: function (xhr, status, error) {
$('#pageLoader').hide();
//alert("Request failed: " + error);
Swal.fire({
icon: 'error',
title: 'Error',
text: error,
showConfirmButton: true,
timer: 3000
});
}
});
}
document.getElementById('resetBtn').addEventListener('click', function () {
event.preventDefault();
location.reload();
});
function loadZoneReport() {
const startDate = $('#startDate').val();
const endDate = $('#endDate').val();
$('#pageLoader').show();
$.ajax({
url: '/gcc/api/greencommittee/report/getabscount',
method: 'GET',
data: {
startDate: startDate,
endDate: endDate
},
success: function (response) {
// Safely extract data whether it's an array or an object
var res = Array.isArray(response) ? response[0] : response;
//console.log("Response:", res);
$('#pageLoader').hide();
if (res.status === "Success") {
if (Array.isArray(res.data) & & res.data.length > 0) {
//console.log("Details Array:", response);
let table = $('#ZoneDatatable').DataTable({
destroy: true,
info: true,
paging: true,
data: res.data, // using response directly
columns: [
{
data: 'zone_name',
className: 'text-center',
render: d => `< a href = "javascript:void(0)" onclick = "openZoneModal('${d}')" class = "text-primary" > ${d}< / a > `
},
{ data: 'total_requests', className: 'text-center' },
{
data: 'ngo_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'NGO', 'ZONE')
},
{
data: 'gcc_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'GCC', 'ZONE')
},
{
data: 'tnfd_pending',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'TNFD', 'ZONE')
},
{
data: 'committee_pending',
className: 'text-center'
},
{
data: 'withdrawn',
className: 'text-center',
render: (d, t, r) => renderPendingCell(d, r, 'WITHDRAW', 'ZONE')
},
{ data: 'completed', className: 'text-center' }
],
dom: 'Bfrtip',
lengthMenu: [
[50, 100, 200, 500, -1],
['50 rows', '100 rows', '200 rows', 'Show all']
],
buttons: [
{
extend: 'pageLength',
className: 'btn btn-warning'
},
{
title: 'Data export',
extend: 'copyHtml5',
text: 'Copy',
className: 'btn btn-secondary',
id: 'copyBtn'
},
{
extend: 'excelHtml5',
text: 'Excel',
className: 'btn btn-secondary',
id: 'excelBtn'
},
{
extend: 'csvHtml5',
text: 'CSV',
className: 'btn btn-secondary',
id: 'csvBtn'
},
{
extend: 'pdfHtml5',
text: 'PDF',
className: 'btn btn-secondary',
id: 'pdfBtn'
}
],
footerCallback: footerSumCallback,
scrollY: 'auto',
scrollX: true,
fixedHeader: false,
autoWidth: false,
responsive: false,
searching: false,
select: false,
processing: false
});
}
else if (Array.isArray(res.data) & & res.data.length === 0) {
$('#pageLoader').hide();
//alert("No data available.");
Swal.fire({
icon: 'info',
title: 'No Data',
text: 'No data available.',
showConfirmButton: true,
timer: 3000
});
}
else {
$('#pageLoader').hide();
//alert("Error: " + response.message);
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Error',
showConfirmButton: true,
timer: 3000
});
}
}
else {
$('#pageLoader').hide();
//alert("Error: " + response.message);
Swal.fire({
icon: 'error',
title: 'Error',
text: res.message,
showConfirmButton: true,
timer: 3000
});
}
},
error: function (xhr, status, error) {
$('#pageLoader').hide();
// alert("Request failed: " + error);
Swal.fire({
icon: 'error',
title: 'Error',
text: error,
showConfirmButton: true,
timer: 3000
});
}
});
}
function footerSumCallback(row, data) {
let api = this.api();
const sum = idx =>
api.column(idx).data()
.reduce((a, b) => Number(a) + Number(b || 0), 0);
for (let i = 1; i < = 7; i++) {
$(api.column(i).footer()).html(sum(i));
}
}
function openPendingModal(zone, ward, type, startDate, endDate, level) {
//console.log("data="+zone+","+ward+","+startDate+","+endDate);
$('#pendingModal').modal('show');
$('#pendingModalTitle').text(
`${type} Pending - Zone ${zone}${ward ? ', Ward ' + ward : ''}`
);
$('#pageLoader').show();
$.ajax({
url: '/gcc/api/greencommittee/report/getPendingDetails',
method: 'GET',
data: {
zone: zone,
ward: ward || null,
type: type,
startDate: startDate || null,
endDate: endDate || null
},
success: function (res) {
$('#pageLoader').hide();
if (res.status === 'Success' & & res.data.length > 0) {
renderComplaintCards(res.data);
} else {
$('#pendingModalBody').html(
`< p class = "text-center text-muted" > No complaints found< / p > `
);
}
},
error: function () {
$('#pageLoader').hide();
Swal.fire('Error', 'Failed to load details', 'error');
}
});
}
function renderComplaintCards(data) {
let html = '';
data.forEach(item => {
// ✅ Name fallback logic
const displayName =
item.p_name & & item.p_name.trim() !== ''
? item.p_name
: [item.dept_name, item.designation].filter(Boolean).join(' - ') || '-';
html += `
< div class = "card mb-3 shadow-sm" >
< div class = "card-header text-white font-weight-bold" style = "background-color:#404e67" >
${item.ref_id} — ${item.r_Cdate}
< / div >
< div class = "card-body" >
< div class = "row align-items-start" >
<!-- LEFT -->
< div class = "col-md-8" >
< p > < strong > Name:< / strong > ${displayName}< / p >
< p > < strong > Mobile:< / strong > ${item.ph_no}< / p >
< p >
< strong > Zone:< / strong > ${item.zone}
< span class = "mx-3" > |< / span >
< strong > Ward:< / strong > ${item.ward}
< span class = "mx-3" > |< / span >
< strong > Total Trees:< / strong > ${item.total_trees}
< / p >
< p > < strong > Address:< / strong > ${item.address}< / p >
< p > < strong > Remarks:< / strong > ${item.remarks}< / p >
< p > < strong > Complaints:< / strong > ${item.comp_details}< / p >
< / div >
<!-- RIGHT (Images) -->
< div class = "col-md-4" >
< div class = "d-flex flex-wrap image-grid" >
${item.image_paths.map(img => `
< img
src="${img.img_path}"
class="img-thumbnail complaint-thumb"
onclick="openImagePreview('${img.img_path}')"
>
`).join('')}
< / div >
< / div >
< / div >
< / div >
< / div > `;
});
$('#pendingModalBody').html(html);
}
function renderPendingCell(value, row, type, level) {
if (!value || value === 0) {
return `< span class = "text-muted" > ${value}< / span > `;
}
const zone = row.zone_name || CURRENT_ZONE;
const ward = row.ward_name || '';
const startDate = $('#startDate').val() || '';
const endDate = $('#endDate').val() || '';
return `
< a href = "javascript:void(0)"
class="text-primary font-weight-bold"
onclick="openPendingModal(
'${zone}',
'${ward}',
'${type}',
'${startDate}',
'${endDate}',
'${level}'
)">
${value}
< / a > `;
}
< / script >
< / body >
< / body >
< / html >