diff --git a/assets/css/ticket.css b/assets/css/ticket.css index 3ddaf61..82b67d0 100644 --- a/assets/css/ticket.css +++ b/assets/css/ticket.css @@ -195,6 +195,19 @@ body.edit-mode .editable-metadata { gap: 0.4rem; } +/* Image thumbnail in attachment list */ +.attachment-thumb { + display: block; + width: 3rem; + height: 3rem; + object-fit: cover; + border-radius: 3px; + border: 1px solid var(--border-color); + cursor: zoom-in; + flex-shrink: 0; +} +.lt-lightbox-trigger { display: block; line-height: 0; } + /* ── Dependencies list ───────────────────────────────────────── */ .dependencies-list { display: flex; diff --git a/assets/js/ticket.js b/assets/js/ticket.js index 705b48a..d2ca407 100644 --- a/assets/js/ticket.js +++ b/assets/js/ticket.js @@ -437,21 +437,53 @@ function updateTicketStatus() { return; // No change needed } - // Warn if comment is required + // Comment required — show modal with textarea so user enters reason inline if (requiresComment) { - 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 - performStatusChange(statusSelect, selectedOption, newStatus); - }, - () => { - // User cancelled, reset to current status - statusSelect.selectedIndex = 0; + const modalId = 'statusCommentModal' + Date.now(); + document.body.insertAdjacentHTML('beforeend', ` +
+ `); + const modal = document.getElementById(modalId); + lt.modal.open(modalId); + const cleanup = (ok) => { lt.modal.close(modalId); setTimeout(() => modal.remove(), 300); if (!ok) statusSelect.selectedIndex = 0; }; + modal.querySelector('[data-modal-close]').addEventListener('click', () => cleanup(false)); + document.getElementById(`${modalId}_cancel`).addEventListener('click', () => cleanup(false)); + document.getElementById(`${modalId}_confirm`).addEventListener('click', () => { + const comment = document.getElementById(`${modalId}_comment`).value.trim(); + if (!comment) { + document.getElementById(`${modalId}_comment`).focus(); + lt.toast('Please enter a reason for this status change.', 'warning'); + return; } - ); + cleanup(true); + // Post comment first, then change status + const ticketId = getTicketIdFromUrl(); + lt.api.post('/api/add_comment.php', { ticket_id: ticketId, comment_text: comment }) + .then(() => performStatusChange(statusSelect, selectedOption, newStatus)) + .catch(() => performStatusChange(statusSelect, selectedOption, newStatus)); + }); + // Focus textarea on open + setTimeout(() => { const ta = document.getElementById(`${modalId}_comment`); if (ta) ta.focus(); }, 100); return; } @@ -929,8 +961,16 @@ function renderAttachments(attachments) { }); const uploadDate = `${lt.time.ago(att.uploaded_at)}`; + const isImage = /\.(png|jpe?g|gif|webp|svg|bmp)$/i.test(att.original_filename); + const imgUrl = `/api/download_attachment.php?id=${att.attachment_id}&inline=1`; + const iconHtml = isImage + ? ` +