feat: Replace browser alerts with terminal-aesthetic notifications
Replaced all native browser dialogs with custom terminal-style UI:
**Utility Functions** (dashboard.js):
- showConfirmModal() - Reusable confirmation modal with type-based colors
- showInputModal() - Text input modal for user prompts
- Both support keyboard shortcuts (ESC to cancel, Enter to submit)
**Alert Replacements** (22 instances):
- Validation warnings → toast.warning() (amber, 2s)
- Error messages → toast.error() (red, 5s)
- Success messages → toast.success() or toast.warning() with details
- Example: "Bulk close: 5 succeeded, 2 failed" vs simple "Operation complete"
**Confirm Replacements** (3 instances):
- dashboard.js:509 - Bulk close confirmation → showConfirmModal()
- ticket.js:417 - Status change warning → showConfirmModal()
- advanced-search.js:321 - Delete filter → showConfirmModal('error' type)
**Prompt Replacement** (1 instance):
- advanced-search.js:151 - Save filter name → showInputModal()
**Benefits**:
✓ Visual consistency - matches terminal CRT aesthetic
✓ Non-blocking - toasts don't interrupt workflow
✓ Better UX - different colors for different message types
✓ Keyboard friendly - ESC/Enter support in modals
✓ Reusable - modal functions available for future use
All dialogs maintain retro aesthetic with:
- ASCII borders (╚ ╝)
- Terminal green glow
- Monospace fonts
- Color-coded by type (amber warning, red error, cyan info)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user