feat: scan existing asset redirects to detail view instead of scan result card

When a user scans a barcode that matches an existing asset:
- Redirect directly to the asset detail view (Assets tab)
- Auto-checkin still runs in background if GPS available
- Show a toast notification with the asset name

When scanning a barcode not in database:
- Switch back to Add Asset tab and show the create form
- Deferred status update survives async startScanning

No backend changes needed.

Closes #39
This commit is contained in:
2026-05-24 17:16:46 -04:00
parent b896a5680d
commit f4cbe00f7f
+37 -5
View File
@@ -416,6 +416,10 @@
.asset-item .ai-icon.cat-Other { background: #1a1b26; }
.asset-item .ai-info { flex: 1; min-width: 0; }
.asset-item .ai-name { font-weight: 600; font-size: 15px; line-height: 1.35; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.asset-item .ai-company { font-weight: 600; font-size: 14px; line-height: 1.3; }
.asset-item .ai-location-line { font-size: 12px; color: var(--text2); margin-top: 1px; display: flex; gap: 4px; flex-wrap: wrap; }
.asset-item .ai-location-line .tag { background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 4px; }
.asset-item .ai-building { font-size: 12px; color: var(--text1); margin-top: 2px; }
.asset-item .ai-address { font-size: 12px; color: var(--text2); margin-top: 2px; }
.asset-item .ai-location { font-size: 13px; color: var(--text1); font-weight: 500; margin-top: 3px; }
.asset-item .ai-meta { font-size: 12px; color: var(--text2); margin-top: 3px; }
@@ -2431,7 +2435,14 @@
try {
const asset = await api(`/api/assets/search?machine_id=${encodeURIComponent(machineId)}`);
showScannedAsset(asset);
// Asset exists — go directly to its detail view
showToast(`${esc(asset.name)} — showing details`, false);
// Auto-checkin if GPS available (background, non-blocking)
if (AppState.gpsLat != null) {
doAutoCheckin(asset.id);
}
switchTab('tabAssets');
viewAsset(asset.id);
} catch (e) {
if (e.message === 'Asset not found' || e.message.includes('404')) {
showNewAssetForm(machineId);
@@ -2498,7 +2509,12 @@
function showNewAssetForm(machineId) {
currentScannedMachineId = machineId;
setScanStatus('Machine ID not in database — create asset below', 'error');
switchTab('tabAddAsset');
// Defer status update so it runs after async startScanning completes
setTimeout(() => {
setScanStatus('Machine ID not in database — create asset below', 'error');
}, 50);
document.getElementById('newMachineId').value = machineId;
document.getElementById('newName').value = '';
@@ -3702,9 +3718,7 @@
<div class="asset-item" onclick="viewAsset(${a.id})">
<div class="ai-icon${iconWide}" style="background:${catColor}22;color:${catColor}">${icon}</div>
<div class="ai-info">
<div class="ai-name">${esc(a.name)}</div>
${a.address ? `<div class="ai-address">📍 ${esc(a.address)}</div>` : ''}
${(a.floor || a.building_number || a.building_name || a.room) ? `<div class="ai-location">${[a.floor, a.building_number, a.building_name, a.room].filter(Boolean).join(' · ')}</div>` : ''}
${renderAssetCardContent(a)}
<div class="ai-meta">
${esc(a.machine_id)}${a.serial_number ? ' · S/N: ' + esc(a.serial_number) : ''}
${a.make ? ' · ' + esc(a.make) : ''}
@@ -3717,6 +3731,24 @@
}).join('');
}
function renderAssetCardContent(a) {
const parts = [];
const company = a.company || a.name;
parts.push(`<div class="ai-company">${esc(company)}</div>`);
const locParts = [];
if (a.location_area) locParts.push(`<span class="tag">${esc(a.location_area)}</span>`);
if (a.place) locParts.push(esc(a.place));
if (locParts.length) parts.push(`<div class="ai-location-line">${locParts.join(' · ')}</div>`);
const bldParts = [];
if (a.building_number) bldParts.push(`Bldg ${esc(a.building_number)}`);
if (a.trailer_number) bldParts.push(`Trailer ${esc(a.trailer_number)}`);
if (a.floor) bldParts.push(esc(a.floor));
if (a.room) bldParts.push(`Room ${esc(a.room)}`);
if (bldParts.length) parts.push(`<div class="ai-building">${bldParts.join(' · ')}</div>`);
if (a.address) parts.push(`<div class="ai-address">📍 ${esc(a.address)}</div>`);
return parts.join('');
}
function setFilter(category, status, make, disney_park) {
assetFilters.category = category;
assetFilters.status = status;