Browse Source

volunteer category wise count

master
AJITHKUMAR12 3 weeks ago
parent
commit
1aa890a045
  1. 9
      src/main/java/com/gcc/commissionerreview/configuration/DataSourceConfig.java
  2. 13
      src/main/java/com/gcc/commissionerreview/controller/VolunteerController.java
  3. 362
      src/main/java/com/gcc/commissionerreview/service/VolunteerService.java
  4. 4
      src/main/resources/application.properties

9
src/main/java/com/gcc/commissionerreview/configuration/DataSourceConfig.java

@ -17,11 +17,12 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement @EnableTransactionManagement
public class DataSourceConfig { public class DataSourceConfig {
// private static String host = "localhost:3306";
// private static String dbpassword = "root";
private static String host = "localhost:3306";
private static String dbpassword = "root";
private static String host = "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
private static String dbpassword = "gcc-facial-password";
// private static String host =
// "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
// private static String dbpassword = "gcc-facial-password";
public static class MyDataSourceConfig { public static class MyDataSourceConfig {
@Primary @Primary

13
src/main/java/com/gcc/commissionerreview/controller/VolunteerController.java

@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.MediaType;
import java.util.Map; import java.util.Map;
@ -17,7 +18,7 @@ public class VolunteerController {
@Autowired @Autowired
private VolunteerService volunteerService; private VolunteerService volunteerService;
@GetMapping("/overall-details")
@GetMapping(value = "/overall-details", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> getOverallDetails() { public ResponseEntity<Map<String, Object>> getOverallDetails() {
return volunteerService.getOverallDetails(); return volunteerService.getOverallDetails();
} }
@ -56,6 +57,7 @@ public class VolunteerController {
public ResponseEntity<Map<String, Object>> getOverallPendingRequest() { public ResponseEntity<Map<String, Object>> getOverallPendingRequest() {
return volunteerService.getOverallPendingRequest(); return volunteerService.getOverallPendingRequest();
} }
/// ////////////////////////////////////// /// //////////////////////////////////////
@GetMapping("/zonewise-details") @GetMapping("/zonewise-details")
public ResponseEntity<Map<String, Object>> getZonewiseDetails() { public ResponseEntity<Map<String, Object>> getZonewiseDetails() {
@ -101,14 +103,23 @@ public class VolunteerController {
public ResponseEntity<Map<String, Object>> getZonewisePendingRequest(@RequestParam String zone) { public ResponseEntity<Map<String, Object>> getZonewisePendingRequest(@RequestParam String zone) {
return volunteerService.getZonewisePendingRequest(zone); return volunteerService.getZonewisePendingRequest(zone);
} }
/// //////////////////////////////////// /// ////////////////////////////////////
@GetMapping("/wardwise-details") @GetMapping("/wardwise-details")
public ResponseEntity<Map<String, Object>> getWardwiseDetails(@RequestParam String zone) { public ResponseEntity<Map<String, Object>> getWardwiseDetails(@RequestParam String zone) {
return volunteerService.getWardwiseDetails(zone); return volunteerService.getWardwiseDetails(zone);
} }
/// ///////////////////////////////////// /// /////////////////////////////////////
@GetMapping("/volunteer-details") @GetMapping("/volunteer-details")
public ResponseEntity<Map<String, Object>> getVolunteerDetails(@RequestParam String zone) { public ResponseEntity<Map<String, Object>> getVolunteerDetails(@RequestParam String zone) {
return volunteerService.getVolunteerDetails(zone); return volunteerService.getVolunteerDetails(zone);
} }
/// /////////////////////////////////////
@GetMapping(value = "/categorywise-details", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> getCategorywiseDetails(@RequestParam String zone) {
return volunteerService.getCategorywiseDetails(zone);
}
} }

362
src/main/java/com/gcc/commissionerreview/service/VolunteerService.java

@ -97,16 +97,102 @@ public class VolunteerService {
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql);
if (data.isEmpty()) {
response.put("status", false);
response.put("message", "Data not found");
response.put("data", data);
return ResponseEntity.ok(response);
}
String sql1 = """
WITH volunteer_zone AS (
SELECT
volunteer_id,
sub_category,
CASE
WHEN TRIM(preferred_zones) = '0' THEN 0
ELSE CAST(
SUBSTRING_INDEX(
REPLACE(TRIM(preferred_zones), ' ', ''),
',',
1
) AS UNSIGNED
)
END AS zone
FROM volunteer_information
WHERE isactive = 1
)
SELECT
CASE
WHEN zone IS NULL THEN 'Grand Total'
WHEN zone = 0 THEN 'Any'
ELSE CAST(zone AS CHAR)
END AS `zone`,
SUM(
CASE
WHEN sub_category = 'Call Centre Support'
THEN 1 ELSE 0
END
) AS `call_centre_support`,
SUM(
CASE
WHEN sub_category = 'Centre Admin Support'
THEN 1 ELSE 0
END
) AS `centre_admin_support`,
SUM(
CASE
WHEN sub_category = 'Cooks & Assistance'
THEN 1 ELSE 0
END
) AS `cooks_and_assistance`,
SUM(
CASE
WHEN sub_category = 'Distribution/Delivery Support'
THEN 1 ELSE 0
END
) AS `distribution/delivery_support`,
SUM(
CASE
WHEN sub_category = 'General Assistance'
THEN 1 ELSE 0
END
) AS `general_assistance`,
SUM(
CASE
WHEN sub_category = 'Graphic Designers'
THEN 1 ELSE 0
END
) AS `graphic_designers`,
SUM(
CASE
WHEN sub_category = 'Medical Assistance/Counselling Service'
THEN 1 ELSE 0
END
) AS `medical_assistance/counselling_service`,
SUM(
CASE
WHEN sub_category = 'Survey'
THEN 1 ELSE 0
END
) AS `survey`,
COUNT(*) AS `grand_total`
FROM volunteer_zone
GROUP BY zone WITH ROLLUP
ORDER BY
CASE WHEN zone IS NULL THEN 1 ELSE 0 END,
CAST(zone AS UNSIGNED)
""";
List<Map<String, Object>> data1 = jdbcVolunteerTemplate.queryForList(sql1);
response.put("status", true); response.put("status", true);
response.put("message", "Data fetched successfully."); response.put("message", "Data fetched successfully.");
response.put("data", data); response.put("data", data);
response.put("categorywise_data", data1);
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
@ -231,7 +317,6 @@ LEFT JOIN complaint_counts cc
ORDER BY vi.volunteer_id ORDER BY vi.volunteer_id
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -365,7 +450,6 @@ ORDER BY vi.volunteer_id
ORDER BY vi.volunteer_id ORDER BY vi.volunteer_id
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -499,7 +583,6 @@ ORDER BY vi.volunteer_id
ORDER BY vi.volunteer_id ORDER BY vi.volunteer_id
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -639,7 +722,6 @@ ORDER BY vi.volunteer_id
ORDER BY vi.volunteer_id ORDER BY vi.volunteer_id
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -669,7 +751,6 @@ ORDER BY vi.volunteer_id
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -688,7 +769,6 @@ ORDER BY vi.volunteer_id
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -718,7 +798,6 @@ ORDER BY vi.volunteer_id
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -739,7 +818,6 @@ ORDER BY vi.volunteer_id
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -769,7 +847,6 @@ ORDER BY vi.volunteer_id
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -790,7 +867,6 @@ ORDER BY vi.volunteer_id
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql);
if (data.isEmpty()) { if (data.isEmpty()) {
response.put("status", false); response.put("status", false);
@ -814,6 +890,7 @@ ORDER BY vi.volunteer_id
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
} }
} }
// public ResponseEntity<Map<String, Object>> getZonewiseDetails() { // public ResponseEntity<Map<String, Object>> getZonewiseDetails() {
// //
// Map<String, Object> response = new HashMap<>(); // Map<String, Object> response = new HashMap<>();
@ -845,7 +922,8 @@ ORDER BY vi.volunteer_id
// response.put("message", "Failed to fetch zone summary."); // response.put("message", "Failed to fetch zone summary.");
// response.put("error", e.getMessage()); // response.put("error", e.getMessage());
// //
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
// return
// ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
// } // }
// } // }
/// //////////////// Zonewise Details /// //////////////// Zonewise Details
@ -992,7 +1070,9 @@ ORDER BY vi.volunteer_id
// """; // """;
String sql = """ String sql = """
WITH zones AS ( WITH zones AS (
SELECT '01' AS zone
SELECT '0' AS zone
UNION ALL SELECT '01'
UNION ALL SELECT '02' UNION ALL SELECT '02'
UNION ALL SELECT '03' UNION ALL SELECT '03'
UNION ALL SELECT '04' UNION ALL SELECT '04'
@ -1009,25 +1089,38 @@ WITH zones AS (
UNION ALL SELECT '15' UNION ALL SELECT '15'
), ),
/*
* Get the latest location of each volunteer
*/
latest_location AS ( latest_location AS (
SELECT SELECT
vlh.volunteer_id, vlh.volunteer_id,
vlh.zone, vlh.zone,
vlh.cdate, vlh.cdate,
vlh.is_active vlh.is_active
FROM volunteer_location_history vlh FROM volunteer_location_history vlh
INNER JOIN ( INNER JOIN (
SELECT SELECT
volunteer_id, volunteer_id,
MAX(cdate) AS latest_cdate MAX(cdate) AS latest_cdate
FROM volunteer_location_history FROM volunteer_location_history
GROUP BY volunteer_id GROUP BY volunteer_id
) latest ) latest
ON vlh.volunteer_id = latest.volunteer_id ON vlh.volunteer_id = latest.volunteer_id
AND vlh.cdate = latest.latest_cdate AND vlh.cdate = latest.latest_cdate
), ),
/*
* Rescue request counts by zone
*/
rescue_counts AS ( rescue_counts AS (
SELECT SELECT
zone, zone,
@ -1052,72 +1145,160 @@ rescue_counts AS (
WHERE zone IS NOT NULL WHERE zone IS NOT NULL
GROUP BY zone GROUP BY zone
),
/*
* Get ONLY the first preferred zone of each volunteer.
*
* Examples:
*
* 1 -> 1
* 1,2,3 -> 1
* 4,5,6 -> 4
* 0 -> 0
* 0,2,3 -> 0
*/
preferred_zone AS (
SELECT
volunteer_id,
CAST(
SUBSTRING_INDEX(
REPLACE(TRIM(preferred_zones), ' ', ''),
',',
1
) AS UNSIGNED
) AS first_zone
FROM volunteer_information
WHERE isactive = 1
AND preferred_zones IS NOT NULL
AND TRIM(preferred_zones) <> ''
) )
SELECT SELECT
z.zone, z.zone,
/* OLD LOGIC:
Volunteer preferred zone */
COUNT(DISTINCT vi.volunteer_id) AS total_volunteers,
/*
* Total volunteers based on FIRST preferred zone only
*/
COUNT(DISTINCT pz.volunteer_id) AS total_volunteers,
/* NEW DISTINCT LOGIC:
Volunteer latest location zone */
/*
* Volunteer latest location zone
*/
COUNT(DISTINCT ll.volunteer_id) AS distinct_total_volunteers, COUNT(DISTINCT ll.volunteer_id) AS distinct_total_volunteers,
/* Active based on latest location */
COUNT(DISTINCT CASE
/*
* Active volunteers based on latest location
*/
COUNT(
DISTINCT CASE
WHEN ll.is_active = 1 WHEN ll.is_active = 1
THEN ll.volunteer_id THEN ll.volunteer_id
END) AS active_volunteers,
END
) AS active_volunteers,
/* Inactive based on latest location */
COUNT(DISTINCT CASE
/*
* Inactive volunteers based on latest location
*/
COUNT(
DISTINCT CASE
WHEN ll.is_active = 0 WHEN ll.is_active = 0
THEN ll.volunteer_id THEN ll.volunteer_id
END) AS inactive_volunteers,
/* Ready to work */
COUNT(DISTINCT CASE
END
) AS inactive_volunteers,
/*
* Ready to work
*
* Active volunteer
* AND
* No active complaint with complaint_status = 2
*/
COUNT(
DISTINCT CASE
WHEN ll.is_active = 1 WHEN ll.is_active = 1
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 SELECT 1
FROM volunteer_complaintlist vc FROM volunteer_complaintlist vc
WHERE vc.volunteer_id = ll.volunteer_id WHERE vc.volunteer_id = ll.volunteer_id
AND vc.is_active = 1 AND vc.is_active = 1
AND vc.is_delete = 0 AND vc.is_delete = 0
AND vc.complaint_status = 2 AND vc.complaint_status = 2
) )
THEN ll.volunteer_id THEN ll.volunteer_id
END) AS ready_to_work,
END
) AS ready_to_work,
COALESCE(rc.total_requests, 0) AS total_requests,
/*
* Rescue request counts
*/
COALESCE(
rc.total_requests,
0
) AS total_requests,
COALESCE(rc.completed_requests, 0) AS completed_requests,
COALESCE(
rc.completed_requests,
0
) AS completed_requests,
COALESCE(rc.pending_requests, 0) AS pending_requests
COALESCE(
rc.pending_requests,
0
) AS pending_requests
FROM zones z FROM zones z
/* OLD TOTAL VOLUNTEER LOGIC */
LEFT JOIN volunteer_information vi
ON vi.preferred_zones = '0'
OR FIND_IN_SET(z.zone, vi.preferred_zones) > 0
/* LATEST LOCATION LOGIC */
/*
* NEW TOTAL VOLUNTEER LOGIC
*
* Only the FIRST preferred zone is considered.
*
* Example:
*
* preferred_zones = '1,2,3'
*
* SUBSTRING_INDEX(...) = '1'
*
* Therefore volunteer is counted only in Zone 1.
*/
LEFT JOIN preferred_zone pz
ON CAST(z.zone AS UNSIGNED) = pz.first_zone
/*
* Latest location logic
*/
LEFT JOIN latest_location ll LEFT JOIN latest_location ll
ON ll.zone = z.zone ON ll.zone = z.zone
/*
* Rescue counts
*/
LEFT JOIN rescue_counts rc LEFT JOIN rescue_counts rc
ON rc.zone = z.zone ON rc.zone = z.zone
GROUP BY GROUP BY
z.zone, z.zone,
rc.total_requests, rc.total_requests,
rc.completed_requests, rc.completed_requests,
rc.pending_requests rc.pending_requests
ORDER BY z.zone
ORDER BY
CAST(z.zone AS UNSIGNED)
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql);
@ -1693,7 +1874,6 @@ ORDER BY z.zone
ORDER BY vi.volunteer_id ORDER BY vi.volunteer_id
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl, zone); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, baseurl, zone);
if (data.isEmpty()) { if (data.isEmpty()) {
@ -1723,7 +1903,6 @@ ORDER BY z.zone
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -1742,7 +1921,6 @@ ORDER BY z.zone
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone);
if (data.isEmpty()) { if (data.isEmpty()) {
@ -1772,7 +1950,6 @@ ORDER BY z.zone
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -1792,7 +1969,6 @@ ORDER BY z.zone
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone);
if (data.isEmpty()) { if (data.isEmpty()) {
@ -1822,7 +1998,6 @@ ORDER BY z.zone
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
try { try {
String sql = """ String sql = """
@ -1842,7 +2017,6 @@ ORDER BY z.zone
ORDER BY cdate DESC ORDER BY cdate DESC
"""; """;
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone); List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone);
if (data.isEmpty()) { if (data.isEmpty()) {
@ -1872,7 +2046,8 @@ ORDER BY z.zone
// //
// try { // try {
// String sql = "SELECT * FROM zonewise_details WHERE zone = ?"; // String sql = "SELECT * FROM zonewise_details WHERE zone = ?";
// List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone);
// List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql,
// zone);
// //
// if (data.isEmpty()) { // if (data.isEmpty()) {
// response.put("status", false); // response.put("status", false);
@ -1892,7 +2067,8 @@ ORDER BY z.zone
// response.put("message", "Failed to fetch data."); // response.put("message", "Failed to fetch data.");
// response.put("error", e.getMessage()); // response.put("error", e.getMessage());
// //
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
// return
// ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
// } // }
// } // }
@ -2034,4 +2210,86 @@ ORDER BY z.zone
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
} }
} }
////// categorywise-details
public ResponseEntity<Map<String, Object>> getCategorywiseDetails(String zone) {
Map<String, Object> response = new HashMap<>();
try {
String sql = """
WITH volunteer_zone AS (
SELECT
volunteer_id,
sub_category,
CASE
WHEN TRIM(preferred_zones) = '0' THEN 0
ELSE CAST(
SUBSTRING_INDEX(
REPLACE(TRIM(preferred_zones), ' ', ''),
',',
1
) AS UNSIGNED
)
END AS zone
FROM volunteer_information
WHERE isactive = 1
)
SELECT
CAST(zone AS CHAR) AS `Zone`,
SUM(CASE WHEN sub_category = 'Call Centre Support'
THEN 1 ELSE 0 END) AS `call_centre_support`,
SUM(CASE WHEN sub_category = 'Centre Admin Support'
THEN 1 ELSE 0 END) AS `centre_admin_support`,
SUM(CASE WHEN sub_category = 'Cooks & Assistance'
THEN 1 ELSE 0 END) AS `cooks_and_assistance`,
SUM(CASE WHEN sub_category = 'Distribution/Delivery Support'
THEN 1 ELSE 0 END) AS `distribution/delivery_support`,
SUM(CASE WHEN sub_category = 'General Assistance'
THEN 1 ELSE 0 END) AS `general_assistance`,
SUM(CASE WHEN sub_category = 'Graphic Designers'
THEN 1 ELSE 0 END) AS `graphic_designers`,
SUM(CASE WHEN sub_category = 'Medical Assistance/Counselling Service'
THEN 1 ELSE 0 END) AS `medical_assistance/counselling_service`,
SUM(CASE WHEN sub_category = 'Survey'
THEN 1 ELSE 0 END) AS `survey`,
COUNT(*) AS `grand_total`
FROM volunteer_zone
WHERE zone = ?
GROUP BY zone
""";
List<Map<String, Object>> data = jdbcVolunteerTemplate.queryForList(sql, zone);
if (data.isEmpty()) {
response.put("status", false);
response.put("message", "Data not found");
response.put("data", data);
return ResponseEntity.ok(response);
}
response.put("status", true);
response.put("message", "Data fetched successfully");
response.put("data", data);
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("status", false);
response.put("message", "Failed to fetch data.");
response.put("error", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
} }

4
src/main/resources/application.properties

@ -1,2 +1,2 @@
spring.profiles.active=prod
#spring.profiles.active=dev
#spring.profiles.active=prod
spring.profiles.active=dev
Loading…
Cancel
Save