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]
|
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
@@ -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 ──────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user