Browse Source

added deleted records to view in manpower and payment tracker files

master
yoganandhank 3 months ago
parent
commit
493ad3912c
  1. 13
      src/main/java/com/gcc_manpower/controller/HomeController.java
  2. 55
      src/main/java/com/gcc_manpower/gccservicepaymenttracker/controller/GccServicePaymentTrackerController.java
  3. 75
      src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java
  4. 29
      src/main/java/com/gcc_manpower/service/AppUserService.java
  5. 7
      src/main/resources/application-local.properties
  6. 6
      src/main/resources/templates/fragments/navbar.html
  7. 278
      src/main/resources/templates/gccservicepaymenttracker/dashboard.html
  8. 238
      src/main/resources/templates/home/home.html

13
src/main/java/com/gcc_manpower/controller/HomeController.java

@ -623,6 +623,19 @@ public class HomeController {
return appUserService.getAllEmployeesWithDeleted(); return appUserService.getAllEmployeesWithDeleted();
} }
@GetMapping("/api/work-orders/deleted")
@ResponseBody
public List<Map<String, Object>> getDeletedWorkOrders(HttpSession session) {
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
return new java.util.ArrayList<>();
}
return appUserService.getDeletedWorkOrders();
}
@PostMapping("/usr-logout") @PostMapping("/usr-logout")
public String logout(HttpSession session) { public String logout(HttpSession session) {
session.invalidate(); session.invalidate();

55
src/main/java/com/gcc_manpower/gccservicepaymenttracker/controller/GccServicePaymentTrackerController.java

@ -297,4 +297,59 @@ public class GccServicePaymentTrackerController {
.cacheControl(CacheControl.noCache().mustRevalidate()) .cacheControl(CacheControl.noCache().mustRevalidate())
.body(paymentTrackerService.getHistory(id)); .body(paymentTrackerService.getHistory(id));
} }
@GetMapping("/api/deleted-list")
@ResponseBody
public ResponseEntity<List<Map<String, Object>>> getDeletedList(HttpSession session) {
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
return ResponseEntity.status(401).body(null);
}
return ResponseEntity.ok()
.cacheControl(CacheControl.noCache().mustRevalidate())
.body(paymentTrackerService.getDeletedDetails());
}
@PostMapping("/api/restore")
@ResponseBody
public Map<String, Object> restoreDetail(
@RequestParam("id") int id,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
String usrRole = (String) session.getAttribute("pay_usrRole");
if (usrRole == null) {
usrRole = (String) session.getAttribute("usrRole");
}
if (!"ADMIN".equalsIgnoreCase(usrRole)) {
response.put("success", false);
response.put("error", "Access denied! Only admins can restore records.");
return response;
}
try {
boolean restored = paymentTrackerService.restoreDetail(id, username);
response.put("success", restored);
if (!restored) {
response.put("error", "Failed to restore record.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
} }

75
src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java

@ -448,4 +448,79 @@ public class GccServicePaymentTrackerService {
return new ArrayList<>(); return new ArrayList<>();
} }
} }
public List<Map<String, Object>> getDeletedDetails() {
String sql = "SELECT p.*, "
+ "t.type_name as resolved_type, "
+ "v.vendor_name as resolved_vendor, "
+ "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details p "
+ "LEFT JOIN type_list t ON p.type REGEXP '^[0-9]+$' AND t.id = p.type "
+ "LEFT JOIN vendors_list v ON p.vendor_name REGEXP '^[0-9]+$' AND v.id = p.vendor_name "
+ "LEFT JOIN status_list s ON p.status REGEXP '^[0-9]+$' AND s.id = p.status "
+ "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) "
+ "WHERE p.is_active = 0 AND p.is_delete = 1 "
+ "ORDER BY p.updated_date DESC";
try {
return jdbcTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching deleted payment tracking details: " + e.getMessage());
return new ArrayList<>();
}
}
public boolean restoreDetail(int id, String updatedBy) {
String sql = "UPDATE payment_tracking_details SET is_active = 1, is_delete = 0, remarks = 'Record restored', updated_by = ?, updated_date = ? WHERE id = ?";
try {
// Retrieve old record first to populate history
Map<String, Object> old = null;
try {
old = jdbcTemplate.queryForMap("SELECT * FROM payment_tracking_details WHERE id = ?", id);
} catch (Exception e) {
System.err.println("Could not find old record for history before restore: " + e.getMessage());
}
int rows = jdbcTemplate.update(sql, updatedBy, new java.sql.Timestamp(System.currentTimeMillis()), id);
if (rows > 0 && old != null) {
try {
String histSql = "INSERT INTO payment_tracking_details_history ("
+ "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, "
+ "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, "
+ "is_active, is_delete, order_by, file_path, cdate, updated_at, updated_by"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(histSql,
id,
old.get("received_date"),
old.get("vendor_name"),
old.get("subject"),
old.get("amount"),
old.get("invoice_no"),
old.get("receiptno"),
old.get("invoice_date"),
old.get("type"),
old.get("physical_file_no"),
"Record restored", // remarks
old.get("tat"),
old.get("status"),
old.get("followup"),
old.get("myfollowup"),
1, // is_active = 1
0, // is_delete = 0
old.get("order_by"),
old.get("file_path"),
old.get("cdate"),
new java.sql.Timestamp(System.currentTimeMillis()),
updatedBy);
} catch (Exception he) {
System.err.println("Error saving history during restore: " + he.getMessage());
}
}
return rows > 0;
} catch (Exception e) {
System.err.println("Error restoring payment tracking detail ID " + id + ": " + e.getMessage());
return false;
}
}
} }

