feat: add No DEX (5d) filter toggle to asset list filter panel

- Add .filter-toggle.warn CSS styles (amber border/text with active bg)
- Add Data Health row with toggle button in filter panel HTML
- Add noDexFilterActive state variable and toggleNoDexFilter() function
- Wire no_dex_days=5 into API URL builder when toggle active
- Integrate into getActiveFilterCount, renderActiveFilterTags, removeFilter, clearAllFilters
This commit is contained in:
2026-05-23 17:41:04 -04:00
parent 9d9e50834c
commit 6016f6a5e6
+62 -35
View File
@@ -405,12 +405,15 @@
width: 42px; height: 42px; border-radius: var(--radius-sm); display: flex;
align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0;
}
.asset-item .ai-icon.ai-icon-wide {
width: 64px; padding: 0 4px;
}
.asset-item .ai-icon .cat-icon-img { width: 28px; height: 28px; object-fit: contain; }
.asset-item .ai-icon.cat-Furniture { background: #1a2e2a; }
.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-icon-img { width: 28px; height: 28px; object-fit: contain; }
.asset-item .ai-info { flex: 1; min-width: 0; }
.asset-item .ai-name { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.asset-item .ai-meta { font-size: 12px; color: var(--text2); margin-top: 2px; }
@@ -679,6 +682,8 @@
background: var(--accent); color: #fff; font-size: 10px; font-weight: 700;
padding: 1px 6px; border-radius: 8px; margin-left: 2px;
}
.filter-toggle.warn { border-color: var(--amber); color: var(--amber); }
.filter-toggle.warn.active { background: rgba(255, 168, 0, 0.15); }
.filter-panel {
display: none; margin-bottom: 8px;
background: var(--card2); border: 1px solid var(--border);
@@ -1176,15 +1181,7 @@
<option value="other">📍 Other</option>
</select>
</div>
<div style="flex:1">
<div class="fp-label">Status</div>
<select id="filterStatus" class="input-field" onchange="onFilterChange()">
<option value="">All statuses</option>
<option value="active">🟢 Active</option>
<option value="maintenance">🟡 Maintenance</option>
<option value="retired">🔴 Retired</option>
</select>
</div>
</div>
<div class="fp-row">
<div style="flex:1">
@@ -1200,6 +1197,15 @@
</select>
</div>
</div>
<div class="fp-row">
<div style="flex:1">
<div class="fp-label">Data Health</div>
<button class="filter-toggle warn" id="filterNoDex" onclick="toggleNoDexFilter()" style="width:100%;">
⚠️ No DEX Report (5d)
</button>
</div>
<div style="flex:1"></div>
</div>
<div class="fp-row">
<div style="flex:1">
<div class="fp-label">Assigned To</div>
@@ -2445,8 +2451,8 @@
el.innerHTML = `
<div class="sr-name">${esc(asset.name)}</div>
<div class="sr-meta">
${esc(asset.machine_id)} · ${esc(asset.category || 'Other')} ·
<span class="status-tag ${asset.status}">${asset.status || 'active'}</span>
${esc(asset.machine_id)} · ${esc(catLabel(asset.category))} ·
</div>
<div class="sr-actions">
<span class="sr-checkin-done" style="color:${hasGps ? '#22c55e' : 'var(--amber)'}">
@@ -3158,12 +3164,11 @@
// ── Category config ───────────────────────────────────────────────────
const CATEGORY_MAP = {
'Bev': { icon: '🥤', color: '#0a7ab5' },
'Snack': { icon: '<img src="icons/chips.png?v=2" class="cat-icon-img" alt="">', color: '#c97d0e' },
'GF Bev': { icon: '🧃', color: '#0a9e5a' },
'GF Food': { icon: '🥪', color: '#8a5c2a' },
'Snack': { icon: '🍿', color: '#c97d0e' },
'Food': { icon: '🍔', color: '#b15a0a' },
'Other': { icon: '📦', color: '#6b7280' },
'Snack/bev': { icon: '🍱', color: '#9b59b6' },
'Snack/food': { icon: '🍱', color: '#9b59b6' },
'Snack/bev': { icon: '🥤🍿', color: '#9b59b6', label: 'Snack & Bev', wide: true },
'Snack/food': { icon: '🍔🍿', color: '#9b59b6', label: 'Snack & Food', wide: true },
'Food': { icon: '🍔', color: '#d35400' },
'Equipment': { icon: '🔧', color: '#2c3e50' },
'Furniture': { icon: '🪑', color: '#8e44ad' },
@@ -3171,6 +3176,10 @@
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'};
function catLabel(cat) {
const e = CATEGORY_MAP[cat];
return e && e.label ? e.label : (cat || 'Other');
}
function parkIcon(key) { return PARK_ICONS[key] || '📍'; }
function parkName(key) { return PARK_NAMES[key] || key; }
@@ -3394,6 +3403,7 @@
// ── Filter state ──────────────────────────────────────────────────────
let assetFilters = { category: null, status: null, make: null, disney_park: null, disney_filter: null, q: '',
customer_id: null, location_id: null, assigned_to: null };
let noDexFilterActive = false;
let assetOffset = 0;
const ASSET_PAGE_SIZE = 2000;
let assetTotal = 0;
@@ -3466,7 +3476,7 @@
var parkEl = document.getElementById('filterDisneyPark');
var custEl = document.getElementById('filterCustomer');
var locEl = document.getElementById('filterLocation');
var stEl = document.getElementById('filterStatus');
var assnEl = document.getElementById('filterAssignedTo');
assetFilters.category = catEl.value || null;
@@ -3482,7 +3492,7 @@
assetFilters.disney_filter = null;
}
assetFilters.customer_id = custEl.value || null;
assetFilters.status = stEl.value || null;
assetFilters.assigned_to = assnEl.value || null;
var prevCust = locEl.dataset.customerId;
@@ -3516,13 +3526,14 @@
function getActiveFilterCount() {
var count = 0;
if (assetFilters.category) count++;
if (assetFilters.status) count++;
if (assetFilters.make) count++;
if (assetFilters.disney_park) count++;
if (assetFilters.disney_filter) count++;
if (assetFilters.customer_id) count++;
if (assetFilters.location_id) count++;
if (assetFilters.assigned_to) count++;
if (noDexFilterActive) count++;
if (assetFilters.q) count++;
return count;
}
@@ -3540,7 +3551,7 @@
var tags = [];
if (assetFilters.q) tags.push({key:'q', label:'\uD83D\uDD0D "' + assetFilters.q + '"'});
if (assetFilters.category) tags.push({key:'category', label:'\uD83D\uDCC2 ' + assetFilters.category});
if (assetFilters.status) tags.push({key:'status', label: statusIcon(assetFilters.status) + ' ' + assetFilters.status});
if (assetFilters.make) tags.push({key:'make', label:'\uD83C\uDFED ' + assetFilters.make});
if (assetFilters.disney_park) tags.push({key:'disney_park', label: parkIcon(assetFilters.disney_park) + ' ' + parkName(assetFilters.disney_park)});
if (assetFilters.disney_filter === 'disney') tags.push({key:'disney_filter', label:'🏰 Disney only'});
@@ -3556,6 +3567,7 @@
tags.push({key:'location_id', label:'\uD83D\uDCCD ' + txt});
}
if (assetFilters.assigned_to) tags.push({key:'assigned_to', label:'\uD83D\uDC64 ' + assetFilters.assigned_to});
if (noDexFilterActive) tags.push({key:'no_dex_days', label:'\u26a0\ufe0f No DEX (5d)'});
el.innerHTML = tags.map(function(t) {
return '<span class="filter-tag">' + esc(t.label) + ' <span class="ft-remove" onclick="removeFilter(\'' + t.key + '\')">\u2715</span></span>';
@@ -3577,9 +3589,6 @@
} else if (key === 'category') {
document.getElementById('filterCategory').value = '';
assetFilters.category = null;
} else if (key === 'status') {
document.getElementById('filterStatus').value = '';
assetFilters.status = null;
} else if (key === 'make') {
document.getElementById('filterMake').value = '';
assetFilters.make = null;
@@ -3601,6 +3610,10 @@
} else if (key === 'assigned_to') {
document.getElementById('filterAssignedTo').value = '';
assetFilters.assigned_to = null;
} else if (key === 'no_dex_days') {
noDexFilterActive = false;
var btn = document.getElementById('filterNoDex');
if (btn) btn.classList.remove('active');
}
renderActiveFilterTags();
assetOffset = 0;
@@ -3619,8 +3632,20 @@
document.getElementById('filterCustomer').value = '';
document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>';
document.getElementById('filterLocation').disabled = true;
document.getElementById('filterStatus').value = '';
document.getElementById('filterAssignedTo').value = '';
noDexFilterActive = false;
var noDexBtn = document.getElementById('filterNoDex');
if (noDexBtn) noDexBtn.classList.remove('active');
renderActiveFilterTags();
assetOffset = 0;
_loadAssets();
}
function toggleNoDexFilter() {
noDexFilterActive = !noDexFilterActive;
var btn = document.getElementById('filterNoDex');
if (btn) btn.classList.toggle('active', noDexFilterActive);
renderActiveFilterTags();
assetOffset = 0;
_loadAssets();
@@ -3631,7 +3656,7 @@
_loadAssets = async function() {
var params = new URLSearchParams();
if (assetFilters.category) params.set('category', assetFilters.category);
if (assetFilters.status) params.set('status', assetFilters.status);
if (assetFilters.make) params.set('make', assetFilters.make);
if (assetFilters.disney_park) params.set('disney_park', assetFilters.disney_park);
if (assetFilters.disney_filter) params.set('disney_filter', assetFilters.disney_filter);
@@ -3639,6 +3664,7 @@
if (assetFilters.customer_id) params.set('customer_id', assetFilters.customer_id);
if (assetFilters.location_id) params.set('location_id', assetFilters.location_id);
if (assetFilters.assigned_to) params.set('assigned_to', assetFilters.assigned_to);
if (noDexFilterActive) params.set('no_dex_days', '5');
params.set('limit', String(ASSET_PAGE_SIZE));
params.set('offset', String(assetOffset));
@@ -3662,20 +3688,21 @@
el.innerHTML = '<div class="empty-state"><div class="es-icon">📦</div>No assets found</div>';
return;
}
el.innerHTML = assets.map(a => {
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' : '';
return `
<div class="asset-item" onclick="viewAsset(${a.id})">
<div class="ai-icon" style="background:${catColor}22;color:${catColor}">${icon}</div>
<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>
<div class="ai-meta">
${esc(a.machine_id)}${a.serial_number ? ' · S/N: ' + esc(a.serial_number) : ''}
${a.make ? ' · ' + esc(a.make) : ''}
${a.disney_park ? ' · <span class="disney-tag">' + parkIcon(a.disney_park) + ' ' + parkName(a.disney_park) + '</span>' : ''}
· <span class="status-tag ${a.status || 'active'}">${a.status || 'active'}</span>
· ${esc(catLabel(a.category))}
</div>
</div>
<div class="ai-arrow"></div>
@@ -3693,7 +3720,7 @@
if (document.getElementById('filterCategory')) document.getElementById('filterCategory').value = category || '';
if (document.getElementById('filterMake')) document.getElementById('filterMake').value = make || '';
if (document.getElementById('filterDisneyPark')) document.getElementById('filterDisneyPark').value = disney_park || '';
if (document.getElementById('filterStatus')) document.getElementById('filterStatus').value = status || '';
renderActiveFilterTags();
assetOffset = 0;
_loadAssets();
@@ -3775,7 +3802,7 @@
document.getElementById('detailName').textContent = a.name;
document.getElementById('detailMeta').innerHTML =
`${esc(a.machine_id)} · <span class="status-tag ${a.status || 'active'}">${a.status || 'active'}</span>${a.disney_park ? ' · <span class="disney-tag">' + parkIcon(a.disney_park) + ' ' + parkName(a.disney_park) + '</span>' : ''}`;
`${esc(a.machine_id)}${a.disney_park ? ' · <span class="disney-tag">' + parkIcon(a.disney_park) + ' ' + parkName(a.disney_park) + '</span>' : ''}`;
// Main details
document.getElementById('detailFields').innerHTML = [
@@ -3783,10 +3810,10 @@
df('Name', a.name),
df('Serial Number', a.serial_number, true),
df('Description', a.description),
df('Category', a.category),
df('Category', catLabel(a.category)),
df('Make', a.make),
df('Model', a.model),
df('Status', statusBadge(a.status)),
].filter(Boolean).join('');
// Disney Park badge
@@ -3896,7 +3923,7 @@
}
function statusBadge(status) {
return `<span class="status-tag ${status || 'active'}">${status || 'active'}</span>`;
return '';
}
function openMapLink() {
@@ -4176,7 +4203,7 @@
<select id="editStatus" class="input-field">
<option value="active" ${a.status === 'active' ? 'selected' : ''}>Active</option>
<option value="maintenance" ${a.status === 'maintenance' ? 'selected' : ''}>Maintenance</option>
<option value="retired" ${a.status === 'retired' ? 'selected' : ''}>Retired</option>
</select>
</div>
<select id="editAssignedTo" class="input-field">