// PIRP Command Palette (Ctrl+K / Cmd+K) (function() { 'use strict'; var overlay = null; var input = null; var resultsList = null; var debounceTimer = null; var activeIndex = -1; var currentResults = []; var typeLabels = { invoice: 'Rechnungen', customer: 'Kunden', expense: 'Ausgaben', journal: 'Journal' }; var typeIcons = { invoice: '\u25B8', customer: '\u25CF', expense: '\u25A0', journal: '\u25C6' }; function create() { overlay = document.createElement('div'); overlay.className = 'cmd-palette-overlay hidden'; overlay.addEventListener('click', function(e) { if (e.target === overlay) close(); }); var modal = document.createElement('div'); modal.className = 'cmd-palette'; input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Suche nach Rechnungen, Kunden, Ausgaben, Journal...'; input.addEventListener('input', function() { clearTimeout(debounceTimer); debounceTimer = setTimeout(doSearch, 200); }); input.addEventListener('keydown', handleKeydown); resultsList = document.createElement('div'); resultsList.className = 'cmd-palette-results'; modal.appendChild(input); modal.appendChild(resultsList); overlay.appendChild(modal); document.body.appendChild(overlay); } function open() { if (!overlay) create(); overlay.classList.remove('hidden'); input.value = ''; resultsList.innerHTML = ''; activeIndex = -1; currentResults = []; setTimeout(function() { input.focus(); }, 10); } function close() { if (overlay) overlay.classList.add('hidden'); } function isOpen() { return overlay && !overlay.classList.contains('hidden'); } function doSearch() { var q = input.value.trim(); if (q.length < 2) { resultsList.innerHTML = ''; activeIndex = -1; currentResults = []; return; } fetch('search_api.php?q=' + encodeURIComponent(q)) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.ok) return; currentResults = data.results; activeIndex = -1; renderResults(data.results); }) .catch(function() {}); } function renderResults(results) { resultsList.innerHTML = ''; if (!results.length) { var empty = document.createElement('div'); empty.className = 'cmd-palette-empty'; empty.textContent = 'Keine Ergebnisse'; resultsList.appendChild(empty); return; } // Group by type var groups = {}; var order = []; results.forEach(function(r) { if (!groups[r.type]) { groups[r.type] = []; order.push(r.type); } groups[r.type].push(r); }); var idx = 0; order.forEach(function(type) { var header = document.createElement('div'); header.className = 'cmd-palette-group'; header.textContent = typeLabels[type] || type; resultsList.appendChild(header); groups[type].forEach(function(r) { var item = document.createElement('a'); item.className = 'cmd-palette-item'; item.href = r.url; item.setAttribute('data-idx', idx); var icon = document.createElement('span'); icon.className = 'cmd-palette-icon'; icon.textContent = typeIcons[r.type] || '\u25B8'; item.appendChild(icon); var text = document.createElement('span'); text.className = 'cmd-palette-text'; var title = document.createElement('span'); title.className = 'cmd-palette-title'; title.textContent = r.title; text.appendChild(title); if (r.subtitle) { var sub = document.createElement('span'); sub.className = 'cmd-palette-subtitle'; sub.textContent = r.subtitle; text.appendChild(sub); } item.appendChild(text); item.addEventListener('mouseenter', function() { setActive(parseInt(item.getAttribute('data-idx'))); }); resultsList.appendChild(item); idx++; }); }); } function setActive(idx) { var items = resultsList.querySelectorAll('.cmd-palette-item'); items.forEach(function(el) { el.classList.remove('active'); }); activeIndex = idx; if (idx >= 0 && idx < items.length) { items[idx].classList.add('active'); items[idx].scrollIntoView({ block: 'nearest' }); } } function handleKeydown(e) { var items = resultsList.querySelectorAll('.cmd-palette-item'); var count = items.length; if (e.key === 'ArrowDown') { e.preventDefault(); setActive(activeIndex < count - 1 ? activeIndex + 1 : 0); } else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(activeIndex > 0 ? activeIndex - 1 : count - 1); } else if (e.key === 'Enter') { e.preventDefault(); if (activeIndex >= 0 && activeIndex < count) { window.location = items[activeIndex].href; } } else if (e.key === 'Escape') { e.preventDefault(); close(); } } // Global keyboard listener document.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); if (isOpen()) { close(); } else { open(); } } if (e.key === 'Escape' && isOpen()) { e.preventDefault(); close(); } }); })();