feat: add Category, Make, Disney Park to filter panel dropdowns with backend endpoints

This commit is contained in:
2026-05-22 18:19:32 -04:00
parent d98a791c96
commit e0412acbe3
2 changed files with 96 additions and 23 deletions
+20
View File
@@ -837,6 +837,26 @@ def list_users():
return [dict(r) for r in rows] return [dict(r) for r in rows]
@app.get("/api/categories")
def list_categories():
conn = get_db()
rows = conn.execute(
"SELECT DISTINCT category FROM assets WHERE category IS NOT NULL AND category != '' ORDER BY category"
).fetchall()
conn.close()
return [r["category"] for r in rows]
@app.get("/api/makes")
def list_makes():
conn = get_db()
rows = conn.execute(
"SELECT DISTINCT make FROM assets WHERE make IS NOT NULL AND make != '' ORDER BY make"
).fetchall()
conn.close()
return [r["make"] for r in rows]
# ─── Task 4: POST /api/assets ────────────────────────────────────────────── # ─── Task 4: POST /api/assets ──────────────────────────────────────────────
+76 -23
View File
@@ -1116,6 +1116,45 @@
</div> </div>
<!-- Filter panel (collapsible) --> <!-- Filter panel (collapsible) -->
<div class="filter-panel" id="filterPanel"> <div class="filter-panel" id="filterPanel">
<div class="fp-row">
<div style="flex:1">
<div class="fp-label">Category</div>
<select id="filterCategory" class="input-field" onchange="onFilterChange()">
<option value="">All categories</option>
</select>
</div>
<div style="flex:1">
<div class="fp-label">Make</div>
<select id="filterMake" class="input-field" onchange="onFilterChange()">
<option value="">All makes</option>
</select>
</div>
</div>
<div class="fp-row">
<div style="flex:1">
<div class="fp-label">Disney Park</div>
<select id="filterDisneyPark" class="input-field" onchange="onFilterChange()">
<option value="">All parks</option>
<option value="magic-kingdom">🏰 Magic Kingdom</option>
<option value="epcot">🌍 Epcot</option>
<option value="hollywood-studios">🎬 Hollywood Studios</option>
<option value="animal-kingdom">🌿 Animal Kingdom</option>
<option value="disney-springs">🛍️ Disney Springs</option>
<option value="resort">🏨 Resort</option>
<option value="office">🏢 Office</option>
<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 class="fp-row">
<div style="flex:1"> <div style="flex:1">
<div class="fp-label">Customer</div> <div class="fp-label">Customer</div>
@@ -1131,21 +1170,13 @@
</div> </div>
</div> </div>
<div class="fp-row"> <div class="fp-row">
<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 style="flex:1"> <div style="flex:1">
<div class="fp-label">Assigned To</div> <div class="fp-label">Assigned To</div>
<select id="filterAssignedTo" class="input-field" onchange="onFilterChange()"> <select id="filterAssignedTo" class="input-field" onchange="onFilterChange()">
<option value="">Anyone</option> <option value="">Anyone</option>
</select> </select>
</div> </div>
<div style="flex:1"></div>
</div> </div>
<div class="fp-actions"> <div class="fp-actions">
<button class="btn btn-outline btn-xs" onclick="clearAllFilters()" style="flex:1;">✕ Clear all filters</button> <button class="btn btn-outline btn-xs" onclick="clearAllFilters()" style="flex:1;">✕ Clear all filters</button>
@@ -3123,17 +3154,27 @@
if (filterDropdownsLoaded) return; if (filterDropdownsLoaded) return;
filterDropdownsLoaded = true; filterDropdownsLoaded = true;
try { try {
// Load categories
var cats = await api('/api/categories');
var catSel = document.getElementById('filterCategory');
catSel.innerHTML = '<option value="">All categories</option>' +
cats.map(function(c) { return '<option value="' + esc(c) + '">' + esc(c) + '</option>'; }).join('');
// Load makes
var makes = await api('/api/makes');
var makeSel = document.getElementById('filterMake');
makeSel.innerHTML = '<option value="">All makes</option>' +
makes.map(function(m) { return '<option value="' + esc(m) + '">' + esc(m) + '</option>'; }).join('');
// Load customers // Load customers
const customers = await api('/api/customers'); var customers = await api('/api/customers');
const custSel = document.getElementById('filterCustomer'); var custSel = document.getElementById('filterCustomer');
custSel.innerHTML = '<option value="">All customers</option>' + custSel.innerHTML = '<option value="">All customers</option>' +
customers.map(c => `<option value="${c.id}">${esc(c.name)}</option>`).join(''); customers.map(function(c) { return '<option value="' + c.id + '">' + esc(c.name) + '</option>'; }).join('');
// Load users for assigned_to filter // Load users for assigned_to filter
const users = await api('/api/users'); var users = await api('/api/users');
const assnSel = document.getElementById('filterAssignedTo'); var assnSel = document.getElementById('filterAssignedTo');
assnSel.innerHTML = '<option value="">Anyone</option>' + assnSel.innerHTML = '<option value="">Anyone</option>' +
users.filter(u => u.role === 'technician' || u.role === 'admin') users.filter(function(u) { return u.role === 'technician' || u.role === 'admin'; })
.map(u => `<option value="${esc(u.username)}">${esc(u.username)}</option>`).join(''); .map(function(u) { return '<option value="' + esc(u.username) + '">' + esc(u.username) + '</option>'; }).join('');
} catch (e) { } catch (e) {
// silent fail — dropdowns just show defaults // silent fail — dropdowns just show defaults
} }
@@ -3158,11 +3199,17 @@
} }
function onFilterChange() { function onFilterChange() {
var catEl = document.getElementById('filterCategory');
var makeEl = document.getElementById('filterMake');
var parkEl = document.getElementById('filterDisneyPark');
var custEl = document.getElementById('filterCustomer'); var custEl = document.getElementById('filterCustomer');
var locEl = document.getElementById('filterLocation'); var locEl = document.getElementById('filterLocation');
var stEl = document.getElementById('filterStatus'); var stEl = document.getElementById('filterStatus');
var assnEl = document.getElementById('filterAssignedTo'); var assnEl = document.getElementById('filterAssignedTo');
assetFilters.category = catEl.value || null;
assetFilters.make = makeEl.value || null;
assetFilters.disney_park = parkEl.value || null;
assetFilters.customer_id = custEl.value || null; assetFilters.customer_id = custEl.value || null;
assetFilters.status = stEl.value || null; assetFilters.status = stEl.value || null;
assetFilters.assigned_to = assnEl.value || null; assetFilters.assigned_to = assnEl.value || null;
@@ -3254,13 +3301,16 @@
assetFilters.q = ''; assetFilters.q = '';
document.getElementById('clearSearch').style.display = 'none'; document.getElementById('clearSearch').style.display = 'none';
} else if (key === 'category') { } else if (key === 'category') {
document.getElementById('filterCategory').value = '';
assetFilters.category = null; assetFilters.category = null;
} else if (key === 'status') { } else if (key === 'status') {
document.getElementById('filterStatus').value = ''; document.getElementById('filterStatus').value = '';
assetFilters.status = null; assetFilters.status = null;
} else if (key === 'make') { } else if (key === 'make') {
document.getElementById('filterMake').value = '';
assetFilters.make = null; assetFilters.make = null;
} else if (key === 'disney_park') { } else if (key === 'disney_park') {
document.getElementById('filterDisneyPark').value = '';
assetFilters.disney_park = null; assetFilters.disney_park = null;
} else if (key === 'customer_id') { } else if (key === 'customer_id') {
document.getElementById('filterCustomer').value = ''; document.getElementById('filterCustomer').value = '';
@@ -3285,6 +3335,9 @@
customer_id: null, location_id: null, assigned_to: null }; customer_id: null, location_id: null, assigned_to: null };
document.getElementById('assetSearch').value = ''; document.getElementById('assetSearch').value = '';
document.getElementById('clearSearch').style.display = 'none'; document.getElementById('clearSearch').style.display = 'none';
document.getElementById('filterCategory').value = '';
document.getElementById('filterMake').value = '';
document.getElementById('filterDisneyPark').value = '';
document.getElementById('filterCustomer').value = ''; document.getElementById('filterCustomer').value = '';
document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>'; document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>';
document.getElementById('filterLocation').disabled = true; document.getElementById('filterLocation').disabled = true;
@@ -3392,14 +3445,14 @@
assetFilters.status = status; assetFilters.status = status;
assetFilters.make = make; assetFilters.make = make;
assetFilters.disney_park = disney_park; assetFilters.disney_park = disney_park;
// Sync dropdowns
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; assetOffset = 0;
loadAssets(); _loadAssets();
}
function clearAssetSearch() {
document.getElementById('assetSearch').value = '';
assetOffset = 0;
loadAssets();
} }
// ── Pagination ────────────────────────────────────────────────────────── // ── Pagination ──────────────────────────────────────────────────────────