feat(dashboard): keyboard navigation
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
`).join('') : '<span class="muted">Keine Vorschläge</span>';
|
||||
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">
|
||||
<span class="conf-badge" style="background:hsl(${item.confidence*120},70%,40%)">${Math.round(item.confidence*100)}%</span>
|
||||
${renderVerdictPill(item)}
|
||||
|
||||
Reference in New Issue
Block a user