// Global Search Component
// Provides unified search across all app content and features
let searchTimeout;
let currentSearchQuery = '';
// Initialize global search
document.addEventListener('DOMContentLoaded', () => {
initGlobalSearch();
});
function initGlobalSearch() {
const searchBtn = document.getElementById('global-search-btn');
const searchModal = document.getElementById('global-search-modal');
const searchInput = document.getElementById('global-search-input');
const searchResults = document.getElementById('global-search-results');
const searchClose = document.getElementById('global-search-close');
if (!searchBtn || !searchModal) return;
// Open search modal
searchBtn?.addEventListener('click', () => {
searchModal.classList.remove('hidden');
setTimeout(() => {
searchModal.classList.add('opacity-100');
searchInput?.focus();
}, 10);
});
// Close search modal
searchClose?.addEventListener('click', closeSearchModal);
// Close on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !searchModal.classList.contains('hidden')) {
closeSearchModal();
}
// Open search with Ctrl+K or Cmd+K
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
searchBtn?.click();
}
});
// Close on backdrop click
searchModal?.addEventListener('click', (e) => {
if (e.target === searchModal) {
closeSearchModal();
}
});
// Handle search input
searchInput?.addEventListener('input', (e) => {
const query = e.target.value.trim();
// Clear previous timeout
clearTimeout(searchTimeout);
// Show loading state
if (query.length >= 2) {
searchResults.innerHTML = '
Searching...
';
// Debounce search
searchTimeout = setTimeout(() => {
performSearch(query);
}, 300);
} else if (query.length === 0) {
showSearchPlaceholder();
} else {
searchResults.innerHTML = '
search
Search for transactions, documents, categories, or features
Press Ctrl+K to open search
`;
}
async function performSearch(query) {
currentSearchQuery = query;
const searchResults = document.getElementById('global-search-results');
try {
const response = await apiCall(`/api/search/?q=${encodeURIComponent(query)}`, {
method: 'GET'
});
if (response.success) {
displaySearchResults(response);
} else {
searchResults.innerHTML = `Search failed. Please try again.
';
}
}
function displaySearchResults(response) {
const searchResults = document.getElementById('global-search-results');
const results = response.results;
const userLang = localStorage.getItem('language') || 'en';
if (response.total_results === 0) {
searchResults.innerHTML = `
search_off
No results found for "${response.query}"
`;
return;
}
let html = '';
// Features
if (results.features && results.features.length > 0) {
html += '
Features
';
results.features.forEach(feature => {
const name = userLang === 'ro' ? feature.name_ro : feature.name;
const desc = userLang === 'ro' ? feature.description_ro : feature.description;
html += `
${feature.icon}
arrow_forward
`;
});
html += '
';
}
// Expenses
if (results.expenses && results.expenses.length > 0) {
html += '
';
}
// Documents
if (results.documents && results.documents.length > 0) {
html += '
Documents
';
results.documents.forEach(doc => {
const date = new Date(doc.created_at).toLocaleDateString(userLang === 'ro' ? 'ro-RO' : 'en-US', { month: 'short', day: 'numeric' });
const ocrBadge = doc.ocr_match ? '
OCR Match' : '';
const fileIcon = doc.file_type === 'PDF' ? 'picture_as_pdf' : 'image';
html += `
`;
});
html += '
';
}
// Categories
if (results.categories && results.categories.length > 0) {
html += '
';
}
// Recurring Expenses
if (results.recurring && results.recurring.length > 0) {
html += '
';
}
html += '
';
searchResults.innerHTML = html;
// Apply translations
if (window.applyTranslations) {
window.applyTranslations();
}
// Handle keyboard navigation between results
const resultElements = searchResults.querySelectorAll('[data-search-result]');
resultElements.forEach((element, index) => {
element.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
resultElements[index + 1]?.focus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (index === 0) {
document.getElementById('global-search-input')?.focus();
} else {
resultElements[index - 1]?.focus();
}
}
});
});
}
// Open document viewer from search
function openDocumentFromSearch(docId, fileType, filename) {
// Close search modal
closeSearchModal();
// Navigate to documents page and open viewer
if (window.location.pathname !== '/documents') {
// Store document to open after navigation
sessionStorage.setItem('openDocumentId', docId);
sessionStorage.setItem('openDocumentType', fileType);
sessionStorage.setItem('openDocumentName', filename);
window.location.href = '/documents';
} else {
// Already on documents page, open directly
if (typeof viewDocument === 'function') {
viewDocument(docId, fileType, filename);
}
}
}
// Helper to escape HTML
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}