feat: revised category icons + unknown type detection
- 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
This commit is contained in:
@@ -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",),
|
||||
|
||||
+29
-24
@@ -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 = `
|
||||
<div class="sr-name">${esc(asset.name)}</div>
|
||||
<div class="sr-name"><span style="margin-right:6px">${categoryInfo(asset.category).icon}</span>${esc(asset.name)}</div>
|
||||
<div class="sr-meta">
|
||||
${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 = `
|
||||
<div class="or-id">${esc(asset.machine_id)} ✅</div>
|
||||
<div class="sr-name" style="font-size:16px;font-weight:700;margin:4px 0;">${esc(asset.name)}</div>
|
||||
<div class="sr-name" style="font-size:16px;font-weight:700;margin:4px 0;"><span style="margin-right:6px">${oci.icon}</span>${esc(asset.name)}</div>
|
||||
<div class="or-meta">${esc(catLabel(asset.category))} · ${esc(asset.status)}</div>`;
|
||||
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 `
|
||||
<div class="asset-item" onclick="viewAsset(${a.id})">
|
||||
<div class="ai-icon${iconWide}" style="background:${catColor}22;color:${catColor}">${icon}</div>
|
||||
<div class="ai-icon${ci.wide ? ' ai-icon-wide' : ''}" style="background:${ci.color}22;color:${ci.color}">${ci.icon}</div>
|
||||
<div class="ai-info">
|
||||
${renderAssetCardContent(a)}
|
||||
<div class="ai-meta">
|
||||
@@ -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 = `<span style="margin-right:8px">${ci.icon}</span>${esc(a.name)}`;
|
||||
document.getElementById('detailMeta').innerHTML =
|
||||
`${esc(a.machine_id)}${a.disney_park ? ' · <span class="disney-tag">' + parkIcon(a.disney_park) + ' ' + parkName(a.disney_park) + '</span>' : ''}`;
|
||||
|
||||
@@ -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('<img') !== -1) icon = '🍽';
|
||||
var popupHtml = '<b>' + esc(a.name) + '</b><br>' + esc(a.machine_id || '') +
|
||||
'<br><button class="btn btn-outline btn-sm" style="margin-top:6px;" ' +
|
||||
'onclick="switchTab(\'tabAssets\');viewAsset(' + a.id + ');">View Details</button>';
|
||||
var catColor = catEntry ? catEntry.color : '#6b7280';
|
||||
var catColor = ci.color;
|
||||
var pinIcon = L.divIcon({
|
||||
className: 'cat-pin-icon',
|
||||
html: '<div style="width:28px;height:28px;border-radius:50%;background:' + catColor +
|
||||
@@ -4754,8 +4759,8 @@
|
||||
var sorted = assets.slice().sort(function(a, b) { return (a.name || '').localeCompare(b.name || ''); });
|
||||
var html = '';
|
||||
sorted.forEach(function(a) {
|
||||
var catEntry = CATEGORY_MAP[a.category];
|
||||
var icon = catEntry ? catEntry.icon : '📦';
|
||||
var ci = categoryInfo(a.category);
|
||||
var icon = ci.icon;
|
||||
if (icon.indexOf('<img') !== -1) icon = '🍽';
|
||||
var hasGps = (a.latitude != null && a.longitude != null);
|
||||
html += '<div class="map-asset-item" onclick="switchTab(\'tabAssets\');viewAsset(' + a.id + ');">' +
|
||||
|
||||
Reference in New Issue
Block a user