Browse Source

changes in payment dashboard page and service

master
yoganandhank 3 months ago
parent
commit
4c0ed35aeb
  1. 19
      src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java
  2. 230
      src/main/resources/templates/gccservicepaymenttracker/dashboard.html

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

@ -97,9 +97,9 @@ public class GccServicePaymentTrackerService {
+ "s.status_name as resolved_status, " + "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name " + "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details p " + "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 type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) "
+ "LEFT JOIN vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) "
+ "LEFT JOIN status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) "
+ "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) " + "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 = 1 AND p.is_delete = 0"); + "WHERE p.is_active = 1 AND p.is_delete = 0");
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
@ -434,9 +434,9 @@ public class GccServicePaymentTrackerService {
+ "s.status_name as resolved_status, " + "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name " + "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details_history h " + "FROM payment_tracking_details_history h "
+ "LEFT JOIN type_list t ON h.type REGEXP '^[0-9]+$' AND t.id = h.type "
+ "LEFT JOIN vendors_list v ON h.vendor_name REGEXP '^[0-9]+$' AND v.id = h.vendor_name "
+ "LEFT JOIN status_list s ON h.status REGEXP '^[0-9]+$' AND s.id = h.status "
+ "LEFT JOIN type_list t ON TRIM(h.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(h.type) AS UNSIGNED) "
+ "LEFT JOIN vendors_list v ON TRIM(h.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(h.vendor_name) AS UNSIGNED) "
+ "LEFT JOIN status_list s ON TRIM(h.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(h.status) AS UNSIGNED) "
+ "LEFT JOIN gcc_manpower.app_user u ON (h.updated_by = CAST(u.usr_id AS CHAR) OR h.updated_by = u.usr_name) " + "LEFT JOIN gcc_manpower.app_user u ON (h.updated_by = CAST(u.usr_id AS CHAR) OR h.updated_by = u.usr_name) "
+ "WHERE h.detail_id = ? " + "WHERE h.detail_id = ? "
+ "ORDER BY h.updated_at DESC"; + "ORDER BY h.updated_at DESC";
@ -456,9 +456,9 @@ public class GccServicePaymentTrackerService {
+ "s.status_name as resolved_status, " + "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name " + "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details p " + "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 type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) "
+ "LEFT JOIN vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) "
+ "LEFT JOIN status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) "
+ "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) " + "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 " + "WHERE p.is_active = 0 AND p.is_delete = 1 "
+ "ORDER BY p.updated_date DESC"; + "ORDER BY p.updated_date DESC";
@ -523,4 +523,5 @@ public class GccServicePaymentTrackerService {
return false; return false;
} }
} }
} }

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

