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
-
957src/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> |
|||
957
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