better filtering and searching
This commit is contained in:
361
assets/js/advanced-search.js
Normal file
361
assets/js/advanced-search.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* 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() {
|
||||
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' },
|
||||
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!`);
|
||||
}
|
||||
loadSavedFilters();
|
||||
} else {
|
||||
if (typeof toast !== 'undefined') {
|
||||
toast.error('Failed to save filter: ' + (result.error || 'Unknown error'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving filter:', error);
|
||||
if (typeof toast !== 'undefined') {
|
||||
toast.error('Error saving filter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (!confirm(`Are you sure you want to delete the filter "${filterName}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/saved_filters.php', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ filter_id: filterId })
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting filter:', error);
|
||||
if (typeof toast !== 'undefined') {
|
||||
toast.error('Error deleting filter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user