feat: local ZXing fallback, Register New Asset button, OCR serial search
Three fixes: 1. Barcode scanner: load ZXing locally with CDN fallback so it works even when CDN is unreachable. 2. Scan result: add 'Register' button to create a new asset from a scanned machine_id — works for both found and not-found results. 3. OCR serial search: when extracted machine_id isn't found, also try searching all numeric strings from OCR text as serial numbers; add 'Register as New Asset' button on OCR not-found result.
This commit is contained in:
+61
-6
@@ -4,7 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<title>Canteen Asset Tracker</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/umd/index.min.js"></script>
|
||||
<!-- ZXing barcode scanner: local-first, CDN as fallback for offline/blocked networks -->
|
||||
<script src="/zxing.min.js" onerror="this.remove();var s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/umd/index.min.js';document.head.appendChild(s)"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js"></script>
|
||||
<!-- Leaflet Map -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
@@ -3144,6 +3145,7 @@
|
||||
${hasGps ? '✓ GPS captured' : '📍 GPS needed'}
|
||||
</span>
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTab('tabAssets');viewAsset(${asset.id});" style="flex:1;">View Details</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="registerNewAsset('${esc(asset.machine_id)}')" style="flex:1;">➕ Register</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="resetBarcodeScanner()" style="flex:1;">Scan New</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -3238,10 +3240,32 @@
|
||||
Try OCR scanning the sticker, or search in the Assets tab.
|
||||
</div>
|
||||
<div style="margin-top:10px;">
|
||||
<button class="btn btn-outline btn-sm" onclick="registerNewAsset('${esc(machineId)}')" style="width:100%;margin-bottom:6px;">➕ Register as New Asset</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="resetBarcodeScanner()" style="width:100%;">Scan New</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Register scanned barcode as a new asset ──────────────────────────
|
||||
function registerNewAsset(machineId) {
|
||||
const name = prompt('Enter a name for the new asset (machine_id: ' + machineId + '):');
|
||||
if (!name) return;
|
||||
api('/api/assets', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
machine_id: machineId,
|
||||
name: name,
|
||||
category: 'other',
|
||||
status: 'active',
|
||||
}),
|
||||
}).then(asset => {
|
||||
showToast('✅ Asset created: ' + esc(asset.name), false);
|
||||
showScannedAsset(asset);
|
||||
}).catch(e => {
|
||||
showToast('❌ Failed to create asset: ' + (e.message || e), true);
|
||||
});
|
||||
}
|
||||
|
||||
function showCheckinForm() {
|
||||
document.getElementById('checkinNotes').value = '';
|
||||
document.getElementById('checkinCard').style.display = 'block';
|
||||
@@ -3570,11 +3594,42 @@
|
||||
} catch (e) {
|
||||
if (e.message === 'Asset not found' || e.message.includes('404')) {
|
||||
const el = document.getElementById('ocrResult');
|
||||
el.innerHTML = `
|
||||
<div class="or-id">${esc(machineId)} ❌</div>
|
||||
<div class="or-meta" style="color:var(--amber);">Machine ID not found in database</div>
|
||||
<div style="margin-top:8px;"><button class="btn btn-outline btn-sm" onclick="retakeOcr()" style="width:100%;">🔄 Retake</button></div>`;
|
||||
setOcrStatus('Asset not found — try scanning the barcode instead', 'error');
|
||||
// Try to find by serial number from OCR raw_text
|
||||
(async function searchOcrSerials() {
|
||||
const rawText = document.querySelector('#ocrResult .or-raw');
|
||||
if (rawText) {
|
||||
const numbers = rawText.textContent.match(/\d{4,}/g);
|
||||
if (numbers) {
|
||||
for (const n of numbers.slice(0, 3)) {
|
||||
try {
|
||||
const bySerial = await api('/api/assets/search?machine_id=' + encodeURIComponent(n.trim()));
|
||||
if (bySerial) {
|
||||
const oci = categoryInfo(bySerial.category);
|
||||
el.innerHTML = `
|
||||
<div class="or-id">${esc(machineId)} ❌ → ${esc(bySerial.machine_id)} ✅</div>
|
||||
<div class="sr-name" style="font-size:16px;font-weight:700;margin:4px 0;"><span style="margin-right:6px">${oci.icon}</span>${esc(bySerial.name)}</div>
|
||||
<div class="or-meta">Found via serial: ${esc(n)} · ${esc(catLabel(bySerial.category))}</div>
|
||||
<div style="margin-top:8px;display:flex;gap:6px;">
|
||||
<button class="btn btn-outline btn-sm" onclick="switchTab('tabAssets');viewAsset(${bySerial.id});" style="flex:1;">View Details</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="resetOcrScanner()" style="flex:1;">Scan New</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="retakeOcr()" style="flex:1;">🔄 Retake</button>
|
||||
</div>`;
|
||||
setOcrStatus('Asset found via serial number!', 'success');
|
||||
AppState.currentAssetId = bySerial.id;
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No serial match either — show not-found
|
||||
el.innerHTML = `
|
||||
<div class="or-id">${esc(machineId)} ❌</div>
|
||||
<div class="or-meta" style="color:var(--amber);">Machine ID not found in database</div>
|
||||
<div style="margin-top:8px;"><button class="btn btn-outline btn-sm" onclick="retakeOcr()" style="width:100%;">🔄 Retake</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="registerNewAsset('${esc(machineId)}')" style="width:100%;margin-top:4px;">➕ Register as New Asset</button></div>`;
|
||||
setOcrStatus('Asset not found', 'error');
|
||||
})();
|
||||
} else {
|
||||
setOcrStatus('Lookup error: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user