Dilip8756's picture
Upload 100 files
58c1398 verified
/**
* Admin Panel - Support Tickets Logic (Premium Refactor)
*/
let allTickets = [];
let filteredTickets = [];
let activeTicketId = null;
async function updateTicketBadges() {
try {
const res = await fetch('/api/admin/tickets');
const tickets = await res.json();
const openCount = tickets.filter(t => t.status === 'open').length;
const badge = document.getElementById('open-tkt-badge');
const navBadge = document.getElementById('open-ticket-count');
if (badge) {
badge.innerText = `${openCount} Open`;
badge.style.display = 'inline-block';
}
if (navBadge) {
navBadge.innerText = openCount;
navBadge.style.display = openCount > 0 ? 'inline-block' : 'none';
}
} catch (e) {}
}
async function loadTickets() {
const container = document.getElementById('ticket-items');
if (!container) return;
container.innerHTML = `
<div class="loading-state" style="padding:40px;">
<i class="fa-solid fa-spinner fa-spin"></i>
<span>Loading Tickets...</span>
</div>`;
try {
const res = await fetch('/api/admin/tickets');
allTickets = await res.json();
applyTicketSearch(); // This will render the list
updateTicketBadges();
} catch (err) {
container.innerHTML = '<div class="error-state">Error loading tickets.</div>';
}
}
function applyTicketSearch() {
const searchInput = document.getElementById('tkt-search');
const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : '';
filteredTickets = allTickets.filter(t => {
const searchable = `${t.ticket_number} ${t.user_name} ${t.subject} ${t.status}`.toLowerCase();
return searchable.includes(searchTerm);
});
renderTicketList(filteredTickets);
}
function filterTkts(status, btn) {
// UI Update for Filter Buttons
document.querySelectorAll('.s-filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Filtering Logic
if (status === 'all') {
filteredTickets = allTickets;
} else {
filteredTickets = allTickets.filter(t => t.status === status);
}
renderTicketList(filteredTickets);
}
function renderTicketList(tickets) {
const container = document.getElementById('ticket-items');
if(!container) return;
if (tickets.length === 0) {
container.innerHTML = `
<div class="empty-state" style="padding:40px; opacity:0.6;">
<i class="fa-solid fa-inbox fa-2x"></i>
<p>No tickets found</p>
</div>`;
return;
}
container.innerHTML = tickets.map(t => {
const isActive = activeTicketId === t.id ? 'active' : '';
const initial = (t.user_name || 'U').charAt(0).toUpperCase();
const priorityColor = t.priority === 'high' ? '#ef4444' : (t.priority === 'medium' ? '#f59e0b' : '#3b82f6');
return `
<div class="tkt-card ${isActive}" onclick="selectTicket('${t.id}')">
<div class="tkt-meta-top">
<span class="tkt-id">#${escapeHtml(t.ticket_number)}</span>
<span class="tkt-date">${escapeHtml(t.date.split(',')[0])}</span>
</div>
<div class="tkt-user">
<div class="tkt-avatar" style="background:${priorityColor}">${initial}</div>
<div class="tkt-info">
<div class="tkt-username">${escapeHtml(t.user_name)}</div>
<div class="tkt-subject">${escapeHtml(t.subject)}</div>
</div>
</div>
<div class="tkt-badges">
<span class="status-pill status-${t.status.toLowerCase()}">${t.status.toUpperCase()}</span>
<span class="status-pill" style="background:rgba(0,0,0,0.05); color:var(--text-muted); font-size:0.65rem;">${t.category.toUpperCase()}</span>
</div>
</div>
`;
}).join('');
}
async function selectTicket(id) {
activeTicketId = id;
renderTicketList(filteredTickets); // Update active class
const workspace = document.getElementById('ticket-detail-col');
if(!workspace) return;
workspace.innerHTML = `
<div class="workspace-loading" style="height:100%; display:flex; align-items:center; justify-content:center; color:var(--text-muted);">
<i class="fa-solid fa-spinner fa-spin fa-2x"></i>
</div>`;
try {
const res = await fetch(`/api/admin/tickets/${id}`);
const t = await res.json();
workspace.innerHTML = `
<!-- Premium Profile Strip -->
<div class="chat-profile-strip">
<div class="chat-user-info">
<h3>${escapeHtml(t.subject)}</h3>
<div class="chat-user-meta">
<span><i class="fa-solid fa-user"></i> ${escapeHtml(t.user_name)}</span>
<span><i class="fa-solid fa-phone"></i> ${escapeHtml(t.user_phone)}</span>
<span><i class="fa-solid fa-hashtag"></i> ${escapeHtml(t.ticket_number)}</span>
</div>
</div>
<div class="chat-header-actions">
<span class="status-pill status-${t.status.toLowerCase()}">${t.status.toUpperCase()}</span>
</div>
</div>
<!-- Modern Chat Window -->
<div class="chat-window" id="chat-window">
${t.messages.map(m => `
<div class="message-bubble ${m.sender === 'admin' ? 'admin' : 'user'}">
<div class="bubble-content">${escapeHtml(m.message)}</div>
<div class="bubble-time">${escapeHtml(m.time)}</div>
</div>
`).join('')}
</div>
<!-- Premium Reply Footer -->
<div class="chat-footer">
<div class="reply-container">
<textarea class="reply-textarea" id="admin-reply-text" rows="3" placeholder="Type your reply here..."></textarea>
<div class="reply-actions-row">
<div class="left-actions">
<button class="status-btn resolve" onclick="updateStatus('${t.id}', 'resolved')" title="Mark as Resolved">
<i class="fa-solid fa-check-circle"></i> Resolve
</button>
<button class="status-btn pending" onclick="updateStatus('${t.id}', 'pending')" title="Mark as Pending">
<i class="fa-solid fa-clock"></i> Wait
</button>
<button class="status-btn close" onclick="updateStatus('${t.id}', 'closed')" title="Close Ticket">
<i class="fa-solid fa-times-circle"></i> Close
</button>
</div>
<button class="send-reply-btn" onclick="sendReply('${t.id}')">
<i class="fa-solid fa-paper-plane"></i> Send Reply
</button>
</div>
</div>
</div>
`;
// Auto-scroll to bottom
const chat = document.getElementById('chat-window');
if(chat) chat.scrollTop = chat.scrollHeight;
} catch (err) {
workspace.innerHTML = '<div class="workspace-error">Failed to load ticket details.</div>';
}
}
async function sendReply(id) {
const textInput = document.getElementById('admin-reply-text');
if(!textInput) return;
const text = textInput.value.trim();
if (!text) return;
const sendBtn = document.querySelector('.send-reply-btn');
if (sendBtn) {
sendBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Sending...';
sendBtn.disabled = true;
}
try {
const res = await fetch(`/api/admin/tickets/${id}/reply`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text })
});
if (res.ok) {
selectTicket(id); // Reload chat
}
} catch (e) {
alert('Error sending reply.');
}
}
async function updateStatus(id, status) {
try {
const res = await fetch(`/api/admin/tickets/${id}/status`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: status })
});
if (res.ok) {
loadTickets(); // Refresh sidebar list
selectTicket(id); // Refresh workspace view
}
} catch (e) {}
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}