316 lines
12 KiB
JavaScript
316 lines
12 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: '',
|
|
extraProduct: null,
|
|
extraVariations: [],
|
|
extraSelectedVariationId: '',
|
|
recommendedOptions: [],
|
|
productSearch: '',
|
|
extraProductSearch: ''
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Get products methods for Alpine.js component
|
|
* @returns {Object}
|
|
*/
|
|
getMethods() {
|
|
return {
|
|
/**
|
|
* Load products from API
|
|
*/
|
|
async loadProducts() {
|
|
try {
|
|
if (this.isLoadingProducts) {
|
|
this.loadingMessage = 'Productgegevens ophalen van server...';
|
|
}
|
|
const data = await ApiService.getProducts();
|
|
|
|
if (this.isLoadingProducts) {
|
|
this.loadingMessage = 'Producten verwerken...';
|
|
}
|
|
// 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");
|
|
if (this.isLoadingProducts) {
|
|
this.loadingMessage = 'Fout bij laden producten';
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle main product selection (no longer auto-adds to cart)
|
|
*/
|
|
selectProduct() {
|
|
const p = this.products.find(x => x.id == this.selectedProductId);
|
|
if (!p) return;
|
|
|
|
console.log('[PRODUCT DEBUG] Selected product:', p);
|
|
|
|
this.activeProduct = p;
|
|
this.variations = p.variation_details || [];
|
|
this.selectedVariationId = '';
|
|
|
|
// Don't auto-add to cart anymore - wait for user to click Add button
|
|
// Don't clear cart anymore - allow adding multiple products
|
|
},
|
|
|
|
/**
|
|
* Add selected product to cart (new method for Add button)
|
|
*/
|
|
addProductToCart() {
|
|
if (!this.activeProduct) return;
|
|
|
|
const p = this.activeProduct;
|
|
|
|
// If variable product, require variation selection
|
|
if (p.type === 'variable') {
|
|
if (!this.selectedVariationId) {
|
|
alert('Selecteer eerst een variatie');
|
|
return;
|
|
}
|
|
|
|
const v = this.variations.find(x => x.id == this.selectedVariationId);
|
|
if (!v) return;
|
|
|
|
console.log('[PRODUCT DEBUG] Adding variable product with variation:', v);
|
|
|
|
this.cart.push({
|
|
id: parseInt(p.id),
|
|
variation_id: parseInt(v.id),
|
|
name: p.name + ' - ' + this.getVarName(v),
|
|
price: v.price,
|
|
isFree: false
|
|
});
|
|
} else {
|
|
// Simple product
|
|
console.log('[PRODUCT DEBUG] Adding simple product');
|
|
|
|
this.cart.push({
|
|
id: parseInt(p.id),
|
|
name: p.name,
|
|
price: p.price,
|
|
isFree: false
|
|
});
|
|
}
|
|
|
|
this.recalculateBogo();
|
|
this.loadRecommendations(p);
|
|
|
|
// Keep selection visible (don't reset)
|
|
},
|
|
|
|
/**
|
|
* Handle variation selection (no longer auto-adds to cart)
|
|
*/
|
|
selectVariation() {
|
|
// Just store the selection, don't add to cart
|
|
// User must click Add button to add to cart
|
|
console.log('[PRODUCT DEBUG] Variation selected:', this.selectedVariationId);
|
|
},
|
|
|
|
/**
|
|
* Select extra product (handles variations like main product)
|
|
*/
|
|
selectExtraProduct() {
|
|
const p = this.products.find(x => x.id == this.extraProductId);
|
|
if (!p) return;
|
|
|
|
console.log('[EXTRA PRODUCT DEBUG] Selected product:', p);
|
|
|
|
this.extraProduct = p;
|
|
this.extraVariations = p.variation_details || [];
|
|
this.extraSelectedVariationId = '';
|
|
|
|
// If not a variable product, add directly
|
|
if (p.type !== 'variable') {
|
|
console.log('[EXTRA PRODUCT DEBUG] Simple product, adding to cart');
|
|
this.cart.push({
|
|
id: parseInt(p.id),
|
|
name: p.name,
|
|
price: p.price,
|
|
isFree: false
|
|
});
|
|
this.recalculateBogo();
|
|
this.mergeRecommendations(p);
|
|
this.extraProductId = '';
|
|
this.extraProduct = null;
|
|
}
|
|
// If variable product, wait for variation selection
|
|
else {
|
|
console.log('[EXTRA PRODUCT DEBUG] Variable product, waiting for variation selection. Variations:', this.extraVariations);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle extra product variation selection
|
|
*/
|
|
selectExtraVariation() {
|
|
const v = this.extraVariations.find(x => x.id == this.extraSelectedVariationId);
|
|
if (!v || !this.extraProduct) return;
|
|
|
|
console.log('[EXTRA PRODUCT DEBUG] Selected variation:', v);
|
|
|
|
this.cart.push({
|
|
id: parseInt(this.extraProduct.id),
|
|
variation_id: parseInt(v.id),
|
|
name: this.extraProduct.name + ' - ' + this.getVarName(v),
|
|
price: v.price,
|
|
isFree: false
|
|
});
|
|
|
|
this.recalculateBogo();
|
|
this.mergeRecommendations(this.extraProduct);
|
|
|
|
// Reset extra product state
|
|
this.extraProductId = '';
|
|
this.extraProduct = null;
|
|
this.extraVariations = [];
|
|
this.extraSelectedVariationId = '';
|
|
},
|
|
|
|
/**
|
|
* Cancel extra product selection
|
|
*/
|
|
cancelExtraProduct() {
|
|
this.extraProductId = '';
|
|
this.extraProduct = null;
|
|
this.extraVariations = [];
|
|
this.extraSelectedVariationId = '';
|
|
},
|
|
|
|
/**
|
|
* 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)));
|
|
},
|
|
|
|
/**
|
|
* Merge recommendations from an additional product (for extra products)
|
|
* @param {Object} product
|
|
*/
|
|
mergeRecommendations(product) {
|
|
console.log('[MERGE RECOMMENDATIONS] Merging recommendations for:', product.name);
|
|
|
|
// Get existing recommendation IDs
|
|
const existingIds = this.recommendedOptions.map(p => parseInt(p.id));
|
|
console.log('[MERGE RECOMMENDATIONS] Existing IDs:', existingIds);
|
|
|
|
// Get new product's recommendations
|
|
let newIds = [];
|
|
if (product.recommended_ids && product.recommended_ids.length) {
|
|
newIds = 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));
|
|
newIds = [...ups, ...crs];
|
|
}
|
|
|
|
newIds = [...new Set(newIds)].filter(Boolean);
|
|
console.log('[MERGE RECOMMENDATIONS] New IDs to add:', newIds);
|
|
|
|
// Merge: add new recommendations that aren't already in the list
|
|
const idsToAdd = newIds.filter(id => !existingIds.includes(id));
|
|
console.log('[MERGE RECOMMENDATIONS] IDs to add (filtered):', idsToAdd);
|
|
|
|
const newRecommendations = this.products.filter(p => idsToAdd.includes(parseInt(p.id)));
|
|
console.log('[MERGE RECOMMENDATIONS] New recommendations to add:', newRecommendations.map(p => p.name));
|
|
|
|
this.recommendedOptions = [...this.recommendedOptions, ...newRecommendations];
|
|
console.log('[MERGE RECOMMENDATIONS] Final recommendations count:', this.recommendedOptions.length);
|
|
},
|
|
|
|
/**
|
|
* 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 = [];
|
|
this.extraProductId = '';
|
|
this.extraProduct = null;
|
|
this.extraVariations = [];
|
|
this.extraSelectedVariationId = '';
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
};
|
|
}
|
|
};
|