2024-11-30 20:26:30 -05:00
|
|
|
function saveTicket() {
|
|
|
|
|
const editables = document.querySelectorAll('.editable');
|
|
|
|
|
const data = {};
|
2025-05-16 20:02:49 -04:00
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2024-11-30 20:26:30 -05:00
|
|
|
|
|
|
|
|
editables.forEach(field => {
|
|
|
|
|
if (field.dataset.field) {
|
2026-01-08 22:40:26 -05:00
|
|
|
// For contenteditable divs, use textContent/innerText; for inputs/textareas, use value
|
|
|
|
|
if (field.hasAttribute('contenteditable')) {
|
|
|
|
|
data[field.dataset.field] = field.textContent.trim();
|
|
|
|
|
} else {
|
|
|
|
|
data[field.dataset.field] = field.value;
|
|
|
|
|
}
|
2024-11-30 20:26:30 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-16 20:02:49 -04:00
|
|
|
// Use the correct API path
|
|
|
|
|
const apiUrl = '/api/update_ticket.php';
|
|
|
|
|
|
|
|
|
|
fetch(apiUrl, {
|
2024-11-30 20:26:30 -05:00
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
2026-01-09 16:13:13 -05:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': window.CSRF_TOKEN
|
2024-11-30 20:26:30 -05:00
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
ticket_id: ticketId,
|
|
|
|
|
...data
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-05-16 20:02:49 -04:00
|
|
|
.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();
|
|
|
|
|
})
|
2024-11-30 20:26:30 -05:00
|
|
|
.then(data => {
|
|
|
|
|
if(data.success) {
|
|
|
|
|
const statusDisplay = document.getElementById('statusDisplay');
|
2025-05-16 20:02:49 -04:00
|
|
|
if (statusDisplay) {
|
|
|
|
|
statusDisplay.className = `status-${data.status}`;
|
|
|
|
|
statusDisplay.textContent = data.status;
|
|
|
|
|
}
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.success('Ticket updated successfully');
|
2025-05-16 20:02:49 -04:00
|
|
|
} else {
|
|
|
|
|
console.error('Error in API response:', data.error || 'Unknown error');
|
2024-11-30 20:26:30 -05:00
|
|
|
}
|
2025-05-16 20:02:49 -04:00
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error updating ticket:', error);
|
2024-11-30 20:26:30 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleEditMode() {
|
|
|
|
|
const editButton = document.getElementById('editButton');
|
2026-01-08 22:40:26 -05:00
|
|
|
const titleField = document.querySelector('.title-input');
|
|
|
|
|
const descriptionField = document.querySelector('textarea[data-field="description"]');
|
2026-01-07 18:49:44 -05:00
|
|
|
const metadataFields = document.querySelectorAll('.editable-metadata');
|
2024-11-30 20:26:30 -05:00
|
|
|
const isEditing = editButton.classList.contains('active');
|
|
|
|
|
|
|
|
|
|
if (!isEditing) {
|
|
|
|
|
editButton.textContent = 'Save Changes';
|
|
|
|
|
editButton.classList.add('active');
|
2026-01-08 22:40:26 -05:00
|
|
|
|
|
|
|
|
// Enable title (contenteditable div)
|
|
|
|
|
if (titleField) {
|
|
|
|
|
titleField.setAttribute('contenteditable', 'true');
|
|
|
|
|
titleField.focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enable description (textarea)
|
|
|
|
|
if (descriptionField) {
|
|
|
|
|
descriptionField.disabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 18:49:44 -05:00
|
|
|
// Enable metadata fields (priority, category, type)
|
|
|
|
|
metadataFields.forEach(field => {
|
|
|
|
|
field.disabled = false;
|
|
|
|
|
});
|
2024-11-30 20:26:30 -05:00
|
|
|
} else {
|
|
|
|
|
saveTicket();
|
|
|
|
|
editButton.textContent = 'Edit Ticket';
|
|
|
|
|
editButton.classList.remove('active');
|
2026-01-08 22:40:26 -05:00
|
|
|
|
|
|
|
|
// Disable title
|
|
|
|
|
if (titleField) {
|
|
|
|
|
titleField.setAttribute('contenteditable', 'false');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disable description
|
|
|
|
|
if (descriptionField) {
|
|
|
|
|
descriptionField.disabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 18:49:44 -05:00
|
|
|
// Disable metadata fields
|
|
|
|
|
metadataFields.forEach(field => {
|
|
|
|
|
field.disabled = true;
|
|
|
|
|
});
|
2024-11-30 20:26:30 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-11 20:52:11 -04:00
|
|
|
|
2024-11-30 20:26:30 -05:00
|
|
|
function addComment() {
|
|
|
|
|
const commentText = document.getElementById('newComment').value;
|
2025-05-16 20:02:49 -04:00
|
|
|
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;
|
2024-11-30 20:26:30 -05:00
|
|
|
|
2025-05-16 20:02:49 -04:00
|
|
|
fetch('/api/add_comment.php', {
|
2024-11-30 20:26:30 -05:00
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
2026-01-09 16:13:13 -05:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': window.CSRF_TOKEN
|
2024-11-30 20:26:30 -05:00
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
ticket_id: ticketId,
|
2025-05-16 20:02:49 -04:00
|
|
|
comment_text: commentText,
|
|
|
|
|
markdown_enabled: isMarkdownEnabled
|
2024-11-30 20:26:30 -05:00
|
|
|
})
|
|
|
|
|
})
|
2025-05-16 20:02:49 -04:00
|
|
|
.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();
|
|
|
|
|
})
|
2024-11-30 20:26:30 -05:00
|
|
|
.then(data => {
|
|
|
|
|
if(data.success) {
|
|
|
|
|
// Clear the comment box
|
|
|
|
|
document.getElementById('newComment').value = '';
|
|
|
|
|
|
2025-09-05 11:08:56 -04:00
|
|
|
// 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 <br> and escape HTML
|
|
|
|
|
displayText = commentText
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''')
|
|
|
|
|
.replace(/\n/g, '<br>');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 16:13:13 -05:00
|
|
|
// Add new comment to the list (using safe DOM API to prevent XSS)
|
2024-11-30 20:26:30 -05:00
|
|
|
const commentsList = document.querySelector('.comments-list');
|
2026-01-09 16:13:13 -05:00
|
|
|
|
|
|
|
|
const commentDiv = document.createElement('div');
|
|
|
|
|
commentDiv.className = 'comment';
|
|
|
|
|
|
|
|
|
|
const headerDiv = document.createElement('div');
|
|
|
|
|
headerDiv.className = 'comment-header';
|
|
|
|
|
|
|
|
|
|
const userSpan = document.createElement('span');
|
|
|
|
|
userSpan.className = 'comment-user';
|
|
|
|
|
userSpan.textContent = data.user_name; // Safe - auto-escapes
|
|
|
|
|
|
|
|
|
|
const dateSpan = document.createElement('span');
|
|
|
|
|
dateSpan.className = 'comment-date';
|
|
|
|
|
dateSpan.textContent = data.created_at; // Safe - auto-escapes
|
|
|
|
|
|
|
|
|
|
const textDiv = document.createElement('div');
|
|
|
|
|
textDiv.className = 'comment-text';
|
|
|
|
|
textDiv.innerHTML = displayText; // displayText already sanitized above
|
|
|
|
|
|
|
|
|
|
headerDiv.appendChild(userSpan);
|
|
|
|
|
headerDiv.appendChild(dateSpan);
|
|
|
|
|
commentDiv.appendChild(headerDiv);
|
|
|
|
|
commentDiv.appendChild(textDiv);
|
|
|
|
|
|
|
|
|
|
commentsList.insertBefore(commentDiv, commentsList.firstChild);
|
2025-05-16 20:02:49 -04:00
|
|
|
} else {
|
|
|
|
|
console.error('Error adding comment:', data.error || 'Unknown error');
|
2024-11-30 20:26:30 -05:00
|
|
|
}
|
2025-05-16 20:02:49 -04:00
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error adding comment:', error);
|
2024-11-30 20:26:30 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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() {
|
2025-09-05 11:08:56 -04:00
|
|
|
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';
|
|
|
|
|
}
|
2024-11-30 20:26:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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');
|
2026-01-01 18:36:34 -05:00
|
|
|
|
2026-01-08 22:36:07 -05:00
|
|
|
// Auto-resize function for textareas
|
|
|
|
|
function autoResizeTextarea(textarea) {
|
|
|
|
|
// Reset height to auto to get the correct scrollHeight
|
|
|
|
|
textarea.style.height = 'auto';
|
|
|
|
|
// Set the height to match the scrollHeight
|
|
|
|
|
textarea.style.height = textarea.scrollHeight + 'px';
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-16 16:36:18 -04:00
|
|
|
// Auto-resize the description textarea to fit content
|
|
|
|
|
const descriptionTextarea = document.querySelector('textarea[data-field="description"]');
|
2025-05-16 20:02:49 -04:00
|
|
|
if (descriptionTextarea) {
|
|
|
|
|
// Initial resize
|
2026-01-08 22:36:07 -05:00
|
|
|
autoResizeTextarea(descriptionTextarea);
|
2026-01-01 18:36:34 -05:00
|
|
|
|
2025-05-16 20:02:49 -04:00
|
|
|
// Resize on input when in edit mode
|
2026-01-08 22:36:07 -05:00
|
|
|
descriptionTextarea.addEventListener('input', function() {
|
|
|
|
|
autoResizeTextarea(this);
|
|
|
|
|
});
|
2025-05-16 16:36:18 -04:00
|
|
|
}
|
2026-01-01 18:36:34 -05:00
|
|
|
|
|
|
|
|
// Initialize assignment handling
|
|
|
|
|
handleAssignmentChange();
|
2026-01-07 18:14:29 -05:00
|
|
|
|
|
|
|
|
// Initialize metadata field handlers (priority, category, type)
|
|
|
|
|
handleMetadataChanges();
|
2024-11-30 20:26:30 -05:00
|
|
|
});
|
|
|
|
|
|
2026-01-01 18:36:34 -05:00
|
|
|
/**
|
|
|
|
|
* 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',
|
2026-01-09 16:13:13 -05:00
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': window.CSRF_TOKEN
|
|
|
|
|
},
|
2026-01-01 18:36:34 -05:00
|
|
|
body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo })
|
|
|
|
|
})
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (!data.success) {
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error('Error updating assignment');
|
2026-01-01 18:36:34 -05:00
|
|
|
console.error(data.error);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('Assignment updated successfully');
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error updating assignment:', error);
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error('Error updating assignment: ' + error.message);
|
2026-01-01 18:36:34 -05:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 18:14:29 -05:00
|
|
|
/**
|
|
|
|
|
* 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',
|
2026-01-09 16:13:13 -05:00
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': window.CSRF_TOKEN
|
|
|
|
|
},
|
2026-01-07 18:14:29 -05:00
|
|
|
body: JSON.stringify({
|
|
|
|
|
ticket_id: ticketId,
|
|
|
|
|
[fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (!data.success) {
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error(`Error updating ${fieldName}`);
|
2026-01-07 18:14:29 -05:00
|
|
|
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);
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error(`Error updating ${fieldName}: ` + error.message);
|
2026-01-07 18:14:29 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 18:57:23 -05:00
|
|
|
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) {
|
2026-01-09 16:54:02 -05:00
|
|
|
showConfirmModal(
|
|
|
|
|
'Status Change Requires Comment',
|
|
|
|
|
`This transition to "${newStatus}" requires a comment explaining the reason.\n\nPlease add a comment before changing the status.`,
|
|
|
|
|
'warning',
|
|
|
|
|
() => {
|
|
|
|
|
// User confirmed, proceed with status change
|
2026-01-09 17:08:11 -05:00
|
|
|
performStatusChange(statusSelect, selectedOption, newStatus);
|
2026-01-09 16:54:02 -05:00
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
// User cancelled, reset to current status
|
|
|
|
|
statusSelect.selectedIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return;
|
2026-01-01 18:57:23 -05:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 17:08:11 -05:00
|
|
|
performStatusChange(statusSelect, selectedOption, newStatus);
|
2026-01-09 17:04:21 -05:00
|
|
|
}
|
2026-01-09 16:54:02 -05:00
|
|
|
|
|
|
|
|
// Extract status change logic into reusable function
|
2026-01-09 17:08:11 -05:00
|
|
|
function performStatusChange(statusSelect, selectedOption, newStatus) {
|
2026-01-01 18:57:23 -05:00
|
|
|
// 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: {
|
2026-01-09 16:13:13 -05:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'X-CSRF-Token': window.CSRF_TOKEN
|
2026-01-01 18:57:23 -05:00
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
ticket_id: ticketId,
|
|
|
|
|
status: newStatus
|
|
|
|
|
})
|
|
|
|
|
})
|
2026-01-08 12:50:11 -05:00
|
|
|
.then(async response => {
|
|
|
|
|
const text = await response.text();
|
|
|
|
|
|
2026-01-01 18:57:23 -05:00
|
|
|
if (!response.ok) {
|
2026-01-08 12:50:11 -05:00
|
|
|
console.error('Server error response:', text);
|
|
|
|
|
try {
|
|
|
|
|
const data = JSON.parse(text);
|
2026-01-08 12:48:06 -05:00
|
|
|
throw new Error(data.error || 'Server returned an error');
|
2026-01-08 12:50:11 -05:00
|
|
|
} catch (parseError) {
|
|
|
|
|
throw new Error(text || 'Network response was not ok');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(text);
|
|
|
|
|
} catch (parseError) {
|
|
|
|
|
console.error('Failed to parse JSON:', text);
|
|
|
|
|
throw new Error('Invalid JSON response from server');
|
2026-01-01 18:57:23 -05:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.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');
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error('Error updating status: ' + (data.error || 'Unknown error'));
|
2026-01-01 18:57:23 -05:00
|
|
|
// Reset to current status
|
|
|
|
|
statusSelect.selectedIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('Error updating status:', error);
|
2026-01-08 22:49:48 -05:00
|
|
|
toast.error('Error updating status: ' + error.message);
|
2026-01-01 18:57:23 -05:00
|
|
|
// Reset to current status
|
|
|
|
|
statusSelect.selectedIndex = 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 20:26:30 -05:00
|
|
|
function showTab(tabName) {
|
|
|
|
|
// Hide all tab contents
|
|
|
|
|
const descriptionTab = document.getElementById('description-tab');
|
|
|
|
|
const commentsTab = document.getElementById('comments-tab');
|
2026-01-06 22:38:46 -05:00
|
|
|
const activityTab = document.getElementById('activity-tab');
|
|
|
|
|
|
2025-05-16 20:02:49 -04:00
|
|
|
if (!descriptionTab || !commentsTab) {
|
|
|
|
|
console.error('Tab elements not found');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-06 22:38:46 -05:00
|
|
|
|
|
|
|
|
// Hide all tabs
|
2024-11-30 20:26:30 -05:00
|
|
|
descriptionTab.style.display = 'none';
|
|
|
|
|
commentsTab.style.display = 'none';
|
2026-01-06 22:38:46 -05:00
|
|
|
if (activityTab) {
|
|
|
|
|
activityTab.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-30 20:26:30 -05:00
|
|
|
// Remove active class from all buttons
|
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
|
|
|
btn.classList.remove('active');
|
|
|
|
|
});
|
2026-01-06 22:38:46 -05:00
|
|
|
|
2024-11-30 20:26:30 -05:00
|
|
|
// Show selected tab and activate its button
|
|
|
|
|
document.getElementById(`${tabName}-tab`).style.display = 'block';
|
|
|
|
|
document.querySelector(`[onclick="showTab('${tabName}')"]`).classList.add('active');
|
|
|
|
|
}
|