/** * 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; } }; } };