function saveTicket() { const editables = document.querySelectorAll('.editable'); const data = {}; // Extract ticket ID from URL (works with both old and new URL formats) let ticketId; if (window.location.href.includes('?id=')) { ticketId = window.location.href.split('id=')[1]; } else { const matches = window.location.pathname.match(/\/ticket\/(\d+)/); ticketId = matches ? matches[1] : null; } if (!ticketId) { console.error('Could not determine ticket ID'); return; } editables.forEach(field => { if (field.dataset.field) { data[field.dataset.field] = field.value; } }); // Use the correct API path const apiUrl = '/api/update_ticket.php'; fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_id: ticketId, ...data }) }) .then(response => { if (!response.ok) { return response.text().then(text => { console.error('Server response:', text); throw new Error('Network response was not ok'); }); } return response.json(); }) .then(data => { if(data.success) { const statusDisplay = document.getElementById('statusDisplay'); if (statusDisplay) { statusDisplay.className = `status-${data.status}`; statusDisplay.textContent = data.status; } console.log('Ticket updated successfully'); } else { console.error('Error in API response:', data.error || 'Unknown error'); } }) .catch(error => { console.error('Error updating ticket:', error); }); } function toggleEditMode() { const editButton = document.getElementById('editButton'); const editables = document.querySelectorAll('.title-input, textarea[data-field="description"]'); const isEditing = editButton.classList.contains('active'); if (!isEditing) { editButton.textContent = 'Save Changes'; editButton.classList.add('active'); editables.forEach(field => { field.disabled = false; if (field.classList.contains('title-input')) { field.focus(); } }); } else { saveTicket(); editButton.textContent = 'Edit Ticket'; editButton.classList.remove('active'); editables.forEach(field => { field.disabled = true; }); } } function addComment() { const commentText = document.getElementById('newComment').value; if (!commentText.trim()) { console.error('Comment text cannot be empty'); return; } // Extract ticket ID from URL (works with both old and new URL formats) let ticketId; if (window.location.href.includes('?id=')) { ticketId = window.location.href.split('id=')[1]; } else { const matches = window.location.pathname.match(/\/ticket\/(\d+)/); ticketId = matches ? matches[1] : null; } if (!ticketId) { console.error('Could not determine ticket ID'); return; } const isMarkdownEnabled = document.getElementById('markdownMaster').checked; fetch('/api/add_comment.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_id: ticketId, comment_text: commentText, markdown_enabled: isMarkdownEnabled }) }) .then(response => { if (!response.ok) { return response.text().then(text => { console.error('Server response:', text); throw new Error('Network response was not ok'); }); } return response.json(); }) .then(data => { if(data.success) { // Clear the comment box document.getElementById('newComment').value = ''; // Format the comment text for display let displayText; if (isMarkdownEnabled) { // For markdown, use marked.parse displayText = marked.parse(commentText); } else { // For non-markdown, convert line breaks to
and escape HTML displayText = commentText .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\n/g, '
'); } // Add new comment to the list const commentsList = document.querySelector('.comments-list'); const newComment = `
${data.user_name} ${data.created_at}
${displayText}
`; commentsList.insertAdjacentHTML('afterbegin', newComment); } else { console.error('Error adding comment:', data.error || 'Unknown error'); } }) .catch(error => { console.error('Error adding comment:', error); }); } function togglePreview() { const preview = document.getElementById('markdownPreview'); const textarea = document.getElementById('newComment'); const isPreviewEnabled = document.getElementById('markdownToggle').checked; preview.style.display = isPreviewEnabled ? 'block' : 'none'; if (isPreviewEnabled) { preview.innerHTML = marked.parse(textarea.value); textarea.addEventListener('input', updatePreview); } else { textarea.removeEventListener('input', updatePreview); } } function updatePreview() { const commentText = document.getElementById('newComment').value; const previewDiv = document.getElementById('markdownPreview'); const isMarkdownEnabled = document.getElementById('markdownMaster').checked; if (isMarkdownEnabled && commentText.trim()) { // For markdown preview, use marked.parse which handles line breaks correctly previewDiv.innerHTML = marked.parse(commentText); previewDiv.style.display = 'block'; } else { previewDiv.style.display = 'none'; } } function toggleMarkdownMode() { const previewToggle = document.getElementById('markdownToggle'); const isMasterEnabled = document.getElementById('markdownMaster').checked; previewToggle.disabled = !isMasterEnabled; if (!isMasterEnabled) { previewToggle.checked = false; document.getElementById('markdownPreview').style.display = 'none'; } } document.addEventListener('DOMContentLoaded', function() { // Show description tab by default showTab('description'); // Auto-resize the description textarea to fit content const descriptionTextarea = document.querySelector('textarea[data-field="description"]'); if (descriptionTextarea) { function autoResizeTextarea() { // Reset height to auto to get the correct scrollHeight descriptionTextarea.style.height = 'auto'; // Set the height to match the scrollHeight descriptionTextarea.style.height = descriptionTextarea.scrollHeight + 'px'; } // Initial resize autoResizeTextarea(); // Resize on input when in edit mode descriptionTextarea.addEventListener('input', autoResizeTextarea); } // Initialize assignment handling handleAssignmentChange(); }); /** * Handle ticket assignment dropdown changes */ function handleAssignmentChange() { const assignedToSelect = document.getElementById('assignedToSelect'); if (!assignedToSelect) return; assignedToSelect.addEventListener('change', function() { const ticketId = window.ticketData.id; const assignedTo = this.value || null; fetch('/api/assign_ticket.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo }) }) .then(response => response.json()) .then(data => { if (!data.success) { alert('Error updating assignment'); console.error(data.error); } else { console.log('Assignment updated successfully'); } }) .catch(error => { console.error('Error updating assignment:', error); alert('Error updating assignment: ' + error.message); }); }); } function updateTicketStatus() { const statusSelect = document.getElementById('statusSelect'); const selectedOption = statusSelect.options[statusSelect.selectedIndex]; const newStatus = selectedOption.value; const requiresComment = selectedOption.dataset.requiresComment === '1'; const requiresAdmin = selectedOption.dataset.requiresAdmin === '1'; // Check if transitioning to the same status (current) if (selectedOption.text.includes('(current)')) { return; // No change needed } // Warn if comment is required if (requiresComment) { const proceed = confirm(`This status change requires a comment. Please add a comment explaining the reason for this transition.\n\nProceed with status change to "${newStatus}"?`); if (!proceed) { // Reset to current status statusSelect.selectedIndex = 0; return; } } // Extract ticket ID let ticketId; if (window.location.href.includes('?id=')) { ticketId = window.location.href.split('id=')[1]; } else { const matches = window.location.pathname.match(/\/ticket\/(\d+)/); ticketId = matches ? matches[1] : null; } if (!ticketId) { console.error('Could not determine ticket ID'); statusSelect.selectedIndex = 0; return; } // Update status via API fetch('/api/update_ticket.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_id: ticketId, status: newStatus }) }) .then(response => { if (!response.ok) { return response.text().then(text => { console.error('Server response:', text); throw new Error('Network response was not ok'); }); } return response.json(); }) .then(data => { if (data.success) { // Update the dropdown to show new status as current const newClass = 'status-' + newStatus.toLowerCase().replace(/ /g, '-'); statusSelect.className = 'editable status-select ' + newClass; // Update the selected option text to show as current selectedOption.text = newStatus + ' (current)'; // Move the selected option to the top statusSelect.remove(statusSelect.selectedIndex); statusSelect.insertBefore(selectedOption, statusSelect.firstChild); statusSelect.selectedIndex = 0; console.log('Status updated successfully to:', newStatus); // Reload page to refresh activity timeline setTimeout(() => { window.location.reload(); }, 500); } else { console.error('Error updating status:', data.error || 'Unknown error'); alert('Error updating status: ' + (data.error || 'Unknown error')); // Reset to current status statusSelect.selectedIndex = 0; } }) .catch(error => { console.error('Error updating status:', error); alert('Error updating status: ' + error.message); // Reset to current status statusSelect.selectedIndex = 0; }); } function showTab(tabName) { // Hide all tab contents const descriptionTab = document.getElementById('description-tab'); const commentsTab = document.getElementById('comments-tab'); const activityTab = document.getElementById('activity-tab'); if (!descriptionTab || !commentsTab) { console.error('Tab elements not found'); return; } // Hide all tabs descriptionTab.style.display = 'none'; commentsTab.style.display = 'none'; if (activityTab) { activityTab.style.display = 'none'; } // Remove active class from all buttons document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); // Show selected tab and activate its button document.getElementById(`${tabName}-tab`).style.display = 'block'; document.querySelector(`[onclick="showTab('${tabName}')"]`).classList.add('active'); }