29
src/main/java/com/gcc_manpower/service/AppUserService.java

@ -937,4 +937,33 @@ public class AppUserService {
} }
return false; return false;
} }
public List<Map<String, Object>> getDeletedWorkOrders() {
String sql = "SELECT " +
" w.id, " +
" w.designation, " +
" w.vid, " +
" COALESCE(v.vendor_name, w.contract_name) AS vendor_name, " +
" w.tender_manpower_designation_wise, " +
" w.available_manpower, " +
" w.vacant_count, " +
" w.File_no, " +
" w.subject, " +
" w.work_order_issue_date, " +
" w.work_order_end_date, " +
" w.updated_at, " +
" COALESCE(u.usr_name, w.updated_by) AS updated_by_name, " +
" w.file_path " +
"FROM employee_workorder_details w " +
"LEFT JOIN vendor_details v ON w.vid = CAST(v.id AS CHAR) " +
"LEFT JOIN app_user u ON w.updated_by = CAST(u.usr_id AS CHAR) " +
"WHERE w.isdelete = 1 " +
"ORDER BY w.updated_at DESC";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching deleted work orders: " + e.getMessage());
return new ArrayList<>();
}
}
} }

7
src/main/resources/application-local.properties

@ -11,13 +11,10 @@ server.port=8084
spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower
spring.datasource.username=root spring.datasource.username=root
spring.datasource.password=root spring.datasource.password=root
#spring.datasource.password=1916008_Saec
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# spring.datasource.url=jdbc:oracle:thin:@10.1.0.14:1521:chncorp
# spring.datasource.username=fileadjust
# spring.datasource.password=fileadjust
# spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# =============================== # ===============================
# JPA / Hibernate Config # JPA / Hibernate Config

6
src/main/resources/templates/fragments/navbar.html

@ -46,6 +46,12 @@
<i class="fa-solid fa-user-slash"></i> Deleted Employees <i class="fa-solid fa-user-slash"></i> Deleted Employees
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" th:href="@{/?action=deletedWorkOrders}"
onclick="if (document.getElementById('deletedWorkOrdersModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('deletedWorkOrdersModal')).show(); return false; }">
<i class="fa-solid fa-file-circle-xmark"></i> Deleted Work Orders
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" th:href="@{/?action=addVendorWorkOrder}" <a class="nav-link" th:href="@{/?action=addVendorWorkOrder}"
onclick="if (document.getElementById('addVendorWorkOrderModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addVendorWorkOrderModal')).show(); return false; }"> onclick="if (document.getElementById('addVendorWorkOrderModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addVendorWorkOrderModal')).show(); return false; }">

