Browse Source
newly added toner management project and changes in manpower , payment tracker ui and api's
master
newly added toner management project and changes in manpower , payment tracker ui and api's
master
13 changed files with 2294 additions and 120 deletions
-
13src/main/java/com/gcc_manpower/config/DataSourceConfig.java
-
9src/main/java/com/gcc_manpower/config/WebConfig.java
-
3src/main/java/com/gcc_manpower/gccservicedocuments/service/GccServiceDocumentsService.java
-
226src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java
-
97src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java
-
1src/main/java/com/gcc_manpower/service/AppUserService.java
-
6src/main/resources/templates/fragments/navbar.html
-
22src/main/resources/templates/fragments/tonerheader.html
-
292src/main/resources/templates/gccservicepaymenttracker/dashboard.html
-
116src/main/resources/templates/gcctonermanagement/login.html
-
670src/main/resources/templates/gcctonermanagement/upload.html
-
955src/main/resources/templates/home/home.html
-
2src/main/resources/templates/home/view-login-details.html
@ -0,0 +1,226 @@ |
|||||
|
package com.gcc_manpower.gcctonermanagement.controller; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.ui.Model; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
import java.io.File; |
||||
|
import com.gcc_manpower.gcctonermanagement.service.TonerService; |
||||
|
import com.gcc_manpower.service.AppUserService; |
||||
|
|
||||
|
import jakarta.servlet.http.HttpSession; |
||||
|
|
||||
|
@Controller |
||||
|
@RequestMapping("/gcctonermanagement") |
||||
|
public class TonerController { |
||||
|
|
||||
|
@Autowired |
||||
|
private AppUserService appUserService; |
||||
|
|
||||
|
@Autowired |
||||
|
private TonerService tonerService; |
||||
|
|
||||
|
@Value("${toner.upload.path:gcctonermanagementfiles}") |
||||
|
private String tonerUploadPathConfig; |
||||
|
|
||||
|
@GetMapping({ "", "/", "/upload" }) |
||||
|
public String viewDashboard(Model model, HttpSession session) { |
||||
|
String username = (String) session.getAttribute("toner_usrName"); |
||||
|
if (username == null) { |
||||
|
return "redirect:/gcctonermanagement/login"; |
||||
|
} |
||||
|
|
||||
|
String usrRole = (String) session.getAttribute("toner_usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
// List<Map<String, Object>> vendorDetails = |
||||
|
// appUserService.getVendorDetailsWithCounts(); |
||||
|
|
||||
|
model.addAttribute("isLoggedIn", true); |
||||
|
model.addAttribute("isAdmin", isAdmin); |
||||
|
model.addAttribute("usrName", username); |
||||
|
model.addAttribute("curPage", "gcctonermanagement"); |
||||
|
// model.addAttribute("vendorDetails", vendorDetails); |
||||
|
|
||||
|
return "gcctonermanagement/upload"; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/login") |
||||
|
public String login(HttpSession session) { |
||||
|
return "gcctonermanagement/login"; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/login") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> validateLogin( |
||||
|
@RequestParam("usrName") String usrName, |
||||
|
@RequestParam("usrPass") String usrPass, |
||||
|
HttpSession session) { |
||||
|
|
||||
|
Map<String, Object> authResult = appUserService.authenticate(usrName, usrPass); |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
|
||||
|
if (Boolean.TRUE.equals(authResult.get("success"))) { |
||||
|
session.setAttribute("toner_usrName", authResult.get("usrName")); |
||||
|
session.setAttribute("toner_usrRole", authResult.get("usrRole")); |
||||
|
session.setMaxInactiveInterval(1800); // 30 minutes session timeout |
||||
|
response.put("result", true); |
||||
|
response.put("usrName", authResult.get("usrName")); |
||||
|
} else { |
||||
|
response.put("result", false); |
||||
|
response.put("error", authResult.get("error")); |
||||
|
} |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/logout") |
||||
|
public String logoutGet(HttpSession session) { |
||||
|
session.removeAttribute("toner_usrName"); |
||||
|
session.removeAttribute("toner_usrRole"); |
||||
|
return "redirect:/gcctonermanagement/login"; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/printers") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getPrinters(HttpSession session) { |
||||
|
if (session.getAttribute("toner_usrName") == null) { |
||||
|
return java.util.Collections.emptyList(); |
||||
|
} |
||||
|
return tonerService.getActivePrinters(); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/toners") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getToners(@RequestParam("pid") int pid, HttpSession session) { |
||||
|
if (session.getAttribute("toner_usrName") == null) { |
||||
|
return java.util.Collections.emptyList(); |
||||
|
} |
||||
|
return tonerService.getActiveTonersByPrinter(pid); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/upload") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> uploadToner( |
||||
|
@RequestParam("companyName") String companyName, |
||||
|
@RequestParam("pid") int pid, |
||||
|
@RequestParam("tid") int tid, |
||||
|
@RequestParam("qty") String qty, |
||||
|
@RequestParam(value = "dcFile", required = false) MultipartFile dcFile, |
||||
|
@RequestParam(value = "userRequestFile", required = false) MultipartFile userRequestFile, |
||||
|
@RequestParam(value = "finalDeliveredFile", required = false) MultipartFile finalDeliveredFile, |
||||
|
HttpSession session) { |
||||
|
|
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String username = (String) session.getAttribute("toner_usrName"); |
||||
|
if (username == null) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized access. Please log in."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
// Validate PDF extension on non-empty files |
||||
|
if (dcFile != null && !dcFile.isEmpty() && !isPdfFile(dcFile)) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Delivery Challan file must be a PDF."); |
||||
|
return response; |
||||
|
} |
||||
|
if (userRequestFile != null && !userRequestFile.isEmpty() && !isPdfFile(userRequestFile)) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "User Request file must be a PDF."); |
||||
|
return response; |
||||
|
} |
||||
|
if (finalDeliveredFile != null && !finalDeliveredFile.isEmpty() && !isPdfFile(finalDeliveredFile)) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Final Delivered file must be a PDF."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile(); |
||||
|
if (!uploadDir.exists()) { |
||||
|
uploadDir.mkdirs(); |
||||
|
} |
||||
|
|
||||
|
// Save files |
||||
|
String dcPath = saveUploadedFile(dcFile, "dc", uploadDir); |
||||
|
String userRequestPath = saveUploadedFile(userRequestFile, "user_request", uploadDir); |
||||
|
String finalDeliveredPath = saveUploadedFile(finalDeliveredFile, "final_delivered", uploadDir); |
||||
|
|
||||
|
boolean saved = tonerService.saveInventoryDetails( |
||||
|
companyName.trim(), |
||||
|
pid, |
||||
|
tid, |
||||
|
qty.trim(), |
||||
|
dcPath, |
||||
|
userRequestPath, |
||||
|
finalDeliveredPath, |
||||
|
username); |
||||
|
|
||||
|
if (saved) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Toner details saved successfully."); |
||||
|
} else { |
||||
|
// Delete newly saved files on DB insert failure |
||||
|
deleteFileIfExists(dcPath, uploadDir); |
||||
|
deleteFileIfExists(userRequestPath, uploadDir); |
||||
|
deleteFileIfExists(finalDeliveredPath, uploadDir); |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to save registration details in the database."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "An error occurred during saving: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
private boolean isPdfFile(MultipartFile file) { |
||||
|
String originalFilename = file.getOriginalFilename(); |
||||
|
if (originalFilename == null) |
||||
|
return false; |
||||
|
return originalFilename.toLowerCase().endsWith(".pdf"); |
||||
|
} |
||||
|
|
||||
|
private String saveUploadedFile(MultipartFile file, String prefix, File uploadDir) throws Exception { |
||||
|
if (file == null || file.isEmpty()) { |
||||
|
return null; |
||||
|
} |
||||
|
String originalFilename = file.getOriginalFilename(); |
||||
|
String ext = ".pdf"; // Since we only allow PDFs |
||||
|
|
||||
|
// Clean filename, make it unique |
||||
|
String savedFilename = prefix + "_" + System.currentTimeMillis() + ext; |
||||
|
File destFile = new File(uploadDir, savedFilename).getAbsoluteFile(); |
||||
|
file.transferTo(destFile); |
||||
|
|
||||
|
return "/gcctonermanagementfiles/" + savedFilename; |
||||
|
} |
||||
|
|
||||
|
private void deleteFileIfExists(String webPath, File uploadDir) { |
||||
|
if (webPath == null || webPath.isEmpty()) |
||||
|
return; |
||||
|
try { |
||||
|
String filename = webPath.substring(webPath.lastIndexOf('/') + 1); |
||||
|
File file = new File(uploadDir, filename).getAbsoluteFile(); |
||||
|
if (file.exists()) { |
||||
|
file.delete(); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error deleting file: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
package com.gcc_manpower.gcctonermanagement.service; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
import javax.sql.DataSource; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
@Service |
||||
|
public class TonerService { |
||||
|
|
||||
|
private JdbcTemplate jdbcTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
public void setDataSource(@Qualifier("mysqlTonerManagementDataSource") DataSource dataSource) { |
||||
|
this.jdbcTemplate = new JdbcTemplate(dataSource); |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getActivePrinters() { |
||||
|
String sql = "SELECT id, printer_name FROM printer_model_list where is_active = 1 AND is_delete = 0 "; |
||||
|
try { |
||||
|
return jdbcTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching printers list: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getActiveTonersByPrinter(int pid) { |
||||
|
String sql = "SELECT id, model_name FROM toner_model_list WHERE pid = ? AND is_active = 1 AND is_delete = 0 "; |
||||
|
try { |
||||
|
return jdbcTemplate.queryForList(sql, pid); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching toners list: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public String getTonerModelName(int tid) { |
||||
|
String sql = "SELECT model_name FROM toner_model_list WHERE id = ?"; |
||||
|
try { |
||||
|
return jdbcTemplate.queryForObject(sql, String.class, tid); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching toner model name for id " + tid + ": " + e.getMessage()); |
||||
|
return "UNKNOWN"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int getMaxInventoryId() { |
||||
|
String sql = "SELECT COALESCE(MAX(id), 0) FROM inventory_registered_details"; |
||||
|
try { |
||||
|
return jdbcTemplate.queryForObject(sql, Integer.class); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching max id from inventory_registered_details: " + e.getMessage()); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean saveInventoryDetails(String companyName, int pid, int tid, String qty, String dcUpload, |
||||
|
String userRequestUpload, String finalDeliveredUpload, String username) { |
||||
|
// 1. Get toner model name |
||||
|
String tonerModel = getTonerModelName(tid); |
||||
|
String cleanModel = tonerModel.trim().replaceAll("\\s+", "-").replaceAll("[^a-zA-Z0-9-]", ""); |
||||
|
|
||||
|
// 2. Get next sequential number |
||||
|
int maxId = getMaxInventoryId(); |
||||
|
int seqNum = 1000 + maxId + 1; |
||||
|
|
||||
|
// 3. Get current year |
||||
|
int year = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR); |
||||
|
|
||||
|
// 4. Construct request number |
||||
|
String reqNo = String.format("%s-%d-%04d", cleanModel, year, seqNum); |
||||
|
|
||||
|
// 5. Insert row |
||||
|
String sql = "INSERT INTO department_requested_details " + |
||||
|
"(req_no, company_name, pid, tid, qty_registered, dc_upload, user_request_upload, final_delivered_upload, cdate, updated_date, updated_by, is_active, is_delete) " |
||||
|
+ |
||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)"; |
||||
|
|
||||
|
java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); |
||||
|
try { |
||||
|
int rows = jdbcTemplate.update(sql, reqNo, companyName, String.valueOf(pid), String.valueOf(tid), qty, |
||||
|
dcUpload, userRequestUpload, finalDeliveredUpload, now, now, username); |
||||
|
return rows > 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error saving inventory details: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<th:block th:fragment="tonerheader"> |
||||
|
<!-- Awesome Font --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css"> |
||||
|
<!-- Bootstrap CSS --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/bootstrap-5.3.2/css/bootstrap.min.css}" type="text/css"> |
||||
|
<!-- CSS for Header --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/css/header-styles.css}" type="text/css"> |
||||
|
<div class="header"> |
||||
|
<div class="logo-left"> |
||||
|
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo"> |
||||
|
<div class="logo-text"> |
||||
|
<h2>GREATER</h2> |
||||
|
<h2>CHENNAI</h2> |
||||
|
<h2>CORPORATION</h2> |
||||
|
<p>பெருநகர சென்னை மாநகராட்சி</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="title"> |
||||
|
<h2>GCC Toner Material Management Details</h2> |
||||
|
</div> |
||||
|
</div> |
||||
|
</th:block> |
||||
@ -0,0 +1,116 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html xmlns:th="http://www.thymeleaf.org" lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>GCC IT Cell Toner Management - Login</title> |
||||
|
|
||||
|
<!-- CSRF Tokens --> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" /> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" /> |
||||
|
|
||||
|
<!-- Include CSS --> |
||||
|
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/login-form-styles.css}"> |
||||
|
|
||||
|
<!-- Awesome Font --> |
||||
|
<link th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" rel="stylesheet" type="text/css"> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<div class="container"> |
||||
|
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation" class="small-logo"> |
||||
|
<h3>GREATER CHENNAI CORPORATION</h3> |
||||
|
<h4>பெருநகர சென்னை மாநகராட்சி</h4> |
||||
|
<br> |
||||
|
<h3>GCC IT Cell Toner Management</h3> |
||||
|
<br> |
||||
|
<h2>Login</h2> |
||||
|
|
||||
|
<div id="error-box" |
||||
|
style="color: #c0392b; font-weight: 500; font-size: 0.85rem; margin-bottom: 1.5rem; text-align: center; display: none;"> |
||||
|
<i class="fa-solid fa-circle-exclamation"></i> <span id="error-text">Invalid username or password!</span> |
||||
|
</div> |
||||
|
|
||||
|
<form id="loginForm" onsubmit="handleLogin(event)"> |
||||
|
<div class="input-field"> |
||||
|
<input type="text" name="usrName" id="usrName" placeholder="Enter Username" required |
||||
|
autocomplete="username"> |
||||
|
<div class="underline"></div> |
||||
|
</div> |
||||
|
<div class="input-field"> |
||||
|
<input type="password" name="usrPass" id="usrPass" placeholder="Enter Password" required |
||||
|
autocomplete="current-password"> |
||||
|
<div class="underline"></div> |
||||
|
</div> |
||||
|
<input type="submit" value="Login" id="submit-btn"> |
||||
|
</form> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Footer Fragment --> |
||||
|
<th:block th:replace="~{fragments/footer :: footer}"></th:block> |
||||
|
|
||||
|
<!-- Custom JS --> |
||||
|
<script th:inline="javascript"> |
||||
|
const contextPath = /*[[@{/}]]*/ ''; |
||||
|
</script> |
||||
|
|
||||
|
<script> |
||||
|
function handleLogin(event) { |
||||
|
event.preventDefault(); |
||||
|
|
||||
|
const errorBox = document.getElementById("error-box"); |
||||
|
const errorText = document.getElementById("error-text"); |
||||
|
const submitBtn = document.getElementById("submit-btn"); |
||||
|
|
||||
|
errorBox.style.display = "none"; |
||||
|
submitBtn.disabled = true; |
||||
|
submitBtn.value = 'Authenticating...'; |
||||
|
|
||||
|
const username = document.getElementById("usrName").value; |
||||
|
const password = document.getElementById("usrPass").value; |
||||
|
|
||||
|
const urlParams = new URLSearchParams(); |
||||
|
urlParams.append('usrName', username); |
||||
|
urlParams.append('usrPass', password); |
||||
|
|
||||
|
// Fetch CSRF tokens |
||||
|
const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content"); |
||||
|
const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content"); |
||||
|
|
||||
|
const headers = { |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}; |
||||
|
if (csrfToken && csrfHeader) { |
||||
|
headers[csrfHeader] = csrfToken; |
||||
|
} |
||||
|
|
||||
|
fetch(contextPath + 'gcctonermanagement/api/login', { |
||||
|
method: 'POST', |
||||
|
headers: headers, |
||||
|
body: urlParams |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(data => { |
||||
|
if (data.result) { |
||||
|
sessionStorage.setItem("toner_usrName", data.usrName); |
||||
|
window.location.href = contextPath + 'gcctonermanagement/upload'; |
||||
|
} else { |
||||
|
submitBtn.disabled = false; |
||||
|
submitBtn.value = 'Login'; |
||||
|
errorText.textContent = data.error || "Authentication failed."; |
||||
|
errorBox.style.display = "block"; |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Login Error:", err); |
||||
|
submitBtn.disabled = false; |
||||
|
submitBtn.value = 'Login'; |
||||
|
errorText.textContent = "Network error. Please try again later."; |
||||
|
errorBox.style.display = "block"; |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,670 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html xmlns:th="http://www.thymeleaf.org" lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
<script th:inline="javascript"> |
||||
|
(function () { |
||||
|
const currentUser = /*[[${session.toner_usrName}]]*/ null; |
||||
|
if (currentUser && sessionStorage.getItem("toner_usrName") !== currentUser) { |
||||
|
window.location.replace(/*[[@{/gcctonermanagement/login}]]*/ '/gcctonermanagement/login'); |
||||
|
} |
||||
|
})(); |
||||
|
</script> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>GCC Toner Upload Portal</title> |
||||
|
|
||||
|
<!-- CSRF Tokens --> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" /> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" /> |
||||
|
|
||||
|
<!-- Header Fragment --> |
||||
|
<th:block th:replace="~{fragments/tonerheader :: tonerheader}"></th:block> |
||||
|
|
||||
|
<!-- Google Fonts --> |
||||
|
<link |
||||
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Poppins:wght@300;400;500;600;700&display=swap" |
||||
|
rel="stylesheet"> |
||||
|
|
||||
|
<!-- Styles --> |
||||
|
<style> |
||||
|
body { |
||||
|
background-color: #f4f7f6; |
||||
|
font-family: 'Poppins', sans-serif; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.main-container { |
||||
|
max-width: 800px; |
||||
|
margin: 40px auto; |
||||
|
padding: 0 20px; |
||||
|
} |
||||
|
|
||||
|
.page-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 30px; |
||||
|
} |
||||
|
|
||||
|
.page-title { |
||||
|
font-family: 'Outfit', sans-serif; |
||||
|
font-size: 2rem; |
||||
|
font-weight: 700; |
||||
|
color: #0f5c78; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.btn-back { |
||||
|
background-color: #ffffff; |
||||
|
color: #475569; |
||||
|
font-weight: 600; |
||||
|
border: 1.5px solid #cbd5e1; |
||||
|
padding: 10px 20px; |
||||
|
border-radius: 30px; |
||||
|
transition: all 0.3s ease; |
||||
|
text-decoration: none; |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.btn-back:hover { |
||||
|
background-color: #f1f5f9; |
||||
|
color: #0f5c78; |
||||
|
border-color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
.upload-card { |
||||
|
background: #ffffff; |
||||
|
border-radius: 20px; |
||||
|
border: 1px solid rgba(15, 92, 120, 0.08); |
||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.04); |
||||
|
padding: 40px; |
||||
|
} |
||||
|
|
||||
|
.form-label { |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 600; |
||||
|
color: #475569; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.form-control-custom { |
||||
|
height: 48px; |
||||
|
border-radius: 10px; |
||||
|
border: 1.5px solid #e2e8f0; |
||||
|
padding: 10px 16px; |
||||
|
font-size: 0.95rem; |
||||
|
font-weight: 500; |
||||
|
color: #334155; |
||||
|
background-color: #f8fafc; |
||||
|
transition: all 0.3s ease; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.form-control-custom:focus { |
||||
|
outline: none; |
||||
|
border-color: #0f5c78; |
||||
|
background-color: #ffffff; |
||||
|
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15); |
||||
|
} |
||||
|
|
||||
|
.form-group { |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.category-select-group { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
/* Drag & Drop Zone */ |
||||
|
.dropzone-container { |
||||
|
border: 2px dashed #cbd5e1; |
||||
|
border-radius: 16px; |
||||
|
padding: 40px 20px; |
||||
|
text-align: center; |
||||
|
background-color: #f8fafc; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
position: relative; |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.dropzone-container:hover, |
||||
|
.dropzone-container.dragover { |
||||
|
border-color: #0f5c78; |
||||
|
background-color: rgba(15, 92, 120, 0.03); |
||||
|
} |
||||
|
|
||||
|
.dropzone-icon { |
||||
|
font-size: 3rem; |
||||
|
color: #94a3b8; |
||||
|
margin-bottom: 16px; |
||||
|
transition: color 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.dropzone-container:hover .dropzone-icon { |
||||
|
color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
.dropzone-text { |
||||
|
font-size: 1rem; |
||||
|
color: #475569; |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.dropzone-hint { |
||||
|
font-size: 0.8rem; |
||||
|
color: #94a3b8; |
||||
|
} |
||||
|
|
||||
|
.file-input-hidden { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
opacity: 0; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.selected-file-banner { |
||||
|
background-color: #f0fdf4; |
||||
|
border: 1px solid #bbf7d0; |
||||
|
border-radius: 10px; |
||||
|
padding: 12px 18px; |
||||
|
margin-bottom: 24px; |
||||
|
display: none; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.selected-file-details { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
color: #166534; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.btn-remove-file { |
||||
|
background: none; |
||||
|
border: none; |
||||
|
color: #b91c1c; |
||||
|
font-size: 1.1rem; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
/* Progress Bar */ |
||||
|
.progress-container { |
||||
|
display: none; |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.progress-bar-wrapper { |
||||
|
background-color: #e2e8f0; |
||||
|
height: 8px; |
||||
|
border-radius: 10px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.progress-bar-fill { |
||||
|
background: linear-gradient(90deg, #0f5c78, #1781a6); |
||||
|
height: 100%; |
||||
|
width: 0%; |
||||
|
transition: width 0.1s ease; |
||||
|
} |
||||
|
|
||||
|
.progress-text { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
font-size: 0.85rem; |
||||
|
font-weight: 600; |
||||
|
color: #64748b; |
||||
|
margin-bottom: 6px; |
||||
|
} |
||||
|
|
||||
|
.btn-submit-upload { |
||||
|
width: 100%; |
||||
|
height: 50px; |
||||
|
border-radius: 12px; |
||||
|
background: linear-gradient(135deg, #0f5c78, #1781a6); |
||||
|
color: #ffffff; |
||||
|
font-weight: 600; |
||||
|
font-size: 1rem; |
||||
|
border: none; |
||||
|
box-shadow: 0 4px 15px rgba(15, 92, 120, 0.2); |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.btn-submit-upload:hover { |
||||
|
background: linear-gradient(135deg, #1781a6, #0f5c78); |
||||
|
transform: translateY(-2px); |
||||
|
box-shadow: 0 6px 20px rgba(15, 92, 120, 0.3); |
||||
|
} |
||||
|
|
||||
|
.btn-submit-upload:disabled { |
||||
|
background: #cbd5e1; |
||||
|
color: #94a3b8; |
||||
|
box-shadow: none; |
||||
|
cursor: not-allowed; |
||||
|
transform: none; |
||||
|
} |
||||
|
|
||||
|
.file-upload-card { |
||||
|
border: 2px dashed #cbd5e1; |
||||
|
background-color: #f8fafc; |
||||
|
border-radius: 12px; |
||||
|
padding: 20px 10px; |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 8px; |
||||
|
height: 120px; |
||||
|
} |
||||
|
|
||||
|
.file-upload-card:hover { |
||||
|
border-color: #0f5c78; |
||||
|
background-color: rgba(15, 92, 120, 0.03); |
||||
|
transform: translateY(-2px); |
||||
|
} |
||||
|
|
||||
|
.file-upload-card.file-selected { |
||||
|
border-color: #166534; |
||||
|
background-color: #f0fdf4; |
||||
|
} |
||||
|
|
||||
|
.upload-card-icon { |
||||
|
font-size: 1.8rem; |
||||
|
color: #94a3b8; |
||||
|
transition: color 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.file-upload-card:hover .upload-card-icon { |
||||
|
color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
.file-upload-card.file-selected .upload-card-icon { |
||||
|
color: #166534; |
||||
|
} |
||||
|
|
||||
|
.upload-card-text { |
||||
|
font-size: 0.85rem; |
||||
|
font-weight: 500; |
||||
|
color: #64748b; |
||||
|
word-break: break-all; |
||||
|
max-width: 100%; |
||||
|
padding: 0 4px; |
||||
|
} |
||||
|
|
||||
|
.file-upload-card.file-selected .upload-card-text { |
||||
|
color: #166534; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
</style> |
||||
|
<!-- SweetAlert2 --> |
||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
|
||||
|
<div class="main-container"> |
||||
|
|
||||
|
<!-- Header --> |
||||
|
<div class="page-header"> |
||||
|
<h1 class="page-title"> |
||||
|
<i class="fa-solid fa-cloud-arrow-up text-primary"></i> |
||||
|
Inward Register |
||||
|
</h1> |
||||
|
<div class="header-actions" style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap;"> |
||||
|
<!-- User welcome badge --> |
||||
|
<div th:if="${isLoggedIn}" class="user-badge" |
||||
|
style="background-color: #e2e8f0; padding: 8px 16px; border-radius: 20px; font-size: 0.9rem; font-weight: 600; color: #475569; display: flex; align-items: center; gap: 8px;"> |
||||
|
<i class="fa-solid fa-circle-user" style="color: #0f5c78; font-size: 1.1rem;"></i> |
||||
|
<span th:text="${usrName}">User</span> |
||||
|
</div> |
||||
|
|
||||
|
<a th:href="@{/gcctonermanagement/view}" class="btn-back"> |
||||
|
<i class="fa-solid fa-arrow-left"></i> |
||||
|
Back to View Report |
||||
|
</a> |
||||
|
|
||||
|
<!-- Logout link --> |
||||
|
<a th:if="${isLoggedIn}" th:href="@{/gcctonermanagement/logout}" class="btn-logout-link" |
||||
|
style="color: #ef4444; font-weight: 600; font-size: 0.95rem; text-decoration: none; display: flex; align-items: center; gap: 6px; transition: color 0.2s ease;" |
||||
|
onmouseover="this.style.color='#b91c1c'" onmouseout="this.style.color='#ef4444'"> |
||||
|
<i class="fa-solid fa-right-from-bracket"></i> |
||||
|
Logout |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Upload Card --> |
||||
|
<div class="upload-card"> |
||||
|
<form id="upload-form" onsubmit="handleUpload(event)"> |
||||
|
|
||||
|
<!-- Company Name --> |
||||
|
<div class="form-group mb-4"> |
||||
|
<label class="form-label" for="company-name-input"> |
||||
|
<i class="fa-solid fa-building me-1 text-primary"></i> Company Name * |
||||
|
</label> |
||||
|
<input type="text" id="company-name-input" required class="form-control-custom" |
||||
|
placeholder="Enter company name" maxlength="30"> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Printer Model Dropdown --> |
||||
|
<div class="form-group mb-4"> |
||||
|
<label class="form-label" for="printer-select"> |
||||
|
<i class="fa-solid fa-print me-1 text-primary"></i> Printer Model * |
||||
|
</label> |
||||
|
<select id="printer-select" required class="form-control-custom" onchange="onPrinterChange()"> |
||||
|
<option value="">-- Select Printer Model --</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Toner Model Dropdown --> |
||||
|
<div class="form-group mb-4"> |
||||
|
<label class="form-label" for="toner-select"> |
||||
|
<i class="fa-solid fa-droplet me-1 text-primary"></i> Toner Model * |
||||
|
</label> |
||||
|
<select id="toner-select" required class="form-control-custom" disabled> |
||||
|
<option value="">-- Select Toner Model --</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Quantity --> |
||||
|
<div class="form-group mb-4"> |
||||
|
<label class="form-label" for="qty-input"> |
||||
|
<i class="fa-solid fa-list-ol me-1 text-primary"></i> Quantity * |
||||
|
</label> |
||||
|
<input type="number" id="qty-input" required class="form-control-custom" |
||||
|
placeholder="Enter quantity" min="1" step="1"> |
||||
|
</div> |
||||
|
|
||||
|
<!-- File Upload Options (DC, User Request, Final Delivered) --> |
||||
|
<div class="row"> |
||||
|
<!-- DC Upload --> |
||||
|
<div class="col-md-4 form-group mb-4"> |
||||
|
<label class="form-label"> |
||||
|
<i class="fa-solid fa-file-pdf me-1 text-danger"></i> Delivery Challan (DC) |
||||
|
</label> |
||||
|
<div class="file-upload-card" onclick="document.getElementById('dc-file').click()"> |
||||
|
<i class="fa-solid fa-cloud-arrow-up upload-card-icon"></i> |
||||
|
<span class="upload-card-text" id="dc-file-label">Choose PDF</span> |
||||
|
<input type="file" id="dc-file" accept=".pdf" class="d-none" |
||||
|
onchange="handleFileSelect(this, 'dc-file-label')"> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- User Request Upload --> |
||||
|
<!-- <div class="col-md-4 form-group mb-4"> |
||||
|
<label class="form-label"> |
||||
|
<i class="fa-solid fa-file-pdf me-1 text-danger"></i> User Request |
||||
|
</label> |
||||
|
<div class="file-upload-card" onclick="document.getElementById('ur-file').click()"> |
||||
|
<i class="fa-solid fa-cloud-arrow-up upload-card-icon"></i> |
||||
|
<span class="upload-card-text" id="ur-file-label">Choose PDF</span> |
||||
|
<input type="file" id="ur-file" accept=".pdf" class="d-none" |
||||
|
onchange="handleFileSelect(this, 'ur-file-label')"> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
|
||||
|
<!-- Final Delivered Upload --> |
||||
|
<!-- <div class="col-md-4 form-group mb-4"> |
||||
|
<label class="form-label"> |
||||
|
<i class="fa-solid fa-file-pdf me-1 text-danger"></i> Final Delivered Document |
||||
|
</label> |
||||
|
<div class="file-upload-card" onclick="document.getElementById('fd-file').click()"> |
||||
|
<i class="fa-solid fa-cloud-arrow-up upload-card-icon"></i> |
||||
|
<span class="upload-card-text" id="fd-file-label">Choose PDF</span> |
||||
|
<input type="file" id="fd-file" accept=".pdf" class="d-none" |
||||
|
onchange="handleFileSelect(this, 'fd-file-label')"> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Progress Bar --> |
||||
|
<div class="progress-container mb-4" id="progress-container"> |
||||
|
<div class="progress-text"> |
||||
|
<span id="upload-status">Saving details...</span> |
||||
|
<span id="progress-percent">0%</span> |
||||
|
</div> |
||||
|
<div class="progress-bar-wrapper"> |
||||
|
<div class="progress-bar-fill" id="progress-fill"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Submit Button --> |
||||
|
<button type="submit" class="btn-submit-upload mt-3" id="submit-btn"> |
||||
|
<i class="fa-solid fa-paper-plane"></i> Save & Upload Details |
||||
|
</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Footer Fragment --> |
||||
|
<th:block th:replace="~{fragments/footer :: footer}"></th:block> |
||||
|
|
||||
|
<script th:inline="javascript"> |
||||
|
const contextPath = /*[[@{/}]]*/ ''; |
||||
|
|
||||
|
document.addEventListener('DOMContentLoaded', () => { |
||||
|
loadPrinters(); |
||||
|
}); |
||||
|
|
||||
|
function loadPrinters() { |
||||
|
fetch(contextPath + 'gcctonermanagement/api/printers') |
||||
|
.then(res => res.json()) |
||||
|
.then(printers => { |
||||
|
const select = document.getElementById('printer-select'); |
||||
|
select.innerHTML = '<option value="">-- Select Printer Model --</option>'; |
||||
|
printers.forEach(p => { |
||||
|
const opt = document.createElement('option'); |
||||
|
opt.value = p.id; |
||||
|
opt.textContent = p.printer_name; |
||||
|
select.appendChild(opt); |
||||
|
}); |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error loading printers:", err); |
||||
|
showNotification('error', 'Error', 'Failed to load printers.'); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function onPrinterChange() { |
||||
|
const pid = document.getElementById('printer-select').value; |
||||
|
const tonerSelect = document.getElementById('toner-select'); |
||||
|
|
||||
|
tonerSelect.innerHTML = '<option value="">-- Select Toner Model --</option>'; |
||||
|
tonerSelect.disabled = true; |
||||
|
|
||||
|
if (!pid) return; |
||||
|
|
||||
|
fetch(contextPath + 'gcctonermanagement/api/toners?pid=' + pid) |
||||
|
.then(res => res.json()) |
||||
|
.then(toners => { |
||||
|
toners.forEach(t => { |
||||
|
const opt = document.createElement('option'); |
||||
|
opt.value = t.id; |
||||
|
opt.textContent = t.model_name; |
||||
|
tonerSelect.appendChild(opt); |
||||
|
}); |
||||
|
if (toners.length > 0) { |
||||
|
tonerSelect.disabled = false; |
||||
|
} else { |
||||
|
const opt = document.createElement('option'); |
||||
|
opt.value = ""; |
||||
|
opt.textContent = "-- No Toners Available --"; |
||||
|
tonerSelect.appendChild(opt); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error loading toners:", err); |
||||
|
showNotification('error', 'Error', 'Failed to load toner models.'); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleFileSelect(input, labelId) { |
||||
|
const label = document.getElementById(labelId); |
||||
|
const card = input.parentElement; |
||||
|
|
||||
|
if (input.files && input.files.length > 0) { |
||||
|
const file = input.files[0]; |
||||
|
if (!file.name.toLowerCase().endsWith('.pdf')) { |
||||
|
showNotification('warning', 'Invalid File Type', 'Please select a PDF file.'); |
||||
|
input.value = ''; |
||||
|
card.classList.remove('file-selected'); |
||||
|
label.textContent = 'Choose PDF'; |
||||
|
return; |
||||
|
} |
||||
|
card.classList.add('file-selected'); |
||||
|
label.textContent = file.name; |
||||
|
} else { |
||||
|
card.classList.remove('file-selected'); |
||||
|
label.textContent = 'Choose PDF'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function showNotification(icon, title, text) { |
||||
|
if (typeof Swal !== 'undefined') { |
||||
|
Swal.fire({ |
||||
|
icon: icon, |
||||
|
title: title, |
||||
|
text: text, |
||||
|
confirmButtonColor: '#0f5c78' |
||||
|
}); |
||||
|
} else { |
||||
|
alert(title + ": " + text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleUpload(event) { |
||||
|
event.preventDefault(); |
||||
|
|
||||
|
const companyName = document.getElementById('company-name-input').value.trim(); |
||||
|
const pid = document.getElementById('printer-select').value; |
||||
|
const tid = document.getElementById('toner-select').value; |
||||
|
const qty = document.getElementById('qty-input').value.trim(); |
||||
|
|
||||
|
if (!companyName || !pid || !tid || !qty) { |
||||
|
showNotification('warning', 'Incomplete Form', 'Please fill in all required fields.'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const dcInput = document.getElementById('dc-file'); |
||||
|
const urInput = document.getElementById('ur-file'); |
||||
|
const fdInput = document.getElementById('fd-file'); |
||||
|
|
||||
|
const formData = new FormData(); |
||||
|
formData.append('companyName', companyName); |
||||
|
formData.append('pid', pid); |
||||
|
formData.append('tid', tid); |
||||
|
formData.append('qty', qty); |
||||
|
|
||||
|
if (dcInput.files.length > 0) formData.append('dcFile', dcInput.files[0]); |
||||
|
if (urInput.files.length > 0) formData.append('userRequestFile', urInput.files[0]); |
||||
|
if (fdInput.files.length > 0) formData.append('finalDeliveredFile', fdInput.files[0]); |
||||
|
|
||||
|
// CSRF security token |
||||
|
const csrfMeta = document.querySelector('meta[name="_csrf"]'); |
||||
|
const csrfHeaderMeta = document.querySelector('meta[name="_csrf_header"]'); |
||||
|
|
||||
|
const xhr = new XMLHttpRequest(); |
||||
|
xhr.open('POST', contextPath + 'gcctonermanagement/api/upload', true); |
||||
|
|
||||
|
if (csrfMeta && csrfHeaderMeta) { |
||||
|
xhr.setRequestHeader(csrfHeaderMeta.content, csrfMeta.content); |
||||
|
} |
||||
|
|
||||
|
// Show progress elements |
||||
|
const progressContainer = document.getElementById('progress-container'); |
||||
|
const progressFill = document.getElementById('progress-fill'); |
||||
|
const progressPercent = document.getElementById('progress-percent'); |
||||
|
const submitBtn = document.getElementById('submit-btn'); |
||||
|
|
||||
|
progressContainer.style.display = 'block'; |
||||
|
submitBtn.disabled = true; |
||||
|
|
||||
|
xhr.upload.addEventListener('progress', (e) => { |
||||
|
if (e.lengthComputable) { |
||||
|
const percent = Math.round((e.loaded / e.total) * 100); |
||||
|
progressFill.style.width = percent + '%'; |
||||
|
progressPercent.textContent = percent + '%'; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
xhr.onload = function () { |
||||
|
if (xhr.status === 200) { |
||||
|
try { |
||||
|
const response = JSON.parse(xhr.responseText); |
||||
|
if (response.success) { |
||||
|
if (typeof Swal !== 'undefined') { |
||||
|
Swal.fire({ |
||||
|
title: 'Success!', |
||||
|
text: response.message || "Details saved successfully!", |
||||
|
icon: 'success', |
||||
|
confirmButtonColor: '#0f5c78' |
||||
|
}).then(() => { |
||||
|
document.getElementById('upload-form').reset(); |
||||
|
resetFileCards(); |
||||
|
}); |
||||
|
} else { |
||||
|
alert("Success: " + (response.message || "Details saved successfully!")); |
||||
|
document.getElementById('upload-form').reset(); |
||||
|
resetFileCards(); |
||||
|
} |
||||
|
} else { |
||||
|
showNotification('error', 'Upload Failed', response.error || "Failed to record toner details."); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
showNotification('error', 'Error', 'Failed to parse response: ' + e.message); |
||||
|
} |
||||
|
} else { |
||||
|
showNotification('error', 'Error', "An error occurred. HTTP Code: " + xhr.status); |
||||
|
} |
||||
|
progressContainer.style.display = 'none'; |
||||
|
submitBtn.disabled = false; |
||||
|
}; |
||||
|
|
||||
|
xhr.onerror = function () { |
||||
|
showNotification('error', 'Error', "Connection error occurred during submission."); |
||||
|
progressContainer.style.display = 'none'; |
||||
|
submitBtn.disabled = false; |
||||
|
}; |
||||
|
|
||||
|
xhr.send(formData); |
||||
|
} |
||||
|
|
||||
|
function resetFileCards() { |
||||
|
const labels = ['dc-file-label', 'ur-file-label', 'fd-file-label']; |
||||
|
labels.forEach(lblId => { |
||||
|
document.getElementById(lblId).textContent = 'Choose PDF'; |
||||
|
}); |
||||
|
const cards = document.querySelectorAll('.file-upload-card'); |
||||
|
cards.forEach(card => { |
||||
|
card.classList.remove('file-selected'); |
||||
|
}); |
||||
|
document.getElementById('toner-select').disabled = true; |
||||
|
} |
||||
|
</script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
955
src/main/resources/templates/home/home.html
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue