Files
tinker_tickets/assets/js/advanced-search.js

368 lines
13 KiB
JavaScript
Raw Normal View History

2026-01-09 11:20:27 -05:00
/**
* Advanced Search Functionality
* Handles complex search queries with date ranges, user filters, and multiple criteria
*/
// Open advanced search modal
function openAdvancedSearch() {
const modal = document.getElementById('advancedSearchModal');
if (modal) {
modal.style.display = 'flex';
document.body.classList.add('modal-open');
loadUsersForSearch();
populateCurrentFilters();
loadSavedFilters();
}
}
// Close advanced search modal
function closeAdvancedSearch() {
const modal = document.getElementById('advancedSearchModal');
if (modal) {
modal.style.display = 'none';
document.body.classList.remove('modal-open');
}
}
// Close modal when clicking on backdrop
function closeOnAdvancedSearchBackdropClick(event) {
const modal = document.getElementById('advancedSearchModal');
if (event.target === modal) {
closeAdvancedSearch();
}
}
// Load users for dropdown
async function loadUsersForSearch() {
try {
const response = await fetch('/api/get_users.php');
const data = await response.json();
if (data.success && data.users) {
const createdBySelect = document.getElementById('adv-created-by');
const assignedToSelect = document.getElementById('adv-assigned-to');
// Clear existing options (except first default option)
while (createdBySelect.options.length > 1) {
createdBySelect.remove(1);
}
while (assignedToSelect.options.length > 2) { // Keep "Any" and "Unassigned"
assignedToSelect.remove(2);
}
// Add users to both dropdowns
data.users.forEach(user => {
const displayName = user.display_name || user.username;
const option1 = document.createElement('option');
option1.value = user.user_id;
option1.textContent = displayName;
createdBySelect.appendChild(option1);
const option2 = document.createElement('option');
option2.value = user.user_id;
option2.textContent = displayName;
assignedToSelect.appendChild(option2);
});
}
} catch (error) {
console.error('Error loading users:', error);
}
}
// Populate form with current URL parameters
function populateCurrentFilters() {
const urlParams = new URLSearchParams(window.location.search);
// Search text
if (urlParams.has('search')) {
document.getElementById('adv-search-text').value = urlParams.get('search');
}
// Status
if (urlParams.has('status')) {
const statuses = urlParams.get('status').split(',');
const statusSelect = document.getElementById('adv-status');
Array.from(statusSelect.options).forEach(option => {
option.selected = statuses.includes(option.value);
});
}
}
// Perform advanced search
function performAdvancedSearch(event) {
event.preventDefault();
const params = new URLSearchParams();
// Search text
const searchText = document.getElementById('adv-search-text').value.trim();
if (searchText) {
params.set('search', searchText);
}
// Date ranges
const createdFrom = document.getElementById('adv-created-from').value;
const createdTo = document.getElementById('adv-created-to').value;
const updatedFrom = document.getElementById('adv-updated-from').value;
const updatedTo = document.getElementById('adv-updated-to').value;
if (createdFrom) params.set('created_from', createdFrom);
if (createdTo) params.set('created_to', createdTo);
if (updatedFrom) params.set('updated_from', updatedFrom);
if (updatedTo) params.set('updated_to', updatedTo);
// Status (multi-select)
const statusSelect = document.getElementById('adv-status');
const selectedStatuses = Array.from(statusSelect.selectedOptions).map(opt => opt.value);
if (selectedStatuses.length > 0) {
params.set('status', selectedStatuses.join(','));
}
// Priority range
const priorityMin = document.getElementById('adv-priority-min').value;
const priorityMax = document.getElementById('adv-priority-max').value;
if (priorityMin) params.set('priority_min', priorityMin);
if (priorityMax) params.set('priority_max', priorityMax);
// Users
const createdBy = document.getElementById('adv-created-by').value;
const assignedTo = document.getElementById('adv-assigned-to').value;
if (createdBy) params.set('created_by', createdBy);
if (assignedTo) params.set('assigned_to', assignedTo);
// Redirect to dashboard with params
window.location.href = '/?' + params.toString();
}
// Reset advanced search form
function resetAdvancedSearch() {
document.getElementById('advancedSearchForm').reset();
// Unselect all multi-select options
const statusSelect = document.getElementById('adv-status');
Array.from(statusSelect.options).forEach(option => {
option.selected = false;
});
}
// Save current search as a filter
async function saveCurrentFilter() {
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>
2026-01-09 16:54:02 -05:00
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;
2026-01-09 11:20:27 -05:00
}
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>
2026-01-09 16:54:02 -05:00
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);
2026-01-09 11:20:27 -05:00
}
}
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>
2026-01-09 16:54:02 -05:00
);
2026-01-09 11:20:27 -05:00
}
// Get current filter criteria from form
function getCurrentFilterCriteria() {
const criteria = {};
const searchText = document.getElementById('adv-search-text').value.trim();
if (searchText) criteria.search = searchText;
const createdFrom = document.getElementById('adv-created-from').value;
if (createdFrom) criteria.created_from = createdFrom;
const createdTo = document.getElementById('adv-created-to').value;
if (createdTo) criteria.created_to = createdTo;
const updatedFrom = document.getElementById('adv-updated-from').value;
if (updatedFrom) criteria.updated_from = updatedFrom;
const updatedTo = document.getElementById('adv-updated-to').value;
if (updatedTo) criteria.updated_to = updatedTo;
const statusSelect = document.getElementById('adv-status');
const selectedStatuses = Array.from(statusSelect.selectedOptions).map(opt => opt.value);
if (selectedStatuses.length > 0) criteria.status = selectedStatuses.join(',');
const priorityMin = document.getElementById('adv-priority-min').value;
if (priorityMin) criteria.priority_min = priorityMin;
const priorityMax = document.getElementById('adv-priority-max').value;
if (priorityMax) criteria.priority_max = priorityMax;
const createdBy = document.getElementById('adv-created-by').value;
if (createdBy) criteria.created_by = createdBy;
const assignedTo = document.getElementById('adv-assigned-to').value;
if (assignedTo) criteria.assigned_to = assignedTo;
return criteria;
}
// Load saved filters
async function loadSavedFilters() {
try {
const response = await fetch('/api/saved_filters.php');
const data = await response.json();
if (data.success && data.filters) {
populateSavedFiltersDropdown(data.filters);
}
} catch (error) {
console.error('Error loading saved filters:', error);
}
}
// Populate saved filters dropdown
function populateSavedFiltersDropdown(filters) {
const dropdown = document.getElementById('saved-filters-select');
if (!dropdown) return;
// Clear existing options except the first (placeholder)
while (dropdown.options.length > 1) {
dropdown.remove(1);
}
// Add saved filters
filters.forEach(filter => {
const option = document.createElement('option');
option.value = filter.filter_id;
option.textContent = filter.filter_name + (filter.is_default ? ' ⭐' : '');
option.dataset.criteria = JSON.stringify(filter.filter_criteria);
dropdown.appendChild(option);
});
}
// Load a saved filter
function loadSavedFilter() {
const dropdown = document.getElementById('saved-filters-select');
const selectedOption = dropdown.options[dropdown.selectedIndex];
if (!selectedOption || !selectedOption.dataset.criteria) return;
try {
const criteria = JSON.parse(selectedOption.dataset.criteria);
applySavedFilterCriteria(criteria);
} catch (error) {
console.error('Error loading filter:', error);
}
}
// Apply saved filter criteria to form
function applySavedFilterCriteria(criteria) {
// Search text
document.getElementById('adv-search-text').value = criteria.search || '';
// Date ranges
document.getElementById('adv-created-from').value = criteria.created_from || '';
document.getElementById('adv-created-to').value = criteria.created_to || '';
document.getElementById('adv-updated-from').value = criteria.updated_from || '';
document.getElementById('adv-updated-to').value = criteria.updated_to || '';
// Status
const statusSelect = document.getElementById('adv-status');
const statuses = criteria.status ? criteria.status.split(',') : [];
Array.from(statusSelect.options).forEach(option => {
option.selected = statuses.includes(option.value);
});
// Priority
document.getElementById('adv-priority-min').value = criteria.priority_min || '';
document.getElementById('adv-priority-max').value = criteria.priority_max || '';
// Users
document.getElementById('adv-created-by').value = criteria.created_by || '';
document.getElementById('adv-assigned-to').value = criteria.assigned_to || '';
}
// Delete saved filter
async function deleteSavedFilter() {
const dropdown = document.getElementById('saved-filters-select');
const selectedOption = dropdown.options[dropdown.selectedIndex];
if (!selectedOption || selectedOption.value === '') {
if (typeof toast !== 'undefined') {
toast.error('Please select a filter to delete');
}
return;
}
const filterId = selectedOption.value;
const filterName = selectedOption.textContent;
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>
2026-01-09 16:54:02 -05:00
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 })
});
const result = await response.json();
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);
2026-01-09 11:20:27 -05:00
}
}
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>
2026-01-09 16:54:02 -05:00
);
2026-01-09 11:20:27 -05:00
}
// Keyboard shortcut (Ctrl+Shift+F)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
e.preventDefault();
openAdvancedSearch();
}
// ESC to close
if (e.key === 'Escape') {
const modal = document.getElementById('advancedSearchModal');
if (modal && modal.style.display === 'flex') {
closeAdvancedSearch();
}
}
});