From f4cbe00f7fbf338c185978d6ee0f2dc20eca00aa Mon Sep 17 00:00:00 2001 From: Shawn Date: Sun, 24 May 2026 17:16:46 -0400 Subject: [PATCH] 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 --- static/index.html | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/static/index.html b/static/index.html index eb152df..d105040 100644 --- a/static/index.html +++ b/static/index.html @@ -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 @@
${icon}
-
${esc(a.name)}
- ${a.address ? `
📍 ${esc(a.address)}
` : ''} - ${(a.floor || a.building_number || a.building_name || a.room) ? `
${[a.floor, a.building_number, a.building_name, a.room].filter(Boolean).join(' · ')}
` : ''} + ${renderAssetCardContent(a)}
${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(`
${esc(company)}
`); + const locParts = []; + if (a.location_area) locParts.push(`${esc(a.location_area)}`); + if (a.place) locParts.push(esc(a.place)); + if (locParts.length) parts.push(`
${locParts.join(' · ')}
`); + 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(`
${bldParts.join(' · ')}
`); + if (a.address) parts.push(`
📍 ${esc(a.address)}
`); + return parts.join(''); + } + function setFilter(category, status, make, disney_park) { assetFilters.category = category; assetFilters.status = status;