feat(dashboard): keyboard navigation

This commit is contained in:
2026-05-31 16:29:15 +02:00
parent 432d758b90
commit f8de7e626b
2 changed files with 47 additions and 1 deletions

View File

@@ -288,6 +288,10 @@ body {
transition: transform 0.15s ease, border-color 0.2s ease; transition: transform 0.15s ease, border-color 0.2s ease;
touch-action: manipulation; 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:active { transform: scale(0.985); }
.card.confirmed { border-left: 4px solid #3a7d3a; } .card.confirmed { border-left: 4px solid #3a7d3a; }
.card.rejected { border-left: 4px solid #8a3a3a; } .card.rejected { border-left: 4px solid #8a3a3a; }

View File

@@ -115,6 +115,7 @@ let state = {
autoRefresh: true, autoRefresh: true,
view: 'cards', view: 'cards',
lastEvent: null, lastEvent: null,
selectedId: null,
}; };
// ─── Fetch ────────────────────────────────────────────────────────────────── // ─── Fetch ──────────────────────────────────────────────────────────────────
@@ -1186,6 +1187,15 @@ function closeModal() {
document.getElementById('detailModal').classList.remove('open'); 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 ───────────────────────────────────────────────────────────── // ─── Pagination ─────────────────────────────────────────────────────────────
function nextPage() { function nextPage() {
state.offset += state.limit; state.offset += state.limit;
@@ -1233,6 +1243,38 @@ document.getElementById('detailModal').addEventListener('click', (e) => {
}); });
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal(); 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() { function renderCardsWithSuggestions() {
const el = document.getElementById('cards'); const el = document.getElementById('cards');
@@ -1246,7 +1288,7 @@ function renderCardsWithSuggestions() {
</div> </div>
`).join('') : '<span class="muted">Keine Vorschläge</span>'; `).join('') : '<span class="muted">Keine Vorschläge</span>';
return ` return `
<div class="card ${item.confirmed ? 'confirmed' : ''} ${item.rejections > 0 ? 'rejected' : ''}" data-id="${item.id}"> <div class="card ${item.id === state.selectedId ? 'selected' : ''} ${item.confirmed ? 'confirmed' : ''} ${item.rejections > 0 ? 'rejected' : ''}" data-id="${item.id}" onclick="selectCard('${item.id}')">
<div class="card-header"> <div class="card-header">
<span class="conf-badge" style="background:hsl(${item.confidence*120},70%,40%)">${Math.round(item.confidence*100)}%</span> <span class="conf-badge" style="background:hsl(${item.confidence*120},70%,40%)">${Math.round(item.confidence*100)}%</span>
${renderVerdictPill(item)} ${renderVerdictPill(item)}