@ -939,6 +939,19 @@
justify-content: center; justify-content: center;
} }
} }
/* Make modal-xl wider to fit multi-column data tables comfortably without squishing text */
@media (min-width: 1200px) {
.modal-xl {
max-width: 94% !important;
}
}
@media (min-width: 1600px) {
.modal-xl {
max-width: 1550px !important;
}
}
</style> </style>
</head> </head>
@ -1191,7 +1204,7 @@
<tr id="vendor-breakdown-thead-tr"> <tr id="vendor-breakdown-thead-tr">
<th style="text-align: left; background-color: #f8fafc;">Vendor Name</th> <th style="text-align: left; background-color: #f8fafc;">Vendor Name</th>
<th style="background-color: #f8fafc;">Total No of files</th> <th style="background-color: #f8fafc;">Total No of files</th>
<th style="background-color: #f0fdf4;">Completed</th>
<th style="background-color: #f0fdf4;">Completed Files</th>
<th style="background-color: #fffbeb;">Awaiting Details</th> <th style="background-color: #fffbeb;">Awaiting Details</th>
<th style="background-color: #f0f9ff;">Pending with Dept</th> <th style="background-color: #f0f9ff;">Pending with Dept</th>
<th style="background-color: #fef2f2;">Pending</th> <th style="background-color: #fef2f2;">Pending</th>
@ -1353,7 +1366,7 @@
<th>Physical File No.</th> <th>Physical File No.</th>
<th>eFile No./ Receipt No.</th> <th>eFile No./ Receipt No.</th>
<th>Subject</th> <th>Subject</th>
<th>Type (SD/ ASD)</th>
<th>Type of File</th>
<th>Vendor Name</th> <th>Vendor Name</th>
<th>Amount</th> <th>Amount</th>
<th>Age of Bill (Days)</th> <th>Age of Bill (Days)</th>
@ -1716,7 +1729,7 @@
<th>Physical File No.</th> <th>Physical File No.</th>
<th>eFile No./ Receipt No.</th> <th>eFile No./ Receipt No.</th>
<th>Subject</th> <th>Subject</th>
<th>Type (SD/ ASD)</th>
<th>Type of File</th>
<th>Vendor Name</th> <th>Vendor Name</th>
<th>Amount</th> <th>Amount</th>
<th>Age of Bill (Days)</th> <th>Age of Bill (Days)</th>
@ -1769,7 +1782,7 @@
<th>Physical File No.</th> <th>Physical File No.</th>
<th>eFile No./ Receipt No.</th> <th>eFile No./ Receipt No.</th>
<th>Subject</th> <th>Subject</th>
<th>Type (SD/ ASD)</th>
<th>Type of File</th>
<th>Vendor Name</th> <th>Vendor Name</th>
<th>Amount</th> <th>Amount</th>
<th>Age of Bill (Days)</th> <th>Age of Bill (Days)</th>
@ -1804,6 +1817,7 @@
let activeCategory = 'all'; let activeCategory = 'all';
let allRecords = []; let allRecords = [];
let currentFilteredRecords = [];
let recordModalObj = null; let recordModalObj = null;
let historyModalObj = null; let historyModalObj = null;
let deletedRecordsModalObj = null; let deletedRecordsModalObj = null;
@ -1864,7 +1878,17 @@
]); ]);
activeTypes = await typesRes.json(); activeTypes = await typesRes.json();
activeVendors = await vendorsRes.json(); activeVendors = await vendorsRes.json();
activeStatus = await statusRes.json();
const fetchedStatus = await statusRes.json();
// Sort activeStatus to have completed first, then pending, then others
const completedStatuses = fetchedStatus.filter(s => getStatusKey(s.status_name) === 'completed');
const pendingStatuses = fetchedStatus.filter(s => getStatusKey(s.status_name) === 'pending');
const otherStatuses = fetchedStatus.filter(s =>
getStatusKey(s.status_name) !== 'completed' &&
getStatusKey(s.status_name) !== 'pending'
);
activeStatus = [...completedStatuses, ...pendingStatuses, ...otherStatuses];
populateDropdowns(); populateDropdowns();
} catch (err) { } catch (err) {
console.error("Error loading types, vendors or statuses:", err); console.error("Error loading types, vendors or statuses:", err);
@ -1880,7 +1904,7 @@
if (name.includes('awaiting') || name.includes('details')) { if (name.includes('awaiting') || name.includes('details')) {
return 'awaiting details'; return 'awaiting details';
} }
if (name.includes('dept') || name.includes('uac') || name.includes('office')) {
if (name.includes('dept') || name.includes('uac') || name.includes('office') || name.includes('fm unit')) {
return 'pending with dept'; return 'pending with dept';
} }
if (name.includes('pending') || name.includes('initiated')) { if (name.includes('pending') || name.includes('initiated')) {
@ -2005,11 +2029,22 @@
// Dynamically populate vendor breakdown table headers // Dynamically populate vendor breakdown table headers
const vendorBreakdownTheadTr = document.getElementById('vendor-breakdown-thead-tr'); const vendorBreakdownTheadTr = document.getElementById('vendor-breakdown-thead-tr');
if (vendorBreakdownTheadTr) { if (vendorBreakdownTheadTr) {
const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed');
const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed');
let vendorHeadHtml = ` let vendorHeadHtml = `
<th style="text-align: left; background-color: #f8fafc;">Vendor Name</th> <th style="text-align: left; background-color: #f8fafc;">Vendor Name</th>
<th style="background-color: #f8fafc;">Total No of files</th> <th style="background-color: #f8fafc;">Total No of files</th>
`; `;
activeStatus.forEach(s => {
completedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name);
vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`;
});
// Custom Pending column header
vendorHeadHtml += `<th style="background-color: #fef2f2; color: #991b1b;">Pending files</th>`;
nonCompletedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name); const style = getStatusStyle(s.status_name);
vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`; vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`;
}); });
@ -2218,7 +2253,6 @@
if (activeClickFilter.col === 'completed' && status.toLowerCase() !== 'completed') return false; if (activeClickFilter.col === 'completed' && status.toLowerCase() !== 'completed') return false;
if (activeClickFilter.col === 'awaiting' && status.toLowerCase() !== 'awaiting details') return false; if (activeClickFilter.col === 'awaiting' && status.toLowerCase() !== 'awaiting details') return false;
if (activeClickFilter.col === 'pending_dept' && status.toLowerCase() !== 'pending with dept') return false; if (activeClickFilter.col === 'pending_dept' && status.toLowerCase() !== 'pending with dept') return false;
if (activeClickFilter.col === 'pending' && (status.toLowerCase() === 'completed' || status.toLowerCase() === 'awaiting details' || status.toLowerCase() === 'pending with dept')) return false;
if (activeClickFilter.col === 'age_1_15' && (age < 1 || age > 15)) return false; if (activeClickFilter.col === 'age_1_15' && (age < 1 || age > 15)) return false;
if (activeClickFilter.col === 'age_16_30' && (age < 16 || age > 30)) return false; if (activeClickFilter.col === 'age_16_30' && (age < 16 || age > 30)) return false;
@ -2249,6 +2283,8 @@
); );
}); });
currentFilteredRecords = filtered;
showLoader(false); showLoader(false);
const totalRecords = filtered.length; const totalRecords = filtered.length;
@ -3077,8 +3113,11 @@
const filtered = vendorBreakdownData.filter(v => v.vendorName.toLowerCase().includes(query)); const filtered = vendorBreakdownData.filter(v => v.vendorName.toLowerCase().includes(query));
const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed');
const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed');
if (filtered.length === 0) { if (filtered.length === 0) {
const colSpan = 2 + activeStatus.length + 6;
const colSpan = 2 + activeStatus.length + 1 + 6;
tbody.innerHTML = `<tr><td colspan="${colSpan}" class="text-secondary py-3">No matching vendors found</td></tr>`; tbody.innerHTML = `<tr><td colspan="${colSpan}" class="text-secondary py-3">No matching vendors found</td></tr>`;
return; return;
} }
@ -3116,7 +3155,21 @@
<td class="cursor-pointer text-primary fw-bold" onclick="openModalForCount('vendor_total', null, 'Vendor: ' + this.getAttribute('data-vendor'), this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${v.total}</td> <td class="cursor-pointer text-primary fw-bold" onclick="openModalForCount('vendor_total', null, 'Vendor: ' + this.getAttribute('data-vendor'), this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${v.total}</td>
`; `;
activeStatus.forEach(s => {
// 1. Completed statuses
let totalCompleted = 0;
completedStatuses.forEach(s => {
const count = v.statusCounts[s.id] || 0;
totalCompleted += count;
const style = getStatusStyle(s.status_name);
trHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('vendor_status', '${s.id}', 'Vendor: ' + this.getAttribute('data-vendor') + ' (${escapeHtml(s.status_name)})', this.getAttribute('data-vendor'), '${s.id}')" data-vendor="${escapeHtml(v.vendorName)}">${count}</td>`;
});
// 2. Custom "Pending" column
const pendingCount = v.total - totalCompleted;
trHtml += `<td class="cursor-pointer text-danger fw-bold" onclick="openModalForCount('vendor_pending_custom', null, 'Vendor: ' + this.getAttribute('data-vendor') + ' (Pending)', this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${pendingCount}</td>`;
// 3. Non-completed statuses
nonCompletedStatuses.forEach(s => {
const count = v.statusCounts[s.id] || 0; const count = v.statusCounts[s.id] || 0;
const style = getStatusStyle(s.status_name); const style = getStatusStyle(s.status_name);
trHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('vendor_status', '${s.id}', 'Vendor: ' + this.getAttribute('data-vendor') + ' (${escapeHtml(s.status_name)})', this.getAttribute('data-vendor'), '${s.id}')" data-vendor="${escapeHtml(v.vendorName)}">${count}</td>`; trHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('vendor_status', '${s.id}', 'Vendor: ' + this.getAttribute('data-vendor') + ' (${escapeHtml(s.status_name)})', this.getAttribute('data-vendor'), '${s.id}')" data-vendor="${escapeHtml(v.vendorName)}">${count}</td>`;
@ -3144,10 +3197,25 @@
<td class="text-start">TOTAL</td> <td class="text-start">TOTAL</td>
<td class="cursor-pointer text-primary fw-bold" onclick="openModalForCount('total', null, 'Total No of Files')">${sumTotal}</td> <td class="cursor-pointer text-primary fw-bold" onclick="openModalForCount('total', null, 'Total No of Files')">${sumTotal}</td>
`; `;
activeStatus.forEach(s => {
// 1. Completed statuses
let sumCompleted = 0;
completedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name); const style = getStatusStyle(s.status_name);
sumCompleted += sumStatuses[s.id];
totalTrHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('status', '${s.id}', 'Status: ' + this.getAttribute('data-status-name'))" data-status-name="${escapeHtml(s.status_name)}">${sumStatuses[s.id]}</td>`; totalTrHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('status', '${s.id}', 'Status: ' + this.getAttribute('data-status-name'))" data-status-name="${escapeHtml(s.status_name)}">${sumStatuses[s.id]}</td>`;
}); });
// 2. Custom "Pending" column
const sumPending = sumTotal - sumCompleted;
totalTrHtml += `<td class="cursor-pointer text-danger fw-bold" onclick="openModalForCount('pending_custom', null, 'Pending Files')">${sumPending}</td>`;
// 3. Non-completed statuses
nonCompletedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name);
totalTrHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('status', '${s.id}', 'Status: ' + this.getAttribute('data-status-name'))" data-status-name="${escapeHtml(s.status_name)}">${sumStatuses[s.id]}</td>`;
});
totalTrHtml += ` totalTrHtml += `
<td class="cursor-pointer" onclick="openModalForCount('age', [1, 15], 'Age: 1-15 Days')">${sumAge1_15}</td> <td class="cursor-pointer" onclick="openModalForCount('age', [1, 15], 'Age: 1-15 Days')">${sumAge1_15}</td>
<td class="cursor-pointer" onclick="openModalForCount('age', [16, 30], 'Age: 16-30 Days')">${sumAge16_30}</td> <td class="cursor-pointer" onclick="openModalForCount('age', [16, 30], 'Age: 16-30 Days')">${sumAge16_30}</td>
@ -3335,6 +3403,27 @@
} }
} }
if (clickType === 'vendor_pending_custom') {
const rowVendor = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim();
if (rowVendor !== vendorName) return false;
const rowStatusStr = String(row.status || '').trim();
const isCompleted = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed').some(s =>
rowStatusStr === String(s.id) ||
rowStatusStr.toLowerCase() === s.status_name.toLowerCase().trim()
);
if (isCompleted) return false;
}
if (clickType === 'pending_custom') {
const rowStatusStr = String(row.status || '').trim();
const isCompleted = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed').some(s =>
rowStatusStr === String(s.id) ||
rowStatusStr.toLowerCase() === s.status_name.toLowerCase().trim()
);
if (isCompleted) return false;
}
if (clickType === 'vendor_total') { if (clickType === 'vendor_total') {
const rowVendor = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim(); const rowVendor = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim();
if (rowVendor !== vendorName) return false; if (rowVendor !== vendorName) return false;
@ -3621,7 +3710,124 @@
} }
function exportMainTableToExcel() { function exportMainTableToExcel() {
exportTableToExcelHelper('tracking-table', 'Payment_Tracking_Details_Main_Report.xlsx');
if (!currentFilteredRecords || currentFilteredRecords.length === 0) {
alert("No records to export.");
return;
}
// Create a temporary table element in memory
const tempTable = document.createElement('table');
// 1. Create head
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th>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 of File</th>
<th>Vendor Name</th>
<th>Amount</th>
<th>Age of Bill (Days)</th>
<th>Status (Pending / Completed)</th>
<th>Remarks</th>
<th>Last Updated At</th>
<th>Last Updated By</th>
</tr>
`;
tempTable.appendChild(thead);
// Helpers
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())) {
const dd = String(d.getDate()).padStart(2, '0');
const MM = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const HH = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${dd}/${MM}/${yyyy} ${HH}:${mm}:${ss}`;
}
} catch (e) {
console.error(e);
}
return dateTimeStr;
};
const getTypeLabel = typeVal => {
const cleanType = String(typeVal || '').trim().toLowerCase();
let label = cleanType;
if (cleanType === 'asd' || cleanType.includes('asd') || cleanType.includes('additional security')) {
label = 'ASD';
} else if (cleanType.includes('agreement')) {
label = 'Agreement';
} else if (cleanType === 'sd' || (cleanType.includes('security') && !cleanType.includes('additional') && !cleanType.includes('asd'))) {
label = 'Security Deposit';
} else if (cleanType.includes('invoice') || cleanType.includes('bill')) {
label = 'Tax Invoice';
} else if (cleanType === 'emd' || cleanType.includes('earnest')) {
label = 'EMD';
} else if (cleanType.includes('guarantee') || cleanType === 'bg') {
label = 'Bank Guarantee';
} else if (cleanType.includes('chellan') || cleanType.includes('challan') || cleanType === 'dc') {
label = 'Delivery Chellan';
} else {
const typeObj = activeTypes.find(t => String(t.id) === cleanType);
if (typeObj) {
return typeObj.type_name;
}
label = typeVal || '-';
}
return label;
};
// 2. Create body
const tbody = document.createElement('tbody');
currentFilteredRecords.forEach((row, index) => {
const tr = document.createElement('tr');
const dispStatus = String(row.resolved_status || getStatusName(row.status) || '').trim();
tr.innerHTML = `
<td>${index + 1}</td>
<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>${getTypeLabel(row.resolved_type || row.type)}</td>
<td>${text(row.resolved_vendor || row.vendor_name)}</td>
<td>${text(row.amount)}</td>
<td>${calculateAgeOfBill(row.received_date)}</td>
<td>${text(dispStatus)}</td>
<td>${text(row.remarks)}</td>
<td>${formatDateTime(row.updated_date)}</td>
<td>${text(row.updated_by_name || '-')}</td>
`;
tbody.appendChild(tr);
});
tempTable.appendChild(tbody);
// Convert to Excel workbook and download
const wb = XLSX.utils.table_to_book(tempTable, { raw: true, sheet: "Sheet1" });
XLSX.writeFile(wb, 'Payment_Tracking_Details_Main_Report.xlsx');
} }
// --- LATEST UPDATES CORNER BADGE LOGIC --- // --- LATEST UPDATES CORNER BADGE LOGIC ---

Loading…
Cancel
Save