// 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 = '
Type at least 2 characters to search
'; } }); // Handle keyboard navigation searchInput?.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown') { e.preventDefault(); const firstResult = searchResults.querySelector('[data-search-result]'); firstResult?.focus(); } }); } function closeSearchModal() { const searchModal = document.getElementById('global-search-modal'); const searchInput = document.getElementById('global-search-input'); searchModal?.classList.remove('opacity-100'); setTimeout(() => { searchModal?.classList.add('hidden'); searchInput.value = ''; showSearchPlaceholder(); }, 200); } function showSearchPlaceholder() { const searchResults = document.getElementById('global-search-results'); 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 = `
${response.message}
`; } } catch (error) { console.error('Search error:', error); 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}
${name}
${desc}
arrow_forward
`; }); html += '
'; } // Expenses if (results.expenses && results.expenses.length > 0) { html += '

Expenses

'; results.expenses.forEach(expense => { const date = new Date(expense.date).toLocaleDateString(userLang === 'ro' ? 'ro-RO' : 'en-US', { month: 'short', day: 'numeric' }); const ocrBadge = expense.ocr_match ? 'OCR Match' : ''; html += `
receipt
${expense.description}
${expense.category_name} ${date} ${ocrBadge}
${formatCurrency(expense.amount, expense.currency)}
`; }); 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 += '

Categories

'; results.categories.forEach(category => { html += `
${category.icon}
${category.name}
arrow_forward
`; }); html += '
'; } // Recurring Expenses if (results.recurring && results.recurring.length > 0) { html += '

Recurring

'; results.recurring.forEach(rec => { const nextDue = new Date(rec.next_due_date).toLocaleDateString(userLang === 'ro' ? 'ro-RO' : 'en-US', { month: 'short', day: 'numeric' }); const statusBadge = rec.is_active ? 'Active' : 'Inactive'; html += `
repeat
${rec.name}
${rec.category_name} Next: ${nextDue} ${statusBadge}
${formatCurrency(rec.amount, rec.currency)}
`; }); 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; }