import { fetchTicket, fetchComments, fetchHistory, createComment, logTime, formatDateTime, formatMinutes, getHistoryIcon, escapeHtml, linkify } from './ticket-shared.js'; document.addEventListener('DOMContentLoaded', async () => { const ticketId = new URLSearchParams(window.location.search).get('id'); if (!ticketId) return (window.location.href = '../admin.html'); const statusSelect = document.getElementById('status-select'); const priorityInput = document.getElementById('priority-text'); const categorySelect = document.getElementById('category-select'); const commentForm = document.getElementById('comment-form'); const titleText = document.getElementById('ticket-title-text'); const titleInput = document.getElementById('ticket-title-input'); const descText = document.getElementById('ticket-description-text'); const descInput = document.getElementById('ticket-description-input'); const commentAuthorInput = document.getElementById('comment-author'); const totalTimeDisplay = document.getElementById('total-time'); const timeForm = document.getElementById('time-booking-form'); let originalStatus = '', originalPriority = '', originalTitle = '', originalDesc = '', originalCategory = ''; // Load categories dynamically async function loadCategories() { try { const res = await fetch('/api/categories'); const categories = await res.json(); if (categorySelect) { categorySelect.innerHTML = categories.map(c => `` ).join(''); } } catch (err) { console.error('Failed to load categories:', err); } } async function loadPage() { await loadCategories(); await Promise.all([loadTicketDetails(), loadComments(), loadHistory()]); } async function loadTicketDetails() { const ticket = await fetchTicket(ticketId); if (!ticket) return alert('Ticket not found'); titleText.textContent = ticket.title; descText.textContent = ticket.description; priorityInput.value = ticket.priority; statusSelect.value = ticket.status; categorySelect.value = ticket.category || ''; document.getElementById('ticket-id-val').textContent = ticket.id; document.getElementById('ticket-created').textContent = formatDateTime(ticket.created_at); document.getElementById('ticket-updated').textContent = formatDateTime(ticket.updated_at); totalTimeDisplay.textContent = formatMinutes(typeof ticket.total_time === 'number' ? ticket.total_time : 0); statusSelect.className = 'form-control status-' + (ticket.status || '').toLowerCase().replace(/\s+/g, '-'); originalStatus = ticket.status; originalPriority = ticket.priority; originalTitle = ticket.title; originalDesc = ticket.description; originalCategory = ticket.category || ''; } async function updateTicket({ status, priority, title, description, category, actor = 'Admin' }) { try { const ticket = await fetchTicket(ticketId); const requester = ticket.requester || ''; const res = await fetch(`/api/tickets/${ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status, priority, title, description, category, author: actor, requester }), }); if (!res.ok) throw new Error('Failed to update'); // Webhooks are handled server-side now — no need to send from here alert('Ticket updated successfully'); titleText.textContent = title; descText.textContent = description; await loadPage(); } catch (err) { console.error('Update failed:', err); alert('Error updating ticket'); } } document.getElementById('update-ticket-btn').addEventListener('click', async () => { const isEditingTitle = titleInput.style.display === 'block'; const isEditingDesc = descInput.style.display === 'block'; const newTitle = isEditingTitle ? titleInput.value.trim() : titleText.textContent; const newDesc = isEditingDesc ? descInput.value.trim() : descText.textContent; if (!confirm('Save changes?')) return; await updateTicket({ status: statusSelect.value, priority: priorityInput.value, title: newTitle, description: newDesc, category: categorySelect.value, }); titleInput.style.display = 'none'; titleText.style.display = 'block'; descInput.style.display = 'none'; descText.style.display = 'block'; }); document.getElementById('close-ticket-btn').addEventListener('click', async () => { if (!confirm('Close this ticket?')) return; await updateTicket({ status: 'Closed', priority: priorityInput.value, title: titleText.textContent, description: descText.textContent, category: categorySelect.value, }); }); titleText.addEventListener('dblclick', () => { titleInput.value = titleText.textContent; titleText.style.display = 'none'; titleInput.style.display = 'block'; titleInput.focus(); }); descText.addEventListener('dblclick', () => { descInput.value = descText.textContent; descText.style.display = 'none'; descInput.style.display = 'block'; descInput.focus(); }); // Restore saved author name const savedAuthor = localStorage.getItem('commentAuthor'); if (savedAuthor) commentAuthorInput.value = savedAuthor; async function loadComments() { const comments = await fetchComments(ticketId); const container = document.getElementById('comments-container'); container.innerHTML = comments.map(comment => { const role = comment.role || 'user'; const isAdmin = role === 'admin'; const avatarUrl = isAdmin ? '/assets/admin.jpg' : '/assets/user.jpg'; return `
${isAdmin ? 'Admin' : 'User'} Avatar
${escapeHtml(comment.author)} ${isAdmin ? 'Admin' : ''} ${formatDateTime(comment.date)}
${linkify(comment.content)}
`; }).join(''); } async function loadHistory() { const history = await fetchHistory(ticketId); const container = document.getElementById('history-container'); container.innerHTML = history.map(h => `
${escapeHtml(h.action)}
${escapeHtml(h.message || '')}
By ${escapeHtml(h.author || h.actor || 'unknown')} ${formatDateTime(h.date || h.timestamp)}
`).join(''); } commentForm.addEventListener('submit', async (e) => { e.preventDefault(); const author = commentAuthorInput.value.trim(); if (!author) return alert('Please enter your name'); const content = document.getElementById('comment-content').value.trim(); if (!content) return alert('Comment required'); try { await createComment(ticketId, { author, role: 'admin', content, timestamp: new Date().toISOString() }); localStorage.setItem('commentAuthor', author); document.getElementById('comment-content').value = ''; await loadComments(); await loadHistory(); } catch (err) { console.error('Failed to add comment:', err); alert('Failed to add comment'); } }); timeForm.addEventListener('submit', async (e) => { e.preventDefault(); const minutes = parseInt(document.getElementById('time-minutes').value); const comment = document.getElementById('time-comment').value.trim(); const actor = commentAuthorInput.value || 'Admin'; if (isNaN(minutes) || minutes <= 0) return alert('Enter valid minutes'); try { await logTime(ticketId, { minutes, comment, author: actor }); const timeComment = `⏱ Time booked: ${formatMinutes(minutes)}${comment ? ` – ${comment}` : ''}`; await createComment(ticketId, { author: actor, role: 'admin', content: timeComment, timestamp: new Date().toISOString() }); document.getElementById('time-minutes').value = ''; document.getElementById('time-comment').value = ''; await loadPage(); } catch (err) { console.error('Error saving time:', err); alert('Error saving time'); } }); // Tabs document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.getElementById(`${btn.dataset.tab}-tab`).classList.add('active'); }); }); await loadPage(); });