master-brain-api / files /admin.js
Dilip8756's picture
Upload 100 files
58c1398 verified
/**
* Admin Panel Logic - Payment Verification + Complaint Tickets
* Aadhaar Pro
*/
// ── State ──────────────────────────────────────────────────────────
let _allTickets = [];
let _activeFilter = 'all';
let _activeTicketId = null;
// ── Init ───────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
loadTransactions();
loadTickets(); // preload so badge count shows on page load
// If URL hash is #support, switch tab automatically
if (window.location.hash === '#support') switchTab('support');
});
// ══════════════════════════════════════════════════════════════════
// TAB SWITCHING
// ══════════════════════════════════════════════════════════════════
function switchTab(tab) {
// Update tab buttons
document.querySelectorAll('.admin-tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(`tab-btn-${tab}`)?.classList.add('active');
// Show/hide sections
document.querySelectorAll('.admin-section').forEach(s => s.classList.remove('active'));
document.getElementById(`section-${tab}`)?.classList.add('active');
// Update breadcrumb
const labels = { payments: 'Payment Queue', support: 'Complaints' };
const bc = document.getElementById('breadcrumb-active');
if (bc) bc.textContent = labels[tab] || tab;
// Load data for the activated tab
if (tab === 'support') loadTickets();
if (tab === 'payments') loadTransactions();
}
// ══════════════════════════════════════════════════════════════════
// PAYMENT VERIFICATION (existing logic preserved)
// ══════════════════════════════════════════════════════════════════
async function loadTransactions() {
const tableBody = document.getElementById('tx-table-body');
if (!tableBody) return;
try {
const res = await fetch('/api/admin/transactions');
const transactions = await res.json();
if (transactions.length === 0) {
tableBody.innerHTML = '<tr><td colspan="7" class="text-center padding-30">No transactions found.</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>
</div>
` : `<span class="text-muted-small"><i class="fa-solid fa-circle-check"></i> Complete</span>`;
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"><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('');
} 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 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 iconCont = 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';
iconCont.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';
iconCont.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); };
});
}
async function verifyTransaction(tx_id, action) {
const confirmed = await showConfirmModal(action);
if (!confirmed) return;
try {
const res = await fetch('/api/admin/verify', {
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.');
}
}
window.verifyTransaction = verifyTransaction;
// ══════════════════════════════════════════════════════════════════
// COMPLAINT / TICKET MANAGEMENT
// ══════════════════════════════════════════════════════════════════
async function loadTickets() {
try {
const res = await fetch('/api/admin/tickets');
_allTickets = await res.json();
renderTicketList();
updateTicketBadge();
} catch (err) {
console.error('Failed to load tickets:', err);
document.getElementById('ticket-items').innerHTML =
'<p style="padding:16px;color:var(--danger,#e74c3c);">Tickets load nahi hue.</p>';
}
}
function updateTicketBadge() {
const openCount = _allTickets.filter(t => t.status === 'open').length;
const badge = document.getElementById('tab-badge');
const navBadge = document.getElementById('open-ticket-count');
[badge, navBadge].forEach(el => {
if (!el) return;
if (openCount > 0) { el.style.display = 'inline'; el.textContent = openCount; }
else { el.style.display = 'none'; }
});
}
function filterTkts(filter, el) {
_activeFilter = filter;
_activeTicketId = null;
document.querySelectorAll('.tf-tab').forEach(t => t.classList.remove('on'));
el.classList.add('on');
renderTicketList();
// Reset detail panel
document.getElementById('ticket-detail-col').innerHTML =
'<div class="detail-empty">Koi ticket select karein</div>';
}
function renderTicketList() {
const container = document.getElementById('ticket-items');
if (!container) return;
const list = _activeFilter === 'all'
? _allTickets
: _allTickets.filter(t => t.status === _activeFilter);
if (list.length === 0) {
container.innerHTML = '<p style="padding:16px;color:var(--text-muted);font-size:0.85rem;">Koi ticket nahi mila.</p>';
return;
}
container.innerHTML = list.map(t => `
<div class="tkt-row${t.id === _activeTicketId ? ' active' : ''}" onclick="openTicket('${t.id}')">
<div class="tkt-row-top">
<span class="priority-dot p-${t.priority}"></span>
<span class="tkt-num">${t.ticket_number}</span>
<span class="tkt-name">${t.user_name}</span>
<span class="tkt-time">${t.date.split(',')[0]}</span>
</div>
<div class="tkt-row-bot">
<span class="tkt-subject">${t.subject}</span>
<span class="spill spill-${t.status}">${statusLabel(t.status)}</span>
</div>
</div>
`).join('');
}
async function openTicket(ticketId) {
_activeTicketId = ticketId;
renderTicketList(); // highlight active row
const col = document.getElementById('ticket-detail-col');
col.innerHTML = '<div class="detail-empty"><i class="fa-solid fa-spinner fa-spin"></i></div>';
try {
const res = await fetch(`/api/admin/tickets/${ticketId}`);
const ticket = await res.json();
if (ticket.error) { col.innerHTML = `<div class="detail-empty">${ticket.error}</div>`; return; }
renderTicketDetail(ticket);
} catch (err) {
col.innerHTML = '<div class="detail-empty">Load karne mein error aaya.</div>';
}
}
function renderTicketDetail(ticket) {
const col = document.getElementById('ticket-detail-col');
const isResolved = ticket.status === 'resolved' || ticket.status === 'closed';
const msgHtml = ticket.messages.map(m => `
<div class="chat-msg from-${m.sender}">
<div class="chat-bubble">${escapeHtml(m.message)}</div>
<div class="chat-meta">${m.sender === 'admin' ? 'Support Team' : ticket.user_name} Β· ${m.time}</div>
</div>
`).join('');
const replySection = isResolved ? `
<div class="reply-footer" style="font-size:0.85rem;color:var(--text-muted);text-align:center;padding:14px;">
<i class="fa-solid fa-circle-check" style="color:#27ae60;"></i> Yeh ticket resolve ho chuka hai.
</div>
` : `
<div class="reply-footer">
<textarea class="reply-textarea" id="admin-reply-input" rows="2"
placeholder="User ko reply likhein..."></textarea>
<div class="reply-actions">
<button class="ra-btn primary" onclick="sendAdminReply('${ticket.id}')">
<i class="fa-solid fa-paper-plane"></i> Reply
</button>
<button class="ra-btn resolve" onclick="updateTicketStatus('${ticket.id}','resolved')">
<i class="fa-solid fa-circle-check"></i> Resolved
</button>
<button class="ra-btn escalate" onclick="updateTicketStatus('${ticket.id}','open','high')">
<i class="fa-solid fa-circle-arrow-up"></i> Escalate
</button>
<button class="ra-btn" onclick="sendPromptAboutTicket('${ticket.id}')">
Wallet adjust β†—
</button>
</div>
</div>
`;
col.innerHTML = `
<div class="detail-head">
<div class="detail-head-top">
<span class="detail-head-num">${ticket.ticket_number}</span>
<span class="detail-head-name">${ticket.user_name}</span>
<span class="spill spill-${ticket.status}">${statusLabel(ticket.status)}</span>
</div>
<div class="detail-head-meta">
<span>${ticket.subject}</span>
<span class="cpill cpill-${ticket.category}">${categoryLabel(ticket.category)}</span>
<span class="priority-dot p-${ticket.priority}" title="${ticket.priority} priority"></span>
<span>${ticket.priority} priority</span>
<span>Β·</span>
<span>${ticket.user_phone}</span>
<span>Β·</span>
<span>${ticket.date}</span>
</div>
</div>
<div class="chat-window" id="chat-window-${ticket.id}">${msgHtml}</div>
${replySection}
`;
// Scroll chat to bottom
const cw = document.getElementById(`chat-window-${ticket.id}`);
if (cw) cw.scrollTop = cw.scrollHeight;
}
async function sendAdminReply(ticketId) {
const input = document.getElementById('admin-reply-input');
const msg = input?.value.trim();
if (!msg) { alert('Reply khali nahi ho sakta.'); return; }
try {
const res = await fetch(`/api/admin/tickets/${ticketId}/reply`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: msg })
});
const data = await res.json();
if (data.success) {
await loadTickets();
openTicket(ticketId);
} else {
alert(data.error || 'Reply send nahi hua.');
}
} catch (err) {
alert('Network error.');
}
}
async function updateTicketStatus(ticketId, status, priority) {
const payload = { status };
if (priority) payload.priority = priority;
try {
const res = await fetch(`/api/admin/tickets/${ticketId}/status`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.success) {
await loadTickets();
openTicket(ticketId);
} else {
alert(data.error || 'Status update nahi hua.');
}
} catch (err) {
alert('Network error.');
}
}
// ── Helpers ────────────────────────────────────────────────────────
function statusLabel(s) {
return { open: 'Open', pending: 'Pending', resolved: 'Resolved', closed: 'Closed' }[s] || s;
}
function categoryLabel(c) {
return { payment: 'Payment', wallet: 'Wallet', refund: 'Refund', other: 'Other' }[c] || c;
}
function escapeHtml(text) {
return String(text)
.replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function sendPromptAboutTicket(id) {
const t = _allTickets.find(x => x.id === id);
if (t) alert(`Wallet adjust ke liye: Admin > Wallet > User search karein (${t.user_name}, ${t.user_phone})`);
}
window.filterTkts = filterTkts;
window.openTicket = openTicket;
window.sendAdminReply = sendAdminReply;
window.updateTicketStatus = updateTicketStatus;
window.switchTab = switchTab;