Files

236 lines
9.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 =>
`<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`
).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 `
<div class="comment ${isAdmin ? 'comment-admin' : ''}">
<img class="comment-avatar" src="${avatarUrl}" alt="${isAdmin ? 'Admin' : 'User'} Avatar">
<div class="comment-content">
<div class="comment-header">
<span class="comment-author">${escapeHtml(comment.author)}</span>
${isAdmin ? '<span class="comment-role-badge">Admin</span>' : ''}
<span class="comment-date">${formatDateTime(comment.date)}</span>
</div>
<div class="comment-text">${linkify(comment.content)}</div>
</div>
</div>`;
}).join('');
}
async function loadHistory() {
const history = await fetchHistory(ticketId);
const container = document.getElementById('history-container');
container.innerHTML = history.map(h => `
<div class="history-item">
<div class="history-icon"><i class="fas fa-${getHistoryIcon(h.action)}"></i></div>
<div class="history-content">
<div class="history-action">${escapeHtml(h.action)}</div>
<div class="history-details">${escapeHtml(h.message || '')}</div>
<div class="history-meta">
<span>By ${escapeHtml(h.author || h.actor || 'unknown')}</span>
<span>${formatDateTime(h.date || h.timestamp)}</span>
</div>
</div>
</div>`).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();
});