Add product pagination and enable BestBuy
Implement client-side pagination for the products grid: add PRODUCTS_PER_PAGE, currentProductPage, offset in /products requests, renderPagination(), and changeProductPage() (with smooth scroll). Reset page when filters or event filters change and render empty-state pagination. Add pagination container to the template and styles for pagination buttons and info. Also enable "bestbuy" in SITES_ENABLED in config.py and add a taskkill line to .claude/settings.local.json for closing stray python.exe processes.
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
"Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(''''Last sync:'''', d[''''last_sync'''']\\); print\\(''''Products:'''', len\\(d[''''products'''']\\)\\); print\\(''''Sample names:''''\\); [print\\('''' -'''', p[''''name''''][:80]\\) for p in d[''''products''''][:5]]\")",
|
"Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(''''Last sync:'''', d[''''last_sync'''']\\); print\\(''''Products:'''', len\\(d[''''products'''']\\)\\); print\\(''''Sample names:''''\\); [print\\('''' -'''', p[''''name''''][:80]\\) for p in d[''''products''''][:5]]\")",
|
||||||
"Bash(PYTHON=\"/c/Users/hocke/Documents/GitHub/pokemon-stock-checker/.venv/Scripts/python.exe\")",
|
"Bash(PYTHON=\"/c/Users/hocke/Documents/GitHub/pokemon-stock-checker/.venv/Scripts/python.exe\")",
|
||||||
"Bash(\"$PYTHON\" -c \":*)",
|
"Bash(\"$PYTHON\" -c \":*)",
|
||||||
"Bash(1 grep:*)"
|
"Bash(1 grep:*)",
|
||||||
|
"Bash(taskkill /F /IM python.exe)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ SORT_BY_NEWEST = True
|
|||||||
# Which sites to monitor
|
# Which sites to monitor
|
||||||
# Note: PokemonCenter has strong bot protection (Imperva) - may need manual workarounds
|
# Note: PokemonCenter has strong bot protection (Imperva) - may need manual workarounds
|
||||||
# Note: Walmart has aggressive bot protection (PerimeterX) - may need stealth mode
|
# Note: Walmart has aggressive bot protection (PerimeterX) - may need stealth mode
|
||||||
SITES_ENABLED = ["target", "gamestop"] # Options: "pokemoncenter", "target", "gamestop", "bestbuy" (disabled - needs rework), "walmart"
|
SITES_ENABLED = ["target", "gamestop", "bestbuy"] # Options: "pokemoncenter", "target", "gamestop", "bestbuy" (disabled - needs rework), "walmart"
|
||||||
|
|
||||||
# PokemonCenter URLs to monitor
|
# PokemonCenter URLs to monitor
|
||||||
POKEMON_CENTER_URLS = [
|
POKEMON_CENTER_URLS = [
|
||||||
|
|||||||
+39
-1
@@ -6,6 +6,8 @@ const API_BASE = '/api';
|
|||||||
let currentPage = 'dashboard';
|
let currentPage = 'dashboard';
|
||||||
let charts = {};
|
let charts = {};
|
||||||
let currentEventFilter = null; // { type: 'new_drop'|'restock', period: 'today'|'week' }
|
let currentEventFilter = null; // { type: 'new_drop'|'restock', period: 'today'|'week' }
|
||||||
|
let currentProductPage = 1;
|
||||||
|
const PRODUCTS_PER_PAGE = 50;
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
@@ -81,6 +83,7 @@ function initEventListeners() {
|
|||||||
if (el) {
|
if (el) {
|
||||||
el.addEventListener('change', () => {
|
el.addEventListener('change', () => {
|
||||||
currentEventFilter = null; // Clear event filter when using regular filters
|
currentEventFilter = null; // Clear event filter when using regular filters
|
||||||
|
currentProductPage = 1;
|
||||||
loadProducts();
|
loadProducts();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,6 +109,7 @@ function clearAllFilters() {
|
|||||||
document.getElementById('filterStock').value = '';
|
document.getElementById('filterStock').value = '';
|
||||||
document.getElementById('filterFavorites').checked = false;
|
document.getElementById('filterFavorites').checked = false;
|
||||||
currentEventFilter = null;
|
currentEventFilter = null;
|
||||||
|
currentProductPage = 1;
|
||||||
loadProducts();
|
loadProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,12 +300,14 @@ async function loadActivityFeed() {
|
|||||||
// Navigate to products filtered by event type
|
// Navigate to products filtered by event type
|
||||||
function goToFilteredProducts(eventType, period) {
|
function goToFilteredProducts(eventType, period) {
|
||||||
currentEventFilter = { type: eventType, period: period };
|
currentEventFilter = { type: eventType, period: period };
|
||||||
|
currentProductPage = 1;
|
||||||
navigateTo('products');
|
navigateTo('products');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear event filter
|
// Clear event filter
|
||||||
function clearEventFilter() {
|
function clearEventFilter() {
|
||||||
currentEventFilter = null;
|
currentEventFilter = null;
|
||||||
|
currentProductPage = 1;
|
||||||
loadProducts();
|
loadProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,8 +317,9 @@ async function loadProducts() {
|
|||||||
const category = document.getElementById('filterCategory')?.value || '';
|
const category = document.getElementById('filterCategory')?.value || '';
|
||||||
const inStock = document.getElementById('filterStock')?.value || '';
|
const inStock = document.getElementById('filterStock')?.value || '';
|
||||||
const favoritesOnly = document.getElementById('filterFavorites')?.checked || false;
|
const favoritesOnly = document.getElementById('filterFavorites')?.checked || false;
|
||||||
|
const offset = (currentProductPage - 1) * PRODUCTS_PER_PAGE;
|
||||||
|
|
||||||
let url = '/products?limit=50';
|
let url = `/products?limit=${PRODUCTS_PER_PAGE}&offset=${offset}`;
|
||||||
if (site) url += `&site=${site}`;
|
if (site) url += `&site=${site}`;
|
||||||
if (category) url += `&category=${encodeURIComponent(category)}`;
|
if (category) url += `&category=${encodeURIComponent(category)}`;
|
||||||
if (inStock) url += `&in_stock=${inStock}`;
|
if (inStock) url += `&in_stock=${inStock}`;
|
||||||
@@ -333,6 +340,7 @@ async function loadProducts() {
|
|||||||
|
|
||||||
if (data.products.length === 0) {
|
if (data.products.length === 0) {
|
||||||
grid.innerHTML = '<div class="loading">No products found</div>';
|
grid.innerHTML = '<div class="loading">No products found</div>';
|
||||||
|
renderPagination(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +384,36 @@ async function loadProducts() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
renderPagination(data.total || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(total) {
|
||||||
|
const container = document.getElementById('productsPagination');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(total / PRODUCTS_PER_PAGE);
|
||||||
|
if (totalPages <= 1) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (currentProductPage - 1) * PRODUCTS_PER_PAGE + 1;
|
||||||
|
const end = Math.min(currentProductPage * PRODUCTS_PER_PAGE, total);
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="pagination">
|
||||||
|
<button class="btn-page" onclick="changeProductPage(${currentProductPage - 1})" ${currentProductPage === 1 ? 'disabled' : ''}>« Prev</button>
|
||||||
|
<span class="page-info">Page ${currentProductPage} of ${totalPages} (${start}–${end} of ${total})</span>
|
||||||
|
<button class="btn-page" onclick="changeProductPage(${currentProductPage + 1})" ${currentProductPage === totalPages ? 'disabled' : ''}>Next »</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeProductPage(page) {
|
||||||
|
currentProductPage = page;
|
||||||
|
loadProducts();
|
||||||
|
document.getElementById('page-products').scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle product image errors by showing store icon
|
// Handle product image errors by showing store icon
|
||||||
|
|||||||
@@ -859,6 +859,42 @@ h3 {
|
|||||||
background: var(--border-color);
|
background: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
#productsPagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-page {
|
||||||
|
padding: 8px 18px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-page:hover:not(:disabled) {
|
||||||
|
background: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-page:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-info {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Settings */
|
/* Settings */
|
||||||
.settings-section {
|
.settings-section {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
<div class="products-grid" id="productsGrid">
|
<div class="products-grid" id="productsGrid">
|
||||||
<div class="loading">Loading products...</div>
|
<div class="loading">Loading products...</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="productsPagination"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Analytics Page -->
|
<!-- Analytics Page -->
|
||||||
|
|||||||
Reference in New Issue
Block a user