From f1a3bb9f006e78c59dc0385b5d6da1267e510d49 Mon Sep 17 00:00:00 2001 From: Shawn Date: Sun, 31 May 2026 00:28:03 -0400 Subject: [PATCH] feat: revised category icons + unknown type detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cleaned CATEGORY_MAP: removed dead entries (Furniture, GF Bev, Snack/bev, Snack/food), fixed duplicate 'Food' key, proper distinct icons for all 6 real categories - Added categoryInfo() helper: ๐Ÿฅค/๐Ÿฟ/๐Ÿ”/๐Ÿ”ง/๐Ÿ”Œ/๐Ÿ“ฆ for known cats, โ“ (red) for unmapped categories, ๐Ÿ“ฆ for null - Updated all render sites (list, map pins, map list, detail view, scan result, OCR result) to use categoryInfo() - Added category icons to detail view and scan/OCR result headers - Synced categories DB table: added Equipment/Appliances with icons, removed unused Unknown - Updated seed data in both server.py and admin_server.py to match real asset categories - Added category_health to /api/stats with unmapped category detection - Added Category Health card to admin dashboard (shown only when unmapped categories exist) - Updated CSS backgrounds for all real categories + .cat-unmapped --- server.py | 5 +++-- static/index.html | 53 ++++++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/server.py b/server.py index 4159309..7088820 100644 --- a/server.py +++ b/server.py @@ -342,8 +342,9 @@ def _seed_if_empty(conn: sqlite3.Connection, table: str, columns: tuple, rows: l def _seed_data(conn: sqlite3.Connection): """Insert default seed data for lookup tables.""" _seed_if_empty(conn, "categories", ("name", "icon"), [ - ("Furniture", "๐Ÿช‘"), ("Appliances", "๐Ÿ”Œ"), - ("Utensils & Serveware", "๐Ÿฝ๏ธ"), ("Equipment", "โš™๏ธ"), ("Other", "๐Ÿ“ฆ"), + ("Bev", "๐Ÿฅค"), ("Snack", "๐Ÿฟ"), + ("Food", "๐Ÿ”"), ("Equipment", "๐Ÿ”ง"), + ("Appliances", "๐Ÿ”Œ"), ("Other", "๐Ÿ“ฆ"), ]) _seed_if_empty(conn, "key_names", ("name",), [ ("MK500",), ("Green Dot",), ("Red Key",), ("Blue Key",), diff --git a/static/index.html b/static/index.html index 962833e..cbfb95e 100644 --- a/static/index.html +++ b/static/index.html @@ -423,11 +423,13 @@ width: 64px; padding: 0 4px; } .asset-item .ai-icon .cat-icon-img { width: 32px; height: 32px; object-fit: contain; } - .asset-item .ai-icon.cat-Furniture { background: #1a2e2a; } + .asset-item .ai-icon.cat-Bev { background: #0a3a65; } + .asset-item .ai-icon.cat-Snack { background: #3a2a10; } + .asset-item .ai-icon.cat-Food { background: #3a1a0a; } + .asset-item .ai-icon.cat-Equipment { background: #1a1a2e; } .asset-item .ai-icon.cat-Appliances { background: #1a2a3e; } - .asset-item .ai-icon.cat-Utensils---Serveware { background: #2a2510; } - .asset-item .ai-icon.cat-Equipment { background: #2a1a2e; } .asset-item .ai-icon.cat-Other { background: #1a1b26; } + .asset-item .ai-icon.cat-unmapped { background: #2a0a0a; } .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; } @@ -2557,7 +2559,7 @@ const el = document.getElementById('scanResult'); el.style.display = 'block'; el.innerHTML = ` -
${esc(asset.name)}
+
${categoryInfo(asset.category).icon}${esc(asset.name)}
${esc(asset.machine_id)} ยท ${esc(catLabel(asset.category))} ยท @@ -2853,9 +2855,10 @@ const asset = await api('/api/assets/search?machine_id=' + encodeURIComponent(machineId)); // Asset found โ€” update OCR result with asset info const el = document.getElementById('ocrResult'); + const oci = categoryInfo(asset.category); el.innerHTML = `
${esc(asset.machine_id)} โœ…
-
${esc(asset.name)}
+
${oci.icon}${esc(asset.name)}
${esc(catLabel(asset.category))} ยท ${esc(asset.status)}
`; setOcrStatus('Asset found!', 'success'); @@ -3141,17 +3144,21 @@ const CATEGORY_MAP = { 'Bev': { icon: '๐Ÿฅค', color: '#0a7ab5' }, 'Snack': { icon: '๐Ÿฟ', color: '#c97d0e' }, - 'Food': { icon: '๐Ÿ”', color: '#b15a0a' }, - 'Other': { icon: '๐Ÿ“ฆ', color: '#6b7280' }, - 'Snack/bev': { icon: '๐Ÿฅค๐Ÿฟ', color: '#9b59b6', label: 'Snack & Bev', wide: true }, - 'Snack/food': { icon: '๐Ÿ”๐Ÿฟ', color: '#9b59b6', label: 'Snack & Food', wide: true }, - 'Food': { icon: '๐Ÿ”', color: '#d35400' }, + 'Food': { icon: '๐Ÿ”', color: '#e67e22' }, 'Equipment': { icon: '๐Ÿ”ง', color: '#2c3e50' }, - 'Furniture': { icon: '๐Ÿช‘', color: '#8e44ad' }, 'Appliances': { icon: '๐Ÿ”Œ', color: '#3b82f6' }, - 'Unknown': { icon: 'โ“', color: '#6b7280' }, - 'GF Bev': { icon: '๐Ÿฅค', color: '#0a7ab5' }, + 'Other': { icon: '๐Ÿ“ฆ', color: '#6b7280' }, }; + const CATEGORY_UNMAPPED_ICON = 'โ“'; + const CATEGORY_UNMAPPED_COLOR = '#b91c1c'; + const CATEGORY_FALLBACK_ICON = '๐Ÿ“ฆ'; // null/missing category + + function categoryInfo(cat) { + if (!cat) return { icon: CATEGORY_FALLBACK_ICON, color: '#6b7280', mapped: false }; + const e = CATEGORY_MAP[cat]; + if (e) return { icon: e.icon, color: e.color, mapped: true, wide: e.wide }; + return { icon: CATEGORY_UNMAPPED_ICON, color: CATEGORY_UNMAPPED_COLOR, mapped: false }; + } const PARK_ICONS = {'magic-kingdom':'๐Ÿฐ','epcot':'๐ŸŒ','hollywood-studios':'๐ŸŽฌ','animal-kingdom':'๐ŸŒฟ','disney-springs':'๐Ÿ›๏ธ','resort':'๐Ÿจ','office':'๐Ÿข','other':'๐Ÿ“'}; const PARK_NAMES = {'magic-kingdom':'Magic Kingdom','epcot':'Epcot','hollywood-studios':'Hollywood Studios','animal-kingdom':'Animal Kingdom','disney-springs':'Disney Springs','resort':'Resort','office':'Office','other':'Other'}; @@ -3694,13 +3701,10 @@ return; } el.innerHTML = assets.filter(a => a.status !== 'retired').map(a => { - const catEntry = CATEGORY_MAP[a.category]; - const icon = catEntry ? catEntry.icon : (a.category ? a.category.charAt(0).toUpperCase() : '๐Ÿ“ฆ'); - const catColor = catEntry ? catEntry.color : '#1a1b26'; - const iconWide = catEntry && catEntry.wide ? ' ai-icon-wide' : ''; + const ci = categoryInfo(a.category); return `
-
${icon}
+
${ci.icon}
${renderAssetCardContent(a)}
@@ -3813,7 +3817,8 @@ try { const a = await api('/api/assets/' + id); - document.getElementById('detailName').textContent = a.name; + const ci = categoryInfo(a.category); + document.getElementById('detailName').innerHTML = `${ci.icon}${esc(a.name)}`; document.getElementById('detailMeta').innerHTML = `${esc(a.machine_id)}${a.disney_park ? ' ยท ' + parkIcon(a.disney_park) + ' ' + parkName(a.disney_park) + '' : ''}`; @@ -4712,13 +4717,13 @@ // Render pins for assets with GPS matched.forEach(function(a) { if (a.latitude != null && a.longitude != null) { - var catEntry = CATEGORY_MAP[a.category]; - var icon = catEntry ? catEntry.icon : (a.category ? a.category.charAt(0).toUpperCase() : '๐Ÿ“ฆ'); + var ci = categoryInfo(a.category); + var icon = ci.icon; if (icon.indexOf('
' + esc(a.machine_id || '') + '
'; - var catColor = catEntry ? catEntry.color : '#6b7280'; + var catColor = ci.color; var pinIcon = L.divIcon({ className: 'cat-pin-icon', html: '
' +