278
src/main/resources/templates/gccservicepaymenttracker/dashboard.html

@ -1278,6 +1278,12 @@
onclick="exportMainTableToExcel()"> onclick="exportMainTableToExcel()">
<i class="fa-solid fa-file-excel"></i> Export to Excel <i class="fa-solid fa-file-excel"></i> Export to Excel
</button> </button>
<button th:if="${isAdmin}"
class="btn btn-sm btn-danger fw-bold px-3 d-flex align-items-center gap-2"
style="border-radius: 8px; height: 36px; font-size: 0.85rem; margin: 0;"
onclick="openDeletedRecordsModal()">
<i class="fa-solid fa-trash-can"></i> Deleted Records
</button>
<button th:if="${isAdmin}" class="btn-add-record" onclick="openAddModal()" <button th:if="${isAdmin}" class="btn-add-record" onclick="openAddModal()"
style="margin: 0; height: 36px;"> style="margin: 0; height: 36px;">
<i class="fa-solid fa-circle-plus"></i> Add Payment Tracking Record <i class="fa-solid fa-circle-plus"></i> Add Payment Tracking Record
@ -1672,6 +1678,14 @@
<option value="all">- All Vendors -</option> <option value="all">- All Vendors -</option>
</select> </select>
</div> </div>
<div class="col-md-3">
<label class="form-label fw-bold text-secondary text-uppercase"
style="font-size: 0.75rem; letter-spacing: 0.5px;">Search:</label>
<input type="text" id="modal-table-search" class="form-control form-control-sm"
placeholder="Search in this table..." oninput="applyModalFilters()">
</div>
<div class="col-md-3"> <div class="col-md-3">
<button <button
class="btn btn-sm btn-success fw-bold px-3 d-inline-flex align-items-center gap-2 w-100 justify-content-center" class="btn btn-sm btn-success fw-bold px-3 d-inline-flex align-items-center gap-2 w-100 justify-content-center"
@ -1721,7 +1735,58 @@
</div> </div>
</div> </div>
<!-- Bootstrap & FontAwesome bundles -->
<!-- Deleted Records Modal -->
<div class="modal fade" id="deletedRecordsModal" tabindex="-1" aria-labelledby="deletedRecordsModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-custom-header" style="background-color: #dc3545;">
<h5 class="modal-custom-title" id="deletedRecordsModalLabel">
<i class="fa-solid fa-trash-can"></i> Deleted Payment Records
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body" style="padding: 1.5rem;">
<!-- Responsive Table Inside Modal -->
<div class="table-responsive"
style="max-height: 450px; overflow-y: auto; border: 1px solid #eef2f6;">
<table class="table grid-table table-striped table-hover align-middle mb-0"
id="deleted-tracking-table">
<thead class="table-light">
<tr>
<th class="text-center" style="width: 50px;">Sr. No.</th>
<th>Received Date</th>
<th>Invoice Date</th>
<th>Physical File No.</th>
<th>eFile No./ Receipt No.</th>
<th>Subject</th>
<th>Type (SD/ ASD)</th>
<th>Vendor Name</th>
<th>Amount</th>
<th>Age of Bill (Days)</th>
<th>Status</th>
<th>Deletion Remarks</th>
<th>Deleted Date</th>
<th>Deleted By</th>
<th class="text-center" style="width: 100px;">Actions</th>
</tr>
</thead>
<tbody id="deleted-tracking-tbody">
<!-- Populated dynamically via JS -->
</tbody>
</table>
<div id="deleted-no-records-found" class="empty-state text-center py-5" style="display: none;">
<i class="fa-solid fa-trash-arrow-up empty-icon"
style="font-size: 2.5rem; color: #cbd5e1; margin-bottom: 10px;"></i>
<h5>No Deleted Records</h5>
<p>No deleted payment tracking records found in the database.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom Page Scripting --> <!-- Custom Page Scripting -->
@ -1733,6 +1798,7 @@
let allRecords = []; let allRecords = [];
let recordModalObj = null; let recordModalObj = null;
let historyModalObj = null; let historyModalObj = null;
let deletedRecordsModalObj = null;
let currentPage = 1; let currentPage = 1;
let pageSize = 20; let pageSize = 20;
@ -1752,6 +1818,7 @@
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
recordModalObj = new bootstrap.Modal(document.getElementById('recordModal')); recordModalObj = new bootstrap.Modal(document.getElementById('recordModal'));
historyModalObj = new bootstrap.Modal(document.getElementById('historyModal')); historyModalObj = new bootstrap.Modal(document.getElementById('historyModal'));
deletedRecordsModalObj = new bootstrap.Modal(document.getElementById('deletedRecordsModal'));
// initDatePickers(); // initDatePickers();
await loadTypesAndVendors(); await loadTypesAndVendors();
loadCounts(); loadCounts();
@ -3139,6 +3206,8 @@
modalTypeSelect.value = 'all'; modalTypeSelect.value = 'all';
modalStatusSelect.value = 'all'; modalStatusSelect.value = 'all';
modalVendorSelect.value = 'all'; modalVendorSelect.value = 'all';
const modalSearchInput = document.getElementById('modal-table-search');
if (modalSearchInput) modalSearchInput.value = '';
// If click was category/type-based // If click was category/type-based
if (clickType === 'category' && clickValue) { if (clickType === 'category' && clickValue) {
@ -3220,6 +3289,8 @@
const selectedType = document.getElementById('modal-filter-type').value; const selectedType = document.getElementById('modal-filter-type').value;
const selectedStatus = document.getElementById('modal-filter-status').value; const selectedStatus = document.getElementById('modal-filter-status').value;
const selectedVendor = document.getElementById('modal-filter-vendor').value; const selectedVendor = document.getElementById('modal-filter-vendor').value;
const searchVal = document.getElementById('modal-table-search')?.value || '';
const query = searchVal.toLowerCase().trim();
const { clickType, clickValue, vendorName, col } = modalClickFilter; const { clickType, clickValue, vendorName, col } = modalClickFilter;
@ -3316,6 +3387,26 @@
if (rowVendor.toLowerCase() !== selectedVendor.toLowerCase()) return false; if (rowVendor.toLowerCase() !== selectedVendor.toLowerCase()) return false;
} }
// 3. Apply the modal search keyword
if (query) {
const vName = String(row.resolved_vendor || row.vendor_name || '').toLowerCase();
const tName = String(row.resolved_type || row.type || '').toLowerCase();
const statusName = String(row.resolved_status || getStatusName(row.status) || '').toLowerCase();
const matchesSearch = (
vName.includes(query) ||
tName.includes(query) ||
(row.subject && String(row.subject).toLowerCase().includes(query)) ||
(row.amount && String(row.amount).toLowerCase().includes(query)) ||
(row.invoice_no && String(row.invoice_no).toLowerCase().includes(query)) ||
(row.receiptno && String(row.receiptno).toLowerCase().includes(query)) ||
(row.physical_file_no && String(row.physical_file_no).toLowerCase().includes(query)) ||
(row.remarks && String(row.remarks).toLowerCase().includes(query)) ||
(row.tat && String(row.tat).toLowerCase().includes(query)) ||
statusName.includes(query)
);
if (!matchesSearch) return false;
}
return true; return true;
}); });
@ -3796,6 +3887,191 @@
}); });
} }
// --- DELETED RECORDS MODAL & RESTORE LOGIC ---
function openDeletedRecordsModal() {
if (!deletedRecordsModalObj) {
deletedRecordsModalObj = new bootstrap.Modal(document.getElementById('deletedRecordsModal'));
}
deletedRecordsModalObj.show();
showLoader(true, 'Fetching deleted records...');
fetch(contextPath + 'gccservicepaymenttracker/api/deleted-list?_=' + new Date().getTime())
.then(response => response.json())
.then(data => {
renderDeletedRecordsTable(data);
showLoader(false);
})
.catch(err => {
console.error("Fetch Deleted Records Error:", err);
alert("Failed to load deleted records.");
showLoader(false);
});
}
function renderDeletedRecordsTable(data) {
const tbody = document.getElementById('deleted-tracking-tbody');
const noRecords = document.getElementById('deleted-no-records-found');
tbody.innerHTML = '';
if (!data || data.length === 0) {
noRecords.style.display = 'block';
return;
}
noRecords.style.display = 'none';
const text = val => (val === null || val === undefined || String(val).trim() === '') ? '-' : val;
const formatDate = dateStr => {
if (!dateStr) return '-';
const parts = dateStr.split('-');
if (parts.length === 3) {
return `${parts[2]}/${parts[1]}/${parts[0]}`;
}
return dateStr;
};
const formatDateTime = dateTimeStr => {
if (!dateTimeStr) return '-';
try {
const d = new Date(dateTimeStr);
if (isNaN(d.getTime())) return dateTimeStr;
const pad = n => String(n).padStart(2, '0');
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
} catch (e) {
return dateTimeStr;
}
};
const getTypeBadge = typeVal => {
const cleanType = String(typeVal || '').trim().toLowerCase();
let label = cleanType;
let badgeClass = 'type-sd';
if (cleanType === 'asd' || cleanType.includes('asd') || cleanType.includes('additional security')) {
label = 'ASD';
badgeClass = 'type-asd';
} else if (cleanType.includes('agreement')) {
label = 'Agreement';
badgeClass = 'type-agreement';
} else if (cleanType === 'sd' || (cleanType.includes('security') && !cleanType.includes('additional') && !cleanType.includes('asd'))) {
label = 'Security Deposit';
badgeClass = 'type-sd';
} else if (cleanType.includes('invoice') || cleanType.includes('bill')) {
label = 'Tax Invoice';
badgeClass = 'type-tax-invoice';
} else if (cleanType === 'emd' || cleanType.includes('earnest')) {
label = 'EMD';
badgeClass = 'type-emd';
} else if (cleanType.includes('guarantee') || cleanType === 'bg') {
label = 'Bank Guarantee';
badgeClass = 'type-bank-guarantee';
} else if (cleanType.includes('chellan') || cleanType.includes('challan') || cleanType === 'dc') {
label = 'Delivery Chellan';
badgeClass = 'type-delivery-chellan';
} else {
const typeObj = activeTypes.find(t => String(t.id) === cleanType);
if (typeObj) {
return getTypeBadge(typeObj.type_name);
}
label = typeVal || '-';
badgeClass = 'status-default';
}
return `<span class="type-badge ${badgeClass}">${label}</span>`;
};
data.forEach((row, index) => {
const tr = document.createElement('tr');
const tdIndex = document.createElement('td');
tdIndex.className = 'text-center fw-semibold text-secondary';
tdIndex.textContent = index + 1;
tr.appendChild(tdIndex);
const dispStatus = String(row.resolved_status || getStatusName(row.status) || '').trim();
tr.innerHTML += `
<td>${formatDate(row.received_date)}</td>
<td>${formatDate(row.invoice_date)}</td>
<td>${text(row.physical_file_no)}</td>
<td>${text(row.receiptno)}</td>
<td>${text(row.subject)}</td>
<td>${getTypeBadge(row.resolved_type || row.type)}</td>
<td class="fw-semibold text-secondary">${text(row.resolved_vendor || row.vendor_name)}</td>
<td class="fw-semibold text-dark">&#8377; ${text(row.amount)}</td>
<td>${calculateAgeOfBill(row.received_date)}</td>
<td><span class="status-badge status-${dispStatus.toLowerCase().replace(/\s+/g, '-')}">${text(dispStatus)}</span></td>
<td><small class="text-danger fw-semibold">${text(row.remarks)}</small></td>
<td>${formatDateTime(row.updated_date)}</td>
<td class="fw-semibold text-secondary">${text(row.updated_by_name)}</td>
`;
// Actions cell
const tdActions = document.createElement('td');
tdActions.className = 'text-center';
tdActions.style.whiteSpace = 'nowrap';
// Restore Button
const btnRestore = document.createElement('button');
btnRestore.className = 'btn btn-sm btn-outline-warning d-inline-flex align-items-center gap-1';
btnRestore.innerHTML = '<i class="fa-solid fa-trash-arrow-up"></i> Restore';
btnRestore.style.fontSize = '0.72rem';
btnRestore.style.fontWeight = '600';
btnRestore.title = 'Restore Payment Record';
btnRestore.onclick = () => {
restoreDeletedRecord(row.id);
};
tdActions.appendChild(btnRestore);
tr.appendChild(tdActions);
tbody.appendChild(tr);
});
}
function restoreDeletedRecord(id) {
if (!confirm("Are you sure you want to restore this payment record?")) {
return;
}
if (deletedRecordsModalObj) {
deletedRecordsModalObj.hide();
}
showLoader(true, 'Restoring record...');
const csrfHeaders = getCsrfHeaders();
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
...csrfHeaders
};
const body = new URLSearchParams();
body.append('id', id);
fetch(contextPath + 'gccservicepaymenttracker/api/restore', {
method: 'POST',
headers: headers,
body: body
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Record successfully restored!');
loadCounts();
loadList();
} else {
alert(data.error || 'Failed to restore record.');
showLoader(false);
}
})
.catch(err => {
console.error("Restore Error:", err);
alert("Failed to restore record due to connection error.");
showLoader(false);
});
}
// Dynamic stacked modals manager to handle overlays and backdrops dynamically // Dynamic stacked modals manager to handle overlays and backdrops dynamically
document.addEventListener('show.bs.modal', function (event) { document.addEventListener('show.bs.modal', function (event) {
// Find currently shown modals // Find currently shown modals

238
src/main/resources/templates/home/home.html

@ -1303,6 +1303,70 @@
</div> </div>
</div> </div>
<!-- Deleted Work Orders Modal -->
<div class="modal fade" id="deletedWorkOrdersModal" tabindex="-1" aria-labelledby="deletedWorkOrdersModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content"
style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);">
<div class="modal-custom-header" style="background-color: #c0392b;">
<h5 class="modal-custom-title" id="deletedWorkOrdersModalLabel">
<i class="fa-solid fa-file-circle-xmark"></i> Deleted / Inactive Work Orders List
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Search and Export Toolbar -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-3 mb-4">
<div class="modal-search-box flex-grow-1 m-0">
<i class="fa-solid fa-magnifying-glass"></i>
<input type="text" id="deleted-work-order-search-input"
placeholder="Search deleted work orders by designation, subject, vendor name, file no..."
oninput="filterDeletedWorkOrders()">
</div>
<button class="export-btn text-nowrap" onclick="exportDeletedWorkOrdersToCSV()"
style="background-color: #c0392b;">
<i class="fa-solid fa-file-csv"></i> Export to CSV
</button>
</div>
<!-- Table of Deleted Work Orders -->
<div class="table-responsive"
style="max-height: 450px; border: 1px solid #eef2f6; border-radius: 8px;">
<table class="table table-hover modal-table" id="deleted-work-orders-table">
<thead>
<tr>
<th style="width: 60px;" class="text-center">S.No</th>
<th>Designation</th>
<th>Vendor Name</th>
<th>File No</th>
<th>Subject</th>
<th>Tender Manpower</th>
<th>Available Manpower</th>
<th>Vacant Count</th>
<th>Issue Date</th>
<th>End Date</th>
<th class="text-center" style="width: 150px;">Deleted Date</th>
<th class="text-center" style="width: 130px;">Deleted By</th>
<th class="text-center" style="width: 120px;">Download</th>
</tr>
</thead>
<tbody id="deleted-work-order-tbody">
<!-- Dynamically populated -->
</tbody>
</table>
<div id="no-deleted-work-orders-found" class="text-center text-muted py-5"
style="display: none;">
<i class="fa-solid fa-file-circle-xmark fs-1 mb-3 text-secondary"></i>
<p class="fw-bold mb-0">No deleted work orders found.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Include the quick-view-modal for product --> <!-- Include the quick-view-modal for product -->
<th:block th:replace="~{modals/login-modal :: login-modal}"></th:block> <th:block th:replace="~{modals/login-modal :: login-modal}"></th:block>
@ -3103,6 +3167,11 @@
if (modalEl) { if (modalEl) {
bootstrap.Modal.getOrCreateInstance(modalEl).show(); bootstrap.Modal.getOrCreateInstance(modalEl).show();
} }
} else if (action === 'deletedWorkOrders') {
const modalEl = document.getElementById('deletedWorkOrdersModal');
if (modalEl) {
bootstrap.Modal.getOrCreateInstance(modalEl).show();
}
} }
// Initialize Flatpickr datepickers for dd/mm/yyyy display format // Initialize Flatpickr datepickers for dd/mm/yyyy display format
@ -3201,6 +3270,12 @@
deletedModalEl.addEventListener("hidden.bs.modal", resetDeletedEmployeesModal); deletedModalEl.addEventListener("hidden.bs.modal", resetDeletedEmployeesModal);
} }
const deletedWOModalEl = document.getElementById("deletedWorkOrdersModal");
if (deletedWOModalEl) {
deletedWOModalEl.addEventListener("show.bs.modal", fetchAndShowDeletedWorkOrders);
deletedWOModalEl.addEventListener("hidden.bs.modal", resetDeletedWorkOrdersModal);
}
// Initialize designation dropdown options on load // Initialize designation dropdown options on load
updateDesignationDropdown(); updateDesignationDropdown();
filterVendorCards(); filterVendorCards();
@ -4024,6 +4099,169 @@
renderDeleteModalTable([]); renderDeleteModalTable([]);
showInitialMessage(); showInitialMessage();
} }
// --- DELETED WORK ORDERS JS LOGIC ---
let deletedWorkOrdersList = [];
let filteredDeletedWorkOrdersList = [];
function fetchAndShowDeletedWorkOrders() {
const tbody = document.getElementById("deleted-work-order-tbody");
tbody.innerHTML = `
<tr>
<td colspan="13" class="text-center py-5">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-secondary fw-semibold">Loading deleted work orders...</p>
</td>
</tr>
`;
document.getElementById("no-deleted-work-orders-found").style.display = "none";
document.getElementById("deleted-work-order-search-input").value = "";
fetch(contextPath + 'api/work-orders/deleted')
.then(res => res.json())
.then(data => {
deletedWorkOrdersList = data;
filterDeletedWorkOrders();
})
.catch(err => {
console.error("Error fetching deleted work orders:", err);
tbody.innerHTML = `
<tr>
<td colspan="13" class="text-center py-5 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i>
<p>Failed to connect to server and fetch database records. Please try again.</p>
</td>
</tr>
`;
});
}
function filterDeletedWorkOrders() {
const query = document.getElementById("deleted-work-order-search-input").value.toLowerCase().trim();
if (query === "") {
filteredDeletedWorkOrdersList = deletedWorkOrdersList;
} else {
filteredDeletedWorkOrdersList = deletedWorkOrdersList.filter(wo => {
return (wo.designation && wo.designation.toLowerCase().includes(query)) ||
(wo.vendor_name && wo.vendor_name.toLowerCase().includes(query)) ||
(wo.File_no && wo.File_no.toLowerCase().includes(query)) ||
(wo.subject && wo.subject.toLowerCase().includes(query)) ||
(wo.contract_name && wo.contract_name.toLowerCase().includes(query));
});
}
renderDeletedWorkOrdersTable(filteredDeletedWorkOrdersList);
}
function renderDeletedWorkOrdersTable(list) {
const tbody = document.getElementById("deleted-work-order-tbody");
const noWOEl = document.getElementById("no-deleted-work-orders-found");
tbody.innerHTML = "";
if (list.length === 0) {
noWOEl.style.display = "block";
return;
}
noWOEl.style.display = "none";
list.forEach((wo, index) => {
const tr = document.createElement("tr");
tr.style.backgroundColor = "#fdf2f2"; // light pink background for soft-deleted rows
let downloadBtn = "-";
if (wo.file_path) {
let url = wo.file_path;
if (contextPath && contextPath.endsWith('/') && url.startsWith('/')) {
url = contextPath + url.substring(1);
} else {
url = contextPath + url;
}
downloadBtn = `
<a href="${url}" target="_blank" class="btn btn-sm btn-outline-danger py-0 px-2 fw-semibold" style="font-size: 0.78rem;">
<i class="fa-solid fa-file-pdf text-danger"></i> PDF
</a>
`;
}
const text = val => (val === null || val === undefined || String(val).trim() === '') ? '-' : val;
const formatDate = dateStr => {
if (!dateStr) return '-';
const parts = dateStr.split('-');
if (parts.length === 3) {
if (parts[0].length === 4) {
return `${parts[2]}/${parts[1]}/${parts[0]}`;
}
}
return dateStr;
};
tr.innerHTML = `
<td class="text-center fw-bold text-secondary">${index + 1}</td>
<td class="fw-bold text-dark">${escapeHtml(wo.designation || '-')}</td>
<td class="fw-semibold text-secondary">${escapeHtml(wo.vendor_name || '-')}</td>
<td class="fw-semibold">${escapeHtml(wo.File_no || '-')}</td>
<td>${escapeHtml(wo.subject || '-')}</td>
<td class="text-center fw-bold">${text(wo.tender_manpower_designation_wise)}</td>
<td class="text-center">${text(wo.available_manpower)}</td>
<td class="text-center">${text(wo.vacant_count)}</td>
<td class="text-center">${formatDate(wo.work_order_issue_date)}</td>
<td class="text-center">${formatDate(wo.work_order_end_date)}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(formatDateTime(wo.updated_at))}</td>
<td class="text-center fw-semibold text-secondary">${escapeHtml(wo.updated_by_name || '-')}</td>
<td class="text-center">${downloadBtn}</td>
`;
tbody.appendChild(tr);
});
}
function resetDeletedWorkOrdersModal() {
document.getElementById("deleted-work-order-search-input").value = "";
document.getElementById("deleted-work-order-tbody").innerHTML = "";
document.getElementById("no-deleted-work-orders-found").style.display = "none";
deletedWorkOrdersList = [];
filteredDeletedWorkOrdersList = [];
}
function exportDeletedWorkOrdersToCSV() {
if (filteredDeletedWorkOrdersList.length === 0) {
alert("No work order records to export!");
return;
}
let csvContent = "data:text/csv;charset=utf-8,";
csvContent += "S.No,Designation,Vendor Name,File No,Subject,Tender Manpower,Available Manpower,Vacant Count,Issue Date,End Date,Deleted Date,Deleted By\n";
filteredDeletedWorkOrdersList.forEach((wo, index) => {
const row = [
index + 1,
`"${(wo.designation || '-').replace(/"/g, '""')}"`,
`"${(wo.vendor_name || '-').replace(/"/g, '""')}"`,
`"${(wo.File_no || '-').replace(/"/g, '""')}"`,
`"${(wo.subject || '-').replace(/"/g, '""')}"`,
wo.tender_manpower_designation_wise || '0',
wo.available_manpower || '0',
wo.vacant_count || '0',
wo.work_order_issue_date || '-',
wo.work_order_end_date || '-',
`"${formatDateTime(wo.updated_at)}"`,
`"${(wo.updated_by_name || '-').replace(/"/g, '""')}"`
];
csvContent += row.join(",") + "\n";
});
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "deleted_work_orders.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
</script> </script>
<script th:src="@{/assets/js/home-script.js}" type="text/javascript" defer></script> <script th:src="@{/assets/js/home-script.js}" type="text/javascript" defer></script>

Loading…
Cancel
Save