Compare commits
2 Commits
a3298e7dbe
...
d86a60c609
| Author | SHA1 | Date | |
|---|---|---|---|
| d86a60c609 | |||
| 998b85e907 |
@@ -148,8 +148,15 @@ function resetAdvancedSearch() {
|
|||||||
|
|
||||||
// Save current search as a filter
|
// Save current search as a filter
|
||||||
async function saveCurrentFilter() {
|
async function saveCurrentFilter() {
|
||||||
const filterName = prompt('Enter a name for this filter:');
|
showInputModal(
|
||||||
if (!filterName || filterName.trim() === '') return;
|
'Save Search Filter',
|
||||||
|
'Enter a name for this filter:',
|
||||||
|
'My Filter',
|
||||||
|
async (filterName) => {
|
||||||
|
if (!filterName || filterName.trim() === '') {
|
||||||
|
toast.warning('Filter name cannot be empty', 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filterCriteria = getCurrentFilterCriteria();
|
const filterCriteria = getCurrentFilterCriteria();
|
||||||
|
|
||||||
@@ -169,21 +176,17 @@ async function saveCurrentFilter() {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (typeof toast !== 'undefined') {
|
toast.success(`Filter "${filterName}" saved successfully!`, 3000);
|
||||||
toast.success(`Filter "${filterName}" saved successfully!`);
|
|
||||||
}
|
|
||||||
loadSavedFilters();
|
loadSavedFilters();
|
||||||
} else {
|
} else {
|
||||||
if (typeof toast !== 'undefined') {
|
toast.error('Failed to save filter: ' + (result.error || 'Unknown error'), 4000);
|
||||||
toast.error('Failed to save filter: ' + (result.error || 'Unknown error'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving filter:', error);
|
console.error('Error saving filter:', error);
|
||||||
if (typeof toast !== 'undefined') {
|
toast.error('Error saving filter', 4000);
|
||||||
toast.error('Error saving filter');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current filter criteria from form
|
// Get current filter criteria from form
|
||||||
@@ -315,10 +318,11 @@ async function deleteSavedFilter() {
|
|||||||
const filterId = selectedOption.value;
|
const filterId = selectedOption.value;
|
||||||
const filterName = selectedOption.textContent;
|
const filterName = selectedOption.textContent;
|
||||||
|
|
||||||
if (!confirm(`Are you sure you want to delete the filter "${filterName}"?`)) {
|
showConfirmModal(
|
||||||
return;
|
`Delete Filter "${filterName}"?`,
|
||||||
}
|
'This action cannot be undone.',
|
||||||
|
'error',
|
||||||
|
async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/saved_filters.php', {
|
const response = await fetch('/api/saved_filters.php', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -332,22 +336,18 @@ async function deleteSavedFilter() {
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (typeof toast !== 'undefined') {
|
toast.success('Filter deleted successfully', 3000);
|
||||||
toast.success('Filter deleted successfully');
|
|
||||||
}
|
|
||||||
loadSavedFilters();
|
loadSavedFilters();
|
||||||
resetAdvancedSearch();
|
resetAdvancedSearch();
|
||||||
} else {
|
} else {
|
||||||
if (typeof toast !== 'undefined') {
|
toast.error('Failed to delete filter', 4000);
|
||||||
toast.error('Failed to delete filter');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting filter:', error);
|
console.error('Error deleting filter:', error);
|
||||||
if (typeof toast !== 'undefined') {
|
toast.error('Error deleting filter', 4000);
|
||||||
toast.error('Error deleting filter');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard shortcut (Ctrl+Shift+F)
|
// Keyboard shortcut (Ctrl+Shift+F)
|
||||||
|
|||||||
@@ -342,12 +342,12 @@ function quickSave() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Error updating ticket:', result.error || 'Unknown error');
|
console.error('Error updating ticket:', result.error || 'Unknown error');
|
||||||
alert('Error updating ticket: ' + (result.error || 'Unknown error'));
|
toast.error('Error updating ticket: ' + (result.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error updating ticket:', error);
|
console.error('Error updating ticket:', error);
|
||||||
alert('Error updating ticket: ' + error.message);
|
toast.error('Error updating ticket: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,12 +446,12 @@ function loadTemplate() {
|
|||||||
console.log('Template loaded:', template.template_name);
|
console.log('Template loaded:', template.template_name);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to load template:', data.error);
|
console.error('Failed to load template:', data.error);
|
||||||
alert('Failed to load template: ' + (data.error || 'Unknown error'));
|
toast.error('Failed to load template: ' + (data.error || 'Unknown error'), 4000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error loading template:', error);
|
console.error('Error loading template:', error);
|
||||||
alert('Error loading template: ' + error.message);
|
toast.error('Error loading template: ' + error.message, 4000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,13 +502,19 @@ function bulkClose() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (ticketIds.length === 0) {
|
if (ticketIds.length === 0) {
|
||||||
alert('No tickets selected');
|
toast.warning('No tickets selected', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!confirm(`Close ${ticketIds.length} ticket(s)?`)) {
|
showConfirmModal(
|
||||||
return;
|
`Close ${ticketIds.length} Ticket(s)?`,
|
||||||
}
|
'Are you sure you want to close these tickets?',
|
||||||
|
'warning',
|
||||||
|
() => performBulkCloseAction(ticketIds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function performBulkCloseAction(ticketIds) {
|
||||||
|
|
||||||
fetch('/api/bulk_operation.php', {
|
fetch('/api/bulk_operation.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -524,15 +530,19 @@ function bulkClose() {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`Bulk Close Complete:\n${data.processed} succeeded\n${data.failed} failed`);
|
if (data.failed > 0) {
|
||||||
window.location.reload();
|
toast.warning(`Bulk close: ${data.processed} succeeded, ${data.failed} failed`, 5000);
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.error || 'Unknown error'));
|
toast.success(`Successfully closed ${data.processed} ticket(s)`, 4000);
|
||||||
|
}
|
||||||
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
|
} else {
|
||||||
|
toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error performing bulk close:', error);
|
console.error('Error performing bulk close:', error);
|
||||||
alert('Error performing bulk close: ' + error.message);
|
toast.error('Bulk close failed: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +550,7 @@ function showBulkAssignModal() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (ticketIds.length === 0) {
|
if (ticketIds.length === 0) {
|
||||||
alert('No tickets selected');
|
toast.warning('No tickets selected', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,7 +620,7 @@ function performBulkAssign() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
alert('Please select a user');
|
toast.warning('Please select a user', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,16 +639,20 @@ function performBulkAssign() {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`Bulk Assign Complete:\n${data.processed} succeeded\n${data.failed} failed`);
|
|
||||||
closeBulkAssignModal();
|
closeBulkAssignModal();
|
||||||
window.location.reload();
|
if (data.failed > 0) {
|
||||||
|
toast.warning(`Bulk assign: ${data.processed} succeeded, ${data.failed} failed`, 5000);
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.error || 'Unknown error'));
|
toast.success(`Successfully assigned ${data.processed} ticket(s)`, 4000);
|
||||||
|
}
|
||||||
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
|
} else {
|
||||||
|
toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error performing bulk assign:', error);
|
console.error('Error performing bulk assign:', error);
|
||||||
alert('Error performing bulk assign: ' + error.message);
|
toast.error('Bulk assign failed: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +660,7 @@ function showBulkPriorityModal() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (ticketIds.length === 0) {
|
if (ticketIds.length === 0) {
|
||||||
alert('No tickets selected');
|
toast.warning('No tickets selected', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,7 +715,7 @@ function performBulkPriority() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (!priority) {
|
if (!priority) {
|
||||||
alert('Please select a priority');
|
toast.warning('Please select a priority', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,16 +734,20 @@ function performBulkPriority() {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`Bulk Priority Update Complete:\n${data.processed} succeeded\n${data.failed} failed`);
|
|
||||||
closeBulkPriorityModal();
|
closeBulkPriorityModal();
|
||||||
window.location.reload();
|
if (data.failed > 0) {
|
||||||
|
toast.warning(`Priority update: ${data.processed} succeeded, ${data.failed} failed`, 5000);
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.error || 'Unknown error'));
|
toast.success(`Successfully updated priority for ${data.processed} ticket(s)`, 4000);
|
||||||
|
}
|
||||||
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
|
} else {
|
||||||
|
toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error performing bulk priority update:', error);
|
console.error('Error performing bulk priority update:', error);
|
||||||
alert('Error performing bulk priority update: ' + error.message);
|
toast.error('Bulk priority update failed: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,7 +795,7 @@ function showBulkStatusModal() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (ticketIds.length === 0) {
|
if (ticketIds.length === 0) {
|
||||||
alert('No tickets selected');
|
toast.warning('No tickets selected', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +849,7 @@ function performBulkStatusChange() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
alert('Please select a status');
|
toast.warning('Please select a status', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,14 +869,19 @@ function performBulkStatusChange() {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
closeBulkStatusModal();
|
closeBulkStatusModal();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
window.location.reload();
|
if (data.failed > 0) {
|
||||||
|
toast.warning(`Status update: ${data.processed} succeeded, ${data.failed} failed`, 5000);
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.error || 'Unknown error'));
|
toast.success(`Successfully updated status for ${data.processed} ticket(s)`, 4000);
|
||||||
|
}
|
||||||
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
|
} else {
|
||||||
|
toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error performing bulk status change:', error);
|
console.error('Error performing bulk status change:', error);
|
||||||
alert('Error performing bulk status change: ' + error.message);
|
toast.error('Bulk status change failed: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,7 +890,7 @@ function showBulkDeleteModal() {
|
|||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (ticketIds.length === 0) {
|
if (ticketIds.length === 0) {
|
||||||
alert('No tickets selected');
|
toast.warning('No tickets selected', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,16 +956,196 @@ function performBulkDelete() {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
closeBulkDeleteModal();
|
closeBulkDeleteModal();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
if (typeof toast !== 'undefined') {
|
toast.success(`Successfully deleted ${ticketIds.length} ticket(s)`, 4000);
|
||||||
toast.success(`Successfully deleted ${ticketIds.length} ticket(s)`);
|
setTimeout(() => window.location.reload(), 1500);
|
||||||
}
|
|
||||||
setTimeout(() => window.location.reload(), 1000);
|
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.error || 'Unknown error'));
|
toast.error('Error: ' + (data.error || 'Unknown error'), 5000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error performing bulk delete:', error);
|
console.error('Error performing bulk delete:', error);
|
||||||
alert('Error performing bulk delete: ' + error.message);
|
toast.error('Bulk delete failed: ' + error.message, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TERMINAL-STYLE MODAL UTILITIES
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a terminal-style confirmation modal
|
||||||
|
* @param {string} title - Modal title
|
||||||
|
* @param {string} message - Message body
|
||||||
|
* @param {string} type - 'warning', 'error', 'info' (affects color)
|
||||||
|
* @param {function} onConfirm - Callback when user confirms
|
||||||
|
* @param {function} onCancel - Optional callback when user cancels
|
||||||
|
*/
|
||||||
|
function showConfirmModal(title, message, type = 'warning', onConfirm, onCancel = null) {
|
||||||
|
const modalId = 'confirmModal' + Date.now();
|
||||||
|
|
||||||
|
// Color scheme based on type
|
||||||
|
const colors = {
|
||||||
|
warning: 'var(--terminal-amber)',
|
||||||
|
error: 'var(--status-closed)',
|
||||||
|
info: 'var(--terminal-cyan)'
|
||||||
|
};
|
||||||
|
const color = colors[type] || colors.warning;
|
||||||
|
|
||||||
|
// Icon based on type
|
||||||
|
const icons = {
|
||||||
|
warning: '⚠',
|
||||||
|
error: '✗',
|
||||||
|
info: 'ℹ'
|
||||||
|
};
|
||||||
|
const icon = icons[type] || icons.warning;
|
||||||
|
|
||||||
|
const modalHtml = `
|
||||||
|
<div class="modal-overlay" id="${modalId}">
|
||||||
|
<div class="modal-content ascii-frame-outer" style="max-width: 500px;">
|
||||||
|
<span class="bottom-left-corner">╚</span>
|
||||||
|
<span class="bottom-right-corner">╝</span>
|
||||||
|
|
||||||
|
<div class="ascii-section-header" style="color: ${color};">
|
||||||
|
${icon} ${title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ascii-content">
|
||||||
|
<div class="ascii-frame-inner">
|
||||||
|
<div class="modal-body" style="padding: 1.5rem; text-align: center;">
|
||||||
|
<p style="color: var(--terminal-green); white-space: pre-line;">
|
||||||
|
${message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ascii-divider"></div>
|
||||||
|
|
||||||
|
<div class="ascii-content">
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-primary" id="${modalId}_confirm">Confirm</button>
|
||||||
|
<button class="btn btn-secondary" id="${modalId}_cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
|
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
const confirmBtn = document.getElementById(`${modalId}_confirm`);
|
||||||
|
const cancelBtn = document.getElementById(`${modalId}_cancel`);
|
||||||
|
|
||||||
|
confirmBtn.addEventListener('click', () => {
|
||||||
|
modal.remove();
|
||||||
|
if (onConfirm) onConfirm();
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelBtn.addEventListener('click', () => {
|
||||||
|
modal.remove();
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESC key to cancel
|
||||||
|
const escHandler = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
modal.remove();
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a terminal-style input modal
|
||||||
|
* @param {string} title - Modal title
|
||||||
|
* @param {string} label - Input field label
|
||||||
|
* @param {string} placeholder - Input placeholder text
|
||||||
|
* @param {function} onSubmit - Callback with input value when submitted
|
||||||
|
* @param {function} onCancel - Optional callback when cancelled
|
||||||
|
*/
|
||||||
|
function showInputModal(title, label, placeholder = '', onSubmit, onCancel = null) {
|
||||||
|
const modalId = 'inputModal' + Date.now();
|
||||||
|
const inputId = modalId + '_input';
|
||||||
|
|
||||||
|
const modalHtml = `
|
||||||
|
<div class="modal-overlay" id="${modalId}">
|
||||||
|
<div class="modal-content ascii-frame-outer" style="max-width: 500px;">
|
||||||
|
<span class="bottom-left-corner">╚</span>
|
||||||
|
<span class="bottom-right-corner">╝</span>
|
||||||
|
|
||||||
|
<div class="ascii-section-header">
|
||||||
|
${title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ascii-content">
|
||||||
|
<div class="ascii-frame-inner">
|
||||||
|
<div class="modal-body" style="padding: 1.5rem;">
|
||||||
|
<label for="${inputId}" style="display: block; margin-bottom: 0.5rem; color: var(--terminal-green);">
|
||||||
|
${label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="${inputId}"
|
||||||
|
class="terminal-input"
|
||||||
|
placeholder="${placeholder}"
|
||||||
|
style="width: 100%; padding: 0.5rem; background: var(--bg-primary); border: 1px solid var(--terminal-green); color: var(--terminal-green); font-family: var(--font-mono);"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ascii-divider"></div>
|
||||||
|
|
||||||
|
<div class="ascii-content">
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-primary" id="${modalId}_submit">Save</button>
|
||||||
|
<button class="btn btn-secondary" id="${modalId}_cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
|
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
const submitBtn = document.getElementById(`${modalId}_submit`);
|
||||||
|
const cancelBtn = document.getElementById(`${modalId}_cancel`);
|
||||||
|
|
||||||
|
// Focus input
|
||||||
|
setTimeout(() => input.focus(), 100);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const value = input.value.trim();
|
||||||
|
modal.remove();
|
||||||
|
if (onSubmit) onSubmit(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
submitBtn.addEventListener('click', handleSubmit);
|
||||||
|
|
||||||
|
// Enter key to submit
|
||||||
|
input.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelBtn.addEventListener('click', () => {
|
||||||
|
modal.remove();
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESC key to cancel
|
||||||
|
const escHandler = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
modal.remove();
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
|||||||
@@ -414,14 +414,27 @@ function updateTicketStatus() {
|
|||||||
|
|
||||||
// Warn if comment is required
|
// Warn if comment is required
|
||||||
if (requiresComment) {
|
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}"?`);
|
showConfirmModal(
|
||||||
if (!proceed) {
|
'Status Change Requires Comment',
|
||||||
// Reset to current status
|
`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, newStatus);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// User cancelled, reset to current status
|
||||||
statusSelect.selectedIndex = 0;
|
statusSelect.selectedIndex = 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
performStatusChange(statusSelect, newStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract status change logic into reusable function
|
||||||
|
function performStatusChange(statusSelect, newStatus) {
|
||||||
// Extract ticket ID
|
// Extract ticket ID
|
||||||
let ticketId;
|
let ticketId;
|
||||||
if (window.location.href.includes('?id=')) {
|
if (window.location.href.includes('?id=')) {
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
/**
|
/**
|
||||||
* Terminal-style toast notification system
|
* Terminal-style toast notification system with queuing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Toast queue management
|
||||||
|
let toastQueue = [];
|
||||||
|
let currentToast = null;
|
||||||
|
|
||||||
function showToast(message, type = 'info', duration = 3000) {
|
function showToast(message, type = 'info', duration = 3000) {
|
||||||
// Remove any existing toasts
|
// Queue if a toast is already showing
|
||||||
const existingToast = document.querySelector('.terminal-toast');
|
if (currentToast) {
|
||||||
if (existingToast) {
|
toastQueue.push({ message, type, duration });
|
||||||
existingToast.remove();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayToast(message, type, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayToast(message, type, duration) {
|
||||||
// Create toast element
|
// Create toast element
|
||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
toast.className = `terminal-toast toast-${type}`;
|
toast.className = `terminal-toast toast-${type}`;
|
||||||
|
currentToast = toast;
|
||||||
|
|
||||||
// Icon based on type
|
// Icon based on type
|
||||||
const icons = {
|
const icons = {
|
||||||
@@ -24,6 +33,7 @@ function showToast(message, type = 'info', duration = 3000) {
|
|||||||
toast.innerHTML = `
|
toast.innerHTML = `
|
||||||
<span class="toast-icon">[${icons[type] || 'ℹ'}]</span>
|
<span class="toast-icon">[${icons[type] || 'ℹ'}]</span>
|
||||||
<span class="toast-message">${message}</span>
|
<span class="toast-message">${message}</span>
|
||||||
|
<span class="toast-close" style="margin-left: auto; cursor: pointer; opacity: 0.7; padding-left: 1rem;">[×]</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add to document
|
// Add to document
|
||||||
@@ -32,11 +42,36 @@ function showToast(message, type = 'info', duration = 3000) {
|
|||||||
// Trigger animation
|
// Trigger animation
|
||||||
setTimeout(() => toast.classList.add('show'), 10);
|
setTimeout(() => toast.classList.add('show'), 10);
|
||||||
|
|
||||||
|
// Manual dismiss handler
|
||||||
|
const closeBtn = toast.querySelector('.toast-close');
|
||||||
|
closeBtn.addEventListener('click', () => dismissToast(toast));
|
||||||
|
|
||||||
// Auto-remove after duration
|
// Auto-remove after duration
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
toast.classList.remove('show');
|
dismissToast(toast);
|
||||||
setTimeout(() => toast.remove(), 300);
|
|
||||||
}, duration);
|
}, duration);
|
||||||
|
|
||||||
|
// Store timeout ID for manual dismiss
|
||||||
|
toast.timeoutId = timeoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissToast(toast) {
|
||||||
|
// Clear auto-dismiss timeout
|
||||||
|
if (toast.timeoutId) {
|
||||||
|
clearTimeout(toast.timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.remove();
|
||||||
|
currentToast = null;
|
||||||
|
|
||||||
|
// Show next toast in queue
|
||||||
|
if (toastQueue.length > 0) {
|
||||||
|
const next = toastQueue.shift();
|
||||||
|
displayToast(next.message, next.type, next.duration);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience functions
|
// Convenience functions
|
||||||
|
|||||||
Reference in New Issue
Block a user