feat: add Category, Make, Disney Park to filter panel dropdowns with backend endpoints
This commit is contained in:
@@ -837,6 +837,26 @@ def list_users():
|
||||
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 ──────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
+76
-23
@@ -1116,6 +1116,45 @@
|
||||
</div>
|
||||
<!-- Filter panel (collapsible) -->
|
||||
<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 style="flex:1">
|
||||
<div class="fp-label">Customer</div>
|
||||
@@ -1131,21 +1170,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<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 class="fp-label">Assigned To</div>
|
||||
<select id="filterAssignedTo" class="input-field" onchange="onFilterChange()">
|
||||
<option value="">Anyone</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="flex:1"></div>
|
||||
</div>
|
||||
<div class="fp-actions">
|
||||
<button class="btn btn-outline btn-xs" onclick="clearAllFilters()" style="flex:1;">✕ Clear all filters</button>
|
||||
@@ -3123,17 +3154,27 @@
|
||||
if (filterDropdownsLoaded) return;
|
||||
filterDropdownsLoaded = true;
|
||||
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
|
||||
const customers = await api('/api/customers');
|
||||
const custSel = document.getElementById('filterCustomer');
|
||||
var customers = await api('/api/customers');
|
||||
var custSel = document.getElementById('filterCustomer');
|
||||
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
|
||||
const users = await api('/api/users');
|
||||
const assnSel = document.getElementById('filterAssignedTo');
|
||||
var users = await api('/api/users');
|
||||
var assnSel = document.getElementById('filterAssignedTo');
|
||||
assnSel.innerHTML = '<option value="">Anyone</option>' +
|
||||
users.filter(u => u.role === 'technician' || u.role === 'admin')
|
||||
.map(u => `<option value="${esc(u.username)}">${esc(u.username)}</option>`).join('');
|
||||
users.filter(function(u) { return u.role === 'technician' || u.role === 'admin'; })
|
||||
.map(function(u) { return '<option value="' + esc(u.username) + '">' + esc(u.username) + '</option>'; }).join('');
|
||||
} catch (e) {
|
||||
// silent fail — dropdowns just show defaults
|
||||
}
|
||||
@@ -3158,11 +3199,17 @@
|
||||
}
|
||||
|
||||
function onFilterChange() {
|
||||
var catEl = document.getElementById('filterCategory');
|
||||
var makeEl = document.getElementById('filterMake');
|
||||
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;
|
||||
assetFilters.make = makeEl.value || null;
|
||||
assetFilters.disney_park = parkEl.value || null;
|
||||
assetFilters.customer_id = custEl.value || null;
|
||||
assetFilters.status = stEl.value || null;
|
||||
assetFilters.assigned_to = assnEl.value || null;
|
||||
@@ -3254,13 +3301,16 @@
|
||||
assetFilters.q = '';
|
||||
document.getElementById('clearSearch').style.display = 'none';
|
||||
} 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;
|
||||
} else if (key === 'disney_park') {
|
||||
document.getElementById('filterDisneyPark').value = '';
|
||||
assetFilters.disney_park = null;
|
||||
} else if (key === 'customer_id') {
|
||||
document.getElementById('filterCustomer').value = '';
|
||||
@@ -3285,6 +3335,9 @@
|
||||
customer_id: null, location_id: null, assigned_to: null };
|
||||
document.getElementById('assetSearch').value = '';
|
||||
document.getElementById('clearSearch').style.display = 'none';
|
||||
document.getElementById('filterCategory').value = '';
|
||||
document.getElementById('filterMake').value = '';
|
||||
document.getElementById('filterDisneyPark').value = '';
|
||||
document.getElementById('filterCustomer').value = '';
|
||||
document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>';
|
||||
document.getElementById('filterLocation').disabled = true;
|
||||
@@ -3392,14 +3445,14 @@
|
||||
assetFilters.status = status;
|
||||
assetFilters.make = make;
|
||||
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;
|
||||
loadAssets();
|
||||
}
|
||||
|
||||
function clearAssetSearch() {
|
||||
document.getElementById('assetSearch').value = '';
|
||||
assetOffset = 0;
|
||||
loadAssets();
|
||||
_loadAssets();
|
||||
}
|
||||
|
||||
// ── Pagination ──────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user