Browse Source

changes in toner management and rental management project api's and UI

master
yoganandhank 2 weeks ago
parent
commit
324075f7d6
  1. 94
      src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java
  2. 57
      src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java
  3. 8
      src/main/resources/templates/gccrentalmanagement/dashboard.html
  4. 379
      src/main/resources/templates/gccrentalmanagement/login.html
  5. 665
      src/main/resources/templates/gcctonermanagement/upload.html

94
src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java

@ -158,6 +158,15 @@ public class TonerController {
return tonerService.getActiveZones(); return tonerService.getActiveZones();
} }
@GetMapping("/api/vendors")
@ResponseBody
public List<Map<String, Object>> getVendors(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return java.util.Collections.emptyList();
}
return tonerService.getActiveVendors();
}
@PostMapping("/api/inward") @PostMapping("/api/inward")
@ResponseBody @ResponseBody
public Map<String, Object> uploadInward( public Map<String, Object> uploadInward(
@ -389,6 +398,91 @@ public class TonerController {
return response; return response;
} }
@PostMapping("/api/request/update-group")
@ResponseBody
public Map<String, Object> updateRequestGroup(
@RequestParam("itcellRefNo") String itcellRefNo,
@RequestParam(value = "newItcellRefNo", required = false) String newItcellRefNo,
@RequestParam("deptId") String deptId,
@RequestParam(value = "zoneId", required = false) String zoneId,
@RequestParam(value = "requestDate", required = false) String requestDate,
@RequestParam(value = "itemsJson", required = false) String itemsJson,
@RequestParam(value = "userRequestFile", required = false) MultipartFile userRequestFile,
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;
}
String updatedBy = appUserService.getUserIdByName(username);
if (updatedBy == null)
updatedBy = username;
if (itcellRefNo == null || itcellRefNo.trim().isEmpty()) {
response.put("success", false);
response.put("error", "IT Cell Reference No. is required.");
return response;
}
if (deptId == null || deptId.trim().isEmpty()) {
response.put("success", false);
response.put("error", "Department is required.");
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;
}
try {
List<Map<String, Object>> items = null;
if (itemsJson != null && !itemsJson.trim().isEmpty()) {
ObjectMapper mapper = new ObjectMapper();
items = mapper.readValue(itemsJson, new TypeReference<List<Map<String, Object>>>() {
});
}
File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile();
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String urPath = null;
if (userRequestFile != null && !userRequestFile.isEmpty()) {
urPath = saveUploadedFile(userRequestFile, "user_request", uploadDir);
}
String safeZoneId = (zoneId != null && !zoneId.trim().isEmpty()) ? zoneId.trim() : null;
boolean updated = tonerService.updateUserRequestGroup(
itcellRefNo.trim(),
(newItcellRefNo != null && !newItcellRefNo.trim().isEmpty()) ? newItcellRefNo.trim()
: itcellRefNo.trim(),
deptId.trim(), safeZoneId, requestDate, urPath, items, updatedBy);
if (updated) {
response.put("success", true);
response.put("message", "User request details updated successfully for all toners.");
} else {
if (urPath != null) {
deleteFileIfExists(urPath, uploadDir);
}
response.put("success", false);
response.put("error", "Failed to update request details in database.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "An error occurred: " + e.getMessage());
e.printStackTrace();
}
return response;
}
@PostMapping("/api/outward") @PostMapping("/api/outward")
@ResponseBody @ResponseBody
public Map<String, Object> uploadOutward( public Map<String, Object> uploadOutward(

57
src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java

@ -136,6 +136,15 @@ public class TonerService {
return map; return map;
} }
public List<Map<String, Object>> getActiveVendors() {
try {
return jdbcTemplate.queryForList(
"SELECT id, vendor_name FROM payment_vendors_list WHERE is_active = 1 AND is_delete = 0 ORDER BY vendor_name ASC");
} catch (Exception e) {
return new ArrayList<>();
}
}
private Map<String, String> getWorkorderFileNumberMap() { private Map<String, String> getWorkorderFileNumberMap() {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
try { try {
@ -770,6 +779,54 @@ public class TonerService {
username); username);
} }
@Transactional
public boolean updateUserRequestGroup(String itcellRefNo, String newItcellRefNo, String deptId, String zoneId,
String requestDate, String newUploadPath, List<Map<String, Object>> items, String username) {
Timestamp now = new Timestamp(System.currentTimeMillis());
try {
String safeZoneId = (zoneId != null && !zoneId.trim().isEmpty() && !"null".equalsIgnoreCase(zoneId))
? zoneId.trim()
: null;
String finalRequestDate = (requestDate != null && !requestDate.trim().isEmpty()) ? requestDate.trim()
: null;
String finalItcell = (newItcellRefNo != null && !newItcellRefNo.trim().isEmpty())
? newItcellRefNo.trim()
: itcellRefNo.trim();
if (newUploadPath != null && !newUploadPath.trim().isEmpty()) {
String sql = "UPDATE toner_department_requested_details SET " +
"itcell_ref_no = ?, dept_id = ?, zone_id = ?, request_date = ?, user_request_upload = ?, updated_date = ?, updated_by = ? "
+
"WHERE TRIM(itcell_ref_no) = TRIM(?)";
jdbcTemplate.update(sql, finalItcell, deptId.trim(), safeZoneId, finalRequestDate, newUploadPath, now,
username, itcellRefNo.trim());
} else {
String sql = "UPDATE toner_department_requested_details SET " +
"itcell_ref_no = ?, dept_id = ?, zone_id = ?, request_date = ?, updated_date = ?, updated_by = ? "
+
"WHERE TRIM(itcell_ref_no) = TRIM(?)";
jdbcTemplate.update(sql, finalItcell, deptId.trim(), safeZoneId, finalRequestDate, now, username,
itcellRefNo.trim());
}
// Update individual item quantities if provided
if (items != null && !items.isEmpty()) {
String updateItemSql = "UPDATE toner_department_requested_details SET qty_requested = ?, updated_date = ?, updated_by = ? WHERE id = ?";
for (Map<String, Object> item : items) {
Integer id = parseIntegerSafely(item.get("id"));
Integer qty = parseIntegerSafely(item.get("qty"));
if (id != null && qty != null && qty > 0) {
jdbcTemplate.update(updateItemSql, String.valueOf(qty), now, username, id);
}
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public List<Map<String, Object>> getAllUserRequests() { public List<Map<String, Object>> getAllUserRequests() {
try { try {
String sql = "SELECT * FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; String sql = "SELECT * FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC";

8
src/main/resources/templates/gccrentalmanagement/dashboard.html

@ -835,11 +835,13 @@
<div class="brand-titles"> <div class="brand-titles">
<h1>Greater Chennai Corporation</h1> <h1>Greater Chennai Corporation</h1>
<p>பெருநகர சென்னை மாநகராட்சி &bull; IT Cell</p> <p>பெருநகர சென்னை மாநகராட்சி &bull; IT Cell</p>
<div class="system-pill">
<i class="fa-solid fa-boxes-packing"></i>
<span>Rental Asset & Material Management Hub</span>
</div> </div>
</div> </div>
<div class="system-pill">
<i class="fa-solid fa-boxes-packing"></i>
<span style="font-size: 1.05rem; font-weight: 800; color: #ffffff;">Rental Asset & Material Management
Hub</span>
</div> </div>
<div class="user-controls"> <div class="user-controls">

379
src/main/resources/templates/gccrentalmanagement/login.html

@ -13,355 +13,51 @@
<!-- Google Fonts --> <!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Poppins:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<!-- Bootstrap CSS -->
<link rel="stylesheet" th:href="@{/assets/bootstrap-5.3.2/css/bootstrap.min.css}" type="text/css">
<!-- Include CSS -->
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/login-form-styles.css}">
<!-- Awesome Font --> <!-- Awesome Font -->
<link th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" rel="stylesheet" type="text/css"> <link th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" rel="stylesheet" type="text/css">
<style>
:root {
--primary-brand: #0d4b68;
--primary-dark: #072e41;
--accent-cyan: #06b6d4;
--accent-amber: #f59e0b;
--gradient-bg: linear-gradient(135deg, #072e41 0%, #0d4b68 50%, #155e75 100%);
--card-bg: rgba(255, 255, 255, 0.96);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background: var(--gradient-bg);
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
position: relative;
overflow-x: hidden;
}
/* Decorative ambient glow circles */
.ambient-glow {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.25;
pointer-events: none;
z-index: 0;
}
.glow-1 {
width: 350px;
height: 350px;
background: #06b6d4;
top: -50px;
left: -50px;
}
.glow-2 {
width: 400px;
height: 400px;
background: #f59e0b;
bottom: -80px;
right: -80px;
}
.login-card {
position: relative;
z-index: 1;
width: 100%;
max-width: 460px;
background: var(--card-bg);
backdrop-filter: blur(12px);
border-radius: 24px;
padding: 40px 36px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.2);
animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(25px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header-branding {
text-align: center;
margin-bottom: 28px;
}
.logo-wrap {
width: 84px;
height: 84px;
margin: 0 auto 16px;
border-radius: 20px;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8px 20px rgba(13, 75, 104, 0.15);
padding: 10px;
transition: transform 0.3s ease;
}
.logo-wrap:hover {
transform: scale(1.05) rotate(2deg);
}
.logo-wrap img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.brand-title-en {
font-family: 'Outfit', sans-serif;
font-size: 1.15rem;
font-weight: 800;
letter-spacing: 0.8px;
color: #0d4b68;
text-transform: uppercase;
margin-bottom: 2px;
}
.brand-title-ta {
font-size: 0.82rem;
color: #475569;
font-weight: 600;
margin-bottom: 12px;
}
.badge-module {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
background: linear-gradient(135deg, #e0f2fe, #e2e8f0);
color: #0369a1;
border: 1px solid #bae6fd;
border-radius: 30px;
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge-module i {
color: #0284c7;
}
.form-title {
font-family: 'Outfit', sans-serif;
font-size: 1.4rem;
font-weight: 700;
color: #0f172a;
margin: 18px 0 6px;
text-align: center;
}
.form-subtitle {
font-size: 0.85rem;
color: #64748b;
text-align: center;
margin-bottom: 24px;
}
.form-floating-custom {
position: relative;
margin-bottom: 20px;
}
.form-floating-custom .input-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
font-size: 1.1rem;
transition: color 0.3s ease;
pointer-events: none;
}
.form-floating-custom input {
width: 100%;
height: 52px;
padding: 12px 16px 12px 46px;
font-size: 0.92rem;
color: #1e293b;
background: #f8fafc;
border: 1.5px solid #e2e8f0;
border-radius: 12px;
outline: none;
transition: all 0.3s ease;
}
.form-floating-custom input:focus {
background: #ffffff;
border-color: #0284c7;
box-shadow: 0 0 0 4px rgba(2, 132, 199, 0.12);
}
.form-floating-custom input:focus + .input-icon {
color: #0284c7;
}
.btn-submit {
width: 100%;
height: 52px;
background: linear-gradient(135deg, #0d4b68 0%, #0284c7 100%);
color: #ffffff;
border: none;
border-radius: 12px;
font-family: 'Outfit', sans-serif;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: all 0.3s ease;
box-shadow: 0 8px 20px rgba(13, 75, 104, 0.25);
margin-top: 10px;
}
.btn-submit:hover:not(:disabled) {
background: linear-gradient(135deg, #093448 0%, #0369a1 100%);
box-shadow: 0 12px 25px rgba(13, 75, 104, 0.35);
transform: translateY(-2px);
}
.btn-submit:disabled {
opacity: 0.7;
cursor: not-allowed;
}
#error-box {
background: #fef2f2;
border: 1px solid #fecaca;
color: #b91c1c;
padding: 12px 16px;
border-radius: 10px;
font-size: 0.84rem;
font-weight: 500;
margin-bottom: 20px;
display: none;
align-items: center;
gap: 10px;
animation: shake 0.4s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.system-badge-footer {
margin-top: 25px;
text-align: center;
font-size: 0.76rem;
color: #94a3b8;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.system-badge-footer span {
display: inline-block;
width: 6px;
height: 6px;
background: #22c55e;
border-radius: 50%;
box-shadow: 0 0 8px #22c55e;
}
.back-portal-link {
display: block;
text-align: center;
margin-top: 18px;
color: #0284c7;
text-decoration: none;
font-size: 0.85rem;
font-weight: 600;
transition: color 0.2s;
}
.back-portal-link:hover {
color: #0369a1;
text-decoration: underline;
}
</style>
</head> </head>
<body> <body>
<div class="ambient-glow glow-1"></div>
<div class="ambient-glow glow-2"></div>
<div class="login-card">
<div class="header-branding">
<div class="logo-wrap">
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
</div>
<h2 class="brand-title-en">Greater Chennai Corporation</h2>
<p class="brand-title-ta">பெருநகர சென்னை மாநகராட்சி</p>
<div class="badge-module">
<i class="fa-solid fa-boxes-packing"></i>
<span>Rental Management System</span>
<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>
<h2>GCC IT Cell Rental Management System</h2>
<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> </div>
</div>
<h3 class="form-title">Portal Sign In</h3>
<p class="form-subtitle">Work Orders • Requests • Challans • Issue & Return</p>
<div id="error-box">
<i class="fa-solid fa-circle-exclamation fs-5"></i>
<span id="error-text">Invalid username or password!</span>
<form id="loginForm" onsubmit="handleRentalLogin(event)">
<div class="input-field">
<input type="text" name="usrName" id="usrName" placeholder="Enter Username" required
autocomplete="username">
<div class="underline"></div>
</div> </div>
<form id="rentalLoginForm" onsubmit="handleRentalLogin(event)">
<div class="form-floating-custom">
<input type="text" name="usrName" id="usrName" placeholder="Enter Username / Employee ID" required autocomplete="username">
<i class="fa-solid fa-user input-icon"></i>
</div>
<div class="form-floating-custom">
<input type="password" name="usrPass" id="usrPass" placeholder="Enter Password" required autocomplete="current-password">
<i class="fa-solid fa-lock input-icon"></i>
<div class="input-field">
<input type="password" name="usrPass" id="usrPass" placeholder="Enter Password" required
autocomplete="current-password">
<div class="underline"></div>
</div> </div>
<button type="submit" id="submit-btn" class="btn-submit">
<span>Sign In to Rental System</span>
<i class="fa-solid fa-arrow-right-to-bracket"></i>
</button>
<input type="submit" value="Login" id="submit-btn">
</form> </form>
<a th:href="@{/}" class="back-portal-link">
<i class="fa-solid fa-arrow-left me-1"></i> Return to Main GCC Home
</a>
<div class="system-badge-footer">
<span></span>
<span>GCC IT Cell Secured System &bull; Active Version 2.0</span>
</div>
</div> </div>
<!-- Footer Fragment -->
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<!-- Custom JS --> <!-- Custom JS -->
<script th:inline="javascript"> <script th:inline="javascript">
const contextPath = /*[[@{/}]]*/ '/'; const contextPath = /*[[@{/}]]*/ '/';
@ -377,7 +73,7 @@
errorBox.style.display = "none"; errorBox.style.display = "none";
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Authenticating...';
submitBtn.value = 'Authenticating...';
const username = document.getElementById("usrName").value.trim(); const username = document.getElementById("usrName").value.trim();
const password = document.getElementById("usrPass").value; const password = document.getElementById("usrPass").value;
@ -386,6 +82,8 @@
urlParams.append('usrName', username); urlParams.append('usrName', username);
urlParams.append('usrPass', password); urlParams.append('usrPass', password);
console.log(username, password);
// Fetch CSRF tokens if available // Fetch CSRF tokens if available
const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content"); const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content");
const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content"); const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
@ -404,23 +102,24 @@
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
console.log(data);
if (data.result) { if (data.result) {
sessionStorage.setItem("rental_usrName", data.usrName); sessionStorage.setItem("rental_usrName", data.usrName);
sessionStorage.setItem("rental_usrRole", data.usrRole || "USER"); sessionStorage.setItem("rental_usrRole", data.usrRole || "USER");
window.location.href = contextPath + 'gccrentalmanagement/dashboard'; window.location.href = contextPath + 'gccrentalmanagement/dashboard';
} else { } else {
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = '<span>Sign In to Rental System</span> <i class="fa-solid fa-arrow-right-to-bracket"></i>';
errorText.textContent = data.error || "Authentication failed. Check your username and password.";
errorBox.style.display = "flex";
submitBtn.value = 'Login';
errorText.textContent = data.error || "Authentication failed.";
errorBox.style.display = "block";
} }
}) })
.catch(err => { .catch(err => {
console.error("Login Error:", err); console.error("Login Error:", err);
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = '<span>Sign In to Rental System</span> <i class="fa-solid fa-arrow-right-to-bracket"></i>';
submitBtn.value = 'Login';
errorText.textContent = "Network error. Please try again later."; errorText.textContent = "Network error. Please try again later.";
errorBox.style.display = "flex";
errorBox.style.display = "block";
}); });
} }
</script> </script>

665
src/main/resources/templates/gcctonermanagement/upload.html

@ -3063,8 +3063,9 @@
} }
function onDashboardPrinterFilterChange() { function onDashboardPrinterFilterChange() {
const pid = document.getElementById('dashboard-printer-filter').value;
const pid = document.getElementById('dashboard-printer-filter')?.value || '';
const tonerSelect = document.getElementById('dashboard-toner-filter'); const tonerSelect = document.getElementById('dashboard-toner-filter');
if (!tonerSelect) return;
tonerSelect.innerHTML = '<option value="">-- All Toners --</option>'; tonerSelect.innerHTML = '<option value="">-- All Toners --</option>';
tonerSelect.disabled = true; tonerSelect.disabled = true;
@ -3121,9 +3122,10 @@
} }
function onDashboardPeriodPresetChange() { function onDashboardPeriodPresetChange() {
const val = document.getElementById('dashboard-period-preset').value;
const val = document.getElementById('dashboard-period-preset')?.value || '';
const startWrapper = document.getElementById('dashboard-start-date-wrapper'); const startWrapper = document.getElementById('dashboard-start-date-wrapper');
const endWrapper = document.getElementById('dashboard-end-date-wrapper'); const endWrapper = document.getElementById('dashboard-end-date-wrapper');
if (startWrapper && endWrapper) {
if (val === 'custom') { if (val === 'custom') {
startWrapper.style.display = 'block'; startWrapper.style.display = 'block';
endWrapper.style.display = 'block'; endWrapper.style.display = 'block';
@ -3132,10 +3134,11 @@
endWrapper.style.display = 'none'; endWrapper.style.display = 'none';
} }
} }
}
function matchesPeriod(dateString) { function matchesPeriod(dateString) {
if (!dateString) return false; if (!dateString) return false;
const preset = document.getElementById('dashboard-period-preset').value;
const preset = document.getElementById('dashboard-period-preset')?.value || 'all';
if (preset === 'all') return true; if (preset === 'all') return true;
const itemDate = new Date(dateString); const itemDate = new Date(dateString);
@ -3162,8 +3165,8 @@
const startOfYear = new Date(now.getFullYear(), 0, 1); const startOfYear = new Date(now.getFullYear(), 0, 1);
return itemDate >= startOfYear; return itemDate >= startOfYear;
} else if (preset === 'custom') { } else if (preset === 'custom') {
const startInput = document.getElementById('dashboard-start-date').value;
const endInput = document.getElementById('dashboard-end-date').value;
const startInput = document.getElementById('dashboard-start-date')?.value || '';
const endInput = document.getElementById('dashboard-end-date')?.value || '';
const filterStart = startInput ? new Date(startInput) : null; const filterStart = startInput ? new Date(startInput) : null;
const filterEnd = endInput ? new Date(endInput) : null; const filterEnd = endInput ? new Date(endInput) : null;
@ -3231,9 +3234,9 @@
} }
function updateDashboardData() { function updateDashboardData() {
const selectedPrinter = document.getElementById('dashboard-printer-filter').value;
const selectedToner = document.getElementById('dashboard-toner-filter').value;
const selectedWorkorder = document.getElementById('dashboard-workorder-filter').value;
const selectedPrinter = document.getElementById('dashboard-printer-filter')?.value || '';
const selectedToner = document.getElementById('dashboard-toner-filter')?.value || '';
const selectedWorkorder = document.getElementById('dashboard-workorder-filter')?.value || '';
const groups = {}; const groups = {};
@ -3548,9 +3551,9 @@
} }
function loadDashboardDetailsData() { function loadDashboardDetailsData() {
const selectedPrinter = document.getElementById('dashboard-printer-filter').value;
const selectedToner = document.getElementById('dashboard-toner-filter').value;
const selectedWorkorder = document.getElementById('dashboard-workorder-filter').value;
const selectedPrinter = document.getElementById('dashboard-printer-filter')?.value || '';
const selectedToner = document.getElementById('dashboard-toner-filter')?.value || '';
const selectedWorkorder = document.getElementById('dashboard-workorder-filter')?.value || '';
let rawData = []; let rawData = [];
if (activeDashboardDetailsType === 'workorder_registered') { if (activeDashboardDetailsType === 'workorder_registered') {
@ -3767,6 +3770,82 @@
} }
}); });
filtered = Object.values(groups);
} else if (activeDashboardDetailsType === 'outward_register') {
const groups = {};
filtered.forEach(item => {
const cleanRef = (item.itcell_ref_no && String(item.itcell_ref_no).trim() !== '' && String(item.itcell_ref_no).trim() !== 'null')
? String(item.itcell_ref_no).trim()
: (item.req_no && String(item.req_no).trim() !== '' ? String(item.req_no).trim() : ('OUT-' + item.id));
const key = cleanRef;
if (!groups[key]) {
groups[key] = {
key: key,
itcell_ref_no: item.itcell_ref_no || '',
req_no: item.req_no || '',
dept_name: item.dept_name || item.dept_id || '-',
dept_id: item.dept_id,
zone_name: item.zone_name || item.zone_id || '-',
zone_id: item.zone_id,
qty_requested: 0,
qty_delivered: 0,
request_date: item.request_date || null,
outward_date: item.outward_date || null,
cdate: item.cdate || null,
updated_date: item.updated_date || null,
final_delivered_uploads: [],
receiver_photos: [],
req_nos: [],
printer_names: [],
model_names: [],
items: []
};
}
groups[key].qty_requested += parseInt(item.qty_requested || 0);
groups[key].qty_delivered += parseInt(item.qty_delivered || 0);
groups[key].items.push(item);
if (item.req_no && !groups[key].req_nos.includes(item.req_no)) {
groups[key].req_nos.push(item.req_no);
}
if (item.printer_name && !groups[key].printer_names.includes(item.printer_name)) {
groups[key].printer_names.push(item.printer_name);
}
if (item.model_name && !groups[key].model_names.includes(item.model_name)) {
groups[key].model_names.push(item.model_name);
}
if (item.request_date && !groups[key].request_date) {
groups[key].request_date = item.request_date;
}
if (item.outward_date && !groups[key].outward_date) {
groups[key].outward_date = item.outward_date;
}
if (item.cdate && (!groups[key].cdate || new Date(item.cdate) > new Date(groups[key].cdate))) {
groups[key].cdate = item.cdate;
}
if (item.updated_date && (!groups[key].updated_date || new Date(item.updated_date) > new Date(groups[key].updated_date))) {
groups[key].updated_date = item.updated_date;
}
if (item.final_delivered_upload) {
const files = item.final_delivered_upload.split(',').map(f => (f || '').trim()).filter(Boolean);
files.forEach(f => {
if (!groups[key].final_delivered_uploads.includes(f)) {
groups[key].final_delivered_uploads.push(f);
}
});
}
if (item.receiver_photo) {
const files = item.receiver_photo.split(',').map(f => (f || '').trim()).filter(Boolean);
files.forEach(f => {
if (!groups[key].receiver_photos.includes(f)) {
groups[key].receiver_photos.push(f);
}
});
}
});
filtered = Object.values(groups); filtered = Object.values(groups);
} else if (activeDashboardDetailsType === 'available_stock') { } else if (activeDashboardDetailsType === 'available_stock') {
const groups = {}; const groups = {};
@ -3894,9 +3973,9 @@
dashboardDetailsCurrentPage = targetPage; dashboardDetailsCurrentPage = targetPage;
} }
const selectedCategory = document.getElementById('dashboard-details-category-filter').value;
const selectedDept = document.getElementById('dashboard-details-dept-filter').value;
const selectedStockStatus = document.getElementById('dashboard-details-stock-filter').value;
const selectedCategory = document.getElementById('dashboard-details-category-filter')?.value || '';
const selectedDept = document.getElementById('dashboard-details-dept-filter')?.value || '';
const selectedStockStatus = document.getElementById('dashboard-details-stock-filter')?.value || '';
const searchQuery = (document.getElementById('dashboard-details-search')?.value || '').toLowerCase().trim(); const searchQuery = (document.getElementById('dashboard-details-search')?.value || '').toLowerCase().trim();
// Apply categorywise (Printer Make), department, stock status, and text search filters // Apply categorywise (Printer Make), department, stock status, and text search filters
@ -3993,7 +4072,7 @@
<th class="text-center">Registered Qty</th> <th class="text-center">Registered Qty</th>
<th>Start Date</th> <th>Start Date</th>
<th>End Date</th> <th>End Date</th>
<th class="text-center">DC Copy</th>
<th class="text-center">Workorder Copy</th>
</tr>`; </tr>`;
pagedData.forEach((item, idx) => { pagedData.forEach((item, idx) => {
@ -4194,11 +4273,10 @@
} else if (activeDashboardDetailsType === 'outward_register') { } else if (activeDashboardDetailsType === 'outward_register') {
thead.innerHTML = ` thead.innerHTML = `
<tr> <tr>
<th>S.No</th>
<th>Req. No</th>
<th style="width: 60px;">S.No</th>
<th>Req. No / IT Cell Ref</th>
<th>Department</th> <th>Department</th>
<th>Printer Model</th>
<th>Toner Model</th>
<th>Zone</th>
<th class="text-center">Qty Req</th> <th class="text-center">Qty Req</th>
<th class="text-center">Qty Issued</th> <th class="text-center">Qty Issued</th>
<th>Requested Date</th> <th>Requested Date</th>
@ -4209,41 +4287,37 @@
pagedData.forEach((item, idx) => { pagedData.forEach((item, idx) => {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
const globalIdx = (dashboardDetailsCurrentPage - 1) * dashboardDetailsPageSize + idx + 1; const globalIdx = (dashboardDetailsCurrentPage - 1) * dashboardDetailsPageSize + idx + 1;
const reqDateFormatted = formatTimestamp(item.cdate);
const dateFormatted = formatTimestamp(item.updated_date);
let docBtn = '';
if (item.final_delivered_upload) {
const paths = item.final_delivered_upload.split(',');
docBtn = paths.map((p, index) => {
const trimmed = p.trim();
const url = trimmed.startsWith('.') ? trimmed.substring(1) : trimmed;
const label = paths.length > 1 ? `View Doc ${index + 1}` : `View Doc`;
return `<a href="${contextPath + url}" target="_blank" class="btn-action btn-view-doc me-1 mb-1" style="display: inline-flex; align-items: center; gap: 4px;"><i class="fa-solid fa-file-pdf"></i> ${label}</a>`;
}).join(' ');
}
let photoBtn = '';
if (item.receiver_photo) {
const photos = item.receiver_photo.split(',');
photoBtn = photos.map((p, pIdx) => {
const trimmed = p.trim();
const url = trimmed.startsWith('.') ? trimmed.substring(1) : trimmed;
const label = photos.length > 1 ? `View Photo ${pIdx + 1}` : `View Photo`;
return `<a href="${contextPath + url}" target="_blank" class="btn-action btn-view-photo me-1 mb-1" style="display: inline-flex; align-items: center; gap: 4px;"><i class="fa-solid fa-image"></i> ${label}</a>`;
}).join(' ');
}
const pdfBtn = `<button type="button" class="btn-action btn-pdf" onclick="downloadOutwardItemPdfAck(${item.id})"><i class="fa-solid fa-file-arrow-down"></i> PDF Ack</button>`;
const reqDateFormatted = (item.request_date && String(item.request_date).trim() !== '' && String(item.request_date).trim() !== 'null')
? formatDateOnly(item.request_date)
: (formatTimestamp(item.cdate) || '-');
const dateFormatted = (item.outward_date && String(item.outward_date).trim() !== '' && String(item.outward_date).trim() !== 'null')
? formatDateOnly(item.outward_date)
: (formatTimestamp(item.updated_date) || '-');
const groupRefParam = item.itcell_ref_no || item.req_no || (item.items && item.items[0] ? item.items[0].req_no : (item.key || ''));
const viewBtn = `<button type="button" class="btn btn-sm btn-primary fw-bold px-3 py-1 me-1 mb-1"
style="border-radius: 20px; font-size: 11px; background-color: #0f5c78; border-color: #0f5c78; box-shadow: 0 2px 4px rgba(15, 92, 120, 0.15);"
onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}')"
title="Click to view distributed toner breakdown">
<i class="fa-solid fa-eye me-1"></i> View (${item.items ? item.items.length : 1})
</button>`;
const modelsCountBadge = item.items && item.items.length > 1
? `<br><span class="badge bg-light text-secondary border mt-1" style="cursor: pointer;" onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}');" title="Click to view ${item.items.length} toners"><i class="fa-solid fa-layer-group me-1"></i>${item.items.length} Toners</span>`
: (item.items && item.items.length === 1 ? `<br><small class="text-muted font-monospace">${escapeHtml(item.items[0].model_name || '')}</small>` : '');
const refLink = `<a href="#" class="fw-bold text-decoration-none d-inline-block text-primary font-monospace" style="border-bottom: 1px dashed #0f5c78; cursor: pointer; font-size: 0.95rem;" onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}'); return false;" title="Click to view distributed toner breakdown">${escapeHtml(item.itcell_ref_no || item.req_no || '-')}</a>`;
tr.innerHTML = ` tr.innerHTML = `
<td class="fw-semibold text-secondary">${globalIdx}</td> <td class="fw-semibold text-secondary">${globalIdx}</td>
<td class="fw-bold text-dark">${escapeHtml(item.req_no)}</td>
<td>${escapeHtml(item.dept_name)}</td>
<td>${escapeHtml(item.printer_name)}</td>
<td><span class="badge-toner-model">${escapeHtml(item.model_name)}</span></td>
<td class="text-center font-monospace fw-bold">${item.qty_requested}</td>
<td class="text-center text-success fw-bold font-monospace">${item.qty_delivered}</td>
<td>${refLink}${modelsCountBadge}</td>
<td>${escapeHtml(item.dept_name || '-')}</td>
<td>${escapeHtml(item.zone_name || '-')}</td>
<td class="text-center font-monospace fw-bold text-dark">${item.qty_requested}</td>
<td class="text-center text-success fw-bold font-monospace">${item.qty_delivered || 0}</td>
<td><small class="text-dark fw-semibold">${reqDateFormatted}</small></td> <td><small class="text-dark fw-semibold">${reqDateFormatted}</small></td>
<td><small class="text-muted">${dateFormatted}</small></td>
<td class="text-center">${docBtn}${photoBtn}${pdfBtn}</td>
<td><small class="text-muted fw-semibold">${dateFormatted}</small></td>
<td class="text-center" style="white-space: nowrap;">${viewBtn}</td>
`; `;
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
@ -5823,7 +5897,7 @@
${getSortHeader('Registered Qty', 'total_qty_registered', 'text-center')} ${getSortHeader('Registered Qty', 'total_qty_registered', 'text-center')}
${getSortHeader('Start Date', 'workorder_start_date')} ${getSortHeader('Start Date', 'workorder_start_date')}
${getSortHeader('End Date', 'workorder_end_date')} ${getSortHeader('End Date', 'workorder_end_date')}
<th class="text-center">DC Copy</th>
<th class="text-center">Workorder Copy</th>
</tr>`; </tr>`;
// Group records by File No (or Ref No) // Group records by File No (or Ref No)
@ -6424,7 +6498,12 @@
} }
const groupRefParam = item.itcell_ref_no || item.req_no || (item.items && item.items[0] ? item.items[0].req_no : ''); const groupRefParam = item.itcell_ref_no || item.req_no || (item.items && item.items[0] ? item.items[0].req_no : '');
const pdfBtn = `<button type="button" class="btn-action btn-pdf mb-1" style="font-size: 11px; padding: 4px 8px;" onclick="downloadOutwardGroupPdfAck('${encodeURIComponent(groupRefParam)}')"><i class="fa-solid fa-file-arrow-down"></i> PDF Ack</button>`;
const viewBtn = `<button type="button" class="btn btn-sm btn-primary fw-bold px-3 py-1 me-1 mb-1"
style="border-radius: 20px; font-size: 11px; background-color: #0f5c78; border-color: #0f5c78; box-shadow: 0 2px 4px rgba(15, 92, 120, 0.15);"
onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}')"
title="Click to view distributed toner breakdown">
<i class="fa-solid fa-eye me-1"></i> View (${item.items ? item.items.length : 1})
</button>`;
const modelsCountBadge = item.items && item.items.length > 1 const modelsCountBadge = item.items && item.items.length > 1
? `<br><span class="badge bg-light text-secondary border mt-1" style="cursor: pointer;" onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}');" title="Click to view ${item.items.length} toners"><i class="fa-solid fa-layer-group me-1"></i>${item.items.length} Toners</span>` ? `<br><span class="badge bg-light text-secondary border mt-1" style="cursor: pointer;" onclick="showOutwardBreakdownModal('${encodeURIComponent(groupRefParam)}');" title="Click to view ${item.items.length} toners"><i class="fa-solid fa-layer-group me-1"></i>${item.items.length} Toners</span>`
@ -6441,7 +6520,7 @@
<td class="text-center text-success fw-bold font-monospace">${item.qty_delivered || 0}</td> <td class="text-center text-success fw-bold font-monospace">${item.qty_delivered || 0}</td>
<td><small class="text-dark fw-semibold">${reqDateFormatted}</small></td> <td><small class="text-dark fw-semibold">${reqDateFormatted}</small></td>
<td><small class="text-muted fw-semibold">${dateFormatted}</small></td> <td><small class="text-muted fw-semibold">${dateFormatted}</small></td>
<td class="text-center">${docBtn}${photoBtn}${pdfBtn}</td>
<td class="text-center" style="white-space: nowrap;">${viewBtn}</td>
`; `;
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
@ -7564,9 +7643,6 @@
<td><small class="text-dark fw-semibold">${dateFormatted}</small></td> <td><small class="text-dark fw-semibold">${dateFormatted}</small></td>
<td class="text-center" style="white-space: nowrap;"> <td class="text-center" style="white-space: nowrap;">
${viewBtn} ${viewBtn}
${docBtn}
${photoBtn}
${pdfBtn}
</td> </td>
`; `;
tbody.appendChild(tr); tbody.appendChild(tr);
@ -7625,6 +7701,7 @@
itcell_ref_no: first.itcell_ref_no || refIdentifier, itcell_ref_no: first.itcell_ref_no || refIdentifier,
req_no: first.req_no || refIdentifier, req_no: first.req_no || refIdentifier,
dept_name: first.dept_name || first.dept_id || '-', dept_name: first.dept_name || first.dept_id || '-',
request_date: first.request_date || '-',
outward_date: first.outward_date || (first.updated_date ? formatDateOnly(first.updated_date) : '-'), outward_date: first.outward_date || (first.updated_date ? formatDateOnly(first.updated_date) : '-'),
items: filtered items: filtered
}; };
@ -7745,6 +7822,7 @@
`; `;
tbody.appendChild(tr); tbody.appendChild(tr);
//<td class="text-center">${docBtn || photoBtn ? (docBtn + ' ' + photoBtn) : '<span class="text-muted">-</span>'}</td>
// <td class="text-center">${singlePdfBtn}</td> // <td class="text-center">${singlePdfBtn}</td>
}); });
@ -7797,15 +7875,18 @@
if (callback) callback(); if (callback) callback();
return; return;
} }
fetch(contextPath + 'gccservicepaymenttracker/api/vendors')
fetch(contextPath + 'gcctonermanagement/api/vendors')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
data.forEach(v => {
(data || []).forEach(v => {
vendorsMap[v.id] = v.vendor_name; vendorsMap[v.id] = v.vendor_name;
}); });
if (callback) callback(); if (callback) callback();
}) })
.catch(err => console.error("Error loading vendors map:", err));
.catch(err => {
console.error("Error loading vendors map:", err);
if (callback) callback();
});
} }
function fetchActiveWorkordersForInward() { function fetchActiveWorkordersForInward() {
@ -8265,13 +8346,33 @@
return; return;
} }
document.getElementById('edit-request-is-group').value = 'false';
document.getElementById('edit-request-id').value = item.id || ''; document.getElementById('edit-request-id').value = item.id || '';
document.getElementById('edit-request-orig-itcell').value = item.itcell_ref_no || '';
document.getElementById('edit-request-req-no').value = item.req_no || '-'; document.getElementById('edit-request-req-no').value = item.req_no || '-';
document.getElementById('edit-request-itcell').value = item.itcell_ref_no || ''; document.getElementById('edit-request-itcell').value = item.itcell_ref_no || '';
document.getElementById('edit-request-qty').value = item.qty_requested || 1; document.getElementById('edit-request-qty').value = item.qty_requested || 1;
const editDateInput = document.getElementById('edit-request-date'); const editDateInput = document.getElementById('edit-request-date');
if (editDateInput) editDateInput.value = item.request_date || ''; if (editDateInput) editDateInput.value = item.request_date || '';
// Update modal title
const titleEl = document.getElementById('editUserRequestModalLabel');
if (titleEl) titleEl.innerHTML = '<i class="fa-solid fa-pen-to-square me-2"></i> Edit User Request Details';
// Show single item container, hide multi item container
const singleContainer = document.getElementById('edit-request-single-container');
const multiContainer = document.getElementById('edit-request-multi-container');
if (singleContainer) singleContainer.style.display = 'flex';
if (multiContainer) multiContainer.style.display = 'none';
// Restore required attributes for single mode
const printerSelect = document.getElementById('edit-request-printer');
const tonerSelect = document.getElementById('edit-request-toner');
const qtyInput = document.getElementById('edit-request-qty');
if (printerSelect) printerSelect.required = true;
if (tonerSelect) tonerSelect.required = true;
if (qtyInput) qtyInput.required = true;
// Reset file input // Reset file input
const fileInput = document.getElementById('edit-request-file'); const fileInput = document.getElementById('edit-request-file');
if (fileInput) fileInput.value = ''; if (fileInput) fileInput.value = '';
@ -8327,7 +8428,6 @@
} }
// Populate printers dropdown // Populate printers dropdown
const printerSelect = document.getElementById('edit-request-printer');
if (printerSelect) { if (printerSelect) {
printerSelect.innerHTML = '<option value="">-- Select Printer --</option>'; printerSelect.innerHTML = '<option value="">-- Select Printer --</option>';
(printersCached || []).forEach(p => { (printersCached || []).forEach(p => {
@ -8358,15 +8458,144 @@
} }
} }
function openEditUserRequestGroupModal(itcellRefNo, items) {
if (!items || items.length === 0) return;
const firstItem = items[0];
document.getElementById('edit-request-is-group').value = 'true';
document.getElementById('edit-request-id').value = firstItem.id || '';
document.getElementById('edit-request-orig-itcell').value = itcellRefNo || '';
document.getElementById('edit-request-req-no').value = `Multi-Toner Request (${items.length} Toners)`;
document.getElementById('edit-request-itcell').value = itcellRefNo || '';
// Populate Requested Date from existing items
const editDateInput = document.getElementById('edit-request-date');
const foundDate = items.find(i => i.request_date && String(i.request_date).trim() !== '' && String(i.request_date).trim() !== 'null')?.request_date || '';
if (editDateInput) editDateInput.value = foundDate;
// Update modal title
const titleEl = document.getElementById('editUserRequestModalLabel');
if (titleEl) titleEl.innerHTML = `<i class="fa-solid fa-pen-to-square me-2"></i> Edit Request Details & Date (${escapeHtml(itcellRefNo || 'Request')})`;
// Hide single item container, show multi item container
const singleContainer = document.getElementById('edit-request-single-container');
const multiContainer = document.getElementById('edit-request-multi-container');
if (singleContainer) singleContainer.style.display = 'none';
if (multiContainer) multiContainer.style.display = 'flex';
// Remove required from single container inputs so form can submit
const printerSelect = document.getElementById('edit-request-printer');
const tonerSelect = document.getElementById('edit-request-toner');
const qtyInput = document.getElementById('edit-request-qty');
if (printerSelect) printerSelect.required = false;
if (tonerSelect) tonerSelect.required = false;
if (qtyInput) qtyInput.required = false;
// Set multi count badge
const multiCountEl = document.getElementById('edit-request-multi-count');
if (multiCountEl) multiCountEl.textContent = items.length;
// Populate multi-toner items table
const multiTbody = document.getElementById('edit-request-multi-tbody');
if (multiTbody) {
multiTbody.innerHTML = '';
items.forEach((it, idx) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="fw-semibold text-secondary text-center">${idx + 1}</td>
<td class="fw-bold text-dark font-monospace">${escapeHtml(it.req_no || '-')}</td>
<td>${escapeHtml(it.printer_name || '-')}</td>
<td><span class="badge-toner-model">${escapeHtml(it.model_name || '-')}</span></td>
<td class="text-center">
<input type="number" min="1" step="1" required
class="form-control form-control-sm text-center edit-multi-qty-input fw-bold mx-auto"
style="width: 100px; border-radius: 6px; border: 1px solid #cbd5e1;"
data-id="${it.id}" value="${it.qty_requested || 1}">
</td>
`;
multiTbody.appendChild(tr);
});
}
// Reset file input
const fileInput = document.getElementById('edit-request-file');
if (fileInput) fileInput.value = '';
const fileLabel = document.getElementById('edit-request-file-label');
if (fileLabel) fileLabel.textContent = 'Upload new PDF to replace (Optional)';
// Show current document link from any item with a file
const currentDocContainer = document.getElementById('edit-request-current-doc');
if (currentDocContainer) {
const foundUpload = items.find(i => i.user_request_upload && String(i.user_request_upload).trim() !== '')?.user_request_upload || '';
if (foundUpload) {
currentDocContainer.innerHTML = `
<div class="d-flex align-items-center gap-2 p-2 bg-light rounded border">
<i class="fa-solid fa-file-pdf text-danger"></i>
<span class="small text-muted text-truncate" style="max-width: 180px;">Current File</span>
<a href="${resolveUploadUrl(foundUpload)}" target="_blank" class="btn btn-sm btn-outline-danger py-0 px-2 ms-auto" style="font-size: 11px;">
<i class="fa-solid fa-eye"></i> View
</a>
</div>
`;
} else {
currentDocContainer.innerHTML = `<span class="small text-muted"><i class="fa-solid fa-ban me-1"></i> No letter uploaded</span>`;
}
}
// Populate departments dropdown
const deptSelect = document.getElementById('edit-request-dept');
if (deptSelect) {
deptSelect.innerHTML = '<option value="">-- Select Department --</option>';
(departmentsCached || []).forEach(d => {
const opt = document.createElement('option');
opt.value = d.dept_name || d.id;
opt.textContent = d.dept_name || `Dept #${d.id}`;
if (String(firstItem.dept_id) === String(d.id) || String(firstItem.dept_name) === String(d.dept_name) || String(firstItem.dept_id) === String(d.dept_name)) {
opt.selected = true;
}
deptSelect.appendChild(opt);
});
}
// Populate zones dropdown
const zoneSelect = document.getElementById('edit-request-zone');
if (zoneSelect) {
zoneSelect.innerHTML = '<option value="">-- Select Zone (Optional) --</option>';
(zonesCached || []).forEach(z => {
const opt = document.createElement('option');
opt.value = z.zone_name || z.id;
opt.textContent = z.zone_name || `Zone #${z.id}`;
if (String(firstItem.zone_id) === String(z.id) || String(firstItem.zone_name) === String(z.zone_name) || String(firstItem.zone_id) === String(z.zone_name)) {
opt.selected = true;
}
zoneSelect.appendChild(opt);
});
}
// Hide breakdown modal if open
const breakdownModalEl = document.getElementById('userRequestBreakdownModal');
if (breakdownModalEl) {
const breakdownModal = bootstrap.Modal.getInstance(breakdownModalEl);
if (breakdownModal) breakdownModal.hide();
}
const editModalEl = document.getElementById('editUserRequestModal');
if (editModalEl) {
const editModal = bootstrap.Modal.getOrCreateInstance(editModalEl);
editModal.show();
}
}
function openEditUserRequestByGroup(itcellRefNo, pid) { function openEditUserRequestByGroup(itcellRefNo, pid) {
const cleanRef = String(itcellRefNo || '').trim();
const items = (userRequestsCached || []).filter(r => const items = (userRequestsCached || []).filter(r =>
String(r.itcell_ref_no).trim() === String(itcellRefNo).trim() &&
(pid === undefined || pid === null || String(r.pid) === String(pid))
(String(r.itcell_ref_no || '').trim() === cleanRef || String(r.req_no || '').trim() === cleanRef) &&
(pid === undefined || pid === null || String(pid) === '' || String(r.pid) === String(pid))
); );
if (items.length === 1) { if (items.length === 1) {
openEditUserRequestModal(items[0].id); openEditUserRequestModal(items[0].id);
} else if (items.length > 1) { } else if (items.length > 1) {
showUserRequestBreakdownModal(itcellRefNo, pid);
openEditUserRequestGroupModal(cleanRef, items);
} else { } else {
showNotification('warning', 'Not Found', 'No records found to edit.'); showNotification('warning', 'Not Found', 'No records found to edit.');
} }
@ -8414,35 +8643,65 @@
function submitEditRequest(event) { function submitEditRequest(event) {
if (event) event.preventDefault(); if (event) event.preventDefault();
const id = document.getElementById('edit-request-id').value;
const isGroup = document.getElementById('edit-request-is-group')?.value === 'true';
const itcellRefNo = document.getElementById('edit-request-itcell').value.trim(); const itcellRefNo = document.getElementById('edit-request-itcell').value.trim();
const origItcell = document.getElementById('edit-request-orig-itcell').value.trim();
const deptId = document.getElementById('edit-request-dept').value.trim(); const deptId = document.getElementById('edit-request-dept').value.trim();
const zoneId = document.getElementById('edit-request-zone').value.trim(); const zoneId = document.getElementById('edit-request-zone').value.trim();
const editDateInput = document.getElementById('edit-request-date');
const requestDate = editDateInput ? editDateInput.value.trim() : '';
const fileInput = document.getElementById('edit-request-file');
if (!itcellRefNo || !deptId) {
showNotification('warning', 'Missing Fields', 'Please fill in all required fields.');
return;
}
const formData = new FormData();
formData.append('deptId', deptId);
formData.append('zoneId', zoneId);
formData.append('requestDate', requestDate);
if (fileInput && fileInput.files.length > 0) {
formData.append('userRequestFile', fileInput.files[0]);
}
let endpoint = contextPath + 'gcctonermanagement/api/request/update';
if (isGroup) {
formData.append('itcellRefNo', origItcell || itcellRefNo);
formData.append('newItcellRefNo', itcellRefNo);
// Collect quantities for each item in the multi-table
const qtyInputs = document.querySelectorAll('.edit-multi-qty-input');
const items = [];
for (let i = 0; i < qtyInputs.length; i++) {
const inp = qtyInputs[i];
const itemId = inp.getAttribute('data-id');
const qtyVal = parseInt(inp.value.trim());
if (isNaN(qtyVal) || qtyVal <= 0) {
showNotification('warning', 'Invalid Quantity', 'Please enter a valid positive quantity for all items.');
return;
}
items.push({ id: itemId, qty: qtyVal });
}
formData.append('itemsJson', JSON.stringify(items));
endpoint = contextPath + 'gcctonermanagement/api/request/update-group';
} else {
const id = document.getElementById('edit-request-id').value;
const pid = document.getElementById('edit-request-printer').value; const pid = document.getElementById('edit-request-printer').value;
const tid = document.getElementById('edit-request-toner').value; const tid = document.getElementById('edit-request-toner').value;
const qtyRequested = document.getElementById('edit-request-qty').value; const qtyRequested = document.getElementById('edit-request-qty').value;
const fileInput = document.getElementById('edit-request-file');
if (!id || !itcellRefNo || !deptId || !pid || !tid || !qtyRequested) {
if (!id || !pid || !tid || !qtyRequested) {
showNotification('warning', 'Missing Fields', 'Please fill in all required fields.'); showNotification('warning', 'Missing Fields', 'Please fill in all required fields.');
return; return;
} }
const formData = new FormData();
formData.append('id', id); formData.append('id', id);
formData.append('itcellRefNo', itcellRefNo); formData.append('itcellRefNo', itcellRefNo);
formData.append('deptId', deptId);
formData.append('zoneId', zoneId);
formData.append('pid', pid); formData.append('pid', pid);
formData.append('tid', tid); formData.append('tid', tid);
formData.append('qtyRequested', qtyRequested); formData.append('qtyRequested', qtyRequested);
const editDateInput = document.getElementById('edit-request-date');
if (editDateInput) {
formData.append('requestDate', editDateInput.value.trim());
}
if (fileInput && fileInput.files.length > 0) {
formData.append('userRequestFile', fileInput.files[0]);
} }
const progressContainer = document.getElementById('edit-request-progress-container'); const progressContainer = document.getElementById('edit-request-progress-container');
@ -8454,7 +8713,7 @@
if (submitBtn) submitBtn.disabled = true; if (submitBtn) submitBtn.disabled = true;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', contextPath + 'gcctonermanagement/api/request/update', true);
xhr.open('POST', endpoint, true);
const csrf = getCsrfMeta(); const csrf = getCsrfMeta();
if (csrf.token && csrf.header) { if (csrf.token && csrf.header) {
@ -8734,11 +8993,11 @@
if (!select) return; if (!select) return;
select.innerHTML = '<option value="">-- Loading Vendors... --</option>'; select.innerHTML = '<option value="">-- Loading Vendors... --</option>';
fetch(contextPath + 'gccservicepaymenttracker/api/vendors')
fetch(contextPath + 'gcctonermanagement/api/vendors')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
select.innerHTML = '<option value="">-- Select Vendor --</option>'; select.innerHTML = '<option value="">-- Select Vendor --</option>';
data.forEach(v => {
(data || []).forEach(v => {
const opt = document.createElement('option'); const opt = document.createElement('option');
opt.value = v.id; opt.value = v.id;
opt.textContent = v.vendor_name; opt.textContent = v.vendor_name;
@ -8810,11 +9069,11 @@
if (!select) return; if (!select) return;
select.innerHTML = '<option value="">-- Loading Vendors... --</option>'; select.innerHTML = '<option value="">-- Loading Vendors... --</option>';
fetch(contextPath + 'gccservicepaymenttracker/api/vendors')
fetch(contextPath + 'gcctonermanagement/api/vendors')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
select.innerHTML = '<option value="">-- Select Vendor --</option>'; select.innerHTML = '<option value="">-- Select Vendor --</option>';
data.forEach(v => {
(data || []).forEach(v => {
const opt = document.createElement('option'); const opt = document.createElement('option');
opt.value = v.id; opt.value = v.id;
opt.textContent = v.vendor_name; opt.textContent = v.vendor_name;
@ -9620,6 +9879,7 @@
itcell_ref_no: selectedKey, itcell_ref_no: selectedKey,
req_no: groupItems[0]?.req_no || selectedKey, req_no: groupItems[0]?.req_no || selectedKey,
dept_name: groupItems[0]?.dept_name || groupItems[0]?.dept_id || '', dept_name: groupItems[0]?.dept_name || groupItems[0]?.dept_id || '',
request_date: groupItems[0]?.request_date || groupItems[0]?.created_date || groupItems[0]?.cdate || '',
outward_date: outwardDate, outward_date: outwardDate,
itemIds: itemsToIssue.map(i => i.id), itemIds: itemsToIssue.map(i => i.id),
items: issuedDetails items: issuedDetails
@ -9801,57 +10061,120 @@
doc.setTextColor(15, 92, 120); doc.setTextColor(15, 92, 120);
doc.text("TONER DELIVERY ACKNOWLEDGEMENT", 105, 45, null, null, "center"); doc.text("TONER DELIVERY ACKNOWLEDGEMENT", 105, 45, null, null, "center");
// Helper to format date strings cleanly
function formatPdfDate(val, fallbackToday = false) {
if (!val) {
if (fallbackToday) {
const now = new Date();
const dd = String(now.getDate()).padStart(2, '0');
const mm = String(now.getMonth() + 1).padStart(2, '0');
const yyyy = now.getFullYear();
return `${dd}/${mm}/${yyyy}`;
}
return '-';
}
if (typeof val === 'string' && /^\d{2}\/\d{2}\/\d{4}/.test(val.trim())) {
return val.trim();
}
try {
const d = new Date(val);
if (!isNaN(d.getTime())) {
const dd = String(d.getDate()).padStart(2, '0');
const mm = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const hh = d.getHours();
const min = d.getMinutes();
if (typeof val === 'string' && (val.includes(':') || val.includes('T'))) {
const hhStr = String(hh).padStart(2, '0');
const minStr = String(min).padStart(2, '0');
return `${dd}/${mm}/${yyyy} ${hhStr}:${minStr}`;
}
return `${dd}/${mm}/${yyyy}`;
}
} catch (e) { }
return String(val);
}
// Header info details (Ref No, Department, Date) // Header info details (Ref No, Department, Date)
doc.setFontSize(9.5);
doc.setFont('Helvetica', 'normal');
doc.setFontSize(9);
doc.setTextColor(51, 65, 85); doc.setTextColor(51, 65, 85);
const refNoText = item.itcell_ref_no || item.req_no || '-';
const deptText = item.dept_name || item.dept_id || '-';
const outwardDateText = item.outward_date || (new Date().toLocaleDateString('en-GB') + ' ' + new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }));
const refNoText = String(item.itcell_ref_no || item.req_no || '-');
const deptText = String(item.dept_name || item.dept_id || '-');
// Determine User Requested Date
let reqDateRaw = item.request_date || item.cdate || item.created_date;
if (!reqDateRaw && item.items && item.items.length > 0) {
reqDateRaw = item.items[0].request_date || item.items[0].cdate || item.items[0].created_date;
}
const userRequestedDate = formatPdfDate(reqDateRaw);
// Determine Issued Date
let outwardDateRaw = item.outward_date || item.updated_date;
if (!outwardDateRaw && item.items && item.items.length > 0) {
outwardDateRaw = item.items[0].outward_date || item.items[0].updated_date;
}
const outwardDateText = formatPdfDate(outwardDateRaw, true);
// Left Column Info
doc.setFont('Helvetica', 'bold'); doc.setFont('Helvetica', 'bold');
doc.text("IT Cell Ref No:", 15, 54); doc.text("IT Cell Ref No:", 15, 54);
doc.setFont('Helvetica', 'normal'); doc.setFont('Helvetica', 'normal');
doc.text(refNoText, 45, 54);
doc.text(refNoText, 43, 54);
doc.setFont('Helvetica', 'bold'); doc.setFont('Helvetica', 'bold');
doc.text("Department:", 15, 60); doc.text("Department:", 15, 60);
doc.setFont('Helvetica', 'normal'); doc.setFont('Helvetica', 'normal');
doc.text(deptText, 45, 60);
const wrappedDept = doc.splitTextToSize(deptText, 70);
doc.text(wrappedDept, 43, 60);
// Right Column Info (Properly aligned with adequate spacing to prevent overlap)
doc.setFont('Helvetica', 'bold'); doc.setFont('Helvetica', 'bold');
doc.text("Delivery Date:", 135, 54);
doc.text("User Requested Date:", 116, 54);
doc.setFont('Helvetica', 'normal'); doc.setFont('Helvetica', 'normal');
doc.text(outwardDateText, 162, 54);
doc.text(userRequestedDate, 156, 54);
let tableColumns = [];
doc.setFont('Helvetica', 'bold');
doc.text("Issued Date:", 116, 60);
doc.setFont('Helvetica', 'normal');
doc.text(outwardDateText, 156, 60);
let tableColumns = ["S.No", "Printer Model", "Toner Model", "Qty Issued"];
let tableRows = []; let tableRows = [];
const getQtyVal = (obj) => {
const q = obj.qty_delivered ?? obj.qtyIssued ?? obj.qty_requested ?? '-';
return String(q);
};
const getTonerModelText = (obj) => {
let name = obj.model_name || obj.toner_model || '-';
if (obj.part_code && !name.includes(obj.part_code)) {
name += ` (${obj.part_code})`;
}
return name;
};
if (item.items && Array.isArray(item.items) && item.items.length > 0) { if (item.items && Array.isArray(item.items) && item.items.length > 0) {
tableColumns = ["S.No", "Printer Model", "Toner Model", "Qty Req", "Qty Issued"];
tableRows = item.items.map((it, idx) => [ tableRows = item.items.map((it, idx) => [
idx + 1, idx + 1,
it.printer_name || '-',
(it.model_name || '-') + (it.part_code ? ` (${it.part_code})` : ''),
it.qty_requested || '-',
it.qty_delivered || '-'
it.printer_name || it.printer_model || '-',
getTonerModelText(it),
getQtyVal(it)
]); ]);
} else { } else {
tableColumns = ["S.No", "Printer Model", "Toner Model", "Qty Req", "Qty Issued"];
tableRows = [ tableRows = [
[ [
1, 1,
item.printer_name || '-',
(item.model_name || '-') + (item.part_code ? ` (${item.part_code})` : ''),
item.qty_requested || '-',
item.qty_delivered || '-'
item.printer_name || item.printer_model || '-',
getTonerModelText(item),
getQtyVal(item)
] ]
]; ];
} }
doc.autoTable({ doc.autoTable({
startY: 66,
startY: 67,
margin: { left: 15, right: 15 }, margin: { left: 15, right: 15 },
head: [tableColumns], head: [tableColumns],
body: tableRows, body: tableRows,
@ -9870,18 +10193,38 @@
lineWidth: 0.1 lineWidth: 0.1
}, },
columnStyles: { columnStyles: {
0: { width: 15, halign: 'center' },
3: { width: 25, halign: 'center', fontStyle: 'bold' },
4: { width: 25, halign: 'center', fontStyle: 'bold', textColor: [22, 101, 52] }
0: { cellWidth: 15, halign: 'center' },
1: { cellWidth: 50, halign: 'left' },
2: { cellWidth: 85, halign: 'left' },
3: { cellWidth: 30, halign: 'center', fontStyle: 'bold', textColor: [22, 101, 52] }
},
didParseCell: function (data) {
if (data.column.index === 0 || data.column.index === 3) {
data.cell.styles.halign = 'center';
}
} }
}); });
const finalY = doc.lastAutoTable ? doc.lastAutoTable.finalY + 15 : 150; const finalY = doc.lastAutoTable ? doc.lastAutoTable.finalY + 15 : 150;
// Signatures row // Signatures row
const sigY = Math.max(finalY + 20, 230);
doc.setDrawColor(203, 213, 225);
doc.setLineWidth(0.25);
const sigY = Math.max(finalY + 20, 225);
// Left Side: Issued By (IT Cell)
// doc.setDrawColor(203, 213, 225);
// doc.setLineWidth(0.25);
// doc.line(15, sigY, 75, sigY);
// doc.setFont('Helvetica', 'bold');
// doc.setFontSize(9);
// doc.setTextColor(71, 85, 105);
// doc.text("Issued By (IT Cell)", 45, sigY + 5, null, null, "center");
// doc.setFont('Helvetica', 'normal');
// doc.setFontSize(8);
// doc.text("Greater Chennai Corporation", 45, sigY + 9, null, null, "center");
// Right Side: Receiver's Signature
doc.line(135, sigY, 195, sigY); doc.line(135, sigY, 195, sigY);
doc.setFont('Helvetica', 'bold'); doc.setFont('Helvetica', 'bold');
@ -9893,13 +10236,13 @@
doc.setFontSize(8); doc.setFontSize(8);
doc.text("Greater Chennai Corporation", 165, sigY + 9, null, null, "center"); doc.text("Greater Chennai Corporation", 165, sigY + 9, null, null, "center");
doc.setFontSize(8);
doc.text("Name: ______________________", 135, sigY + 18);
doc.text("Mobile Number: _____________", 135, sigY + 25);
doc.text("Designation: ________________", 135, sigY + 32);
doc.setFontSize(8.5);
doc.text("Name: ______________________", 130, sigY + 18);
doc.text("Mobile Number: _____________", 130, sigY + 25);
doc.text("Designation: ________________", 130, sigY + 32);
if (signatureDataUrl) { if (signatureDataUrl) {
doc.addImage(signatureDataUrl, 'PNG', 20, sigY - 20, 40, 18);
doc.addImage(signatureDataUrl, 'PNG', 15, sigY - 20, 40, 18);
} }
callback(doc); callback(doc);
@ -9931,12 +10274,16 @@
fetch(contextPath + 'gcctonermanagement/api/workorder/history') fetch(contextPath + 'gcctonermanagement/api/workorder/history')
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
workorderHistoryCached = data;
renderWorkorderHistoryTable('workorder-history-table-body', data, true, 1);
workorderHistoryCached = data || [];
renderWorkorderHistoryTable('workorder-history-table-body', workorderHistoryCached, true, 1);
populateReportFileNoFilters(); populateReportFileNoFilters();
applyReportFilters(); applyReportFilters();
try {
if (typeof populateDashboardWorkOrders === 'function') populateDashboardWorkOrders(); if (typeof populateDashboardWorkOrders === 'function') populateDashboardWorkOrders();
if (typeof updateDashboardData === 'function') updateDashboardData(); if (typeof updateDashboardData === 'function') updateDashboardData();
} catch (e) {
console.warn("Dashboard sync skipped:", e);
}
}) })
.catch(err => { .catch(err => {
console.error("Error fetching workorder history: ", err); console.error("Error fetching workorder history: ", err);
@ -9964,7 +10311,7 @@
<th class="text-center">Total Qty</th> <th class="text-center">Total Qty</th>
<th>Start Date</th> <th>Start Date</th>
<th>End Date</th> <th>End Date</th>
<th class="text-center">DC Copy</th>
<th class="text-center">Workorder Copy</th>
<th class="text-center">Action</th> <th class="text-center">Action</th>
</tr> </tr>
`; `;
@ -12003,6 +12350,11 @@
<h5 class="modal-title fw-bold" id="userRequestBreakdownModalLabel"> <h5 class="modal-title fw-bold" id="userRequestBreakdownModalLabel">
<i class="fa-solid fa-file-invoice me-2"></i> User Request Details Breakdown <i class="fa-solid fa-file-invoice me-2"></i> User Request Details Breakdown
</h5> </h5>
<button type="button" class="btn btn-sm btn-outline-light fw-bold ms-auto me-2"
style="border-radius: 8px; font-size: 0.82rem;" id="user-breakdown-edit-btn"
onclick="openEditUserRequestByGroup(document.getElementById('user-breakdown-ref').textContent)">
<i class="fa-solid fa-pen-to-square me-1"></i> Edit Request / Date
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button> aria-label="Close"></button>
</div> </div>
@ -12142,6 +12494,8 @@
<form id="edit-request-form" onsubmit="submitEditRequest(event)"> <form id="edit-request-form" onsubmit="submitEditRequest(event)">
<div class="modal-body p-4"> <div class="modal-body p-4">
<input type="hidden" id="edit-request-id" name="id"> <input type="hidden" id="edit-request-id" name="id">
<input type="hidden" id="edit-request-is-group" name="isGroup" value="false">
<input type="hidden" id="edit-request-orig-itcell" name="origItcell">
<div class="row g-3"> <div class="row g-3">
<!-- Request No (Readonly display) --> <!-- Request No (Readonly display) -->
@ -12158,8 +12512,9 @@
<label class="form-label fw-semibold text-secondary" for="edit-request-itcell"> <label class="form-label fw-semibold text-secondary" for="edit-request-itcell">
<i class="fa-solid fa-stamp text-primary me-1"></i> IT Cell Reference No. * <i class="fa-solid fa-stamp text-primary me-1"></i> IT Cell Reference No. *
</label> </label>
<input type="text" id="edit-request-itcell" required class="form-control-custom"
placeholder="e.g. IT/2026/001" maxlength="45">
<input type="text" id="edit-request-itcell" readonly class="form-control-custom"
style="background-color: #f1f5f9; font-weight: bold;" placeholder="e.g. IT/2026/001"
maxlength="45">
</div> </div>
<!-- User Requested Date --> <!-- User Requested Date -->
@ -12173,8 +12528,7 @@
<!-- Department --> <!-- Department -->
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-semibold text-secondary" for="edit-request-dept"> <label class="form-label fw-semibold text-secondary" for="edit-request-dept">
<i class="fa-solid fa-landmark text-primary me-1"></i> Department / Office Name
*
<i class="fa-solid fa-landmark text-primary me-1"></i> Department / Office Name *
</label> </label>
<select id="edit-request-dept" required class="form-control-custom"> <select id="edit-request-dept" required class="form-control-custom">
<option value="">-- Select Department --</option> <option value="">-- Select Department --</option>
@ -12192,6 +12546,30 @@
</select> </select>
</div> </div>
<!-- Current Uploaded File & New File Upload -->
<div class="col-12 col-md-6">
<label class="form-label fw-semibold text-secondary">
<i class="fa-solid fa-file-pdf text-danger me-1"></i> Request Letter PDF
</label>
<div id="edit-request-current-doc" class="mb-2">
<!-- Populated dynamically with current file link -->
</div>
<div class="file-upload-card py-2"
onclick="document.getElementById('edit-request-file').click()"
style="border-radius: 8px; cursor: pointer;">
<i class="fa-solid fa-cloud-arrow-up text-primary me-2"></i>
<span class="upload-card-text" id="edit-request-file-label"
style="font-size: 0.85rem;">
Upload new PDF to replace (Optional)
</span>
<input type="file" id="edit-request-file" accept=".pdf" class="d-none"
onchange="handleFileSelection(this, 'edit-request-file-label')">
</div>
</div>
</div>
<!-- Single Item Container (Visible for Single Toner Request) -->
<div class="row g-3 mt-1" id="edit-request-single-container">
<!-- Printer Model --> <!-- Printer Model -->
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<label class="form-label fw-semibold text-secondary" for="edit-request-printer"> <label class="form-label fw-semibold text-secondary" for="edit-request-printer">
@ -12214,7 +12592,7 @@
</div> </div>
<!-- Given Count (Quantity Requested) --> <!-- Given Count (Quantity Requested) -->
<div class="col-12 col-md-6">
<div class="col-12">
<label class="form-label fw-semibold text-secondary" for="edit-request-qty"> <label class="form-label fw-semibold text-secondary" for="edit-request-qty">
<i class="fa-solid fa-boxes-stacked text-primary me-1"></i> Given Count / Qty <i class="fa-solid fa-boxes-stacked text-primary me-1"></i> Given Count / Qty
Requested * Requested *
@ -12222,25 +12600,36 @@
<input type="number" id="edit-request-qty" required min="1" step="1" <input type="number" id="edit-request-qty" required min="1" step="1"
class="form-control-custom" placeholder="e.g. 5"> class="form-control-custom" placeholder="e.g. 5">
</div> </div>
</div>
<!-- Current Uploaded File & New File Upload -->
<div class="col-12 col-md-6">
<label class="form-label fw-semibold text-secondary">
<i class="fa-solid fa-file-pdf text-danger me-1"></i> Request Letter PDF
</label>
<div id="edit-request-current-doc" class="mb-2">
<!-- Populated dynamically with current file link -->
<!-- Multi Items Container (Visible for Multi-Toner Request) -->
<div class="row g-3 mt-2" id="edit-request-multi-container" style="display: none;">
<div class="col-12">
<div class="alert alert-info py-2 px-3 mb-2 d-flex align-items-center gap-2"
style="font-size: 0.85rem; border-radius: 8px;">
<i class="fa-solid fa-circle-info fs-5 text-primary"></i>
<div><strong>Multi-Toner Request:</strong> Updating the <strong>User Requested
Date</strong>, Department, Zone, and Letter above applies to all <strong
id="edit-request-multi-count">0</strong> toners in this request. You can
also adjust individual quantities below.</div>
</div> </div>
<div class="file-upload-card py-2"
onclick="document.getElementById('edit-request-file').click()"
style="border-radius: 8px; cursor: pointer;">
<i class="fa-solid fa-cloud-arrow-up text-primary me-2"></i>
<span class="upload-card-text" id="edit-request-file-label"
style="font-size: 0.85rem;">
Upload new PDF to replace (Optional)
</span>
<input type="file" id="edit-request-file" accept=".pdf" class="d-none"
onchange="handleFileSelection(this, 'edit-request-file-label')">
<div class="table-responsive border rounded"
style="max-height: 240px; overflow-y: auto;">
<table class="table table-hover table-striped align-middle mb-0"
style="font-size: 0.88rem;">
<thead class="table-light sticky-top">
<tr>
<th style="width: 50px;">S.No</th>
<th>Req. No</th>
<th>Printer Model</th>
<th>Toner Model</th>
<th class="text-center" style="width: 140px;">Qty Requested *</th>
</tr>
</thead>
<tbody id="edit-request-multi-tbody">
<!-- Populated dynamically -->
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save