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 ) {
data [ field . dataset . field ] = field . value ;
}
} ) ;
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 : {
'Content-Type' : 'application/json'
} ,
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 ;
}
console . log ( 'Ticket updated successfully' ) ;
} 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' ) ;
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' ) ;
2024-11-30 20:26:30 -05:00
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 ;
} ) ;
2024-11-30 20:26:30 -05:00
} 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 ;
} ) ;
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 : {
'Content-Type' : 'application/json'
} ,
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>' ) ;
}
2024-11-30 20:26:30 -05:00
// 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 } < / s p a n >
< span class = "comment-date" > $ { data . created _at } < / s p a n >
< / d i v >
2025-09-05 11:08:56 -04:00
< div class = "comment-text" > $ { displayText } < / d i v >
2024-11-30 20:26:30 -05:00
< / d i v >
` ;
commentsList . insertAdjacentHTML ( 'afterbegin' , newComment ) ;
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
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 ) {
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' ;
}
2026-01-01 18:36:34 -05:00
2025-05-16 20:02:49 -04:00
// Initial resize
autoResizeTextarea ( ) ;
2026-01-01 18:36:34 -05:00
2025-05-16 20:02:49 -04:00
// Resize on input when in edit mode
descriptionTextarea . addEventListener ( 'input' , autoResizeTextarea ) ;
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' ,
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 ) ;
} ) ;
} ) ;
}
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' ,
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 ) ;
} ) ;
}
}
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 ) {
const proceed = confirm ( ` This status change requires a comment. Please add a comment explaining the reason for this transition. \n \n Proceed 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 ( ) ;
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' ) ;
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 ;
} ) ;
}
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' ) ;
}