Files
tinker_tickets/assets/js/ticket.js

502 lines
17 KiB
JavaScript
Raw Normal View History

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');
2025-03-11 20:52:11 -04:00
const editables = document.querySelectorAll('.title-input, textarea[data-field="description"]');
2026-01-07 18:49:44 -05:00
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();
}
});
2026-01-07 18:49:44 -05:00
// 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;
});
2026-01-07 18:49:44 -05:00
// Disable metadata fields
metadataFields.forEach(field => {
field.disabled = true;
});
}
}
2025-03-11 20:52:11 -04:00
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 <br> and escape HTML
displayText = commentText
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/\n/g, '<br>');
}
// Add new comment to the list
const commentsList = document.querySelector('.comments-list');
const newComment = `
<div class="comment">
<div class="comment-header">
<span class="comment-user">${data.user_name}</span>
<span class="comment-date">${data.created_at}</span>
</div>
<div class="comment-text">${displayText}</div>
</div>
`;
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');
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';
}
// Auto-resize the title textarea to fit content
const titleTextarea = document.querySelector('.title-input');
if (titleTextarea) {
// Initial resize
autoResizeTextarea(titleTextarea);
// Resize on input when in edit mode
titleTextarea.addEventListener('input', function() {
autoResizeTextarea(this);
});
}
// Auto-resize the description textarea to fit content
const descriptionTextarea = document.querySelector('textarea[data-field="description"]');
if (descriptionTextarea) {
// Initial resize
2026-01-08 22:36:07 -05:00
autoResizeTextarea(descriptionTextarea);
// Resize on input when in edit mode
2026-01-08 22:36:07 -05:00
descriptionTextarea.addEventListener('input', function() {
autoResizeTextarea(this);
});
}
// 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
})
})
2026-01-08 12:50:11 -05:00
.then(async response => {
const text = await response.text();
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');
}
})
.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');
}