Dilip8756's picture
Upload 100 files
58c1398 verified
/**
* Admin Panel - Payments Queue Logic
*/
let allTransactions = [];
let currentPaymentTab = 'pending';
let filteredTransactions = [];
let currentRejectionTxId = null;
async function loadTransactions() {
const tableBody = document.getElementById('tx-table-body');
if (!tableBody) return;
try {
const res = await fetch('/api/admin/transactions');
allTransactions = await res.json();
if (allTransactions.length === 0) {
tableBody.innerHTML = '<tr><td colspan="7" class="text-center padding-30">No transactions found.</td></tr>';
updatePaymentCounts();
return;
}
filterAndDisplayTransactions();
} catch (err) {
console.error('Failed to load transactions:', err);
tableBody.innerHTML = '<tr><td colspan="7" class="error-state">Failed to fetch transactions.</td></tr>';
}
}
function refreshPayments() {
loadTransactions();
if(typeof showToast === 'function') {
showToast('success', 'Payments Refreshed', 'Payment queue has been updated');
}
}
function switchPaymentTab(tabName) {
currentPaymentTab = tabName;
// Update tab buttons
document.querySelectorAll('.payment-tab-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(`tab-${tabName}`).classList.add('active');
// Filter and display transactions
filterAndDisplayTransactions();
}
function filterAndDisplayTransactions() {
// Apply tab filter
filteredTransactions = allTransactions.filter(tx => {
if (currentPaymentTab === 'pending') return tx.status === 'pending';
if (currentPaymentTab === 'approved') return tx.status === 'approved';
if (currentPaymentTab === 'rejected') return tx.status === 'rejected';
return true;
});
// Apply search filter
const searchInput = document.getElementById('payment-search');
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredTransactions = filteredTransactions.filter(tx =>
(tx.user || '').toLowerCase().includes(searchTerm) ||
(tx.phone || '').toLowerCase().includes(searchTerm) ||
(tx.utr || '').toLowerCase().includes(searchTerm)
);
}
// Apply amount filter
const filterSelect = document.getElementById('payment-filter');
const amountFilter = filterSelect ? filterSelect.value : 'all';
if (amountFilter !== 'all') {
filteredTransactions = filteredTransactions.filter(tx => {
const amount = parseFloat(tx.amount) || 0;
switch (amountFilter) {
case '0-500': return amount <= 500;
case '500-1000': return amount > 500 && amount <= 1000;
case '1000-5000': return amount > 1000 && amount <= 5000;
case '5000+': return amount > 5000;
default: return true;
}
});
}
// Apply sorting
const sortSelect = document.getElementById('payment-sort');
const sortBy = sortSelect ? sortSelect.value : 'recent';
filteredTransactions.sort((a, b) => {
switch (sortBy) {
case 'newest':
case 'recent': return new Date(b.date) - new Date(a.date);
case 'oldest': return new Date(a.date) - new Date(b.date);
case 'amount-high': return parseFloat(b.amount) - parseFloat(a.amount);
case 'amount-low': return parseFloat(a.amount) - parseFloat(b.amount);
default: return 0;
}
});
// Update counts
updatePaymentCounts();
// Display transactions
renderTransactions(filteredTransactions);
}
function updatePaymentCounts() {
const pendingCount = allTransactions.filter(t => t.status === 'pending').length;
const approvedCount = allTransactions.filter(t => t.status === 'approved').length;
const rejectedCount = allTransactions.filter(t => t.status === 'rejected').length;
const pendingEl = document.getElementById('pending-count');
const approvedEl = document.getElementById('approved-count');
const rejectedEl = document.getElementById('rejected-count');
if (pendingEl) {
pendingEl.textContent = pendingCount;
pendingEl.style.display = pendingCount > 0 ? 'inline-block' : 'none';
}
if (approvedEl) {
approvedEl.textContent = approvedCount;
approvedEl.style.display = approvedCount > 0 ? 'inline-block' : 'none';
}
if (rejectedEl) {
rejectedEl.textContent = rejectedCount;
rejectedEl.style.display = rejectedCount > 0 ? 'inline-block' : 'none';
}
}
function renderTransactions(transactions) {
const tableBody = document.getElementById('tx-table-body');
if (!tableBody) return;
if (transactions.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="7" class="text-center padding-30">
<i class="fa-solid fa-inbox" style="font-size:2rem; color:var(--text-muted); margin-bottom:10px;"></i>
<p style="color:var(--text-muted);">No ${currentPaymentTab} transactions found</p>
</td>
</tr>
`;
return;
}
tableBody.innerHTML = transactions.map(t => {
const statusClass = `status-${t.status.toLowerCase()}`;
const actions = t.status === 'pending' ? `
<div class="admin-actions">
<button onclick="verifyTransaction('${t.tx_id}', 'approve')" class="btn-verify btn-approve">
<i class="fa-solid fa-check"></i> Approve
</button>
<button onclick="verifyTransaction('${t.tx_id}', 'reject')" class="btn-verify btn-reject">
<i class="fa-solid fa-xmark"></i> Reject
</button>
<button onclick="showPaymentDetails('${t.tx_id}')" class="btn-verify btn-details">
<i class="fa-solid fa-eye"></i> Details
</button>
</div>
` : `
<div class="admin-actions">
<span class="text-muted-small"><i class="fa-solid fa-circle-check"></i> Complete</span>
<button onclick="showPaymentDetails('${t.tx_id}')" class="btn-verify btn-details">
<i class="fa-solid fa-eye"></i> View
</button>
</div>
`;
return `
<tr>
<td class="text-muted-small">${t.date}</td>
<td>
<div class="user-cell">
<strong>${t.user}</strong>
<small>${t.phone}</small>
</div>
</td>
<td class="balance-amount">₹${t.amount}</td>
<td><code>${t.utr}</code></td>
<td>
${t.screenshot ? `
<a href="${t.screenshot}" target="_blank" class="proof-link" onclick="event.stopPropagation();">
<i class="fa-solid fa-image"></i> View Proof
</a>
` : '<span class="text-muted-small">N/A</span>'}
</td>
<td>
<span class="status-pill ${statusClass}">${t.status.toUpperCase()}</span>
</td>
<td>${actions}</td>
</tr>
`;
}).join('');
}
async function showPaymentDetails(txId) {
const transaction = allTransactions.find(t => t.tx_id === txId);
if (!transaction) return;
const modal = document.getElementById('payment-details-modal');
const modalTitle = document.getElementById('modal-title');
const modalBody = document.getElementById('modal-body');
const modalActions = document.getElementById('modal-actions');
if(!modal || !modalTitle || !modalBody || !modalActions) return;
modalTitle.textContent = `Payment Details - ${transaction.utr}`;
let rejectionCommentHtml = '';
if (transaction.status === 'rejected' && transaction.rejection_comment) {
rejectionCommentHtml = `
<div style="margin-bottom:20px; padding:15px; background:rgba(255, 118, 117, 0.1); border-radius:8px; border-left:4px solid #ff7675;">
<strong style="color:#ff7675;">Rejection Reason:</strong><br>
<p style="margin:8px 0 0 0; color:var(--text-color);">${transaction.rejection_comment}</p>
</div>
`;
}
modalBody.innerHTML = `
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:20px;">
<div>
<strong>User:</strong><br>
${transaction.user}<br>
<small style="color:var(--text-muted);">${transaction.phone}</small>
</div>
<div>
<strong>Amount:</strong><br>
<span style="font-weight:700; color:var(--primary-color);">₹${transaction.amount}</span>
</div>
<div>
<strong>Status:</strong><br>
<span class="status-pill status-${transaction.status.toLowerCase()}">${transaction.status.toUpperCase()}</span>
</div>
<div>
<strong>Date:</strong><br>
${transaction.date}
</div>
</div>
${rejectionCommentHtml}
<div style="margin-bottom:20px;">
<strong>UTR Number:</strong><br>
<code style="background:var(--code-bg); padding:8px 12px; border-radius:6px; display:inline-block; margin-top:4px;">${transaction.utr}</code>
</div>
<div style="margin-bottom:20px;">
<strong>Screenshot Proof:</strong><br>
${transaction.screenshot ? `
<a href="${transaction.screenshot}" target="_blank" style="color:var(--primary-color); text-decoration:underline; margin-top:4px; display:inline-block;">
<i class="fa-solid fa-image"></i> View Payment Proof
</a>
` : '<span style="color:var(--text-muted);">No screenshot provided</span>'}
</div>
`;
if (transaction.status === 'pending') {
modalActions.innerHTML = `
<button onclick="verifyTransaction('${transaction.tx_id}', 'approve')" class="btn-verify btn-approve" style="padding:8px 16px;">
<i class="fa-solid fa-check"></i> Approve
</button>
<button onclick="verifyTransaction('${transaction.tx_id}', 'reject')" class="btn-verify btn-reject" style="padding:8px 16px;">
<i class="fa-solid fa-xmark"></i> Reject
</button>
`;
} else {
modalActions.innerHTML = '';
}
modal.style.display = 'flex';
}
function closePaymentModal() {
const modal = document.getElementById('payment-details-modal');
if (modal) modal.style.display = 'none';
}
function showConfirmModal(action) {
return new Promise((resolve) => {
const modal = document.getElementById('custom-confirm-modal');
const title = document.getElementById('modal-title');
const message = document.getElementById('modal-message');
const confirmBtn = document.getElementById('modal-confirm-btn');
const cancelBtn = document.getElementById('modal-cancel-btn');
const icon = document.getElementById('modal-icon');
const iconContainer = document.getElementById('modal-icon-container');
if (!modal) {
resolve(confirm(`Are you sure you want to ${action} this transaction?`));
return;
}
if (action === 'approve') {
title.innerText = 'Approve Payment?';
message.innerHTML = 'This will securely add the <br>requested funds to the user profile.';
icon.className = 'fa-solid fa-check-circle';
iconContainer.style.color = '#00b894';
confirmBtn.style.background = '#00b894';
confirmBtn.style.boxShadow = '0 10px 25px rgba(0, 184, 148, 0.3)';
confirmBtn.innerText = 'Yes, Approve';
} else {
title.innerText = 'Reject Payment?';
message.innerHTML = 'This request will be permanently<br> declined and user will not get funds.';
icon.className = 'fa-solid fa-triangle-exclamation';
iconContainer.style.color = '#ff7675';
confirmBtn.style.background = '#ff7675';
confirmBtn.style.boxShadow = '0 10px 25px rgba(255, 118, 117, 0.3)';
confirmBtn.innerText = 'Yes, Reject';
}
modal.style.display = 'flex';
const cleanup = () => {
confirmBtn.onclick = null;
cancelBtn.onclick = null;
modal.style.display = 'none';
};
confirmBtn.onclick = () => { cleanup(); resolve(true); };
cancelBtn.onclick = () => { cleanup(); resolve(false); };
});
}
function showRejectionCommentModal(txId) {
currentRejectionTxId = txId;
const modal = document.getElementById('rejection-comment-modal');
const commentInput = document.getElementById('rejection-comment');
if(modal) modal.style.display = 'flex';
if(commentInput) {
commentInput.value = '';
commentInput.focus();
}
}
function closeRejectionModal() {
const modal = document.getElementById('rejection-comment-modal');
if (modal) modal.style.display = 'none';
currentRejectionTxId = null;
}
async function confirmRejectionWithComment() {
const commentInput = document.getElementById('rejection-comment');
const errorElement = document.getElementById('rejection-error');
if(!commentInput || !errorElement) return;
const comment = commentInput.value.trim();
if (!comment) {
errorElement.style.display = 'block';
commentInput.style.borderColor = '#ff7675';
commentInput.style.boxShadow = '0 0 0 3px rgba(255, 118, 117, 0.1)';
commentInput.classList.add('shake-animation');
setTimeout(() => commentInput.classList.remove('shake-animation'), 500);
commentInput.focus();
return;
}
if (!currentRejectionTxId) {
alert('No transaction selected for rejection.');
return;
}
errorElement.style.display = 'none';
commentInput.style.borderColor = 'var(--border-color)';
commentInput.style.boxShadow = 'none';
try {
const res = await fetch('/api/admin/verify-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tx_id: currentRejectionTxId,
action: 'reject',
comment: comment
})
});
const data = await res.json();
if (data.success) {
closeRejectionModal();
loadTransactions();
if(typeof showToast === 'function') {
showToast('success', 'Payment Rejected', 'Payment has been rejected with your comment.');
}
} else {
alert(data.error || 'Rejection failed');
}
} catch (err) {
console.error('Rejection error:', err);
alert('A network error occurred.');
}
}
async function verifyTransaction(tx_id, action) {
if (action === 'reject') {
showRejectionCommentModal(tx_id);
return;
}
const isConfirmed = await showConfirmModal(action);
if (!isConfirmed) return;
try {
const res = await fetch('/api/admin/verify-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tx_id, action })
});
const data = await res.json();
if (data.success) {
loadTransactions();
} else {
alert(data.error || 'Verification failed');
}
} catch (err) {
console.error('Verification error:', err);
alert('A network error occurred.');
}
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('payment-search');
const filterSelect = document.getElementById('payment-filter');
const sortSelect = document.getElementById('payment-sort');
if (searchInput) searchInput.addEventListener('input', debounce(filterAndDisplayTransactions, 300));
if (filterSelect) filterSelect.addEventListener('change', filterAndDisplayTransactions);
if (sortSelect) sortSelect.addEventListener('change', filterAndDisplayTransactions);
});