Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 042afc19e2 | |||
| 28d049b52a | |||
| 8e478aeba4 | |||
| 1b38d1f0c8 |
+194
-123
@@ -445,7 +445,7 @@
|
||||
<!-- Customizable Filter Panel -->
|
||||
|
||||
<!-- Student card / quick scan workflow -->
|
||||
<div class="library-scan-panel">
|
||||
<div class="library-scan-panel">
|
||||
<div class="library-scan-controls">
|
||||
<select id="scanModeSelect" aria-label="Scan-Modus">
|
||||
<option value="card_only">Nur Ausweis erfassen</option>
|
||||
@@ -458,8 +458,10 @@
|
||||
<div id="scanStatus" class="library-scan-status">
|
||||
Hinweis: Im Schnellmodus zuerst den Schülerausweis scannen, danach den Buch-/Mediencode.
|
||||
</div>
|
||||
<div id="scanReaderWrap" class="library-scan-reader-wrap">
|
||||
<div id="libraryQrReader" class="library-scan-reader"></div>
|
||||
<div id="scanReaderWrap" class="library-scan-reader-wrap" style="display: none;">
|
||||
<div id="libraryQrReader" class="library-scan-reader">
|
||||
<div id="library-scanner-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filterPanel" class="library-filter-panel">
|
||||
@@ -847,28 +849,186 @@
|
||||
document.getElementById('detailModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// Keep track of the scanner state globally/outer scope
|
||||
let scannerRunning = false;
|
||||
// Keep track of the scanner state globally/outer scope
|
||||
let scannerRunning = false; // Maps directly to your 'isScanning' flag style
|
||||
let activeScannerCallback = null; // Dynamically routes the scanned barcode to the caller
|
||||
let lastScanValue = null;
|
||||
let lastScanAt = 0;
|
||||
let activeStudentCardId = ''; // Managed globally by your system
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// 1. CORE SCANNER ROUTING ENGINE (QUAGGA2)
|
||||
// =========================================================================
|
||||
|
||||
function startScanner(targetCallback) {
|
||||
// if (scannerRunning) return;
|
||||
|
||||
const readerWrap = document.getElementById('scanReaderWrap');
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
|
||||
// Store the custom action that should happen when a barcode is found
|
||||
activeScannerCallback = targetCallback;
|
||||
|
||||
if (readerWrap) readerWrap.style.display = 'block';
|
||||
setScanStatus('Initializing camera...', 'warn');
|
||||
|
||||
Quagga.init({
|
||||
inputStream: {
|
||||
name: "Live",
|
||||
type: "LiveStream",
|
||||
// Targets your interior relative viewport box
|
||||
target: document.querySelector('#library-scanner-container'),
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment" // Force rear camera
|
||||
},
|
||||
},
|
||||
decoder: {
|
||||
readers: [
|
||||
"code_128_reader",
|
||||
"ean_reader",
|
||||
"code_39_reader",
|
||||
"upc_reader",
|
||||
"codabar_reader",
|
||||
"i2of5_reader"
|
||||
]
|
||||
}
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
console.error('Scanner start failed:', err);
|
||||
if (readerWrap) readerWrap.style.display = 'none';
|
||||
const detail = (err && (err.message || err.name)) ? ` (${err.message || err.name})` : '';
|
||||
setScanStatus(`Scanner konnte nicht gestartet werden${detail}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
Quagga.start();
|
||||
scannerRunning = true;
|
||||
|
||||
// Update the main toggle button text if no specific edit callback is provided
|
||||
if (!targetCallback && toggleBtn) {
|
||||
toggleBtn.textContent = 'Scanner stoppen';
|
||||
}
|
||||
setScanStatus('Scanner aktiv. Jetzt Code scannen.', 'warn');
|
||||
});
|
||||
}
|
||||
|
||||
function stopScanner() {
|
||||
if (!scannerRunning) return;
|
||||
|
||||
const readerWrap = document.getElementById('scanReaderWrap');
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
|
||||
Quagga.stop();
|
||||
|
||||
scannerRunning = false;
|
||||
activeScannerCallback = null; // Clear out callback routing
|
||||
|
||||
if (readerWrap) readerWrap.style.display = 'none';
|
||||
if (toggleBtn) toggleBtn.textContent = 'Scanner starten';
|
||||
setScanStatus('Scanner gestoppt.', 'warn');
|
||||
}
|
||||
|
||||
// Unified global reader callback routing data based on who opened the scanner
|
||||
Quagga.onDetected(function(data) {
|
||||
if (!data || !data.codeResult || !data.codeResult.code) return;
|
||||
|
||||
const barcode = String(data.codeResult.code || '').trim();
|
||||
console.log("Barcode detected:", barcode);
|
||||
|
||||
// Keep a local reference to the active callback before stopping the engine
|
||||
const currentCallback = activeScannerCallback;
|
||||
|
||||
// Critical: Stop scanning immediately to prevent multiple triggers
|
||||
stopScanner();
|
||||
|
||||
// 1. If an explicit modal/edit function asked for the scan, route to it directly
|
||||
if (typeof currentCallback === "function") {
|
||||
currentCallback(barcode);
|
||||
} else {
|
||||
// 2. Fallback Default: Pass it to your library dashboard's primary business processing
|
||||
handleScanSuccess(barcode);
|
||||
}
|
||||
});
|
||||
|
||||
// Event Listener for the main standalone library processing scanner button
|
||||
document.getElementById('toggleScannerBtn')?.addEventListener('click', async function() {
|
||||
if (scannerRunning) {
|
||||
await stopScanner();
|
||||
} else {
|
||||
await startScanner(null); // Passing null flags it to run default library processing
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// 2. LIBRARY WORKFLOW AND MODAL IMPLEMENTATIONS
|
||||
// =========================================================================
|
||||
|
||||
function handleScanSuccess(decodedText) {
|
||||
const scannedCode = normalizeScannedCode(decodedText);
|
||||
if (!scannedCode) return;
|
||||
|
||||
// Debounce checks
|
||||
const now = Date.now();
|
||||
if (scannedCode === lastScanValue && (now - lastScanAt) < 1500) {
|
||||
return;
|
||||
}
|
||||
lastScanValue = scannedCode;
|
||||
lastScanAt = now;
|
||||
|
||||
const mode = (document.getElementById('scanModeSelect') || {}).value || 'card_only';
|
||||
if (mode === 'card_only') {
|
||||
setActiveStudentCard(scannedCode);
|
||||
setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok');
|
||||
return;
|
||||
}
|
||||
|
||||
processQuickToggleScan(scannedCode);
|
||||
}
|
||||
|
||||
function scanIntoEditCode() {
|
||||
const scanReaderWrap = document.getElementById('scanReaderWrap');
|
||||
const editCodeInput = document.getElementById('edit-code4');
|
||||
const scanEditBtn = document.getElementById('scan-edit-code-btn');
|
||||
if (!scanReaderWrap || !editCodeInput || !scanEditBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle close if it's already running
|
||||
if (scannerRunning && scanReaderWrap.style.display !== 'none') {
|
||||
stopScanner();
|
||||
scanEditBtn.textContent = 'Barcode scannen';
|
||||
return;
|
||||
}
|
||||
|
||||
scanEditBtn.textContent = 'Scanner schließen';
|
||||
|
||||
// Launch the scanner with custom callback routing to the item edit fields
|
||||
startScanner(function(decodedText) {
|
||||
editCodeInput.value = decodedText;
|
||||
validateCodeField(editCodeInput, document.getElementById('edit-item-id')?.value || null);
|
||||
scanEditBtn.textContent = 'Barcode scannen';
|
||||
});
|
||||
}
|
||||
|
||||
function borrowItem(itemId) {
|
||||
const selectedItem = (libraryItems || []).find(item => item._id === itemId);
|
||||
if (selectedItem && selectedItem.LibraryDisplayStatus === 'damaged') {
|
||||
alert('Dieses Medium ist als defekt/zerstör markiert und kann nicht ausgeliehen werden.');
|
||||
alert('Dieses Medium ist als defekt/zerstört markiert und kann nicht ausgeliehen werden.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const defaultCardId = activeStudentCardId || '';
|
||||
const cardId = (window.prompt('Bitte Schülerausweis-ID eingeben:', defaultCardId) || '').trim().toUpperCase();
|
||||
if (!cardId) {
|
||||
alert('Ausleihe abgebrochen: Für Bibliotheksmedien ist eine gültige Schülerausweis-ID erforderlich.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setActiveStudentCard(cardId);
|
||||
|
||||
|
||||
const durationInput = (window.prompt('Ausleihdauer in Tagen (optional):') || '').trim();
|
||||
const maxAvailable = Math.max(1, parseInt(selectedItem?.AvailableGroupedCount || selectedItem?.Quantity || 1, 10) || 1);
|
||||
const countPrompt = (window.prompt(`Anzahl ausleihen? (Standard: 1, verfügbar: ${maxAvailable})`, '1') || '').trim();
|
||||
@@ -880,29 +1040,29 @@
|
||||
alert(`Es sind nur ${maxAvailable} Exemplar(e) verfügbar.`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = `/ausleihen/${itemId}`;
|
||||
|
||||
|
||||
const csrfField = document.createElement('input');
|
||||
csrfField.type = 'hidden';
|
||||
csrfField.name = 'csrf_token';
|
||||
csrfField.value = '{{ csrf_token }}';
|
||||
form.appendChild(csrfField);
|
||||
|
||||
|
||||
const cardField = document.createElement('input');
|
||||
cardField.type = 'hidden';
|
||||
cardField.name = 'borrower_card_id';
|
||||
cardField.value = cardId;
|
||||
form.appendChild(cardField);
|
||||
|
||||
|
||||
const returnTargetField = document.createElement('input');
|
||||
returnTargetField.type = 'hidden';
|
||||
returnTargetField.name = 'return_to';
|
||||
returnTargetField.value = 'library';
|
||||
form.appendChild(returnTargetField);
|
||||
|
||||
|
||||
if (durationInput) {
|
||||
const durationField = document.createElement('input');
|
||||
durationField.type = 'hidden';
|
||||
@@ -910,17 +1070,17 @@
|
||||
durationField.value = durationInput;
|
||||
form.appendChild(durationField);
|
||||
}
|
||||
|
||||
|
||||
const countField = document.createElement('input');
|
||||
countField.type = 'hidden';
|
||||
countField.name = 'exemplare_count';
|
||||
countField.value = String(borrowCount || 1);
|
||||
form.appendChild(countField);
|
||||
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
|
||||
function setScanStatus(message, kind = '') {
|
||||
const el = document.getElementById('scanStatus');
|
||||
if (!el) return;
|
||||
@@ -930,7 +1090,7 @@
|
||||
el.classList.add(kind);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setActiveStudentCard(cardId) {
|
||||
activeStudentCardId = (cardId || '').trim().toUpperCase();
|
||||
const input = document.getElementById('activeStudentCard');
|
||||
@@ -938,18 +1098,18 @@
|
||||
input.value = activeStudentCardId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function normalizeScannedCode(code) {
|
||||
return (code || '').trim();
|
||||
}
|
||||
|
||||
|
||||
async function processQuickToggleScan(scannedCode) {
|
||||
if (!activeStudentCardId) {
|
||||
setActiveStudentCard(scannedCode);
|
||||
setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
setScanStatus('Verarbeite Mediencode...', 'warn');
|
||||
const response = await fetch('/api/library_scan_action', {
|
||||
@@ -960,13 +1120,13 @@
|
||||
item_code: scannedCode
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.ok) {
|
||||
setScanStatus(result.message || 'Scan-Aktion fehlgeschlagen.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (result.action === 'borrowed') {
|
||||
setScanStatus(`Ausgeliehen: ${result.item_name}`, 'ok');
|
||||
} else if (result.action === 'returned') {
|
||||
@@ -974,125 +1134,36 @@
|
||||
} else {
|
||||
setScanStatus(result.message || 'Aktion durchgeführt.', 'ok');
|
||||
}
|
||||
|
||||
|
||||
await loadLibraryItems();
|
||||
} catch (err) {
|
||||
console.error('Quick scan action failed:', err);
|
||||
setScanStatus('Fehler beim Verarbeiten des Scans.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function handleScanSuccess(decodedText) {
|
||||
const scannedCode = normalizeScannedCode(decodedText);
|
||||
if (!scannedCode) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (scannedCode === lastScanValue && (now - lastScanAt) < 1500) {
|
||||
return;
|
||||
}
|
||||
lastScanValue = scannedCode;
|
||||
lastScanAt = now;
|
||||
|
||||
const mode = (document.getElementById('scanModeSelect') || {}).value || 'card_only';
|
||||
if (mode === 'card_only') {
|
||||
setActiveStudentCard(scannedCode);
|
||||
setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok');
|
||||
return;
|
||||
}
|
||||
|
||||
processQuickToggleScan(scannedCode);
|
||||
}
|
||||
|
||||
// Global Quagga reader hook
|
||||
Quagga.onDetected(function(data) {
|
||||
if (data && data.codeResult && data.codeResult.code) {
|
||||
handleScanSuccess(data.codeResult.code);
|
||||
}
|
||||
});
|
||||
|
||||
async function startScanner() {
|
||||
if (scannerRunning) return;
|
||||
|
||||
const readerWrap = document.getElementById('scanReaderWrap');
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
if (readerWrap) readerWrap.style.display = 'block';
|
||||
|
||||
setScanStatus('Initializing camera...', 'warn');
|
||||
|
||||
Quagga.init({
|
||||
inputStream: {
|
||||
name: "Live",
|
||||
type: "LiveStream",
|
||||
// Points to the interior viewport layout box
|
||||
target: document.querySelector('#libraryQrReader'),
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment" // Force rear mobile lenses
|
||||
},
|
||||
},
|
||||
decoder: {
|
||||
// Limited down precisely to standard library/ID configurations
|
||||
readers: [
|
||||
"code_128_reader",
|
||||
"ean_reader",
|
||||
"code_39_reader",
|
||||
"upc_reader",
|
||||
"codabar_reader",
|
||||
"i2of5_reader" // Matches original 'ITF' format mapping
|
||||
]
|
||||
}
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
console.error('Scanner start failed:', err);
|
||||
if (readerWrap) readerWrap.style.display = 'none';
|
||||
const detail = (err && (err.message || err.name)) ? ` (${err.message || err.name})` : '';
|
||||
setScanStatus(`Scanner konnte nicht gestartet werden${detail}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
Quagga.start();
|
||||
scannerRunning = true;
|
||||
if (toggleBtn) toggleBtn.textContent = 'Scanner stoppen';
|
||||
setScanStatus('Scanner aktiv. Jetzt Code scannen.', 'warn');
|
||||
});
|
||||
}
|
||||
|
||||
async function stopScanner() {
|
||||
if (!scannerRunning) return;
|
||||
const readerWrap = document.getElementById('scanReaderWrap');
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
|
||||
Quagga.stop();
|
||||
|
||||
scannerRunning = false;
|
||||
if (readerWrap) readerWrap.style.display = 'none';
|
||||
if (toggleBtn) toggleBtn.textContent = 'Scanner starten';
|
||||
setScanStatus('Scanner gestoppt.', 'warn');
|
||||
}
|
||||
|
||||
|
||||
function wireScannerUi() {
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
const resetBtn = document.getElementById('resetCardBtn');
|
||||
const modeSelect = document.getElementById('scanModeSelect');
|
||||
|
||||
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', async () => {
|
||||
if (scannerRunning) {
|
||||
await stopScanner();
|
||||
} else {
|
||||
await startScanner();
|
||||
await startScanner(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', () => {
|
||||
setActiveStudentCard('');
|
||||
setScanStatus('Ausweis zurückgesetzt. Bitte neu scannen.', 'warn');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (modeSelect) {
|
||||
modeSelect.addEventListener('change', () => {
|
||||
if (modeSelect.value === 'quick_toggle' && !activeStudentCardId) {
|
||||
@@ -1105,18 +1176,18 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getLibraryItemById(itemId) {
|
||||
return libraryItems.find(i => String(i._id) === String(itemId));
|
||||
}
|
||||
|
||||
|
||||
function openEditLibraryItem(itemId) {
|
||||
const item = getLibraryItemById(itemId);
|
||||
if (!item) {
|
||||
alert('Element nicht gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const modal = document.getElementById('editLibraryModal');
|
||||
document.getElementById('editLibraryItemId').value = item._id || '';
|
||||
document.getElementById('editLibraryName').value = item.Name || '';
|
||||
@@ -1128,7 +1199,7 @@
|
||||
document.getElementById('editLibraryType').value = (item.ItemType || 'book');
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
|
||||
function closeEditLibraryModal() {
|
||||
const modal = document.getElementById('editLibraryModal');
|
||||
modal.style.display = 'none';
|
||||
|
||||
Reference in New Issue
Block a user