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
+7
View File
@@ -65,6 +65,13 @@ def get_stats():
"""Get dashboard stats summary""" """Get dashboard stats summary"""
db = get_database() db = get_database()
stats = db.get_dashboard_stats() stats = db.get_dashboard_stats()
# If no check in database, fall back to scraper state's last_check
if not stats.get('last_check'):
state = scraper_state.get_state()
if state.get('last_check'):
stats['last_check'] = {'checked_at': state['last_check']}
return jsonify(stats) return jsonify(stats)
+6 -1
View File
@@ -15,7 +15,12 @@ from flask_cors import CORS
from src.database import get_database from src.database import get_database
from src.favorites import get_favorites_manager from src.favorites import get_favorites_manager
from .api import api_bp
# Handle both direct execution and package import
try:
from .api import api_bp
except ImportError:
from api import api_bp
# Create Flask app # Create Flask app
app = Flask(__name__) app = Flask(__name__)
+43 -12
View File
@@ -86,6 +86,9 @@ function initEventListeners() {
} }
}); });
// Clear filters button
document.getElementById('clearFiltersBtn')?.addEventListener('click', clearAllFilters);
// Add favorite button // Add favorite button
document.getElementById('addFavoriteBtn')?.addEventListener('click', addFavorite); 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 // API Helpers
async function api(endpoint, options = {}) { async function api(endpoint, options = {}) {
try { try {
@@ -328,23 +341,23 @@ async function loadProducts() {
const cleanName = cleanProductName(product.name); const cleanName = cleanProductName(product.name);
const hasImage = product.image_url && !product.image_url.includes('data:') && product.image_url.length > 10; 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 ` return `
<div class="product-card ${product.in_stock ? 'in-stock' : 'out-of-stock'} ${product.is_favorite ? 'favorite' : ''}" <div class="product-card ${product.in_stock ? 'in-stock' : 'out-of-stock'} ${product.is_favorite ? 'favorite' : ''}"
onclick="showProductDetails(${product.id})"> onclick="showProductDetails(${product.id})">
<div class="product-image-container"> <div class="product-image-container">
${hasImage ${hasImage
? `<img src="${product.image_url}" class="product-image" alt="" onerror="this.outerHTML=\`${storeLogoHtml.replace(/`/g, '\\`').replace(/\n/g, '')}\`">` ? `<img src="${product.image_url}" class="product-image" alt="" onerror="handleProductImageError(this, '${product.site}')">`
: 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>`
} }
<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>
<div class="product-name">${cleanName}</div> <div class="product-name">${cleanName}</div>
<div class="product-meta"> <div class="product-meta">
@@ -356,7 +369,7 @@ async function loadProducts() {
<div class="product-actions"> <div class="product-actions">
<span class="product-site">${storeInfo.name}</span> <span class="product-site">${storeInfo.name}</span>
<button class="btn-favorite ${product.is_favorite ? 'active' : ''}" <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;'} ${product.is_favorite ? '&#9733;' : '&#9734;'}
</button> </button>
</div> </div>
@@ -365,6 +378,24 @@ async function loadProducts() {
}).join(''); }).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) { async function showProductDetails(productId) {
const product = await api(`/products/${productId}`); const product = await api(`/products/${productId}`);
const history = await api(`/products/${productId}/history`); const history = await api(`/products/${productId}/history`);
+37
View File
@@ -502,6 +502,13 @@ h3 {
cursor: pointer; cursor: pointer;
} }
.btn-clear-filters {
margin-left: auto;
padding: 8px 16px !important;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
/* Products Grid */ /* Products Grid */
.products-grid { .products-grid {
display: grid; display: grid;
@@ -545,6 +552,36 @@ h3 {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; 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 { .product-image {
+5 -1
View File
@@ -80,8 +80,11 @@
<div class="filters"> <div class="filters">
<select id="filterSite"> <select id="filterSite">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="pokemoncenter">PokemonCenter</option> <option value="pokemoncenter">Pokemon Center</option>
<option value="target">Target</option> <option value="target">Target</option>
<option value="bestbuy">Best Buy</option>
<option value="gamestop">GameStop</option>
<option value="walmart">Walmart</option>
</select> </select>
<select id="filterCategory"> <select id="filterCategory">
<option value="">All Categories</option> <option value="">All Categories</option>
@@ -101,6 +104,7 @@
<input type="checkbox" id="filterFavorites"> <input type="checkbox" id="filterFavorites">
Favorites Only Favorites Only
</label> </label>
<button id="clearFiltersBtn" class="btn-secondary btn-clear-filters">Clear Filters</button>
</div> </div>
<div class="products-grid" id="productsGrid"> <div class="products-grid" id="productsGrid">