Compare commits
3 commits
5eb039ffe0
...
b15e9bc977
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b15e9bc977 | ||
|
|
dc44ace78f | ||
|
|
40ce973bd6 |
6 changed files with 479 additions and 240 deletions
2
.flake8
2
.flake8
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 88
|
max-line-length = 88
|
||||||
max-complexity = 16
|
max-complexity = 30
|
||||||
# B = bugbear
|
# B = bugbear
|
||||||
# B9 = bugbear opinionated (incl line length)
|
# B9 = bugbear opinionated (incl line length)
|
||||||
select = C,E,F,W,B,B9
|
select = C,E,F,W,B,B9
|
||||||
|
|
|
||||||
15
eslint.config.js
Normal file
15
eslint.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
"**/*.pyc",
|
||||||
|
"**/__pycache__/**",
|
||||||
|
"ocb/**",
|
||||||
|
"setup/**",
|
||||||
|
".git/**",
|
||||||
|
"dist/**",
|
||||||
|
"build/**",
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -1380,7 +1380,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
["/eskaera/<int:order_id>/load-products-ajax"],
|
["/eskaera/<int:order_id>/load-products-ajax"],
|
||||||
type="json",
|
type="http",
|
||||||
auth="user",
|
auth="user",
|
||||||
website=True,
|
website=True,
|
||||||
methods=["POST"],
|
methods=["POST"],
|
||||||
|
|
@ -1407,17 +1407,27 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get page from POST
|
# Parse JSON body for parameters (type="http" doesn't auto-parse JSON)
|
||||||
|
params = {}
|
||||||
try:
|
try:
|
||||||
page = int(post.get("page", 1))
|
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)))
|
||||||
if page < 1:
|
if page < 1:
|
||||||
page = 1
|
page = 1
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
page = 1
|
page = 1
|
||||||
|
|
||||||
# Get filters
|
# Get filters
|
||||||
search_query = post.get("search", "").strip()
|
search_query = params.get("search", post.get("search", "")).strip()
|
||||||
category_filter = str(post.get("category", "0"))
|
category_filter = str(params.get("category", post.get("category", "0")))
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"load_products_ajax: order_id=%d, page=%d, search=%s, category=%s",
|
"load_products_ajax: order_id=%d, page=%d, search=%s, category=%s",
|
||||||
|
|
@ -1477,7 +1487,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
lambda p: p.categ_id.id in order_cat_ids
|
lambda p: p.categ_id.id in order_cat_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
filtered_products = cat_filtered
|
# Preserve search filter by using intersection
|
||||||
|
filtered_products = filtered_products & cat_filtered
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
"load_products_ajax: Invalid category filter: %s", str(e)
|
"load_products_ajax: Invalid category filter: %s", str(e)
|
||||||
|
|
@ -1556,7 +1567,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Render HTML
|
# Render HTML
|
||||||
html = request.env["ir.ui.view"]._render(
|
html = request.env["ir.ui.view"]._render_template(
|
||||||
"website_sale_aplicoop.eskaera_shop_products",
|
"website_sale_aplicoop.eskaera_shop_products",
|
||||||
{
|
{
|
||||||
"group_order": group_order,
|
"group_order": group_order,
|
||||||
|
|
@ -1571,13 +1582,18 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return request.make_response(
|
||||||
"html": html,
|
json.dumps(
|
||||||
"has_next": has_next,
|
{
|
||||||
"next_page": page + 1,
|
"html": html,
|
||||||
"total": total_products,
|
"has_next": has_next,
|
||||||
"page": page,
|
"next_page": page + 1,
|
||||||
}
|
"total": total_products,
|
||||||
|
"page": page,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[("Content-Type", "application/json")],
|
||||||
|
)
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
["/eskaera/add-to-cart"],
|
["/eskaera/add-to-cart"],
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,26 @@
|
||||||
|
|
||||||
console.log("[INFINITE_SCROLL] Script loaded!");
|
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 () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
|
@ -38,12 +58,14 @@ console.log("[INFINITE_SCROLL] Script loaded!");
|
||||||
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
|
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
|
||||||
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
|
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
|
||||||
|
|
||||||
// Check if there are more products to load
|
// Check if there are more products to load from data attribute
|
||||||
var hasNextBtn = document.getElementById("load-more-btn");
|
var hasNextAttr = configEl.getAttribute("data-has-next");
|
||||||
this.hasMore = hasNextBtn && hasNextBtn.offsetParent !== null; // offsetParent === null means hidden
|
this.hasMore = hasNextAttr === "true" || hasNextAttr === "True";
|
||||||
|
|
||||||
if (!this.hasMore) {
|
if (!this.hasMore) {
|
||||||
console.log("[INFINITE_SCROLL] No more products to load");
|
console.log(
|
||||||
|
"[INFINITE_SCROLL] No more products to load (has_next=" + hasNextAttr + ")"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +130,44 @@ console.log("[INFINITE_SCROLL] Script loaded!");
|
||||||
console.log("[INFINITE_SCROLL] Fallback button listener attached");
|
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 () {
|
loadNextPage: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function () {
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.realtimeSearch = {
|
window.realtimeSearch = {
|
||||||
searchInput: null,
|
searchInput: null,
|
||||||
|
|
@ -16,32 +16,34 @@
|
||||||
selectedTags: new Set(), // Set of selected tag IDs (for OR logic filtering)
|
selectedTags: new Set(), // Set of selected tag IDs (for OR logic filtering)
|
||||||
availableTags: {}, // Maps tag ID to {id, name, count}
|
availableTags: {}, // Maps tag ID to {id, name, count}
|
||||||
|
|
||||||
init: function() {
|
init: function () {
|
||||||
console.log('[realtimeSearch] Initializing...');
|
console.log("[realtimeSearch] Initializing...");
|
||||||
|
|
||||||
// searchInput y categorySelect ya fueron asignados por tryInit()
|
// searchInput y categorySelect ya fueron asignados por tryInit()
|
||||||
console.log('[realtimeSearch] Search input:', this.searchInput);
|
console.log("[realtimeSearch] Search input:", this.searchInput);
|
||||||
console.log('[realtimeSearch] Category select:', this.categorySelect);
|
console.log("[realtimeSearch] Category select:", this.categorySelect);
|
||||||
|
|
||||||
if (!this.searchInput) {
|
if (!this.searchInput) {
|
||||||
console.error('[realtimeSearch] ERROR: Search input not found!');
|
console.error("[realtimeSearch] ERROR: Search input not found!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.categorySelect) {
|
if (!this.categorySelect) {
|
||||||
console.error('[realtimeSearch] ERROR: Category select not found!');
|
console.error("[realtimeSearch] ERROR: Category select not found!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._buildCategoryHierarchyFromDOM();
|
this._buildCategoryHierarchyFromDOM();
|
||||||
this._storeAllProducts();
|
this._storeAllProducts();
|
||||||
console.log('[realtimeSearch] After _storeAllProducts(), calling _attachEventListeners()...');
|
console.log(
|
||||||
|
"[realtimeSearch] After _storeAllProducts(), calling _attachEventListeners()..."
|
||||||
|
);
|
||||||
this._attachEventListeners();
|
this._attachEventListeners();
|
||||||
console.log('[realtimeSearch] ✓ Initialized successfully');
|
console.log("[realtimeSearch] ✓ Initialized successfully");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_buildCategoryHierarchyFromDOM: function() {
|
_buildCategoryHierarchyFromDOM: function () {
|
||||||
/**
|
/**
|
||||||
* Construye un mapa de jerarquía de categorías desde las opciones del select.
|
* Construye un mapa de jerarquía de categorías desde las opciones del select.
|
||||||
* Ahora todas las opciones son planas pero con indentación visual (↳ arrows).
|
* Ahora todas las opciones son planas pero con indentación visual (↳ arrows).
|
||||||
|
|
@ -50,11 +52,11 @@
|
||||||
* Estructura: categoryHierarchy[parentCategoryId] = [childCategoryId1, childCategoryId2, ...]
|
* Estructura: categoryHierarchy[parentCategoryId] = [childCategoryId1, childCategoryId2, ...]
|
||||||
*/
|
*/
|
||||||
var self = this;
|
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
|
var optionStack = []; // Stack para mantener los padres en cada nivel
|
||||||
|
|
||||||
allOptions.forEach(function(option) {
|
allOptions.forEach(function (option) {
|
||||||
var categoryId = option.getAttribute('value');
|
var categoryId = option.getAttribute("value");
|
||||||
var text = option.textContent;
|
var text = option.textContent;
|
||||||
|
|
||||||
// Contar arrows para determinar profundidad
|
// Contar arrows para determinar profundidad
|
||||||
|
|
@ -87,29 +89,35 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[realtimeSearch] Complete category hierarchy built:', self.categoryHierarchy);
|
console.log(
|
||||||
|
"[realtimeSearch] Complete category hierarchy built:",
|
||||||
|
self.categoryHierarchy
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_storeAllProducts: function() {
|
_storeAllProducts: function () {
|
||||||
var productCards = document.querySelectorAll('.product-card');
|
var productCards = document.querySelectorAll(".product-card");
|
||||||
console.log('[realtimeSearch] Found ' + productCards.length + ' product cards');
|
console.log("[realtimeSearch] Found " + productCards.length + " product cards");
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.allProducts = [];
|
this.allProducts = [];
|
||||||
|
|
||||||
productCards.forEach(function(card, index) {
|
productCards.forEach(function (card, index) {
|
||||||
var name = card.getAttribute('data-product-name') || '';
|
var name = card.getAttribute("data-product-name") || "";
|
||||||
var categoryId = card.getAttribute('data-category-id') || '';
|
var categoryId = card.getAttribute("data-category-id") || "";
|
||||||
var tagIdsStr = card.getAttribute('data-product-tags') || '';
|
var tagIdsStr = card.getAttribute("data-product-tags") || "";
|
||||||
|
|
||||||
// Parse tag IDs from comma-separated string
|
// Parse tag IDs from comma-separated string
|
||||||
var tagIds = [];
|
var tagIds = [];
|
||||||
if (tagIdsStr) {
|
if (tagIdsStr) {
|
||||||
tagIds = tagIdsStr.split(',').map(function(id) {
|
tagIds = tagIdsStr
|
||||||
return parseInt(id.trim(), 10);
|
.split(",")
|
||||||
}).filter(function(id) {
|
.map(function (id) {
|
||||||
return !isNaN(id);
|
return parseInt(id.trim(), 10);
|
||||||
});
|
})
|
||||||
|
.filter(function (id) {
|
||||||
|
return !isNaN(id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.allProducts.push({
|
self.allProducts.push({
|
||||||
|
|
@ -117,14 +125,14 @@
|
||||||
name: name.toLowerCase(),
|
name: name.toLowerCase(),
|
||||||
category: categoryId.toString(),
|
category: categoryId.toString(),
|
||||||
originalCategory: categoryId,
|
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() {
|
_attachEventListeners: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Initialize available tags from DOM
|
// Initialize available tags from DOM
|
||||||
|
|
@ -134,131 +142,175 @@
|
||||||
self.originalTagColors = {}; // Maps tag ID to original color
|
self.originalTagColors = {}; // Maps tag ID to original color
|
||||||
|
|
||||||
// Store last values at instance level so polling can access them
|
// Store last values at instance level so polling can access them
|
||||||
self.lastSearchValue = '';
|
self.lastSearchValue = "";
|
||||||
self.lastCategoryValue = '';
|
self.lastCategoryValue = "";
|
||||||
|
|
||||||
// Prevent form submission completely
|
// Prevent form submission completely
|
||||||
var form = self.searchInput.closest('form');
|
var form = self.searchInput.closest("form");
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener("submit", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log('[realtimeSearch] Form submission prevented and stopped');
|
console.log("[realtimeSearch] Form submission prevented and stopped");
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent Enter key from submitting
|
// Prevent Enter key from submitting
|
||||||
self.searchInput.addEventListener('keypress', function(e) {
|
self.searchInput.addEventListener("keypress", function (e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
console.log('[realtimeSearch] Enter key prevented on search input');
|
console.log("[realtimeSearch] Enter key prevented on search input");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Search input: listen to 'input' for real-time filtering
|
// Search input: listen to 'input' for real-time filtering
|
||||||
self.searchInput.addEventListener('input', function(e) {
|
self.searchInput.addEventListener("input", function (e) {
|
||||||
try {
|
try {
|
||||||
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
|
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
|
||||||
self._filterProducts();
|
self._filterProducts();
|
||||||
} catch (error) {
|
} 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
|
// Also keep 'keyup' for extra compatibility
|
||||||
self.searchInput.addEventListener('keyup', function(e) {
|
self.searchInput.addEventListener("keyup", function (e) {
|
||||||
try {
|
try {
|
||||||
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
|
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
|
||||||
self._filterProducts();
|
self._filterProducts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[realtimeSearch] Error in keyup listener:', error.message);
|
console.error("[realtimeSearch] Error in keyup listener:", error.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Category select
|
// Category select
|
||||||
self.categorySelect.addEventListener('change', function(e) {
|
self.categorySelect.addEventListener("change", function (e) {
|
||||||
try {
|
try {
|
||||||
console.log('[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"');
|
console.log(
|
||||||
|
'[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"'
|
||||||
|
);
|
||||||
self._filterProducts();
|
self._filterProducts();
|
||||||
} catch (error) {
|
} 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)
|
// Tag filter badges: click to toggle selection (independent state)
|
||||||
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
|
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
|
// Get theme colors from CSS variables
|
||||||
var rootStyles = getComputedStyle(document.documentElement);
|
var rootStyles = getComputedStyle(document.documentElement);
|
||||||
var primaryColor = rootStyles.getPropertyValue('--bs-primary').trim() ||
|
var primaryColor =
|
||||||
rootStyles.getPropertyValue('--primary').trim() ||
|
rootStyles.getPropertyValue("--bs-primary").trim() ||
|
||||||
'#0d6efd';
|
rootStyles.getPropertyValue("--primary").trim() ||
|
||||||
var secondaryColor = rootStyles.getPropertyValue('--bs-secondary').trim() ||
|
"#0d6efd";
|
||||||
rootStyles.getPropertyValue('--secondary').trim() ||
|
var secondaryColor =
|
||||||
'#6c757d';
|
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
|
// Store original colors for each badge BEFORE adding event listeners
|
||||||
tagBadges.forEach(function(badge) {
|
tagBadges.forEach(function (badge) {
|
||||||
var tagId = parseInt(badge.getAttribute('data-tag-id'), 10);
|
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
|
||||||
var tagColor = badge.getAttribute('data-tag-color');
|
var tagColor = badge.getAttribute("data-tag-color");
|
||||||
|
|
||||||
// Store the original color (either from data-tag-color or use secondary for tags without color)
|
// Store the original color (either from data-tag-color or use secondary for tags without color)
|
||||||
if (tagColor) {
|
if (tagColor) {
|
||||||
self.originalTagColors[tagId] = 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 {
|
} else {
|
||||||
self.originalTagColors[tagId] = 'var(--bs-secondary, ' + secondaryColor + ')';
|
self.originalTagColors[tagId] = "var(--bs-secondary, " + secondaryColor + ")";
|
||||||
console.log('[realtimeSearch] Tag ' + tagId + ' has no color, using secondary');
|
console.log("[realtimeSearch] Tag " + tagId + " has no color, using secondary");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tagBadges.forEach(function(badge) {
|
tagBadges.forEach(function (badge) {
|
||||||
badge.addEventListener('click', function(e) {
|
badge.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
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];
|
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
|
// Toggle tag selection
|
||||||
if (self.selectedTags.has(tagId)) {
|
if (self.selectedTags.has(tagId)) {
|
||||||
// Deselect
|
// Deselect
|
||||||
self.selectedTags.delete(tagId);
|
self.selectedTags.delete(tagId);
|
||||||
console.log('[realtimeSearch] Tag ' + tagId + ' deselected');
|
console.log("[realtimeSearch] Tag " + tagId + " deselected");
|
||||||
} else {
|
} else {
|
||||||
// Select
|
// Select
|
||||||
self.selectedTags.add(tagId);
|
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
|
// Update colors for ALL badges based on selection state
|
||||||
tagBadges.forEach(function(badge) {
|
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) {
|
if (self.selectedTags.size === 0) {
|
||||||
// No tags selected: restore all to original colors
|
// No tags selected: restore all to original colors
|
||||||
var originalColor = self.originalTagColors[id];
|
var originalColor = self.originalTagColors[id];
|
||||||
badge.style.setProperty('background-color', originalColor, 'important');
|
badge.style.setProperty("background-color", originalColor, "important");
|
||||||
badge.style.setProperty('border-color', originalColor, 'important');
|
badge.style.setProperty("border-color", originalColor, "important");
|
||||||
badge.style.setProperty('color', '#ffffff', 'important');
|
badge.style.setProperty("color", "#ffffff", "important");
|
||||||
console.log('[realtimeSearch] Badge ' + id + ' reset to original color (no selection)');
|
console.log(
|
||||||
|
"[realtimeSearch] Badge " +
|
||||||
|
id +
|
||||||
|
" reset to original color (no selection)"
|
||||||
|
);
|
||||||
} else if (self.selectedTags.has(id)) {
|
} else if (self.selectedTags.has(id)) {
|
||||||
// Selected: primary color
|
// Selected: primary color
|
||||||
badge.style.setProperty('background-color', 'var(--bs-primary, ' + primaryColor + ')', 'important');
|
badge.style.setProperty(
|
||||||
badge.style.setProperty('border-color', 'var(--bs-primary, ' + primaryColor + ')', 'important');
|
"background-color",
|
||||||
badge.style.setProperty('color', '#ffffff', 'important');
|
"var(--bs-primary, " + primaryColor + ")",
|
||||||
console.log('[realtimeSearch] Badge ' + id + ' set to primary (selected)');
|
"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 {
|
} else {
|
||||||
// Not selected but others are: secondary color
|
// Not selected but others are: secondary color
|
||||||
badge.style.setProperty('background-color', 'var(--bs-secondary, ' + secondaryColor + ')', 'important');
|
badge.style.setProperty(
|
||||||
badge.style.setProperty('border-color', 'var(--bs-secondary, ' + secondaryColor + ')', 'important');
|
"background-color",
|
||||||
badge.style.setProperty('color', '#ffffff', 'important');
|
"var(--bs-secondary, " + secondaryColor + ")",
|
||||||
console.log('[realtimeSearch] Badge ' + id + ' set to secondary (not selected)');
|
"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)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -269,60 +321,120 @@
|
||||||
|
|
||||||
// POLLING FALLBACK: Since Odoo components may intercept events,
|
// POLLING FALLBACK: Since Odoo components may intercept events,
|
||||||
// use polling to detect value changes
|
// use polling to detect value changes
|
||||||
console.log('[realtimeSearch] 🚀 POLLING INITIALIZATION STARTING');
|
console.log("[realtimeSearch] 🚀 POLLING INITIALIZATION STARTING");
|
||||||
console.log('[realtimeSearch] Search input element:', self.searchInput);
|
console.log("[realtimeSearch] Search input element:", self.searchInput);
|
||||||
console.log('[realtimeSearch] Category select element:', self.categorySelect);
|
console.log("[realtimeSearch] Category select element:", self.categorySelect);
|
||||||
|
|
||||||
var pollingCounter = 0;
|
var pollingCounter = 0;
|
||||||
var pollInterval = setInterval(function() {
|
var pollInterval = setInterval(function () {
|
||||||
try {
|
try {
|
||||||
pollingCounter++;
|
pollingCounter++;
|
||||||
|
|
||||||
// Try multiple ways to get the search value
|
// Try multiple ways to get the search value
|
||||||
var currentSearchValue = self.searchInput.value || '';
|
var currentSearchValue = self.searchInput.value || "";
|
||||||
var currentSearchAttr = self.searchInput.getAttribute('value') || '';
|
var currentSearchAttr = self.searchInput.getAttribute("value") || "";
|
||||||
var currentSearchDataValue = self.searchInput.getAttribute('data-value') || '';
|
var currentSearchDataValue = self.searchInput.getAttribute("data-value") || "";
|
||||||
var currentSearchInnerText = self.searchInput.innerText || '';
|
var currentSearchInnerText = self.searchInput.innerText || "";
|
||||||
|
|
||||||
var currentCategoryValue = self.categorySelect ? (self.categorySelect.value || '') : '';
|
var currentCategoryValue = self.categorySelect
|
||||||
|
? self.categorySelect.value || ""
|
||||||
|
: "";
|
||||||
|
|
||||||
// FIRST POLL: Detailed debug
|
// FIRST POLL: Detailed debug
|
||||||
if (pollingCounter === 1) {
|
if (pollingCounter === 1) {
|
||||||
console.log('═══════════════════════════════════════════');
|
console.log("═══════════════════════════════════════════");
|
||||||
console.log('[realtimeSearch] 🔍 FIRST POLLING DEBUG (POLL #1)');
|
console.log("[realtimeSearch] 🔍 FIRST POLLING DEBUG (POLL #1)");
|
||||||
console.log('═══════════════════════════════════════════');
|
console.log("═══════════════════════════════════════════");
|
||||||
console.log('Search input .value:', JSON.stringify(currentSearchValue));
|
console.log("Search input .value:", JSON.stringify(currentSearchValue));
|
||||||
console.log('Search input getAttribute("value"):', JSON.stringify(currentSearchAttr));
|
console.log(
|
||||||
console.log('Search input getAttribute("data-value"):', JSON.stringify(currentSearchDataValue));
|
'Search input getAttribute("value"):',
|
||||||
console.log('Search input innerText:', JSON.stringify(currentSearchInnerText));
|
JSON.stringify(currentSearchAttr)
|
||||||
console.log('Category select .value:', JSON.stringify(currentCategoryValue));
|
);
|
||||||
console.log('Last stored values - search:"' + self.lastSearchValue + '" category:"' + self.lastCategoryValue + '"');
|
console.log(
|
||||||
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)
|
// Log every 20 polls (reduce spam)
|
||||||
if (pollingCounter % 20 === 0) {
|
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
|
// Check for ANY change in either field
|
||||||
if (currentSearchValue !== self.lastSearchValue || currentCategoryValue !== self.lastCategoryValue) {
|
if (
|
||||||
console.log('[realtimeSearch] ⚡ CHANGE DETECTED: search="' + currentSearchValue + '" (was:"' + self.lastSearchValue + '") | category="' + currentCategoryValue + '" (was:"' + self.lastCategoryValue + '")');
|
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.lastSearchValue = currentSearchValue;
|
||||||
self.lastCategoryValue = currentCategoryValue;
|
self.lastCategoryValue = currentCategoryValue;
|
||||||
self._filterProducts();
|
|
||||||
|
// 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) {
|
} catch (error) {
|
||||||
console.error('[realtimeSearch] ❌ Error in polling:', error.message);
|
console.error("[realtimeSearch] ❌ Error in polling:", error.message);
|
||||||
}
|
}
|
||||||
}, 300); // Check every 300ms
|
}, 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() {
|
_initializeAvailableTags: function () {
|
||||||
/**
|
/**
|
||||||
* Initialize availableTags map from the DOM tag filter badges.
|
* Initialize availableTags map from the DOM tag filter badges.
|
||||||
* Format: availableTags[tagId] = {id, name, count}
|
* Format: availableTags[tagId] = {id, name, count}
|
||||||
|
|
@ -330,30 +442,41 @@
|
||||||
var self = this;
|
var self = this;
|
||||||
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
|
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
|
||||||
|
|
||||||
tagBadges.forEach(function(badge) {
|
tagBadges.forEach(function (badge) {
|
||||||
var tagId = parseInt(badge.getAttribute('data-tag-id'), 10);
|
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
|
||||||
var tagName = badge.getAttribute('data-tag-name') || '';
|
var tagName = badge.getAttribute("data-tag-name") || "";
|
||||||
var countSpan = badge.querySelector('.tag-count');
|
var countSpan = badge.querySelector(".tag-count");
|
||||||
var count = countSpan ? parseInt(countSpan.textContent, 10) : 0;
|
var count = countSpan ? parseInt(countSpan.textContent, 10) : 0;
|
||||||
|
|
||||||
self.availableTags[tagId] = {
|
self.availableTags[tagId] = {
|
||||||
id: tagId,
|
id: tagId,
|
||||||
name: tagName,
|
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() {
|
_filterProducts: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var searchQuery = (self.searchInput.value || '').toLowerCase().trim();
|
var searchQuery = (self.searchInput.value || "").toLowerCase().trim();
|
||||||
var selectedCategoryId = (self.categorySelect.value || '').toString();
|
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)
|
// Build a set of allowed category IDs (selected category + ALL descendants recursively)
|
||||||
var allowedCategories = {};
|
var allowedCategories = {};
|
||||||
|
|
@ -362,10 +485,10 @@
|
||||||
allowedCategories[selectedCategoryId] = true;
|
allowedCategories[selectedCategoryId] = true;
|
||||||
|
|
||||||
// Recursive function to get all descendants
|
// Recursive function to get all descendants
|
||||||
var getAllDescendants = function(parentId) {
|
var getAllDescendants = function (parentId) {
|
||||||
var descendants = [];
|
var descendants = [];
|
||||||
if (self.categoryHierarchy[parentId]) {
|
if (self.categoryHierarchy[parentId]) {
|
||||||
self.categoryHierarchy[parentId].forEach(function(childId) {
|
self.categoryHierarchy[parentId].forEach(function (childId) {
|
||||||
descendants.push(childId);
|
descendants.push(childId);
|
||||||
allowedCategories[childId] = true;
|
allowedCategories[childId] = true;
|
||||||
// Recursivamente obtener descendientes del hijo
|
// Recursivamente obtener descendientes del hijo
|
||||||
|
|
@ -377,8 +500,17 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
var allDescendants = getAllDescendants(selectedCategoryId);
|
var allDescendants = getAllDescendants(selectedCategoryId);
|
||||||
console.log('[realtimeSearch] Selected category ' + selectedCategoryId + ' has ' + allDescendants.length + ' total descendants');
|
console.log(
|
||||||
console.log('[realtimeSearch] Allowed categories:', Object.keys(allowedCategories));
|
"[realtimeSearch] Selected category " +
|
||||||
|
selectedCategoryId +
|
||||||
|
" has " +
|
||||||
|
allDescendants.length +
|
||||||
|
" total descendants"
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[realtimeSearch] Allowed categories:",
|
||||||
|
Object.keys(allowedCategories)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var visibleCount = 0;
|
var visibleCount = 0;
|
||||||
|
|
@ -390,14 +522,15 @@
|
||||||
tagCounts[tagId] = 0;
|
tagCounts[tagId] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.allProducts.forEach(function(product) {
|
self.allProducts.forEach(function (product) {
|
||||||
var nameMatches = !searchQuery || product.name.indexOf(searchQuery) !== -1;
|
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)
|
// Tag filtering: if tags are selected, product must have AT LEAST ONE selected tag (OR logic)
|
||||||
var tagMatches = true;
|
var tagMatches = true;
|
||||||
if (self.selectedTags.size > 0) {
|
if (self.selectedTags.size > 0) {
|
||||||
tagMatches = product.tags.some(function(productTagId) {
|
tagMatches = product.tags.some(function (productTagId) {
|
||||||
return self.selectedTags.has(productTagId);
|
return self.selectedTags.has(productTagId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -405,17 +538,17 @@
|
||||||
var shouldShow = nameMatches && categoryMatches && tagMatches;
|
var shouldShow = nameMatches && categoryMatches && tagMatches;
|
||||||
|
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
product.element.classList.remove('hidden-product');
|
product.element.classList.remove("hidden-product");
|
||||||
visibleCount++;
|
visibleCount++;
|
||||||
|
|
||||||
// Count this product's tags toward the dynamic counters
|
// Count this product's tags toward the dynamic counters
|
||||||
product.tags.forEach(function(tagId) {
|
product.tags.forEach(function (tagId) {
|
||||||
if (tagCounts.hasOwnProperty(tagId)) {
|
if (tagCounts.hasOwnProperty(tagId)) {
|
||||||
tagCounts[tagId]++;
|
tagCounts[tagId]++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
product.element.classList.add('hidden-product');
|
product.element.classList.add("hidden-product");
|
||||||
hiddenCount++;
|
hiddenCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -424,71 +557,85 @@
|
||||||
for (var tagId in tagCounts) {
|
for (var tagId in tagCounts) {
|
||||||
var badge = document.querySelector('[data-tag-id="' + tagId + '"]');
|
var badge = document.querySelector('[data-tag-id="' + tagId + '"]');
|
||||||
if (badge) {
|
if (badge) {
|
||||||
var countSpan = badge.querySelector('.tag-count');
|
var countSpan = badge.querySelector(".tag-count");
|
||||||
if (countSpan) {
|
if (countSpan) {
|
||||||
countSpan.textContent = tagCounts[tagId];
|
countSpan.textContent = tagCounts[tagId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[realtimeSearch] Filter result: visible=' + visibleCount + ' hidden=' + hiddenCount);
|
console.log(
|
||||||
|
"[realtimeSearch] Filter result: visible=" +
|
||||||
|
visibleCount +
|
||||||
|
" hidden=" +
|
||||||
|
hiddenCount
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[realtimeSearch] ERROR in _filterProducts():', error.message);
|
console.error("[realtimeSearch] ERROR in _filterProducts():", error.message);
|
||||||
console.error('[realtimeSearch] Stack:', error.stack);
|
console.error("[realtimeSearch] Stack:", error.stack);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize when DOM is ready
|
// 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() {
|
function tryInit() {
|
||||||
try {
|
try {
|
||||||
console.log('[realtimeSearch] Attempting initialization...');
|
console.log("[realtimeSearch] Attempting initialization...");
|
||||||
|
|
||||||
// Query product cards
|
// Query product cards
|
||||||
var productCards = document.querySelectorAll('.product-card');
|
var productCards = document.querySelectorAll(".product-card");
|
||||||
console.log('[realtimeSearch] Found ' + productCards.length + ' product cards');
|
console.log("[realtimeSearch] Found " + productCards.length + " product cards");
|
||||||
|
|
||||||
// Use the NEW pure HTML input with ID (not transformed by Odoo)
|
// Use the NEW pure HTML input with ID (not transformed by Odoo)
|
||||||
var searchInput = document.getElementById('realtime-search-input');
|
var searchInput = document.getElementById("realtime-search-input");
|
||||||
console.log('[realtimeSearch] Search input found:', !!searchInput);
|
console.log("[realtimeSearch] Search input found:", !!searchInput);
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
console.log('[realtimeSearch] Search input class:', searchInput.className);
|
console.log("[realtimeSearch] Search input class:", searchInput.className);
|
||||||
console.log('[realtimeSearch] Search input type:', searchInput.type);
|
console.log("[realtimeSearch] Search input type:", searchInput.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category select with ID (not transformed by Odoo)
|
// Category select with ID (not transformed by Odoo)
|
||||||
var categorySelect = document.getElementById('realtime-category-select');
|
var categorySelect = document.getElementById("realtime-category-select");
|
||||||
console.log('[realtimeSearch] Category select found:', !!categorySelect);
|
console.log("[realtimeSearch] Category select found:", !!categorySelect);
|
||||||
|
|
||||||
if (productCards.length > 0 && searchInput) {
|
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()
|
// Assign elements to window.realtimeSearch BEFORE calling init()
|
||||||
window.realtimeSearch.searchInput = searchInput;
|
window.realtimeSearch.searchInput = searchInput;
|
||||||
window.realtimeSearch.categorySelect = categorySelect;
|
window.realtimeSearch.categorySelect = categorySelect;
|
||||||
window.realtimeSearch.init();
|
window.realtimeSearch.init();
|
||||||
console.log('[realtimeSearch] ✓ Initialization complete!');
|
console.log("[realtimeSearch] ✓ Initialization complete!");
|
||||||
} else {
|
} 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) {
|
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);
|
setTimeout(tryInit, 500);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[realtimeSearch] ERROR in tryInit():', error.message);
|
console.error("[realtimeSearch] ERROR in tryInit():", error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === "loading") {
|
||||||
console.log('[realtimeSearch] Adding DOMContentLoaded listener');
|
console.log("[realtimeSearch] Adding DOMContentLoaded listener");
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
console.log('[realtimeSearch] DOMContentLoaded fired');
|
console.log("[realtimeSearch] DOMContentLoaded fired");
|
||||||
tryInit();
|
tryInit();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[realtimeSearch] DOM already loaded, initializing with delay');
|
console.log("[realtimeSearch] DOM already loaded, initializing with delay");
|
||||||
setTimeout(tryInit, 500);
|
setTimeout(tryInit, 500);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -586,6 +586,7 @@
|
||||||
t-attf-data-category="{{ selected_category }}"
|
t-attf-data-category="{{ selected_category }}"
|
||||||
t-attf-data-per-page="{{ per_page }}"
|
t-attf-data-per-page="{{ per_page }}"
|
||||||
t-attf-data-current-page="{{ current_page }}"
|
t-attf-data-current-page="{{ current_page }}"
|
||||||
|
t-attf-data-has-next="{{ 'true' if has_next else 'false' }}"
|
||||||
class="d-none">
|
class="d-none">
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -656,19 +657,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts (in dependency order) -->
|
<!-- Scripts (in dependency order) -->
|
||||||
<!-- Load i18n_manager first - fetches translations from server -->
|
<!-- Load i18n_manager first - fetches translations from server -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_manager.js" />
|
||||||
<!-- Keep legacy helpers for backwards compatibility -->
|
<!-- Keep legacy helpers for backwards compatibility -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/i18n_helpers.js" />
|
||||||
<!-- Main shop functionality (depends on i18nManager) -->
|
<!-- Main shop functionality (depends on i18nManager) -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/website_sale.js" />
|
||||||
<!-- UI enhancements -->
|
<!-- UI enhancements -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/checkout_labels.js" />
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/home_delivery.js" />
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/realtime_search.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/realtime_search.js" />
|
||||||
<!-- Infinite scroll for lazy loading products -->
|
<!-- Infinite scroll for lazy loading products -->
|
||||||
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/infinite_scroll.js" />
|
<script type="text/javascript" src="/website_sale_aplicoop/static/src/js/infinite_scroll.js" />
|
||||||
|
|
||||||
<!-- Initialize tooltips using native title attribute -->
|
<!-- Initialize tooltips using native title attribute -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue