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 metadataFields = document.querySelectorAll('.editable-metadata'); 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(); } }); // Enable metadata fields (priority, category, type) metadataFields.forEach(field => { field.disabled = false; }); } else { saveTicket(); editButton.textContent = 'Edit Ticket'; editButton.classList.remove('active'); editables.forEach(field => { field.disabled = true; }); // Disable metadata fields metadataFields.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(); // Initialize metadata field handlers (priority, category, type) handleMetadataChanges(); }); /** * 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); }); }); } /** * Handle metadata field changes (priority, category, type) */ function handleMetadataChanges() { const prioritySelect = document.getElementById('prioritySelect'); const categorySelect = document.getElementById('categorySelect'); const typeSelect = document.getElementById('typeSelect'); // Helper function to update ticket field function updateTicketField(fieldName, newValue) { const ticketId = window.ticketData.id; fetch('/api/update_ticket.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket_id: ticketId, [fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue }) }) .then(response => response.json()) .then(data => { if (!data.success) { alert(`Error updating ${fieldName}`); console.error(data.error); } else { console.log(`${fieldName} updated successfully to:`, newValue); // Update window.ticketData window.ticketData[fieldName] = fieldName === 'priority' ? parseInt(newValue) : newValue; // For priority, also update the priority indicator if it exists if (fieldName === 'priority') { const priorityIndicator = document.querySelector('.priority-indicator'); if (priorityIndicator) { priorityIndicator.className = `priority-indicator priority-${newValue}`; priorityIndicator.textContent = 'P' + newValue; } // Update ticket container priority attribute const ticketContainer = document.querySelector('.ticket-container'); if (ticketContainer) { ticketContainer.setAttribute('data-priority', newValue); } } } }) .catch(error => { console.error(`Error updating ${fieldName}:`, error); alert(`Error updating ${fieldName}: ` + error.message); }); } // Priority change handler if (prioritySelect) { prioritySelect.addEventListener('change', function() { updateTicketField('priority', this.value); }); } // Category change handler if (categorySelect) { categorySelect.addEventListener('change', function() { updateTicketField('category', this.value); }); } // Type change handler if (typeSelect) { typeSelect.addEventListener('change', function() { updateTicketField('type', this.value); }); } } 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'); }