2026-01-10 15:30:05 +01:00

176 lines
5.6 KiB
JavaScript

/**
* Products Component - Handles product selection and recommendations
*/
const ProductsComponent = {
/**
* Initialize products state
* @returns {Object}
*/
getInitialState() {
return {
products: [],
activeProduct: null,
selectedProductId: '',
selectedVariationId: '',
variations: [],
extraProductId: '',
recommendedOptions: [],
productSearch: '',
extraProductSearch: ''
};
},
/**
* Get products methods for Alpine.js component
* @returns {Object}
*/
getMethods() {
return {
/**
* Load products from API
*/
async loadProducts() {
try {
const data = await ApiService.getProducts();
// Sort products alphabetically by name
this.products = data.sort((a, b) => a.name.localeCompare(b.name, 'nl'));
} catch (e) {
console.error("Failed to load products");
}
},
/**
* Handle main product selection
*/
selectProduct() {
const p = this.products.find(x => x.id == this.selectedProductId);
if (!p) return;
this.activeProduct = p;
this.variations = p.variation_details || [];
this.cart = [];
this.selectedVariationId = '';
if (p.type !== 'variable') {
this.cart.push({
id: parseInt(p.id),
name: p.name,
price: p.price
});
this.loadRecommendations(p);
}
},
/**
* Handle variation selection
*/
selectVariation() {
const v = this.variations.find(x => x.id == this.selectedVariationId);
if (!v) return;
this.cart = [{
id: parseInt(this.activeProduct.id),
variation_id: parseInt(v.id),
name: this.activeProduct.name + ' - ' + this.getVarName(v),
price: v.price
}];
this.loadRecommendations(this.activeProduct);
},
/**
* Add extra product to cart
*/
addExtraItem() {
const p = this.products.find(x => x.id == this.extraProductId);
if (!p) return;
this.cart.push({
id: parseInt(p.id),
name: p.name,
price: p.price
});
this.extraProductId = '';
},
/**
* Load product recommendations
* @param {Object} product
*/
loadRecommendations(product) {
this.recommendedOptions = [];
// Prefer combined list from API
let ids = [];
if (product.recommended_ids && product.recommended_ids.length) {
ids = product.recommended_ids.map(id => parseInt(id));
} else {
// Fallback: combine Woo upsells + cross-sells
const ups = (product.upsell_ids || []).map(id => parseInt(id));
const crs = (product.cross_sell_ids || []).map(id => parseInt(id));
ids = [...ups, ...crs];
}
ids = [...new Set(ids)].filter(Boolean);
// Filter to products we have in our products list
this.recommendedOptions = this.products.filter(p => ids.includes(parseInt(p.id)));
},
/**
* Get variation name from attributes
* @param {Object} v - Variation object
* @returns {string}
*/
getVarName(v) {
return v.attributes.map(a => a.option).join(' ');
},
/**
* Reset product selection for new order
*/
resetProducts() {
this.selectedProductId = '';
this.selectedVariationId = '';
this.activeProduct = null;
this.variations = [];
this.recommendedOptions = [];
}
};
},
/**
* Get computed properties for Alpine.js component
* @returns {Object}
*/
getComputed() {
return {
/**
* Filter products by search term
* @returns {Array}
*/
filteredProducts() {
let filtered = this.products;
if (this.productSearch.trim()) {
const search = this.productSearch.toLowerCase().trim();
filtered = this.products.filter(p => p.name.toLowerCase().includes(search));
}
return filtered;
},
/**
* Filter extra products by search term
* @returns {Array}
*/
filteredExtraProducts() {
let filtered = this.products;
if (this.extraProductSearch.trim()) {
const search = this.extraProductSearch.toLowerCase().trim();
filtered = this.products.filter(p => p.name.toLowerCase().includes(search));
}
return filtered;
}
};
}
};