Browse Source
added greencommittee controller,service and attendance controller ,service and html pages
master
added greencommittee controller,service and attendance controller ,service and html pages
master
7 changed files with 1887 additions and 1 deletions
-
29src/main/java/com/gcc/commissionerreview/configuration/DataSourceConfig.java
-
71src/main/java/com/gcc/commissionerreview/controller/AttendanceController.java
-
116src/main/java/com/gcc/commissionerreview/controller/GreenCommitteeApicontroller.java
-
136src/main/java/com/gcc/commissionerreview/service/AttendanceService.java
-
392src/main/java/com/gcc/commissionerreview/service/GreenCommitteeService.java
-
407src/main/resources/templates/modules/attendance-dashboard.html
-
737src/main/resources/templates/modules/greencommittee/gcreport.html
@ -0,0 +1,71 @@ |
|||
package com.gcc.commissionerreview.controller; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
import com.gcc.commissionerreview.service.AttendanceService; |
|||
|
|||
@RestController |
|||
@RequestMapping("/commissionerreview/api/attendanceController") |
|||
public class AttendanceController { |
|||
|
|||
@Autowired |
|||
AttendanceService attendanceService; |
|||
|
|||
@GetMapping("/abstractCounts") |
|||
public List<Map<String, Object>> getAbstractCounts() { |
|||
return attendanceService.getAbstractCounts(); |
|||
} |
|||
|
|||
@GetMapping("/zoneSummary") |
|||
public List<Map<String, Object>> getZoneSummary(@RequestParam(name = "empType") String empType, |
|||
@RequestParam(name = "fromDate", required = false) String fromDate, |
|||
@RequestParam(name = "toDate", required = false) String toDate) { |
|||
|
|||
List<Map<String, Object>> result = attendanceService.getZoneSummary(empType, fromDate, toDate); |
|||
System.out.println("Zone Summary Result for "+empType+ ":"+ result); |
|||
return result; |
|||
|
|||
// return attendanceService.getZoneSummary(empType, fromDate, toDate); |
|||
} |
|||
|
|||
@GetMapping("/zoneSummaryPark") |
|||
public List<Map<String, Object>> getZoneSummaryPark( |
|||
@RequestParam(name = "fromDate", required = false) String fromDate, |
|||
@RequestParam(name = "toDate", required = false) String toDate) { |
|||
List<Map<String, Object>> result = attendanceService.getZoneSummary("Park", fromDate, toDate); |
|||
System.out.println("Zone Summary Result for Park: " + result); |
|||
return result; |
|||
} |
|||
|
|||
@GetMapping("/zoneSummaryNULM") |
|||
public List<Map<String, Object>> getZoneSummaryNULM( |
|||
@RequestParam(name = "fromDate", required = false) String fromDate, |
|||
@RequestParam(name = "toDate", required = false) String toDate) { |
|||
List<Map<String, Object>> result = attendanceService.getZoneSummary("NULM", fromDate, toDate); |
|||
System.out.println("Zone Summary Result for NULM: " + result); |
|||
return result; |
|||
} |
|||
|
|||
@GetMapping("/detailed") |
|||
public List<Map<String, Object>> getDetailed(@RequestParam(name = "empType") String empType, |
|||
@RequestParam(name = "category") String category, |
|||
@RequestParam(name = "fromDate", required = false) String fromDate, |
|||
@RequestParam(name = "toDate", required = false) String toDate, |
|||
@RequestParam(name = "zone", required = false) Integer zone) { |
|||
|
|||
List<Map<String, Object>> result = attendanceService.getDetailedList(empType, category, fromDate, toDate, zone); |
|||
System.out.println("Detailed Result: " + result); |
|||
|
|||
return result; |
|||
|
|||
// return attendanceService.getDetailedList(empType, category, fromDate, toDate, zone); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
package com.gcc.commissionerreview.controller; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import com.gcc.commissionerreview.service.GreenCommitteeService; |
|||
|
|||
@RestController |
|||
@RequestMapping("/commissionerreview/api/greencommittee") |
|||
public class GreenCommitteeApicontroller { |
|||
|
|||
@Autowired |
|||
private GreenCommitteeService gcservice; |
|||
|
|||
@GetMapping("/getZoneReport") |
|||
public Map<String, Object> getabscount(@RequestParam(required = false) String startDate, |
|||
@RequestParam(required = false) String endDate) |
|||
{ |
|||
|
|||
Map<String, Object> response = new HashMap<>(); |
|||
|
|||
try { |
|||
List<Map<String, Object>> data = gcservice.getabscount(startDate, endDate); |
|||
|
|||
response.put("data", data); |
|||
response.put("message", "Zone-wise abstract report"); |
|||
response.put("status", "Success"); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
|
|||
response.put("data", Collections.emptyList()); |
|||
response.put("message", "Failed to fetch abstract report"); |
|||
response.put("status", "Error"); |
|||
} |
|||
|
|||
return response; |
|||
} |
|||
|
|||
|
|||
@GetMapping("/getWardDetailsByZone") |
|||
public Map<String, Object> getWardDetailsByZone(@RequestParam(required = true) String zone, |
|||
@RequestParam(required = false) String startDate, |
|||
@RequestParam(required = false) String endDate) |
|||
{ |
|||
|
|||
Map<String, Object> response = new HashMap<>(); |
|||
|
|||
try { |
|||
List<Map<String, Object>> data = gcservice.getWardDetailsByZone(zone,startDate,endDate); |
|||
|
|||
response.put("data", data); |
|||
response.put("message", "Ward-wise abstract report"); |
|||
response.put("status", "Success"); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
|
|||
response.put("data", Collections.emptyList()); |
|||
response.put("message", "Failed to fetch abstract report"); |
|||
response.put("status", "Error"); |
|||
} |
|||
|
|||
return response; |
|||
} |
|||
|
|||
@GetMapping("/getPendingDetails") |
|||
public Map<String, Object> getPendingDetails( |
|||
@RequestParam String zone, |
|||
@RequestParam(required = false) String ward, |
|||
@RequestParam String type, |
|||
@RequestParam(required = false) String startDate, |
|||
@RequestParam(required = false) String endDate) { |
|||
|
|||
|
|||
Map<String, Object> response = new HashMap<>(); |
|||
|
|||
try { |
|||
List<String> refIds =gcservice.getPendingRefIds(zone, ward, type,startDate, endDate); |
|||
|
|||
List<Map<String, Object>> dataList = new ArrayList<>(); |
|||
|
|||
for (String refId : refIds) { |
|||
Map<String, Object> fullData = gcservice.getfulldetailsdata(refId); |
|||
if (fullData != null && !fullData.isEmpty()) { |
|||
dataList.add((Map<String, Object>) fullData.get("data")); |
|||
} |
|||
} |
|||
|
|||
response.put("data", dataList); |
|||
response.put("message", "Complaint List"); |
|||
response.put("status", "Success"); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
response.put("data", Collections.emptyList()); |
|||
response.put("message", "Failed to fetch complaints"); |
|||
response.put("status", "Error"); |
|||
} |
|||
|
|||
|
|||
return response; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
package com.gcc.commissionerreview.service; |
|||
|
|||
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; |
|||
|
|||
import java.sql.Date; |
|||
import java.time.LocalDate; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
import java.util.Map; |
|||
|
|||
@Service |
|||
public class AttendanceService { |
|||
|
|||
@Autowired |
|||
private JdbcTemplate jdbcTemplate; |
|||
|
|||
@Autowired |
|||
public void setFeedbackDataSource(@Qualifier("mysqlNULMDataSource") DataSource dataSource) { |
|||
this.jdbcTemplate = new JdbcTemplate(dataSource); |
|||
} |
|||
|
|||
private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("dd-MM-yyyy", Locale.ENGLISH); |
|||
|
|||
public List<Map<String, Object>> getAbstractCounts() { |
|||
String sql = "SELECT emp_type, count(enrollment_id) as totalCount FROM enrollment_table " |
|||
+ "where isactive =1 and isdelete = 0 and zone != 0 group by emp_type"; |
|||
|
|||
return jdbcTemplate.queryForList(sql); |
|||
} |
|||
|
|||
public List<Map<String, Object>> getZoneSummary(String empType, String fromDateStr, String toDateStr) { |
|||
LocalDate fromDate; |
|||
LocalDate toDate; |
|||
|
|||
if (fromDateStr == null || fromDateStr.trim().isEmpty()) { |
|||
fromDate = LocalDate.now(); |
|||
} else { |
|||
fromDate = LocalDate.parse(fromDateStr, INPUT_FORMAT); |
|||
} |
|||
|
|||
if (toDateStr == null || toDateStr.trim().isEmpty()) { |
|||
toDate = LocalDate.now(); |
|||
} else { |
|||
toDate = LocalDate.parse(toDateStr, INPUT_FORMAT); |
|||
} |
|||
|
|||
// Convert to datetime range (start of fromDate to end of toDate) |
|||
String fromDateTime = fromDate + " 00:00:00"; |
|||
String toDateTime = toDate + " 23:59:59"; |
|||
|
|||
System.out.println("EmpType: " + empType + ", From: " + fromDateTime + ", To: " + toDateTime); |
|||
|
|||
String sql = "SELECT et.zone, count(distinct et.enrollment_id) as TotalCount, " |
|||
+ "count(distinct case when att.indatetime >= ? and att.indatetime <= ? then att.enrollment_id end) as AttendanceMarked, " |
|||
+ "count(distinct case when att.oddatetime >= ? and att.oddatetime <= ? then att.enrollment_id end) as ODCount, " |
|||
+ "count(distinct case when att.leavedatetime >= ? and att.leavedatetime <= ? then att.enrollment_id end) as LeaveCount " |
|||
+ "FROM enrollment_table et " |
|||
+ "left join attendance att on et.enrollment_id = att.enrollment_id " |
|||
+ "where et.isactive =1 and et.isdelete = 0 and et.emp_type = ? and et.zone !=0 " |
|||
+ "group by et.zone order by et.zone"; |
|||
|
|||
return jdbcTemplate.queryForList(sql, new Object[] { fromDateTime, toDateTime, fromDateTime, toDateTime, fromDateTime, toDateTime, empType }); |
|||
} |
|||
|
|||
public List<Map<String, Object>> getDetailedList(String empType, String category, String fromDateStr, String toDateStr, Integer zone) { |
|||
LocalDate fromDate; |
|||
LocalDate toDate; |
|||
|
|||
if (fromDateStr == null || fromDateStr.trim().isEmpty()) { |
|||
fromDate = LocalDate.now(); |
|||
} else { |
|||
fromDate = LocalDate.parse(fromDateStr, INPUT_FORMAT); |
|||
} |
|||
|
|||
if (toDateStr == null || toDateStr.trim().isEmpty()) { |
|||
toDate = LocalDate.now(); |
|||
} else { |
|||
toDate = LocalDate.parse(toDateStr, INPUT_FORMAT); |
|||
} |
|||
|
|||
// Convert to datetime range |
|||
String fromDateTime = fromDate + " 00:00:00"; |
|||
String toDateTime = toDate + " 23:59:59"; |
|||
|
|||
// determine which datetime column to filter/order by |
|||
String dateColumn; |
|||
String filterDateColumn; |
|||
switch (category == null ? "present" : category.toLowerCase()) { |
|||
case "od": |
|||
dateColumn = "att.oddatetime"; |
|||
filterDateColumn = "att.oddatetime"; |
|||
break; |
|||
case "leave": |
|||
dateColumn = "att.leavedatetime"; |
|||
filterDateColumn = "att.leavedatetime"; |
|||
break; |
|||
default: |
|||
dateColumn = "att.indatetime"; |
|||
filterDateColumn = "att.indatetime"; |
|||
} |
|||
|
|||
// Base query - simplified and optimized |
|||
String sql = "SELECT et.enrollment_id, et.name, et.designation, et.department, et.incharge_name, et.incharge_phoneno, et.emp_id, " |
|||
+ "DATE_FORMAT(att.indatetime, '%d-%m-%Y %h:%i %p') as intime, att.inphoto as inphoto, att.inby, " |
|||
+ "DATE_FORMAT(att.outdatetime, '%d-%m-%Y %h:%i %p') as outtime, att.outphoto as outphoto, att.outby, " |
|||
+ "DATE_FORMAT(att.oddatetime, '%d-%m-%Y %h:%i %p') as odtime, att.odby, et.zone, " |
|||
+ "DATE_FORMAT(att.leavedatetime, '%d-%m-%Y %h:%i %p') as leavedatetime, att.leaveby " |
|||
+ "FROM enrollment_table et " |
|||
+ "inner join attendance att on et.enrollment_id = att.enrollment_id " |
|||
+ " and " + filterDateColumn + " >= ? and " + filterDateColumn + " <= ? " |
|||
+ "where et.isactive =1 and et.isdelete = 0 and et.emp_type = ? and et.zone !=0 " |
|||
+ " and " + filterDateColumn + " is not null "; |
|||
|
|||
java.util.List<Object> params = new java.util.ArrayList<>(); |
|||
params.add(fromDateTime); |
|||
params.add(toDateTime); |
|||
params.add(empType); |
|||
|
|||
if (zone != null) { |
|||
sql += " and et.zone = ? "; |
|||
params.add(zone); |
|||
} |
|||
|
|||
sql += " order by " + dateColumn + " desc "; |
|||
|
|||
System.out.println("EmpType: " + empType + ", Category: " + category + ", From: " + fromDateTime + ", To: " + toDateTime + ", Zone: " + zone); |
|||
return jdbcTemplate.queryForList(sql, params.toArray()); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,392 @@ |
|||
package com.gcc.commissionerreview.service; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import javax.sql.DataSource; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Qualifier; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.core.env.Environment; |
|||
import org.springframework.jdbc.core.JdbcTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import com.gcc.commissionerreview.configuration.AppConfig; |
|||
|
|||
@Service |
|||
public class GreenCommitteeService { |
|||
|
|||
|
|||
@Autowired |
|||
private JdbcTemplate jdbcTemplate; |
|||
@Autowired |
|||
private Environment environment; |
|||
|
|||
@Autowired |
|||
private AppConfig appConfig; |
|||
|
|||
@Autowired |
|||
@Value("${fileBaseUrl}") |
|||
|
|||
private String fileBaseUrl; |
|||
|
|||
public GreenCommitteeService (Environment environment) { |
|||
|
|||
this.environment = environment; |
|||
|
|||
this.fileBaseUrl = environment.getProperty("fileBaseUrl"); |
|||
} |
|||
|
|||
@Autowired |
|||
public void setFeedbackDataSource(@Qualifier("mysqlGreenCommitteeDataSource") DataSource dataSource) { |
|||
this.jdbcTemplate = new JdbcTemplate(dataSource); |
|||
} |
|||
|
|||
public List<Map<String, Object>> getabscount(String startDate, String endDate) { |
|||
|
|||
String baseSql = |
|||
"SELECT " + |
|||
" zm.zone_name, " + |
|||
" COALESCE(COUNT(DISTINCT rd.ref_id), 0) AS total_requests, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='NGO' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id=id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS ngo_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='GCC' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id=id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS gcc_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='TNFD' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id=id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS tnfd_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND ( " + |
|||
" SELECT COUNT(DISTINCT inspection_by) " + |
|||
" FROM inspection_data id " + |
|||
" WHERE id.ref_id = rd.ref_id AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id=id.reinspection_id) ) " + |
|||
" ) = 3 " + |
|||
" THEN 1 ELSE 0 END), 0) AS committee_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE WHEN rd.is_withdraw = 1 THEN 1 ELSE 0 END), 0) AS withdrawn, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" THEN 1 ELSE 0 END), 0) AS completed " + |
|||
|
|||
"FROM zone_master zm " + |
|||
"LEFT JOIN reg_details rd " + |
|||
" ON rd.zone = zm.zone_name " + |
|||
" AND rd.is_active = 1 "; |
|||
|
|||
StringBuilder sql = new StringBuilder(baseSql); |
|||
List<Object> params = new ArrayList<>(); |
|||
|
|||
if (startDate != null && !startDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) >= ? "); |
|||
params.add(startDate); |
|||
} |
|||
|
|||
if (endDate != null && !endDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) <= ? "); |
|||
params.add(endDate); |
|||
} |
|||
|
|||
sql.append( |
|||
" WHERE zm.is_active = 1 " + |
|||
" AND zm.is_delete = 0 " + |
|||
" GROUP BY zm.zone_name " + |
|||
" ORDER BY zm.zone_name " |
|||
); |
|||
|
|||
return jdbcTemplate.queryForList(sql.toString(), params.toArray()); |
|||
} |
|||
|
|||
|
|||
public List<Map<String, Object>> getWardDetailsByZone( |
|||
String zone, |
|||
String startDate, |
|||
String endDate) { |
|||
|
|||
String baseSql = |
|||
"SELECT " + |
|||
" wm.ward_name, " + |
|||
|
|||
" COALESCE(COUNT(DISTINCT rd.ref_id), 0) AS total_requests, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='NGO' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS ngo_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='GCC' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS gcc_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='TNFD' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
" ) " + |
|||
" THEN 1 ELSE 0 END), 0) AS tnfd_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IS NOT NULL " + |
|||
" AND rd.is_withdraw = 0 " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
" AND ( " + |
|||
" SELECT COUNT(DISTINCT inspection_by) " + |
|||
" FROM inspection_data id " + |
|||
" WHERE id.ref_id = rd.ref_id AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
" ) = 3 " + |
|||
" THEN 1 ELSE 0 END), 0) AS committee_pending, " + |
|||
|
|||
" COALESCE(SUM(CASE WHEN rd.is_withdraw=1 THEN 1 ELSE 0 END), 0) AS withdrawn, " + |
|||
|
|||
" COALESCE(SUM(CASE " + |
|||
" WHEN rd.ref_id IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
" THEN 1 ELSE 0 END), 0) AS completed " + |
|||
|
|||
"FROM ward_master wm " + |
|||
"JOIN zone_master zm ON zm.id = wm.zone_id AND zm.zone_name = ? " + |
|||
"LEFT JOIN reg_details rd " + |
|||
" ON rd.ward = wm.ward_name " + |
|||
" AND rd.is_active = 1 "; |
|||
|
|||
StringBuilder sql = new StringBuilder(baseSql); |
|||
List<Object> params = new ArrayList<>(); |
|||
|
|||
|
|||
params.add(zone); |
|||
|
|||
|
|||
if (startDate != null && !startDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) >= ? "); |
|||
params.add(startDate); |
|||
} |
|||
|
|||
if (endDate != null && !endDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) <= ? "); |
|||
params.add(endDate); |
|||
} |
|||
|
|||
sql.append( |
|||
" WHERE wm.is_active=1 AND wm.is_delete=0 " + |
|||
" GROUP BY wm.zone_id, wm.ward_name " + |
|||
" ORDER BY wm.ward_name " |
|||
); |
|||
|
|||
return jdbcTemplate.queryForList(sql.toString(), params.toArray()); |
|||
} |
|||
|
|||
|
|||
|
|||
public List<String> getPendingRefIds( |
|||
String zone, |
|||
String ward, |
|||
String type, |
|||
String startDate, |
|||
String endDate) { |
|||
|
|||
String baseSql; |
|||
|
|||
switch (type.toUpperCase()) { |
|||
|
|||
case "NGO": |
|||
baseSql = |
|||
"SELECT rd.ref_id FROM reg_details rd " + |
|||
"WHERE rd.zone=? " + |
|||
"AND rd.is_active=1 AND rd.is_withdraw=0 " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='NGO' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
")"; |
|||
break; |
|||
|
|||
case "GCC": |
|||
baseSql = |
|||
"SELECT rd.ref_id FROM reg_details rd " + |
|||
"WHERE rd.zone=? " + |
|||
"AND rd.is_active=1 AND rd.is_withdraw=0 " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='GCC' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
")"; |
|||
break; |
|||
|
|||
case "TNFD": |
|||
baseSql = |
|||
"SELECT rd.ref_id FROM reg_details rd " + |
|||
"WHERE rd.zone=? " + |
|||
"AND rd.is_active=1 AND rd.is_withdraw=0 " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM committee_data WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN (SELECT ref_id FROM reinspection WHERE isactive=1) " + |
|||
"AND rd.ref_id NOT IN ( " + |
|||
" SELECT id.ref_id FROM inspection_data id " + |
|||
" WHERE id.inspection_by='TNFD' AND id.isactive=1 " + |
|||
" AND ( (rd.reinspection_id IS NULL AND id.reinspection_id IS NULL) " + |
|||
" OR (rd.reinspection_id = id.reinspection_id) ) " + |
|||
")"; |
|||
break; |
|||
|
|||
case "WITHDRAW": |
|||
baseSql = |
|||
"SELECT rd.ref_id FROM reg_details rd " + |
|||
"WHERE rd.zone=? AND rd.is_withdraw=1"; |
|||
break; |
|||
|
|||
default: |
|||
return Collections.emptyList(); |
|||
} |
|||
|
|||
StringBuilder sql = new StringBuilder(baseSql); |
|||
List<Object> params = new ArrayList<>(); |
|||
|
|||
params.add(zone); |
|||
|
|||
if (ward != null && !ward.isEmpty()) { |
|||
sql.append(" AND rd.ward = ? "); |
|||
params.add(ward); |
|||
} |
|||
|
|||
if (startDate != null && !startDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) >= ? "); |
|||
params.add(startDate); |
|||
} |
|||
|
|||
if (endDate != null && !endDate.isEmpty()) { |
|||
sql.append(" AND DATE(rd.cdate) <= ? "); |
|||
params.add(endDate); |
|||
} |
|||
|
|||
return jdbcTemplate.query( |
|||
sql.toString(), |
|||
params.toArray(), |
|||
(rs, rowNum) -> rs.getString("ref_id") |
|||
); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
public Map<String, Object> getfulldetailsdata(String refId) { |
|||
|
|||
Map<String, Object> response = new HashMap<>(); |
|||
|
|||
// 1 — Application details |
|||
Map<String, Object> app = jdbcTemplate.queryForMap( |
|||
"SELECT *,DATE_FORMAT(cdate, '%d-%m-%Y %l:%i %p') as r_Cdate,IFNULL(reinspection_id, '') AS com_reinspection_id FROM reg_details WHERE ref_id=? AND is_active=1", |
|||
refId |
|||
); |
|||
|
|||
// 2 — Complaint details (grouped) |
|||
List<Map<String, Object>> complaints = jdbcTemplate.queryForList( |
|||
"SELECT cn.name AS nature, SUM(cd.no_of_trees) AS count " + |
|||
"FROM complaint_details cd JOIN comp_nature cn ON cn.id=cd.comp_nature_id " + |
|||
"WHERE cd.ref_id=? GROUP BY cn.name", refId |
|||
); |
|||
|
|||
String compDetails = complaints.stream() |
|||
.map(c -> c.get("nature") + "-" + c.get("count")) |
|||
.collect(Collectors.joining(",")); |
|||
|
|||
// 3 — Complaint images |
|||
List<Map<String, Object>> imagePaths = |
|||
jdbcTemplate.queryForList( |
|||
"SELECT img_path FROM img_uploads WHERE ref_id=? AND is_active=1", |
|||
new Object[]{refId} |
|||
).stream() |
|||
.map(row -> { |
|||
Map<String, Object> img = new HashMap<>(); |
|||
img.put( |
|||
"img_path", |
|||
fileBaseUrl + "/gccofficialapp/files" + row.get("img_path").toString() |
|||
); |
|||
return img; |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
|
|||
app.put("complaints", complaints); |
|||
app.put("comp_details", compDetails); |
|||
app.put("image_paths", imagePaths); |
|||
|
|||
response.put("data", app); |
|||
|
|||
return response; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,407 @@ |
|||
<!doctype html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<title>Attendance Dashboard</title> |
|||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> |
|||
<style> |
|||
.spinner-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
min-height: 200px; |
|||
} |
|||
.spinner-border { |
|||
width: 3rem; |
|||
height: 3rem; |
|||
color: #007bff; |
|||
} |
|||
.zone-summary-row { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
gap: 20px; |
|||
} |
|||
@media (max-width: 768px) { |
|||
.zone-summary-row { |
|||
grid-template-columns: 1fr; |
|||
} |
|||
} |
|||
.zone-summary-card { |
|||
border: 1px solid #ddd; |
|||
border-radius: 4px; |
|||
padding: 15px; |
|||
background-color: #f9f9f9; |
|||
} |
|||
.disabled-count { |
|||
color: #6c757d; |
|||
cursor: default; |
|||
text-decoration: none; |
|||
display: inline-block; |
|||
padding: 0 4px; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body class="p-3"> |
|||
<div class="container"> |
|||
<h1 class="mb-4">Attendance Dashboard</h1> |
|||
|
|||
<div class="row mb-3"> |
|||
<div class="col-md-3"> |
|||
<label class="form-label">From</label> |
|||
<input id="fromDate" type="date" class="form-control" /> |
|||
</div> |
|||
<div class="col-md-3"> |
|||
<label class="form-label">To</label> |
|||
<input id="toDate" type="date" class="form-control" /> |
|||
</div> |
|||
<div class="col-md-6 d-flex align-items-end"> |
|||
<div> |
|||
<button id="loadSummaryBtn" class="btn btn-secondary">Load</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<h4>Abstract Counts</h4> |
|||
<div id="abstractCounts" class="mb-4"></div> |
|||
|
|||
<h4>Zone Summary</h4> |
|||
<div class="zone-summary-row"> |
|||
<div class="zone-summary-card"> |
|||
<h5>Park</h5> |
|||
<div id="zoneSummaryPark"></div> |
|||
</div> |
|||
<div class="zone-summary-card"> |
|||
<h5>NULM</h5> |
|||
<div id="zoneSummaryNulm"></div> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<script> |
|||
(function () { |
|||
const apiBase = '/commissionerreview/api/attendanceController'; |
|||
|
|||
function pad(n) { return n < 10 ? '0' + n : n; } |
|||
|
|||
// convert Date -> yyyy-mm-dd for input[type=date] |
|||
function toIsoYyyyMmDd(date) { |
|||
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()); |
|||
} |
|||
|
|||
// convert input value (either yyyy-mm-dd or dd-mm-yyyy) -> dd-mm-yyyy for API |
|||
function toApiDdMmYyyy(val) { |
|||
if (!val) return val; |
|||
// if already in dd-mm-yyyy (contains two '-') and first part length 2 |
|||
const parts = val.split('-'); |
|||
if (parts.length === 3) { |
|||
if (parts[0].length === 4) { |
|||
// yyyy-mm-dd -> dd-mm-yyyy |
|||
return pad(parseInt(parts[2], 10)) + '-' + pad(parseInt(parts[1], 10)) + '-' + parts[0]; |
|||
} else { |
|||
// assume already dd-mm-yyyy |
|||
return val; |
|||
} |
|||
} |
|||
return val; |
|||
} |
|||
|
|||
function setDefaults() { |
|||
const today = new Date(); |
|||
const iso = toIsoYyyyMmDd(today); |
|||
document.getElementById('fromDate').value = iso; |
|||
document.getElementById('toDate').value = iso; |
|||
} |
|||
|
|||
function setDateLimits() { |
|||
const from = document.getElementById('fromDate'); |
|||
const to = document.getElementById('toDate'); |
|||
const todayIso = toIsoYyyyMmDd(new Date()); |
|||
from.max = todayIso; |
|||
to.max = todayIso; |
|||
if (from.value) to.min = from.value; |
|||
|
|||
from.addEventListener('change', () => { |
|||
const fv = from.value; |
|||
if (fv) { |
|||
to.min = fv; |
|||
if (to.value && to.value < fv) to.value = fv; |
|||
} else { |
|||
to.min = ''; |
|||
} |
|||
}); |
|||
|
|||
to.addEventListener('change', () => { |
|||
if (from.value && to.value && to.value < from.value) { |
|||
to.value = from.value; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
async function loadAbstractCounts() { |
|||
const target = document.getElementById('abstractCounts'); |
|||
target.innerHTML = createLoader(); |
|||
try { |
|||
const res = await fetch(apiBase + '/abstractCounts'); |
|||
const data = await res.json(); |
|||
if (!Array.isArray(data)) { target.innerText = JSON.stringify(data); return; } |
|||
let html = '<div class="row">'; |
|||
data.forEach(item => { |
|||
html += `<div class="col-md-3"><div class="card p-2 mb-2"><strong>${item.emp_type}</strong><div>${item.totalCount}</div></div></div>`; |
|||
}); |
|||
html += '</div>'; |
|||
target.innerHTML = html; |
|||
} catch (e) { target.innerHTML = `<div class="alert alert-danger">Error: ${e}</div>`; } |
|||
} |
|||
|
|||
function createLoader() { |
|||
return '<div class="spinner-container"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>'; |
|||
} |
|||
|
|||
function renderTable(target, data, empType) { |
|||
if (!Array.isArray(data)) { target.innerText = JSON.stringify(data); return; } |
|||
if (data.length === 0) { target.innerHTML = '<div class="alert alert-info">No records</div>'; return; } |
|||
const rows = data.slice().sort((a,b) => String(a.zone||'').localeCompare(String(b.zone||''), undefined, {numeric: true})); |
|||
let html = '<table class="table table-sm table-striped"><thead><tr><th>Zone</th><th>Total</th><th>Present</th><th>OD</th><th>Leave</th></tr></thead><tbody>'; |
|||
rows.forEach(r => { |
|||
const zone = r.zone; |
|||
const total = Number(r.TotalCount) || 0; |
|||
const present = Number(r.AttendanceMarked) || 0; |
|||
const od = Number(r.ODCount) || 0; |
|||
const leave = Number(r.LeaveCount) || 0; |
|||
|
|||
html += `<tr><td>${zone}</td>`; |
|||
html += `<td>${total}</td>`; |
|||
html += `<td>` + (present > 0 |
|||
? `<a href="#" class="zone-count" data-zone="${zone}" data-emptype="${empType}" data-category="present">${present}</a>` |
|||
: `<span class="disabled-count">${present}</span>`) + `</td>`; |
|||
html += `<td>` + (od > 0 |
|||
? `<a href="#" class="zone-count" data-zone="${zone}" data-emptype="${empType}" data-category="od">${od}</a>` |
|||
: `<span class="disabled-count">${od}</span>`) + `</td>`; |
|||
html += `<td>` + (leave > 0 |
|||
? `<a href="#" class="zone-count" data-zone="${zone}" data-emptype="${empType}" data-category="leave">${leave}</a>` |
|||
: `<span class="disabled-count">${leave}</span>`) + `</td>`; |
|||
html += '</tr>'; |
|||
}); |
|||
html += '</tbody></table>'; |
|||
target.innerHTML = html; |
|||
} |
|||
|
|||
async function loadZoneSummary() { |
|||
const rawFrom = document.getElementById('fromDate').value; |
|||
const rawTo = document.getElementById('toDate').value; |
|||
const fromDate = toApiDdMmYyyy(rawFrom); |
|||
const toDate = toApiDdMmYyyy(rawTo); |
|||
const parkTarget = document.getElementById('zoneSummaryPark'); |
|||
const nulmTarget = document.getElementById('zoneSummaryNulm'); |
|||
|
|||
// Show loaders |
|||
parkTarget.innerHTML = createLoader(); |
|||
nulmTarget.innerHTML = createLoader(); |
|||
|
|||
try { |
|||
// Load Park independently |
|||
fetch(`${apiBase}/zoneSummaryPark?fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`) |
|||
.then(res => { |
|||
if (!res.ok) { |
|||
console.error('Park zoneSummary error', res.status); |
|||
parkTarget.innerHTML = `<div class="alert alert-danger">Error loading Park summary (${res.status})</div>`; |
|||
return Promise.reject(new Error('Park fetch failed')); |
|||
} |
|||
return res.json(); |
|||
}) |
|||
.then(parkData => { |
|||
console.log('Park data loaded:', parkData); |
|||
renderTable(parkTarget, parkData, 'Park'); |
|||
}) |
|||
.catch(e => { |
|||
console.error('Park error:', e); |
|||
parkTarget.innerHTML = `<div class="alert alert-danger">Error: ${e.message}</div>`; |
|||
}); |
|||
|
|||
// Load NULM independently |
|||
fetch(`${apiBase}/zoneSummaryNULM?fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`) |
|||
.then(res => { |
|||
if (!res.ok) { |
|||
console.error('NULM zoneSummary error', res.status); |
|||
nulmTarget.innerHTML = `<div class="alert alert-danger">Error loading NULM summary (${res.status})</div>`; |
|||
return Promise.reject(new Error('NULM fetch failed')); |
|||
} |
|||
return res.json(); |
|||
}) |
|||
.then(nulmData => { |
|||
console.log('NULM data loaded:', nulmData); |
|||
renderTable(nulmTarget, nulmData, 'NULM'); |
|||
}) |
|||
.catch(e => { |
|||
console.error('NULM error:', e); |
|||
nulmTarget.innerHTML = `<div class="alert alert-danger">Error: ${e.message}</div>`; |
|||
}); |
|||
} catch (e) { |
|||
parkTarget.innerHTML = `<div class="alert alert-danger">Error: ${e}</div>`; |
|||
nulmTarget.innerHTML = `<div class="alert alert-danger">Error: ${e}</div>`; |
|||
} |
|||
} |
|||
|
|||
// loadDetailed may be called with explicit empType/category/zone (when clicking a zone count) |
|||
async function loadDetailed(empType, category, zoneVal) { |
|||
// show details in modal |
|||
const rawFrom = document.getElementById('fromDate').value; |
|||
const rawTo = document.getElementById('toDate').value; |
|||
const fromDate = toApiDdMmYyyy(rawFrom); |
|||
const toDate = toApiDdMmYyyy(rawTo); |
|||
const modalEl = document.getElementById('detailModal'); |
|||
const modalBody = document.getElementById('detailModalBody'); |
|||
const modalTitle = document.getElementById('detailModalLabel'); |
|||
if (!empType || !category || !zoneVal) { |
|||
modalTitle.innerText = 'Details'; |
|||
modalBody.innerHTML = '<div class="text-warning">Missing parameters. Click a count to view details.</div>'; |
|||
new bootstrap.Modal(modalEl).show(); |
|||
return; |
|||
} |
|||
|
|||
modalTitle.innerText = `${empType} — Zone ${zoneVal} — ${category.toUpperCase()} (${rawFrom} → ${rawTo})`; |
|||
modalBody.innerHTML = createLoader(); |
|||
// Show modal immediately with loader |
|||
new bootstrap.Modal(modalEl).show(); |
|||
|
|||
try { |
|||
let url = `${apiBase}/detailed?empType=${encodeURIComponent(empType)}&category=${encodeURIComponent(category)}&fromDate=${encodeURIComponent(fromDate)}&toDate=${encodeURIComponent(toDate)}`; |
|||
url += `&zone=${encodeURIComponent(zoneVal)}`; |
|||
const res = await fetch(url); |
|||
if (!res.ok) { |
|||
const text = await res.text().catch(() => ''); |
|||
modalBody.innerHTML = `<div class="alert alert-danger">Error ${res.status}: ${text}</div>`; |
|||
return; |
|||
} |
|||
const data = await res.json(); |
|||
if (!Array.isArray(data)) { modalBody.innerText = JSON.stringify(data); return; } |
|||
if (data.length === 0) { modalBody.innerHTML = '<div class="alert alert-info">No records</div>'; return; } |
|||
|
|||
// Define columns to display based on category |
|||
let displayColumns = []; |
|||
const catLower = (category || 'present').toLowerCase(); |
|||
|
|||
if (catLower === 'present') { |
|||
displayColumns = ['enrollment_id', 'name', 'designation', 'department', 'intime', 'inphoto', 'outtime', 'outphoto']; |
|||
} else if (catLower === 'leave') { |
|||
displayColumns = ['enrollment_id', 'name', 'designation', 'department', 'leavedatetime', 'leaveby']; |
|||
} else if (catLower === 'od') { |
|||
displayColumns = ['enrollment_id', 'name', 'designation', 'department', 'odtime', 'odby']; |
|||
} else { |
|||
displayColumns = Object.keys(data[0]); |
|||
} |
|||
|
|||
// Filter data to include only displayColumns, handle case-insensitive matching |
|||
const filteredData = data.map(row => { |
|||
const filtered = {}; |
|||
displayColumns.forEach(col => { |
|||
// Try exact match first, then case-insensitive |
|||
const key = Object.keys(row).find(k => k === col || k.toLowerCase() === col.toLowerCase()); |
|||
if (key) { |
|||
filtered[col] = row[key]; |
|||
} |
|||
}); |
|||
return filtered; |
|||
}); |
|||
|
|||
function prettyName(key) { |
|||
const map = { |
|||
enrollment_id: 'Enrollment ID', |
|||
name: 'Name', |
|||
designation: 'Designation', |
|||
department: 'Department', |
|||
intime: 'In Time', |
|||
inphoto: 'In Photo', |
|||
outtime: 'Out Time', |
|||
outphoto: 'Out Photo', |
|||
leavedatetime: 'Leave DateTime', |
|||
leaveby: 'Leave By', |
|||
odtime: 'OD DateTime', |
|||
odby: 'OD By', |
|||
}; |
|||
if (map[key]) return map[key]; |
|||
return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); |
|||
} |
|||
|
|||
let html = '<div class="table-responsive"><table class="table table-sm table-bordered"><thead><tr>'; |
|||
// add serial number header |
|||
html += '<th>S.No</th>'; |
|||
displayColumns.forEach(k => { |
|||
html += `<th>${prettyName(k)}</th>`; |
|||
}); |
|||
html += '</tr></thead><tbody>'; |
|||
filteredData.forEach((row, idx) => { |
|||
html += '<tr>'; |
|||
// serial number cell |
|||
html += `<td>${idx + 1}</td>`; |
|||
displayColumns.forEach(k => { |
|||
const val = row[k]; |
|||
// Handle photo columns - show as thumbnail if it's an image |
|||
if ((k === 'inphoto' || k === 'outphoto') && val) { |
|||
html += `<td><img src="${val}" alt="photo" style="max-width: 100px; max-height: 100px; cursor: pointer;" onclick="this.style.maxWidth='500px'; this.style.maxHeight='500px';"></td>`; |
|||
} else { |
|||
html += `<td>${val != null ? val : '-'}</td>`; |
|||
} |
|||
}); |
|||
html += '</tr>'; |
|||
}); |
|||
html += '</tbody></table></div>'; |
|||
modalBody.innerHTML = html; |
|||
} catch (e) { |
|||
modalBody.innerHTML = `<div class="alert alert-danger">Error: ${e}</div>`; |
|||
} |
|||
} |
|||
|
|||
document.addEventListener('DOMContentLoaded', () => { |
|||
setDefaults(); |
|||
document.getElementById('loadSummaryBtn').addEventListener('click', () => { |
|||
const rawFrom = document.getElementById('fromDate').value; |
|||
const rawTo = document.getElementById('toDate').value; |
|||
if (rawFrom && rawTo && rawTo < rawFrom) { |
|||
alert('To date must be the same as or later than From date'); |
|||
return; |
|||
} |
|||
loadAbstractCounts(); loadZoneSummary(); |
|||
}); |
|||
// delegate clicks on zone counts to load detailed view |
|||
document.addEventListener('click', (ev) => { |
|||
const a = ev.target.closest && ev.target.closest('.zone-count'); |
|||
if (!a) return; |
|||
ev.preventDefault(); |
|||
const zone = a.getAttribute('data-zone'); |
|||
const empType = a.getAttribute('data-emptype'); |
|||
const category = a.getAttribute('data-category'); |
|||
// call detailed loader with explicit params |
|||
loadDetailed(empType, category, zone); |
|||
}); |
|||
// set date constraints and load initial |
|||
setDateLimits(); |
|||
// load initial |
|||
loadAbstractCounts(); |
|||
loadZoneSummary(); |
|||
}); |
|||
|
|||
})(); |
|||
</script> |
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> |
|||
<!-- Detail modal --> |
|||
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true"> |
|||
<div class="modal-dialog modal-xl modal-dialog-scrollable"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<h5 class="modal-title" id="detailModalLabel">Details</h5> |
|||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|||
</div> |
|||
<div class="modal-body" id="detailModalBody"> |
|||
Loading... |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,737 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<title>Green Committe 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; |
|||
} |
|||
|
|||
.dataTables_wrapper .dt-buttons { |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
.dataTables_filter { |
|||
margin-bottom: 25px; /* adjust as needed */ |
|||
} |
|||
|
|||
.page-header-title h4 { |
|||
margin: 20px 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
#zoneDetailsTable_wrapper .dataTables_scrollBody { |
|||
border: none !important; |
|||
} |
|||
|
|||
#zoneDetailsTable td, #zoneDetailsTable th { |
|||
vertical-align: middle; |
|||
text-align: center; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.filter-btn { |
|||
height: 38px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.page-header-title { |
|||
margin-bottom: 25px; |
|||
} |
|||
|
|||
#pendingModal .modal-body{ |
|||
min-height: 60vh; |
|||
max-height: 70vh; |
|||
overflow-y: auto; |
|||
} |
|||
/* 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; |
|||
} |
|||
|
|||
|
|||
</style> |
|||
|
|||
</head> |
|||
|
|||
<body> |
|||
<div class="page-header-title"> |
|||
<h4>Green Committee - Zonewise Report</h4> |
|||
</div> |
|||
|
|||
<div class="container-fluid mb-4"> |
|||
<div class="row justify-content-center align-items-end gx-3"> |
|||
|
|||
<!-- Start Date --> |
|||
<div class="col-lg-3 col-md-4 col-sm-6 mb-2"> |
|||
<label class="font-weight-bold mb-1">From Date</label> |
|||
<input type="date" id="startDate" class="form-control" onclick="this.showPicker()"> |
|||
</div> |
|||
|
|||
<!-- End Date --> |
|||
<div class="col-lg-3 col-md-4 col-sm-6 mb-2"> |
|||
<label class="font-weight-bold mb-1">To Date</label> |
|||
<input type="date" id="endDate" class="form-control" onclick="this.showPicker()"> |
|||
</div> |
|||
|
|||
|
|||
<!-- Buttons --> |
|||
<div class="col-lg-3 col-md-4 col-sm-12 mb-2"> |
|||
<label class="font-weight-bold mb-1 d-none d-md-block"> </label> |
|||
<div class="d-flex gap-2"> |
|||
<button |
|||
class="btn btn-success flex-fill" |
|||
onclick="loadZoneReport()"> |
|||
Apply Filter |
|||
</button> |
|||
|
|||
<button |
|||
class="btn btn-outline-secondary flex-fill" |
|||
onclick="resetvalues()"> |
|||
Reset |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
<div id="opSummaryTable" style="display: block;" class="container"> |
|||
<table id="ZoneOpDatatable" class="table table-striped table-bordered display nowrap w-100"> |
|||
<thead> |
|||
<tr> |
|||
<th class="text-center">Zones</th> |
|||
<th class="text-center">Total Request</th> |
|||
<th class="text-center">NGO Pending</th> |
|||
<th class="text-center">GCC Pending</th> |
|||
<th class="text-center">TNFD Pending</th> |
|||
<th class="text-center">Committee Pending</th> |
|||
<th class="text-center">Withdraw</th> |
|||
<th class="text-center">Completed</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 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="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|||
</div> |
|||
<div class="modal-body"> |
|||
|
|||
<table id="WardDatatable" class="table table-striped table-bordered display nowrap w-100"> |
|||
<thead> |
|||
<tr> |
|||
<th class="text-center">Wards</th> |
|||
<th class="text-center">Total Request</th> |
|||
<th class="text-center">NGO Pending</th> |
|||
<th class="text-center">GCC Pending</th> |
|||
<th class="text-center">TNFD Pending</th> |
|||
<th class="text-center">Committee Pending</th> |
|||
<th class="text-center">Withdraw</th> |
|||
<th class="text-center">Completed</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
|
|||
|
|||
</tbody> |
|||
</table> |
|||
|
|||
</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="btn-close" data-bs-dismiss="modal" aria-label="Close"></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 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> |
|||
<!-- Add the 'Please wait' text below the spinner --> |
|||
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p> |
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
|
|||
<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/dist/js/bootstrap.bundle.min.js"></script> |
|||
|
|||
<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> |
|||
<!-- SweetAlert --> |
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
|||
|
|||
<script> |
|||
|
|||
let CURRENT_ZONE = null; |
|||
|
|||
function resetvalues(){ |
|||
location.reload(); |
|||
} |
|||
function openImagePreview(src) { |
|||
$('#previewImage').attr('src', src); |
|||
$('#imagePreviewModal').modal('show'); |
|||
} |
|||
|
|||
function loadZoneReport(){ |
|||
const startDate = $('#startDate').val(); // yyyy-mm-dd or "" |
|||
const endDate = $('#endDate').val(); |
|||
|
|||
$('#pageLoader').show(); |
|||
$.ajax({ |
|||
url: '/commissionerreview/api/greencommittee/getZoneReport', |
|||
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 = $('#ZoneOpDatatable').DataTable({ |
|||
destroy: true, |
|||
autoWidth: true, |
|||
info: true, |
|||
data: res.data, // using response directly |
|||
columns: [ |
|||
{ |
|||
data: 'zone_name', |
|||
render: d => `<a href="#" onclick="openZoneModal('${d}')">${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: 'frtip', |
|||
lengthMenu: [ |
|||
[50, 100, 200, 500, -1], |
|||
['50 rows', '100 rows', '200 rows', 'Show all'] |
|||
], |
|||
footerCallback: footerSumCallback, |
|||
scrollY: 'auto', |
|||
scrollX: true, |
|||
responsive: false, |
|||
searching: false, |
|||
ordering: 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 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: '/commissionerreview/api/greencommittee/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: 'frtip', |
|||
lengthMenu: [ |
|||
[50, 100, 200, 500, -1], |
|||
['50 rows', '100 rows', '200 rows', 'Show all'] |
|||
], |
|||
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 |
|||
}); |
|||
|
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
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: '/commissionerreview/api/greencommittee/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 bg-info text-white font-weight-bold"> |
|||
${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>`; |
|||
} |
|||
|
|||
|
|||
$(document).ready(function () { |
|||
|
|||
loadZoneReport(); |
|||
|
|||
// 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(''); |
|||
} |
|||
}); |
|||
|
|||
$('#zoneDetailsModal').on('shown.bs.modal', function () { |
|||
if ($.fn.DataTable.isDataTable('#WardDatatable')) { |
|||
let questionDatatable = $('#WardDatatable').DataTable(); |
|||
questionDatatable.columns.adjust().draw(); |
|||
} |
|||
}); |
|||
|
|||
}); |
|||
|
|||
|
|||
</script> |
|||
</body> |
|||
|
|||
</html> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue