From f8de7e626b43c19d8568f593d6a22901ff8a73a5 Mon Sep 17 00:00:00 2001 From: Otto Date: Sun, 31 May 2026 16:29:15 +0200 Subject: [PATCH] feat(dashboard): keyboard navigation --- static/style.css | 4 ++++ templates/dashboard.html | 44 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/static/style.css b/static/style.css index bdbb526..973ea74 100644 --- a/static/style.css +++ b/static/style.css @@ -288,6 +288,10 @@ body { transition: transform 0.15s ease, border-color 0.2s ease; touch-action: manipulation; } +.card.selected { + border-color: #6c8af5; + box-shadow: 0 0 0 1px rgba(108,138,245,0.25) inset; +} .card:active { transform: scale(0.985); } .card.confirmed { border-left: 4px solid #3a7d3a; } .card.rejected { border-left: 4px solid #8a3a3a; } diff --git a/templates/dashboard.html b/templates/dashboard.html index c967e19..49f5e2b 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -115,6 +115,7 @@ let state = { autoRefresh: true, view: 'cards', lastEvent: null, + selectedId: null, }; // ─── Fetch ────────────────────────────────────────────────────────────────── @@ -1186,6 +1187,15 @@ function closeModal() { document.getElementById('detailModal').classList.remove('open'); } +function selectCard(id) { + state.selectedId = id; + renderCardsWithSuggestions(); + setTimeout(() => { + const el = document.querySelector(`.card[data-id=\"${id}\"]`); + if (el) el.scrollIntoView({block: 'nearest', behavior: 'smooth'}); + }, 0); +} + // ─── Pagination ───────────────────────────────────────────────────────────── function nextPage() { state.offset += state.limit; @@ -1233,6 +1243,38 @@ document.getElementById('detailModal').addEventListener('click', (e) => { }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); + if (e.key === '/' && !(e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT'))) { + e.preventDefault(); + document.getElementById('searchInput')?.focus(); + } + + if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT')) return; + if (document.getElementById('detailModal')?.classList.contains('open')) return; + + if (e.key === 'g') setView('graph'); + if (e.key === 's') setView('status'); + if (e.key === '1') setView('cards'); + + if (state.view !== 'cards') return; + if (!state.items || !state.items.length) return; + + const idxOf = (id) => state.items.findIndex(x => x.id === id); + let idx = state.selectedId ? idxOf(state.selectedId) : -1; + if (idx < 0) idx = 0; + + if (e.key === 'j') { + idx = Math.min(state.items.length - 1, idx + 1); + selectCard(state.items[idx].id); + } else if (e.key === 'k') { + idx = Math.max(0, idx - 1); + selectCard(state.items[idx].id); + } else if (e.key === 'Enter') { + showDetail(state.items[idx].id); + } else if (e.key === 'c') { + confirm(state.items[idx].id, e); + } else if (e.key === 'r') { + reject(state.items[idx].id, e); + } }); function renderCardsWithSuggestions() { const el = document.getElementById('cards'); @@ -1246,7 +1288,7 @@ function renderCardsWithSuggestions() { `).join('') : 'Keine Vorschläge'; return ` -
+
${Math.round(item.confidence*100)}% ${renderVerdictPill(item)}