diff --git a/assets/js/advanced-search.js b/assets/js/advanced-search.js index b5eabfe..8cbc812 100644 --- a/assets/js/advanced-search.js +++ b/assets/js/advanced-search.js @@ -148,42 +148,45 @@ function resetAdvancedSearch() { // Save current search as a filter async function saveCurrentFilter() { - const filterName = prompt('Enter a name for this filter:'); - if (!filterName || filterName.trim() === '') return; - - const filterCriteria = getCurrentFilterCriteria(); - - try { - const response = await fetch('/api/saved_filters.php', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': window.CSRF_TOKEN - }, - body: JSON.stringify({ - filter_name: filterName.trim(), - filter_criteria: filterCriteria - }) - }); - - const result = await response.json(); - - if (result.success) { - if (typeof toast !== 'undefined') { - toast.success(`Filter "${filterName}" saved successfully!`); + showInputModal( + '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; } - loadSavedFilters(); - } else { - if (typeof toast !== 'undefined') { - toast.error('Failed to save filter: ' + (result.error || 'Unknown error')); + + const filterCriteria = getCurrentFilterCriteria(); + + try { + const response = await fetch('/api/saved_filters.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': window.CSRF_TOKEN + }, + body: JSON.stringify({ + filter_name: filterName.trim(), + filter_criteria: filterCriteria + }) + }); + + const result = await response.json(); + + if (result.success) { + toast.success(`Filter "${filterName}" saved successfully!`, 3000); + loadSavedFilters(); + } else { + toast.error('Failed to save filter: ' + (result.error || 'Unknown error'), 4000); + } + } catch (error) { + console.error('Error saving filter:', error); + toast.error('Error saving filter', 4000); } } - } catch (error) { - console.error('Error saving filter:', error); - if (typeof toast !== 'undefined') { - toast.error('Error saving filter'); - } - } + ); } // Get current filter criteria from form @@ -315,39 +318,36 @@ async function deleteSavedFilter() { const filterId = selectedOption.value; const filterName = selectedOption.textContent; - if (!confirm(`Are you sure you want to delete the filter "${filterName}"?`)) { - return; - } + showConfirmModal( + `Delete Filter "${filterName}"?`, + 'This action cannot be undone.', + 'error', + async () => { + try { + const response = await fetch('/api/saved_filters.php', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': window.CSRF_TOKEN + }, + body: JSON.stringify({ filter_id: filterId }) + }); - try { - const response = await fetch('/api/saved_filters.php', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': window.CSRF_TOKEN - }, - body: JSON.stringify({ filter_id: filterId }) - }); + const result = await response.json(); - const result = await response.json(); - - if (result.success) { - if (typeof toast !== 'undefined') { - toast.success('Filter deleted successfully'); - } - loadSavedFilters(); - resetAdvancedSearch(); - } else { - if (typeof toast !== 'undefined') { - toast.error('Failed to delete filter'); + if (result.success) { + toast.success('Filter deleted successfully', 3000); + loadSavedFilters(); + resetAdvancedSearch(); + } else { + toast.error('Failed to delete filter', 4000); + } + } catch (error) { + console.error('Error deleting filter:', error); + toast.error('Error deleting filter', 4000); } } - } catch (error) { - console.error('Error deleting filter:', error); - if (typeof toast !== 'undefined') { - toast.error('Error deleting filter'); - } - } + ); } // Keyboard shortcut (Ctrl+Shift+F) diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index 852ce42..b628880 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -342,12 +342,12 @@ function quickSave() { } else { 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 => { 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); } else { 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 => { 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(); if (ticketIds.length === 0) { - alert('No tickets selected'); + toast.warning('No tickets selected', 2000); return; } - if (!confirm(`Close ${ticketIds.length} ticket(s)?`)) { - return; - } + showConfirmModal( + `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', { method: 'POST', @@ -524,15 +530,19 @@ function bulkClose() { .then(response => response.json()) .then(data => { if (data.success) { - alert(`Bulk Close Complete:\n${data.processed} succeeded\n${data.failed} failed`); - window.location.reload(); + if (data.failed > 0) { + toast.warning(`Bulk close: ${data.processed} succeeded, ${data.failed} failed`, 5000); + } else { + toast.success(`Successfully closed ${data.processed} ticket(s)`, 4000); + } + setTimeout(() => window.location.reload(), 1500); } else { - alert('Error: ' + (data.error || 'Unknown error')); + toast.error('Error: ' + (data.error || 'Unknown error'), 5000); } }) .catch(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(); if (ticketIds.length === 0) { - alert('No tickets selected'); + toast.warning('No tickets selected', 2000); return; } @@ -610,7 +620,7 @@ function performBulkAssign() { const ticketIds = getSelectedTicketIds(); if (!userId) { - alert('Please select a user'); + toast.warning('Please select a user', 2000); return; } @@ -629,16 +639,20 @@ function performBulkAssign() { .then(response => response.json()) .then(data => { if (data.success) { - alert(`Bulk Assign Complete:\n${data.processed} succeeded\n${data.failed} failed`); closeBulkAssignModal(); - window.location.reload(); + if (data.failed > 0) { + toast.warning(`Bulk assign: ${data.processed} succeeded, ${data.failed} failed`, 5000); + } else { + toast.success(`Successfully assigned ${data.processed} ticket(s)`, 4000); + } + setTimeout(() => window.location.reload(), 1500); } else { - alert('Error: ' + (data.error || 'Unknown error')); + toast.error('Error: ' + (data.error || 'Unknown error'), 5000); } }) .catch(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(); if (ticketIds.length === 0) { - alert('No tickets selected'); + toast.warning('No tickets selected', 2000); return; } @@ -701,7 +715,7 @@ function performBulkPriority() { const ticketIds = getSelectedTicketIds(); if (!priority) { - alert('Please select a priority'); + toast.warning('Please select a priority', 2000); return; } @@ -720,16 +734,20 @@ function performBulkPriority() { .then(response => response.json()) .then(data => { if (data.success) { - alert(`Bulk Priority Update Complete:\n${data.processed} succeeded\n${data.failed} failed`); closeBulkPriorityModal(); - window.location.reload(); + if (data.failed > 0) { + toast.warning(`Priority update: ${data.processed} succeeded, ${data.failed} failed`, 5000); + } else { + toast.success(`Successfully updated priority for ${data.processed} ticket(s)`, 4000); + } + setTimeout(() => window.location.reload(), 1500); } else { - alert('Error: ' + (data.error || 'Unknown error')); + toast.error('Error: ' + (data.error || 'Unknown error'), 5000); } }) .catch(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(); if (ticketIds.length === 0) { - alert('No tickets selected'); + toast.warning('No tickets selected', 2000); return; } @@ -831,7 +849,7 @@ function performBulkStatusChange() { const ticketIds = getSelectedTicketIds(); if (!status) { - alert('Please select a status'); + toast.warning('Please select a status', 2000); return; } @@ -851,14 +869,19 @@ function performBulkStatusChange() { .then(data => { closeBulkStatusModal(); if (data.success) { - window.location.reload(); + if (data.failed > 0) { + toast.warning(`Status update: ${data.processed} succeeded, ${data.failed} failed`, 5000); + } else { + toast.success(`Successfully updated status for ${data.processed} ticket(s)`, 4000); + } + setTimeout(() => window.location.reload(), 1500); } else { - alert('Error: ' + (data.error || 'Unknown error')); + toast.error('Error: ' + (data.error || 'Unknown error'), 5000); } }) .catch(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(); if (ticketIds.length === 0) { - alert('No tickets selected'); + toast.warning('No tickets selected', 2000); return; } @@ -933,16 +956,196 @@ function performBulkDelete() { .then(data => { closeBulkDeleteModal(); if (data.success) { - if (typeof toast !== 'undefined') { - toast.success(`Successfully deleted ${ticketIds.length} ticket(s)`); - } - setTimeout(() => window.location.reload(), 1000); + toast.success(`Successfully deleted ${ticketIds.length} ticket(s)`, 4000); + setTimeout(() => window.location.reload(), 1500); } else { - alert('Error: ' + (data.error || 'Unknown error')); + toast.error('Error: ' + (data.error || 'Unknown error'), 5000); } }) .catch(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 = ` + + `; + + 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 = ` + + `; + + 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); +} diff --git a/assets/js/ticket.js b/assets/js/ticket.js index 2a3ecf3..b8af11b 100644 --- a/assets/js/ticket.js +++ b/assets/js/ticket.js @@ -414,14 +414,27 @@ function updateTicketStatus() { // 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; - } + 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, newStatus); + }, + () => { + // User cancelled, reset to current status + statusSelect.selectedIndex = 0; + } + ); + return; } + performStatusChange(statusSelect, newStatus); +}); + +// Extract status change logic into reusable function +function performStatusChange(statusSelect, newStatus) { // Extract ticket ID let ticketId; if (window.location.href.includes('?id=')) {