// Product Variants Management // Handles dynamic variant input fields based on selected VariantCollection class ProductVariantsManager { constructor() { this.variantCollectionSelect = document.getElementById('VariantCollectionId'); this.variantsJsonTextarea = document.getElementById('VariantsJson'); this.dynamicFieldsContainer = document.getElementById('dynamic-variant-fields'); this.advancedToggle = document.getElementById('toggle-advanced-variants'); this.advancedSection = document.getElementById('advanced-variant-section'); this.productStockInput = document.getElementById('StockQuantity'); this.productWeightUnitSelect = document.getElementById('WeightUnit'); if (!this.variantCollectionSelect || !this.variantsJsonTextarea || !this.dynamicFieldsContainer) { console.error('ProductVariantsManager: Required elements not found'); return; } this.currentProperties = []; this.init(); } init() { // Listen for variant collection selection changes this.variantCollectionSelect.addEventListener('change', (e) => { this.handleVariantCollectionChange(e.target.value); }); // Toggle advanced JSON editing if (this.advancedToggle) { this.advancedToggle.addEventListener('click', (e) => { e.preventDefault(); this.toggleAdvancedMode(); }); } // Load existing variant data if in edit mode if (this.variantCollectionSelect.value) { this.handleVariantCollectionChange(this.variantCollectionSelect.value); } else if (this.variantsJsonTextarea.value && this.variantsJsonTextarea.value.trim() !== '') { // No collection selected but has JSON - show advanced mode this.showAdvancedMode(); } // Form submission handler const form = this.variantCollectionSelect.closest('form'); if (form) { form.addEventListener('submit', (e) => { this.serializeVariantsToJson(); }); } } async handleVariantCollectionChange(collectionId) { if (!collectionId || collectionId === '') { this.clearDynamicFields(); this.currentProperties = []; return; } try { const response = await fetch(`/Admin/Products/GetVariantCollection?id=${collectionId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const collection = await response.json(); console.log('Loaded variant collection:', collection); console.log('PropertiesJson raw:', collection.propertiesJson); let propertyDefinitions = {}; try { const parsed = JSON.parse(collection.propertiesJson || '{}'); console.log('Parsed properties:', parsed); console.log('Parsed type:', typeof parsed, 'Is array:', Array.isArray(parsed)); if (typeof parsed === 'object' && !Array.isArray(parsed) && parsed !== null) { propertyDefinitions = parsed; } else if (Array.isArray(parsed)) { const converted = {}; parsed.forEach(item => { if (typeof item === 'string') { converted[item] = null; } else if (typeof item === 'object' && item.name) { converted[item.name] = item.values || null; } }); propertyDefinitions = converted; } else { console.error('Unexpected PropertiesJson format:', parsed); propertyDefinitions = {}; } console.log('Property definitions:', propertyDefinitions); } catch (err) { console.error('Error parsing PropertiesJson:', err); propertyDefinitions = {}; } this.currentProperties = propertyDefinitions; let existingVariants = []; if (this.variantsJsonTextarea.value && this.variantsJsonTextarea.value.trim() !== '') { try { existingVariants = JSON.parse(this.variantsJsonTextarea.value); if (!Array.isArray(existingVariants)) { existingVariants = []; } } catch (err) { console.warn('Could not parse existing variants JSON:', err); existingVariants = []; } } this.generateDynamicFields(propertyDefinitions, existingVariants); } catch (error) { console.error('Error loading variant collection:', error); alert('Failed to load variant collection properties. Please try again.'); } } generateDynamicFields(propertyDefinitions, existingVariants = []) { this.clearDynamicFields(); if (!propertyDefinitions || Object.keys(propertyDefinitions).length === 0) { this.dynamicFieldsContainer.innerHTML = '

This variant collection has no properties defined.

'; return; } let variantsToDisplay = existingVariants; let isAutoGenerated = false; if (!variantsToDisplay || variantsToDisplay.length === 0) { variantsToDisplay = this.generateCombinations(propertyDefinitions); isAutoGenerated = true; console.log('Auto-generated combinations:', variantsToDisplay); } else { console.log('Using existing variants:', variantsToDisplay); } if (variantsToDisplay.length === 0) { this.dynamicFieldsContainer.innerHTML = '

No variant combinations to generate. Please configure property values in the variant collection.

