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:
2026-01-09 16:54:02 -05:00
parent a3298e7dbe
commit 998b85e907
3 changed files with 320 additions and 104 deletions

View File

@@ -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)