Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-02-18 - [Accessibility & Interaction Feedback]
**Learning:** Custom interactive elements (like cards used as buttons) must have proper ARIA roles, tabindex, and keyboard event handlers (Enter/Space) to be accessible. Additionally, async buttons should always provide visual loading states and use a `finally` block in JS to ensure the button is restored if the modal stays open or on error.
**Action:** Always check for `role="button"` and `tabindex="0"` on non-button interactive elements, and implement robust button state management for async calls.
17 changes: 16 additions & 1 deletion app/static/js/quickscan.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,13 @@ const QuickScan = {
return;
}

const btn = document.getElementById('quickScanConfirmBtn');
const originalHTML = btn ? btn.innerHTML : '';
if (btn) {
btn.disabled = true;
btn.innerHTML = '<span class="loading loading-spinner loading-xs"></span> Verarbeitung...';
}

try {
// Daten für die API zusammenstellen
const requestData = {
Expand Down Expand Up @@ -1108,11 +1115,19 @@ const QuickScan = {
} else {
showQuickScanToast('success', data.message);
this.reset();
document.getElementById('quickScanModal').close();
const modal = document.getElementById('quickScanModal');
if (modal) modal.close();
}
} catch (error) {
console.error('Error:', error);
showQuickScanToast('error', 'Ein Fehler ist aufgetreten');
} finally {
if (btn) {
btn.disabled = false;
btn.innerHTML = originalHTML;
// Re-validate button state based on remaining inputs if modal wasn't closed
updateQuickScanButton();
}
}
}
};
Expand Down
7 changes: 6 additions & 1 deletion app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
}

#quickScanTrigger:focus-visible {
outline: 2px solid #4f46e5;
outline-offset: 2px;
}

