feat: Add clear filters functionality and improve product filter options in dashboard

This commit is contained in:
2026-03-28 13:02:13 -04:00
parent 25cfa80f20
commit ad5d179ba3
5 changed files with 98 additions and 14 deletions
+43 -12
View File
@@ -86,6 +86,9 @@ function initEventListeners() {
}
});
// Clear filters button
document.getElementById('clearFiltersBtn')?.addEventListener('click', clearAllFilters);
// Add favorite button
document.getElementById('addFavoriteBtn')?.addEventListener('click', addFavorite);
@@ -96,6 +99,16 @@ function initEventListeners() {
});
}
// Clear all product filters
function clearAllFilters() {
document.getElementById('filterSite').value = '';
document.getElementById('filterCategory').value = '';
document.getElementById('filterStock').value = '';
document.getElementById('filterFavorites').checked = false;
currentEventFilter = null;
loadProducts();
}
// API Helpers
async function api(endpoint, options = {}) {
try {
@@ -328,23 +341,23 @@ async function loadProducts() {
const cleanName = cleanProductName(product.name);
const hasImage = product.image_url && !product.image_url.includes('data:') && product.image_url.length > 10;
// Build store logo fallback HTML
const storeLogoHtml = `
<div class="product-store-icon" style="background:${storeInfo.bgColor};">
<img src="${storeInfo.logo}" alt="${storeInfo.name}" class="product-store-logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="product-store-fallback" style="display:none; color:${storeInfo.color};">${storeInfo.fallback}</span>
</div>
`;
return `
<div class="product-card ${product.in_stock ? 'in-stock' : 'out-of-stock'} ${product.is_favorite ? 'favorite' : ''}"
onclick="showProductDetails(${product.id})">
<div class="product-image-container">
${hasImage
? `<img src="${product.image_url}" class="product-image" alt="" onerror="this.outerHTML=\`${storeLogoHtml.replace(/`/g, '\\`').replace(/\n/g, '')}\`">`
: storeLogoHtml
? `<img src="${product.image_url}" class="product-image" alt="" onerror="handleProductImageError(this, '${product.site}')">`
: `<div class="product-store-icon" style="background:${storeInfo.bgColor};">
<img src="${storeInfo.logo}" alt="${storeInfo.name}" class="product-store-logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="product-store-fallback" style="display:none; color:${storeInfo.color};">${storeInfo.fallback}</span>
</div>`
}
<div class="product-store-badge" style="background:${storeInfo.bgColor};">
<img src="${storeInfo.logo}" alt="${storeInfo.name}" class="store-badge-logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="store-badge-fallback" style="display:none; color:${storeInfo.color};">${storeInfo.fallback}</span>
</div>
</div>
<div class="product-name">${cleanName}</div>
<div class="product-meta">
@@ -356,7 +369,7 @@ async function loadProducts() {
<div class="product-actions">
<span class="product-site">${storeInfo.name}</span>
<button class="btn-favorite ${product.is_favorite ? 'active' : ''}"
onclick="event.stopPropagation(); toggleFavorite('${product.url}', '${cleanName}', ${product.favorite_id || 'null'})">
onclick="event.stopPropagation(); toggleFavorite('${product.url}', '${cleanName.replace(/'/g, "\\'")}', ${product.favorite_id || 'null'})">
${product.is_favorite ? '&#9733;' : '&#9734;'}
</button>
</div>
@@ -365,6 +378,24 @@ async function loadProducts() {
}).join('');
}
// Handle product image errors by showing store icon
function handleProductImageError(img, site) {
const storeInfo = STORE_INFO[site] || { name: site, color: '#333', bgColor: '#666', logo: '', fallback: '?' };
const container = img.parentElement;
img.style.display = 'none';
// Create fallback element
const fallback = document.createElement('div');
fallback.className = 'product-store-icon';
fallback.style.background = storeInfo.bgColor;
fallback.innerHTML = `
<img src="${storeInfo.logo}" alt="${storeInfo.name}" class="product-store-logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<span class="product-store-fallback" style="display:none; color:${storeInfo.color};">${storeInfo.fallback}</span>
`;
container.insertBefore(fallback, img);
}
async function showProductDetails(productId) {
const product = await api(`/products/${productId}`);
const history = await api(`/products/${productId}/history`);
+37
View File
@@ -502,6 +502,13 @@ h3 {
cursor: pointer;
}
.btn-clear-filters {
margin-left: auto;
padding: 8px 16px !important;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
/* Products Grid */
.products-grid {
display: grid;
@@ -545,6 +552,36 @@ h3 {
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
/* Store badge overlay on product images */
.product-store-badge {
position: absolute;
bottom: 8px;
right: 8px;
width: 32px;
height: 32px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
z-index: 10;
}
.store-badge-logo {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.store-badge-fallback {
font-size: 12px;
font-weight: 800;
align-items: center;
justify-content: center;
}
.product-image {