'; return; } const variantRows = variantsToDisplay.map((combo, index) => this.createVariantRow(combo, index, propertyDefinitions)).join(''); const alertMessage = isAutoGenerated ? `Auto-Generated Variants: ${variantsToDisplay.length} variant combination(s) created. Fill in optional details for each variant.` : `Existing Variants: ${variantsToDisplay.length} variant(s) loaded. You can modify or add new variants below.`; this.dynamicFieldsContainer.innerHTML = `
${alertMessage}
${variantRows}
`; document.getElementById('add-variant-row')?.addEventListener('click', () => { this.addCustomVariantRow(propertyDefinitions); }); this.attachVariantRowEventHandlers(); } attachVariantRowEventHandlers() { document.querySelectorAll('.remove-variant-row').forEach(btn => { btn.addEventListener('click', (e) => { const row = e.target.closest('.variant-row'); if (row) { row.remove(); this.updateStockCalculation(); } }); }); document.querySelectorAll('.toggle-variant-details').forEach(btn => { btn.addEventListener('click', (e) => { const rowIndex = btn.dataset.row; const detailsSection = document.querySelector(`.variant-details-section[data-row="${rowIndex}"]`); const icon = btn.querySelector('i'); if (detailsSection) { if (detailsSection.style.display === 'none') { detailsSection.style.display = 'block'; icon.classList.remove('fa-chevron-down'); icon.classList.add('fa-chevron-up'); } else { detailsSection.style.display = 'none'; icon.classList.remove('fa-chevron-up'); icon.classList.add('fa-chevron-down'); } } }); }); document.querySelectorAll('.variant-stock').forEach(input => { input.addEventListener('input', () => { this.updateStockCalculation(); }); }); this.updateStockCalculation(); } updateStockCalculation() { if (!this.productStockInput) return; const variantStockInputs = document.querySelectorAll('.variant-stock'); let totalVariantStock = 0; let hasVariantStock = false; variantStockInputs.forEach(input => { const value = input.value.trim(); if (value && !isNaN(value)) { totalVariantStock += parseInt(value); hasVariantStock = true; } }); const stockLabel = this.productStockInput.closest('.mb-3')?.querySelector('label'); let warningIcon = document.getElementById('stock-variant-warning'); if (hasVariantStock) { this.productStockInput.disabled = true; this.productStockInput.value = totalVariantStock; this.productStockInput.classList.add('bg-light'); if (!warningIcon && stockLabel) { warningIcon = document.createElement('i'); warningIcon.id = 'stock-variant-warning'; warningIcon.className = 'fas fa-calculator text-info ms-2'; warningIcon.style.cursor = 'pointer'; warningIcon.setAttribute('data-bs-toggle', 'tooltip'); warningIcon.setAttribute('data-bs-placement', 'top'); warningIcon.setAttribute('title', `Calculated from variants: ${totalVariantStock} units total. Modify variant stock quantities to change total.`); stockLabel.appendChild(warningIcon); if (typeof bootstrap !== 'undefined') { new bootstrap.Tooltip(warningIcon); } } else if (warningIcon) { warningIcon.setAttribute('title', `Calculated from variants: ${totalVariantStock} units total. Modify variant stock quantities to change total.`); if (typeof bootstrap !== 'undefined') { const tooltip = bootstrap.Tooltip.getInstance(warningIcon); if (tooltip) { tooltip.dispose(); new bootstrap.Tooltip(warningIcon); } } } } else { this.productStockInput.disabled = false; this.productStockInput.classList.remove('bg-light'); if (warningIcon) { const tooltip = bootstrap?.Tooltip?.getInstance(warningIcon); if (tooltip) { tooltip.dispose(); } warningIcon.remove(); } } } generateCombinations(propertyDefinitions) { const properties = Object.keys(propertyDefinitions); if (properties.length === 0) return []; const propertyValues = properties.map(prop => { const values = propertyDefinitions[prop]; return { name: prop, values: Array.isArray(values) && values.length > 0 ? values : [null] }; }); const combinations = []; const generate = (index, current) => { if (index === propertyValues.length) { combinations.push({...current}); return; } const prop = propertyValues[index]; for (const value of prop.values) { current[prop.name] = value; generate(index + 1, current); } }; generate(0, {}); return combinations; } createVariantRow(variantData, index, propertyDefinitions) { const propertyFields = Object.entries(propertyDefinitions).map(([propName, propValues]) => { const currentValue = variantData[propName]; let fieldHtml; if (Array.isArray(propValues) && propValues.length > 0) { const options = propValues.map(val => `` ).join(''); fieldHtml = `
`; } else { fieldHtml = `
`; } return fieldHtml; }).join(''); const variantLabel = Object.entries(variantData) .filter(([k, v]) => v !== null && k !== 'Label' && k !== 'Price' && k !== 'StockQty' && k !== 'Weight' && k !== 'WeightUnit') .map(([k, v]) => v) .join(' / '); const existingLabel = variantData.Label || ''; const existingStockQty = variantData.StockQty || ''; const existingWeight = variantData.Weight || ''; const existingWeightUnit = variantData.WeightUnit !== undefined ? variantData.WeightUnit : (this.productWeightUnitSelect ? this.productWeightUnitSelect.value : '0'); const weightUnitOptions = ` `; return `
#${index + 1}
${propertyFields}
`; } addCustomVariantRow(propertyDefinitions) { const container = document.getElementById('variant-rows-container'); if (!container) return; const existingRows = container.querySelectorAll('.variant-row').length; const emptyVariant = {}; Object.keys(propertyDefinitions).forEach(prop => { emptyVariant[prop] = null; }); const newRowHtml = this.createVariantRow(emptyVariant, existingRows, propertyDefinitions); container.insertAdjacentHTML('beforeend', newRowHtml); this.attachVariantRowEventHandlers(); } populateDynamicFields(variantData) { if (!variantData || typeof variantData !== 'object') { return; } Object.keys(variantData).forEach(key => { const input = document.getElementById(`variant-${key}`); if (input) { input.value = variantData[key]; } }); } clearDynamicFields() { this.dynamicFieldsContainer.innerHTML = ''; } serializeVariantsToJson() { if (!this.variantCollectionSelect.value) { return; } const variantRows = document.querySelectorAll('.variant-row'); if (variantRows.length === 0) { this.variantsJsonTextarea.value = ''; return; } const allVariants = []; variantRows.forEach((row, rowIndex) => { const variantData = {}; let hasValues = false; const propertyInputs = row.querySelectorAll('.variant-property'); propertyInputs.forEach(input => { const property = input.dataset.property; const value = input.value.trim(); if (value) { variantData[property] = value; hasValues = true; } }); const labelInput = row.querySelector('.variant-label'); if (labelInput && labelInput.value.trim()) { variantData['Label'] = labelInput.value.trim(); hasValues = true; } const priceInput = row.querySelector('.variant-price'); if (priceInput && priceInput.value.trim()) { variantData['Price'] = parseFloat(priceInput.value.trim()); hasValues = true; } const stockInput = row.querySelector('.variant-stock'); if (stockInput && stockInput.value.trim()) { variantData['StockQty'] = parseInt(stockInput.value.trim()); hasValues = true; } const weightInput = row.querySelector('.variant-weight'); if (weightInput && weightInput.value.trim()) { variantData['Weight'] = parseFloat(weightInput.value.trim()); hasValues = true; const weightUnitSelect = row.querySelector('.variant-weight-unit'); if (weightUnitSelect) { variantData['WeightUnit'] = parseInt(weightUnitSelect.value); } } if (hasValues) { allVariants.push(variantData); } }); this.variantsJsonTextarea.value = allVariants.length > 0 ? JSON.stringify(allVariants) : ''; console.log('Serialized variants:', this.variantsJsonTextarea.value); } toggleAdvancedMode() { if (this.advancedSection.style.display === 'none') { this.showAdvancedMode(); } else { this.hideAdvancedMode(); } } showAdvancedMode() { if (this.advancedSection) { this.advancedSection.style.display = 'block'; if (this.advancedToggle) { this.advancedToggle.innerHTML = ' Hide Advanced JSON Editor'; } } } hideAdvancedMode() { if (this.advancedSection) { this.advancedSection.style.display = 'none'; if (this.advancedToggle) { this.advancedToggle.innerHTML = ' Show Advanced JSON Editor'; } } } } // Initialize on DOM ready document.addEventListener('DOMContentLoaded', function() { if (document.getElementById('VariantCollectionId')) { window.productVariantsManager = new ProductVariantsManager(); console.log('ProductVariantsManager initialized'); } });