/* Trennlinie */
.sidebar-divider {
width: 85%;
Expand Down Expand Up @@ -399,7 +404,7 @@ <h1 class="text-xl font-bold text-slate-900 truncate">
</a>
{% if current_user.is_authenticated %}
<div class="dropdown dropdown-end">
<button type="button" tabindex="0" class="btn btn-ghost btn-sm text-slate-600 hover:bg-slate-100">
<button type="button" tabindex="0" class="btn btn-ghost btn-sm text-slate-600 hover:bg-slate-100" aria-label="Abteilung wechseln">
<i class="fas fa-building mr-2"></i>
<span class="hidden sm:inline">{{ departments_ctx.current or departments.current or 'Abteilung wählen' }}</span>
</button>
Expand Down
32 changes: 22 additions & 10 deletions app/templates/components/quickscan_modal.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
<!-- QuickScan Modal -->
<dialog id="quickScanModal" class="modal">
<div class="modal-box w-full max-w-4xl max-h-[95vh] flex flex-col p-4 relative overflow-y-auto">
<button class="absolute top-2 right-2 btn btn-sm btn-circle btn-ghost text-xl" onclick="document.getElementById('quickScanModal').close()" aria-label="Schließen">&times;</button>
<button class="absolute top-2 right-2 btn btn-sm btn-circle btn-ghost text-xl" onclick="document.getElementById('quickScanModal').close()" aria-label="Schließen"><span aria-hidden="true">&times;</span></button>
<h3 class="font-bold text-base mb-4 text-center">QuickScan</h3>
<div class="flex flex-col md:flex-row gap-6 mb-6 justify-center items-center">
<!-- Karte: Werkzeug/Verbrauchsgut -->
<div id="itemCard" class="card w-[320px] h-[320px] bg-base-200 cursor-pointer transition hover:shadow-2xl hover:scale-[1.03] active:scale-[0.98] border-2 border-transparent" onclick="QuickScan.activateInput('item')">
<div id="itemCard"
role="button"
tabindex="0"
aria-labelledby="itemCardTitle"
onkeydown="if(event.key==='Enter' || event.key===' ') { event.preventDefault(); this.click(); }"
class="card w-[320px] h-[320px] bg-base-200 cursor-pointer transition hover:shadow-2xl hover:scale-[1.03] active:scale-[0.98] border-2 border-transparent focus:outline-none focus:ring-4 focus:ring-primary/50"
onclick="QuickScan.activateInput('item')">
<div class="card-body flex flex-col items-center justify-center h-full">
<i class="fas fa-tools text-2xl text-primary mb-1"></i>
<h2 class="card-title text-base mb-1">Werkzeug / Verbrauchsgut</h2>
<i class="fas fa-tools text-2xl text-primary mb-1" aria-hidden="true"></i>
<h2 id="itemCardTitle" class="card-title text-base mb-1">Werkzeug / Verbrauchsgut</h2>
<div id="itemCardContent">
<p class="text-sm opacity-50">Klicken &amp; Barcode scannen oder eingeben</p>
</div>
</div>
</div>
<!-- Karte: Mitarbeiter -->
<div id="workerCard" class="card w-[320px] h-[320px] bg-base-200 cursor-pointer transition hover:shadow-2xl hover:scale-[1.03] active:scale-[0.98] border-2 border-transparent" onclick="QuickScan.activateInput('worker')">
<div id="workerCard"
role="button"
tabindex="0"
aria-labelledby="workerCardTitle"
onkeydown="if(event.key==='Enter' || event.key===' ') { event.preventDefault(); this.click(); }"
class="card w-[320px] h-[320px] bg-base-200 cursor-pointer transition hover:shadow-2xl hover:scale-[1.03] active:scale-[0.98] border-2 border-transparent focus:outline-none focus:ring-4 focus:ring-secondary/50"
onclick="QuickScan.activateInput('worker')">
<div class="card-body flex flex-col items-center justify-center h-full">
<i class="fas fa-id-badge text-2xl text-secondary mb-1"></i>
<h2 class="card-title text-base mb-1">Mitarbeiter</h2>
<i class="fas fa-id-badge text-2xl text-secondary mb-1" aria-hidden="true"></i>
<h2 id="workerCardTitle" class="card-title text-base mb-1">Mitarbeiter</h2>
<div id="workerCardContent">
<p class="text-sm opacity-50">Klicken &amp; Barcode scannen oder eingeben</p>
</div>
Expand All @@ -28,7 +40,7 @@ <h2 class="card-title text-base mb-1">Mitarbeiter</h2>
<!-- Sichtbares Eingabefeld, das nur bei aktiver Karte angezeigt wird -->
<div class="flex flex-col items-center gap-2 mt-4">
<div id="quickScanActiveInputContainer" class="hidden w-full max-w-sm">
<input type="text" id="quickScanActiveInput" class="input input-bordered w-full" autocomplete="off" />
<input type="text" id="quickScanActiveInput" class="input input-bordered w-full" autocomplete="off" aria-label="Barcode scannen oder eingeben" />
</div>
<div class="flex gap-2 mt-2">
<button class="btn btn-ghost hover:btn-warning active:scale-95 transition" type="button" onclick="QuickScan.reset()">Reset</button>
Expand All @@ -47,7 +59,7 @@ <h3 class="font-bold text-lg mb-4">Menge eingeben</h3>
<label class="label">
<span class="label-text">Menge</span>
</label>
<input type="number" id="quantityInput" class="input input-bordered" min="1" value="1" style="height: 3rem; line-height: 1.5; padding: 0.5rem 1rem;">
<input type="number" id="quantityInput" class="input input-bordered" min="1" value="1" style="height: 3rem; line-height: 1.5; padding: 0.5rem 1rem;" aria-label="Menge">
</div>
<div class="flex justify-end space-x-4">
<button type="button" onclick="QuickScan.closeQuantityModal()" class="btn btn-ghost">Abbrechen</button>
Expand All @@ -66,7 +78,7 @@ <h3 class="font-bold text-lg mb-4">Rückgabedatum festlegen</h3>
<label class="label">
<span class="label-text">Voraussichtliche Rückgabe</span>
</label>
<input type="date" id="returnDateInput" class="input input-bordered" style="height: 3rem; line-height: 1.5; padding: 0.5rem 1rem;">
<input type="date" id="returnDateInput" class="input input-bordered" style="height: 3rem; line-height: 1.5; padding: 0.5rem 1rem;" aria-label="Rückgabedatum">
</div>
<div class="text-sm text-base-content/60 mt-2">
<i class="fas fa-info-circle mr-1"></i>
Expand Down
5 changes: 3 additions & 2 deletions app/templates/shared/list_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
<input type="text"
placeholder="Suchen..."
class="input input-bordered w-full"
id="searchInput">
<button class="btn btn-square">
id="searchInput"
aria-label="Suchen">
<button class="btn btn-square" aria-label="Suchen">
<i class="fas fa-search"></i>
</button>
</div>
Expand Down