Compare commits

..

No commits in common. "b15e9bc977794e187aaad03b3f436279bb04ce82" and "5eb039ffe067ae4217d5496426f211823ac8b0dd" have entirely different histories.

6 changed files with 240 additions and 479 deletions

View file

@ -4,7 +4,7 @@
[flake8]
max-line-length = 88
max-complexity = 30
max-complexity = 16
# B = bugbear
# B9 = bugbear opinionated (incl line length)
select = C,E,F,W,B,B9

View file

@ -1,15 +0,0 @@
module.exports = [
{
ignores: [
"node_modules/**",
"**/*.pyc",
"**/__pycache__/**",
"ocb/**",
"setup/**",
".git/**",
"dist/**",
"build/**",
],
rules: {},
},
];

View file

@ -1380,7 +1380,7 @@ class AplicoopWebsiteSale(WebsiteSale):
@http.route(
["/eskaera/<int:order_id>/load-products-ajax"],
type="http",
type="json",
auth="user",
website=True,
methods=["POST"],
@ -1407,27 +1407,17 @@ class AplicoopWebsiteSale(WebsiteSale):
)
)
# Parse JSON body for parameters (type="http" doesn't auto-parse JSON)
params = {}
# Get page from POST
try:
if request.httprequest.content_length:
data = request.httprequest.get_data(as_text=True)
if data:
params = json.loads(data)
except (ValueError, json.JSONDecodeError, AttributeError):
params = {}
# Get page from POST/JSON
try:
page = int(params.get("page", post.get("page", 1)))
page = int(post.get("page", 1))
if page < 1:
page = 1
except (ValueError, TypeError):
page = 1
# Get filters
search_query = params.get("search", post.get("search", "")).strip()
category_filter = str(params.get("category", post.get("category", "0")))
search_query = post.get("search", "").strip()
category_filter = str(post.get("category", "0"))
_logger.info(
"load_products_ajax: order_id=%d, page=%d, search=%s, category=%s",
@ -1487,8 +1477,7 @@ class AplicoopWebsiteSale(WebsiteSale):
lambda p: p.categ_id.id in order_cat_ids
)
# Preserve search filter by using intersection
filtered_products = filtered_products & cat_filtered
filtered_products = cat_filtered
except (ValueError, TypeError) as e:
_logger.warning(
"load_products_ajax: Invalid category filter: %s", str(e)
@ -1567,7 +1556,7 @@ class AplicoopWebsiteSale(WebsiteSale):
}
# Render HTML
html = request.env["ir.ui.view"]._render_template(
html = request.env["ir.ui.view"]._render(
"website_sale_aplicoop.eskaera_shop_products",
{
"group_order": group_order,
@ -1582,18 +1571,13 @@ class AplicoopWebsiteSale(WebsiteSale):
},
)
return request.make_response(
json.dumps(
{
return {
"html": html,
"has_next": has_next,
"next_page": page + 1,
"total": total_products,
"page": page,
}
),
[("Content-Type", "application/json")],
)
@http.route(
["/eskaera/add-to-cart"],

View file

@ -7,26 +7,6 @@
console.log("[INFINITE_SCROLL] Script loaded!");
// Visual indicator for debugging
if (typeof document !== "undefined") {
try {
var debugDiv = document.createElement("div");
debugDiv.innerHTML = "[INFINITE_SCROLL LOADED]";
debugDiv.style.position = "fixed";
debugDiv.style.top = "0";
debugDiv.style.right = "0";
debugDiv.style.backgroundColor = "#00ff00";
debugDiv.style.color = "#000";
debugDiv.style.padding = "5px 10px";
debugDiv.style.fontSize = "12px";
debugDiv.style.zIndex = "99999";
debugDiv.id = "infinite-scroll-debug";
document.body.appendChild(debugDiv);
} catch (e) {
console.error("[INFINITE_SCROLL] Error adding debug div:", e);
}
}
(function () {
"use strict";
@ -58,14 +38,12 @@ if (typeof document !== "undefined") {
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
// Check if there are more products to load from data attribute
var hasNextAttr = configEl.getAttribute("data-has-next");
this.hasMore = hasNextAttr === "true" || hasNextAttr === "True";
// Check if there are more products to load
var hasNextBtn = document.getElementById("load-more-btn");
this.hasMore = hasNextBtn && hasNextBtn.offsetParent !== null; // offsetParent === null means hidden
if (!this.hasMore) {
console.log(
"[INFINITE_SCROLL] No more products to load (has_next=" + hasNextAttr + ")"
);
console.log("[INFINITE_SCROLL] No more products to load");
return;
}
@ -130,44 +108,6 @@ if (typeof document !== "undefined") {
console.log("[INFINITE_SCROLL] Fallback button listener attached");
},
resetWithFilters: function (searchQuery, categoryId) {
/**
* Reset infinite scroll to page 1 with new filters and reload products.
* Called by realtime_search when filters change.
*/
console.log(
"[INFINITE_SCROLL] Resetting with filters: search=" +
searchQuery +
" category=" +
categoryId
);
this.searchQuery = searchQuery || "";
this.category = categoryId || "0";
this.currentPage = 0; // Set to 0 so loadNextPage() increments to 1
this.isLoading = false;
this.hasMore = true;
// Update the config element data attributes for consistency
var configEl = document.getElementById("eskaera-config");
if (configEl) {
configEl.setAttribute("data-search", this.searchQuery);
configEl.setAttribute("data-category", this.category);
configEl.setAttribute("data-current-page", "1");
configEl.setAttribute("data-has-next", "true");
}
// Clear the grid and reload from page 1
var grid = document.getElementById("products-grid");
if (grid) {
grid.innerHTML = "";
console.log("[INFINITE_SCROLL] Grid cleared");
}
// Load first page with new filters
this.loadNextPage();
},
loadNextPage: function () {
var self = this;
this.isLoading = true;

View file

@ -4,7 +4,7 @@
*/
(function() {
"use strict";
'use strict';
window.realtimeSearch = {
searchInput: null,
@ -17,29 +17,27 @@
availableTags: {}, // Maps tag ID to {id, name, count}
init: function() {
console.log("[realtimeSearch] Initializing...");
console.log('[realtimeSearch] Initializing...');
// searchInput y categorySelect ya fueron asignados por tryInit()
console.log("[realtimeSearch] Search input:", this.searchInput);
console.log("[realtimeSearch] Category select:", this.categorySelect);
console.log('[realtimeSearch] Search input:', this.searchInput);
console.log('[realtimeSearch] Category select:', this.categorySelect);
if (!this.searchInput) {
console.error("[realtimeSearch] ERROR: Search input not found!");
console.error('[realtimeSearch] ERROR: Search input not found!');
return false;
}
if (!this.categorySelect) {
console.error("[realtimeSearch] ERROR: Category select not found!");
console.error('[realtimeSearch] ERROR: Category select not found!');
return false;
}
this._buildCategoryHierarchyFromDOM();
this._storeAllProducts();
console.log(
"[realtimeSearch] After _storeAllProducts(), calling _attachEventListeners()..."
);
console.log('[realtimeSearch] After _storeAllProducts(), calling _attachEventListeners()...');
this._attachEventListeners();
console.log("[realtimeSearch] ✓ Initialized successfully");
console.log('[realtimeSearch] ✓ Initialized successfully');
return true;
},
@ -52,11 +50,11 @@
* Estructura: categoryHierarchy[parentCategoryId] = [childCategoryId1, childCategoryId2, ...]
*/
var self = this;
var allOptions = this.categorySelect.querySelectorAll("option[value]");
var allOptions = this.categorySelect.querySelectorAll('option[value]');
var optionStack = []; // Stack para mantener los padres en cada nivel
allOptions.forEach(function(option) {
var categoryId = option.getAttribute("value");
var categoryId = option.getAttribute('value');
var text = option.textContent;
// Contar arrows para determinar profundidad
@ -89,33 +87,27 @@
}
});
console.log(
"[realtimeSearch] Complete category hierarchy built:",
self.categoryHierarchy
);
console.log('[realtimeSearch] Complete category hierarchy built:', self.categoryHierarchy);
},
_storeAllProducts: function() {
var productCards = document.querySelectorAll(".product-card");
console.log("[realtimeSearch] Found " + productCards.length + " product cards");
var productCards = document.querySelectorAll('.product-card');
console.log('[realtimeSearch] Found ' + productCards.length + ' product cards');
var self = this;
this.allProducts = [];
productCards.forEach(function(card, index) {
var name = card.getAttribute("data-product-name") || "";
var categoryId = card.getAttribute("data-category-id") || "";
var tagIdsStr = card.getAttribute("data-product-tags") || "";
var name = card.getAttribute('data-product-name') || '';
var categoryId = card.getAttribute('data-category-id') || '';
var tagIdsStr = card.getAttribute('data-product-tags') || '';
// Parse tag IDs from comma-separated string
var tagIds = [];
if (tagIdsStr) {
tagIds = tagIdsStr
.split(",")
.map(function (id) {
tagIds = tagIdsStr.split(',').map(function(id) {
return parseInt(id.trim(), 10);
})
.filter(function (id) {
}).filter(function(id) {
return !isNaN(id);
});
}
@ -125,11 +117,11 @@
name: name.toLowerCase(),
category: categoryId.toString(),
originalCategory: categoryId,
tags: tagIds, // Array of tag IDs for this product
tags: tagIds // Array of tag IDs for this product
});
});
console.log("[realtimeSearch] Total products stored: " + this.allProducts.length);
console.log('[realtimeSearch] Total products stored: ' + this.allProducts.length);
},
_attachEventListeners: function() {
@ -142,175 +134,131 @@
self.originalTagColors = {}; // Maps tag ID to original color
// Store last values at instance level so polling can access them
self.lastSearchValue = "";
self.lastCategoryValue = "";
self.lastSearchValue = '';
self.lastCategoryValue = '';
// Prevent form submission completely
var form = self.searchInput.closest("form");
var form = self.searchInput.closest('form');
if (form) {
form.addEventListener("submit", function (e) {
form.addEventListener('submit', function(e) {
e.preventDefault();
e.stopPropagation();
console.log("[realtimeSearch] Form submission prevented and stopped");
console.log('[realtimeSearch] Form submission prevented and stopped');
return false;
});
}
// Prevent Enter key from submitting
self.searchInput.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
self.searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
console.log("[realtimeSearch] Enter key prevented on search input");
console.log('[realtimeSearch] Enter key prevented on search input');
return false;
}
});
// Search input: listen to 'input' for real-time filtering
self.searchInput.addEventListener("input", function (e) {
self.searchInput.addEventListener('input', function(e) {
try {
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
self._filterProducts();
} catch (error) {
console.error("[realtimeSearch] Error in input listener:", error.message);
console.error('[realtimeSearch] Error in input listener:', error.message);
}
});
// Also keep 'keyup' for extra compatibility
self.searchInput.addEventListener("keyup", function (e) {
self.searchInput.addEventListener('keyup', function(e) {
try {
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
self._filterProducts();
} catch (error) {
console.error("[realtimeSearch] Error in keyup listener:", error.message);
console.error('[realtimeSearch] Error in keyup listener:', error.message);
}
});
// Category select
self.categorySelect.addEventListener("change", function (e) {
self.categorySelect.addEventListener('change', function(e) {
try {
console.log(
'[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"'
);
console.log('[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"');
self._filterProducts();
} catch (error) {
console.error(
"[realtimeSearch] Error in category change listener:",
error.message
);
console.error('[realtimeSearch] Error in category change listener:', error.message);
}
});
// Tag filter badges: click to toggle selection (independent state)
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
console.log("[realtimeSearch] Found " + tagBadges.length + " tag filter badges");
console.log('[realtimeSearch] Found ' + tagBadges.length + ' tag filter badges');
// Get theme colors from CSS variables
var rootStyles = getComputedStyle(document.documentElement);
var primaryColor =
rootStyles.getPropertyValue("--bs-primary").trim() ||
rootStyles.getPropertyValue("--primary").trim() ||
"#0d6efd";
var secondaryColor =
rootStyles.getPropertyValue("--bs-secondary").trim() ||
rootStyles.getPropertyValue("--secondary").trim() ||
"#6c757d";
var primaryColor = rootStyles.getPropertyValue('--bs-primary').trim() ||
rootStyles.getPropertyValue('--primary').trim() ||
'#0d6efd';
var secondaryColor = rootStyles.getPropertyValue('--bs-secondary').trim() ||
rootStyles.getPropertyValue('--secondary').trim() ||
'#6c757d';
console.log(
"[realtimeSearch] Theme colors - Primary:",
primaryColor,
"Secondary:",
secondaryColor
);
console.log('[realtimeSearch] Theme colors - Primary:', primaryColor, 'Secondary:', secondaryColor);
// Store original colors for each badge BEFORE adding event listeners
tagBadges.forEach(function(badge) {
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
var tagColor = badge.getAttribute("data-tag-color");
var tagId = parseInt(badge.getAttribute('data-tag-id'), 10);
var tagColor = badge.getAttribute('data-tag-color');
// Store the original color (either from data-tag-color or use secondary for tags without color)
if (tagColor) {
self.originalTagColors[tagId] = tagColor;
console.log(
"[realtimeSearch] Stored original color for tag " + tagId + ": " + tagColor
);
console.log('[realtimeSearch] Stored original color for tag ' + tagId + ': ' + tagColor);
} else {
self.originalTagColors[tagId] = "var(--bs-secondary, " + secondaryColor + ")";
console.log("[realtimeSearch] Tag " + tagId + " has no color, using secondary");
self.originalTagColors[tagId] = 'var(--bs-secondary, ' + secondaryColor + ')';
console.log('[realtimeSearch] Tag ' + tagId + ' has no color, using secondary');
}
});
tagBadges.forEach(function(badge) {
badge.addEventListener("click", function (e) {
badge.addEventListener('click', function(e) {
e.preventDefault();
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
var tagId = parseInt(badge.getAttribute('data-tag-id'), 10);
var originalColor = self.originalTagColors[tagId];
console.log(
"[realtimeSearch] Tag badge clicked: " +
tagId +
" (original color: " +
originalColor +
")"
);
console.log('[realtimeSearch] Tag badge clicked: ' + tagId + ' (original color: ' + originalColor + ')');
// Toggle tag selection
if (self.selectedTags.has(tagId)) {
// Deselect
self.selectedTags.delete(tagId);
console.log("[realtimeSearch] Tag " + tagId + " deselected");
console.log('[realtimeSearch] Tag ' + tagId + ' deselected');
} else {
// Select
self.selectedTags.add(tagId);
console.log("[realtimeSearch] Tag " + tagId + " selected");
console.log('[realtimeSearch] Tag ' + tagId + ' selected');
}
// Update colors for ALL badges based on selection state
tagBadges.forEach(function(badge) {
var id = parseInt(badge.getAttribute("data-tag-id"), 10);
var id = parseInt(badge.getAttribute('data-tag-id'), 10);
if (self.selectedTags.size === 0) {
// No tags selected: restore all to original colors
var originalColor = self.originalTagColors[id];
badge.style.setProperty("background-color", originalColor, "important");
badge.style.setProperty("border-color", originalColor, "important");
badge.style.setProperty("color", "#ffffff", "important");
console.log(
"[realtimeSearch] Badge " +
id +
" reset to original color (no selection)"
);
badge.style.setProperty('background-color', originalColor, 'important');
badge.style.setProperty('border-color', originalColor, 'important');
badge.style.setProperty('color', '#ffffff', 'important');
console.log('[realtimeSearch] Badge ' + id + ' reset to original color (no selection)');
} else if (self.selectedTags.has(id)) {
// Selected: primary color
badge.style.setProperty(
"background-color",
"var(--bs-primary, " + primaryColor + ")",
"important"
);
badge.style.setProperty(
"border-color",
"var(--bs-primary, " + primaryColor + ")",
"important"
);
badge.style.setProperty("color", "#ffffff", "important");
console.log(
"[realtimeSearch] Badge " + id + " set to primary (selected)"
);
badge.style.setProperty('background-color', 'var(--bs-primary, ' + primaryColor + ')', 'important');
badge.style.setProperty('border-color', 'var(--bs-primary, ' + primaryColor + ')', 'important');
badge.style.setProperty('color', '#ffffff', 'important');
console.log('[realtimeSearch] Badge ' + id + ' set to primary (selected)');
} else {
// Not selected but others are: secondary color
badge.style.setProperty(
"background-color",
"var(--bs-secondary, " + secondaryColor + ")",
"important"
);
badge.style.setProperty(
"border-color",
"var(--bs-secondary, " + secondaryColor + ")",
"important"
);
badge.style.setProperty("color", "#ffffff", "important");
console.log(
"[realtimeSearch] Badge " + id + " set to secondary (not selected)"
);
badge.style.setProperty('background-color', 'var(--bs-secondary, ' + secondaryColor + ')', 'important');
badge.style.setProperty('border-color', 'var(--bs-secondary, ' + secondaryColor + ')', 'important');
badge.style.setProperty('color', '#ffffff', 'important');
console.log('[realtimeSearch] Badge ' + id + ' set to secondary (not selected)');
}
});
@ -321,9 +269,9 @@
// POLLING FALLBACK: Since Odoo components may intercept events,
// use polling to detect value changes
console.log("[realtimeSearch] 🚀 POLLING INITIALIZATION STARTING");
console.log("[realtimeSearch] Search input element:", self.searchInput);
console.log("[realtimeSearch] Category select element:", self.categorySelect);
console.log('[realtimeSearch] 🚀 POLLING INITIALIZATION STARTING');
console.log('[realtimeSearch] Search input element:', self.searchInput);
console.log('[realtimeSearch] Category select element:', self.categorySelect);
var pollingCounter = 0;
var pollInterval = setInterval(function() {
@ -331,107 +279,47 @@
pollingCounter++;
// Try multiple ways to get the search value
var currentSearchValue = self.searchInput.value || "";
var currentSearchAttr = self.searchInput.getAttribute("value") || "";
var currentSearchDataValue = self.searchInput.getAttribute("data-value") || "";
var currentSearchInnerText = self.searchInput.innerText || "";
var currentSearchValue = self.searchInput.value || '';
var currentSearchAttr = self.searchInput.getAttribute('value') || '';
var currentSearchDataValue = self.searchInput.getAttribute('data-value') || '';
var currentSearchInnerText = self.searchInput.innerText || '';
var currentCategoryValue = self.categorySelect
? self.categorySelect.value || ""
: "";
var currentCategoryValue = self.categorySelect ? (self.categorySelect.value || '') : '';
// FIRST POLL: Detailed debug
if (pollingCounter === 1) {
console.log("═══════════════════════════════════════════");
console.log("[realtimeSearch] 🔍 FIRST POLLING DEBUG (POLL #1)");
console.log("═══════════════════════════════════════════");
console.log("Search input .value:", JSON.stringify(currentSearchValue));
console.log(
'Search input getAttribute("value"):',
JSON.stringify(currentSearchAttr)
);
console.log(
'Search input getAttribute("data-value"):',
JSON.stringify(currentSearchDataValue)
);
console.log(
"Search input innerText:",
JSON.stringify(currentSearchInnerText)
);
console.log(
"Category select .value:",
JSON.stringify(currentCategoryValue)
);
console.log(
'Last stored values - search:"' +
self.lastSearchValue +
'" category:"' +
self.lastCategoryValue +
'"'
);
console.log("═══════════════════════════════════════════");
console.log('═══════════════════════════════════════════');
console.log('[realtimeSearch] 🔍 FIRST POLLING DEBUG (POLL #1)');
console.log('═══════════════════════════════════════════');
console.log('Search input .value:', JSON.stringify(currentSearchValue));
console.log('Search input getAttribute("value"):', JSON.stringify(currentSearchAttr));
console.log('Search input getAttribute("data-value"):', JSON.stringify(currentSearchDataValue));
console.log('Search input innerText:', JSON.stringify(currentSearchInnerText));
console.log('Category select .value:', JSON.stringify(currentCategoryValue));
console.log('Last stored values - search:"' + self.lastSearchValue + '" category:"' + self.lastCategoryValue + '"');
console.log('═══════════════════════════════════════════');
}
// Log every 20 polls (reduce spam)
if (pollingCounter % 20 === 0) {
console.log(
"[realtimeSearch] POLLING #" +
pollingCounter +
': search="' +
currentSearchValue +
'" category="' +
currentCategoryValue +
'"'
);
console.log('[realtimeSearch] POLLING #' + pollingCounter + ': search="' + currentSearchValue + '" category="' + currentCategoryValue + '"');
}
// Check for ANY change in either field
if (
currentSearchValue !== self.lastSearchValue ||
currentCategoryValue !== self.lastCategoryValue
) {
console.log(
'[realtimeSearch] ⚡ CHANGE DETECTED: search="' +
currentSearchValue +
'" (was:"' +
self.lastSearchValue +
'") | category="' +
currentCategoryValue +
'" (was:"' +
self.lastCategoryValue +
'")'
);
if (currentSearchValue !== self.lastSearchValue || currentCategoryValue !== self.lastCategoryValue) {
console.log('[realtimeSearch] ⚡ CHANGE DETECTED: search="' + currentSearchValue + '" (was:"' + self.lastSearchValue + '") | category="' + currentCategoryValue + '" (was:"' + self.lastCategoryValue + '")');
self.lastSearchValue = currentSearchValue;
self.lastCategoryValue = currentCategoryValue;
// Reset infinite scroll with new filters (will reload from server)
if (
window.infiniteScroll &&
typeof window.infiniteScroll.resetWithFilters === "function"
) {
console.log(
"[realtimeSearch] Calling infiniteScroll.resetWithFilters()"
);
window.infiniteScroll.resetWithFilters(
currentSearchValue,
currentCategoryValue
);
} else {
// Fallback: filter locally (but this only filters loaded products)
console.log(
"[realtimeSearch] infiniteScroll not available, filtering locally only"
);
self._filterProducts();
}
}
} catch (error) {
console.error("[realtimeSearch] ❌ Error in polling:", error.message);
console.error('[realtimeSearch] ❌ Error in polling:', error.message);
}
}, 300); // Check every 300ms
console.log("[realtimeSearch] ✅ Polling interval started with ID:", pollInterval);
console.log('[realtimeSearch] ✅ Polling interval started with ID:', pollInterval);
console.log("[realtimeSearch] Event listeners attached with polling fallback");
console.log('[realtimeSearch] Event listeners attached with polling fallback');
},
_initializeAvailableTags: function() {
@ -443,40 +331,29 @@
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
tagBadges.forEach(function(badge) {
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
var tagName = badge.getAttribute("data-tag-name") || "";
var countSpan = badge.querySelector(".tag-count");
var tagId = parseInt(badge.getAttribute('data-tag-id'), 10);
var tagName = badge.getAttribute('data-tag-name') || '';
var countSpan = badge.querySelector('.tag-count');
var count = countSpan ? parseInt(countSpan.textContent, 10) : 0;
self.availableTags[tagId] = {
id: tagId,
name: tagName,
count: count,
count: count
};
});
console.log(
"[realtimeSearch] Initialized " +
Object.keys(self.availableTags).length +
" available tags"
);
console.log('[realtimeSearch] Initialized ' + Object.keys(self.availableTags).length + ' available tags');
},
_filterProducts: function() {
var self = this;
try {
var searchQuery = (self.searchInput.value || "").toLowerCase().trim();
var selectedCategoryId = (self.categorySelect.value || "").toString();
var searchQuery = (self.searchInput.value || '').toLowerCase().trim();
var selectedCategoryId = (self.categorySelect.value || '').toString();
console.log(
"[realtimeSearch] Filtering: search=" +
searchQuery +
" category=" +
selectedCategoryId +
" tags=" +
Array.from(self.selectedTags).join(",")
);
console.log('[realtimeSearch] Filtering: search=' + searchQuery + ' category=' + selectedCategoryId + ' tags=' + Array.from(self.selectedTags).join(','));
// Build a set of allowed category IDs (selected category + ALL descendants recursively)
var allowedCategories = {};
@ -500,17 +377,8 @@
};
var allDescendants = getAllDescendants(selectedCategoryId);
console.log(
"[realtimeSearch] Selected category " +
selectedCategoryId +
" has " +
allDescendants.length +
" total descendants"
);
console.log(
"[realtimeSearch] Allowed categories:",
Object.keys(allowedCategories)
);
console.log('[realtimeSearch] Selected category ' + selectedCategoryId + ' has ' + allDescendants.length + ' total descendants');
console.log('[realtimeSearch] Allowed categories:', Object.keys(allowedCategories));
}
var visibleCount = 0;
@ -524,8 +392,7 @@
self.allProducts.forEach(function(product) {
var nameMatches = !searchQuery || product.name.indexOf(searchQuery) !== -1;
var categoryMatches =
!selectedCategoryId || allowedCategories[product.category];
var categoryMatches = !selectedCategoryId || allowedCategories[product.category];
// Tag filtering: if tags are selected, product must have AT LEAST ONE selected tag (OR logic)
var tagMatches = true;
@ -538,7 +405,7 @@
var shouldShow = nameMatches && categoryMatches && tagMatches;
if (shouldShow) {
product.element.classList.remove("hidden-product");
product.element.classList.remove('hidden-product');
visibleCount++;
// Count this product's tags toward the dynamic counters
@ -548,7 +415,7 @@
}
});
} else {
product.element.classList.add("hidden-product");
product.element.classList.add('hidden-product');
hiddenCount++;
}
});
@ -557,85 +424,71 @@
for (var tagId in tagCounts) {
var badge = document.querySelector('[data-tag-id="' + tagId + '"]');
if (badge) {
var countSpan = badge.querySelector(".tag-count");
var countSpan = badge.querySelector('.tag-count');
if (countSpan) {
countSpan.textContent = tagCounts[tagId];
}
}
}
console.log(
"[realtimeSearch] Filter result: visible=" +
visibleCount +
" hidden=" +
hiddenCount
);
console.log('[realtimeSearch] Filter result: visible=' + visibleCount + ' hidden=' + hiddenCount);
} catch (error) {
console.error("[realtimeSearch] ERROR in _filterProducts():", error.message);
console.error("[realtimeSearch] Stack:", error.stack);
console.error('[realtimeSearch] ERROR in _filterProducts():', error.message);
console.error('[realtimeSearch] Stack:', error.stack);
}
}
},
};
// Initialize when DOM is ready
console.log("[realtimeSearch] Script loaded, DOM state: " + document.readyState);
console.log('[realtimeSearch] Script loaded, DOM state: ' + document.readyState);
function tryInit() {
try {
console.log("[realtimeSearch] Attempting initialization...");
console.log('[realtimeSearch] Attempting initialization...');
// Query product cards
var productCards = document.querySelectorAll(".product-card");
console.log("[realtimeSearch] Found " + productCards.length + " product cards");
var productCards = document.querySelectorAll('.product-card');
console.log('[realtimeSearch] Found ' + productCards.length + ' product cards');
// Use the NEW pure HTML input with ID (not transformed by Odoo)
var searchInput = document.getElementById("realtime-search-input");
console.log("[realtimeSearch] Search input found:", !!searchInput);
var searchInput = document.getElementById('realtime-search-input');
console.log('[realtimeSearch] Search input found:', !!searchInput);
if (searchInput) {
console.log("[realtimeSearch] Search input class:", searchInput.className);
console.log("[realtimeSearch] Search input type:", searchInput.type);
console.log('[realtimeSearch] Search input class:', searchInput.className);
console.log('[realtimeSearch] Search input type:', searchInput.type);
}
// Category select with ID (not transformed by Odoo)
var categorySelect = document.getElementById("realtime-category-select");
console.log("[realtimeSearch] Category select found:", !!categorySelect);
var categorySelect = document.getElementById('realtime-category-select');
console.log('[realtimeSearch] Category select found:', !!categorySelect);
if (productCards.length > 0 && searchInput) {
console.log("[realtimeSearch] ✓ All elements found! Initializing...");
console.log('[realtimeSearch] ✓ All elements found! Initializing...');
// Assign elements to window.realtimeSearch BEFORE calling init()
window.realtimeSearch.searchInput = searchInput;
window.realtimeSearch.categorySelect = categorySelect;
window.realtimeSearch.init();
console.log("[realtimeSearch] ✓ Initialization complete!");
console.log('[realtimeSearch] ✓ Initialization complete!');
} else {
console.log(
"[realtimeSearch] Waiting for elements... (products:" +
productCards.length +
", search:" +
!!searchInput +
")"
);
console.log('[realtimeSearch] Waiting for elements... (products:' + productCards.length + ', search:' + !!searchInput + ')');
if (productCards.length === 0) {
console.log(
"[realtimeSearch] No product cards found. Current HTML body length:",
document.body.innerHTML.length
);
console.log('[realtimeSearch] No product cards found. Current HTML body length:', document.body.innerHTML.length);
}
setTimeout(tryInit, 500);
}
} catch (error) {
console.error("[realtimeSearch] ERROR in tryInit():", error.message);
console.error('[realtimeSearch] ERROR in tryInit():', error.message);
}
}
if (document.readyState === "loading") {
console.log("[realtimeSearch] Adding DOMContentLoaded listener");
document.addEventListener("DOMContentLoaded", function () {
console.log("[realtimeSearch] DOMContentLoaded fired");
if (document.readyState === 'loading') {
console.log('[realtimeSearch] Adding DOMContentLoaded listener');
document.addEventListener('DOMContentLoaded', function() {
console.log('[realtimeSearch] DOMContentLoaded fired');
tryInit();
});
} else {
console.log("[realtimeSearch] DOM already loaded, initializing with delay");
console.log('[realtimeSearch] DOM already loaded, initializing with delay');
setTimeout(tryInit, 500);
}
})();

View file

@ -586,7 +586,6 @@
t-attf-data-category="{{ selected_category }}"
t-attf-data-per-page="{{ per_page }}"
t-attf-data-current-page="{{ current_page }}"
t-attf-data-has-next="{{ 'true' if has_next else 'false' }}"
class="d-none">
</div>
</t>