jQuery(function ($) { $('a[href="admin.php?page=formipay-products"]').addClass('current').closest('li').addClass('current'); function autoset_variation_name() { var repeater_single = $('.product_variation_attributes.repeater [parent_repeater="parent"] > .wpcfto-field-content > .wpcfto-repeater-single'); $.each(repeater_single, function (key, parent) { var repeater_child = $(parent).find('[field_native_name="product_variation_attributes"]'); var attribute_name = repeater_child.find(`[name="product_variation_attributes_${key}_attribute_name"]`).val(); var attribute_type = repeater_child.find(`[name="product_variation_attributes_${key}_attribute_type"]`).val(); var repeater_child_single = repeater_child.find('.wpcfto-repeater-single'); $.each(repeater_child_single, function (index, child) { var label_field = $(`input[name="product_variation_attributes_${key}_attribute_variations_${index}_variation_label"]`); var name_field = $(`input[name="product_variation_attributes_${key}_attribute_variations_${index}_variation_name"]`); var color_field = $(`input[name="product_variation_attributes_${key}_attribute_variations_${index}_variation_color"]`); var color_field_row = color_field.closest('.wpcfto-repeater-field'); if (attribute_type == 'color') { color_field_row.show(); } else { color_field_row.hide(); } }); }); } $(document).on('change blur', '[field_native_name_inner="variation_label"] input', function () { autoset_variation_name(); }); $(document).on('click', '.stm_metaboxes_grid .stm_metaboxes_grid__inner .wpcfto-repeater .addArea', function () { autoset_variation_name(); }); var onMetaboxLoaded = setInterval(() => { var repeater_single = $('.product_variation_attributes.repeater [parent_repeater="parent"] > .wpcfto-field-content > .wpcfto-repeater-single'); if (repeater_single.length > 0) { autoset_variation_name(); clearInterval(onMetaboxLoaded); } }, 250); var waitForTable = setInterval(() => { if ($('#product-variables-table').length > 0) { clearInterval(waitForTable); // --- PERBAIKAN UTAMA DI SINI --- Vue.component('price-input', { // Gunakan 'value' sebagai prop, sesuai konvensi v-model Vue 2 props: { value: [Number, String], // Diubah dari modelValue currencySymbol: String, currencyDecimalDigits: { type: Number, default: 2 }, disabled: { type: Boolean, default: false } }, data() { return { inputValue: this.value // Diubah dari modelValue }; }, watch: { // Amati perubahan pada prop 'value' value(newValue) { // Diubah dari modelValue this.inputValue = newValue; } }, methods: { onInput(e) { const value = e.target.value; this.inputValue = value; // Pancarkan event 'input', sesuai konvensi v-model Vue 2 this.$emit('input', value); // Diubah dari 'update:modelValue' } }, computed: { stepValue() { return Math.pow(10, -this.currencyDecimalDigits); } }, template: `
{{ currencySymbol }}
` }); // --- AKHIR DARI PERBAIKAN --- new Vue({ el: '#product-variables-table', data() { return { tableRows: [], deletedKeys: [], isPhysical: window.product_details?.product_is_physical || false, pricingMethod: window.product_details?.product_pricing_method || 'auto', manualPrices: [], productHasVariation: window.product_details?.product_has_variation || false, jsonValue: '[]', attributeRepeaterWatcher: null, manualPricesWatcher: null, _debounceTimer: null, productType: window.product_details?.product_type || 'digital', currencyDecimalDigits: parseInt(window.product_details?.default_currency_decimal_digits) || 2, currencySymbol: window.product_details?.default_currency_symbol || '$' }; }, async mounted() { await this.initializeOrClearTable(); this.setupAttributeRepeaterSync(); this.setupManualPricesSync(); const typeRadios = document.querySelectorAll('input[name="product_type"]'); typeRadios.forEach(radio => { radio.addEventListener('change', e => { this.productType = e.target.value; this.isPhysical = e.target.value === 'physical'; }); }); const pricingRadios = document.querySelectorAll('input[name="product_pricing_method"]'); pricingRadios.forEach(radio => { radio.addEventListener('change', async e => { this.pricingMethod = e.target.value; await this.rebuildTable(); }); }); const hasVariationToggle = document.querySelector('input[name="product_has_variation"]'); if (hasVariationToggle) { hasVariationToggle.addEventListener('change', async (e) => { this.productHasVariation = e.target.checked; await this.initializeOrClearTable(); }); } }, beforeDestroy() { if (this.attributeRepeaterWatcher) { this.attributeRepeaterWatcher.disconnect(); } if (this.manualPricesWatcher) { this.manualPricesWatcher.disconnect(); } }, methods: { async initializeOrClearTable() { if (!this.productHasVariation) { this.tableRows = []; this.updateJson(); return; } await this.syncManualPrices(); await this.loadProductVariables(); }, _ensureRowDataStructure(row) { if (this.pricingMethod === 'manual') { if (typeof row.prices === 'undefined') { row.prices = JSON.parse(JSON.stringify(this.manualPrices)); delete row.price; delete row.sale; } } else { if (typeof row.price === 'undefined') { row.price = 0; row.sale = 0; delete row.prices; } } if (typeof row.expanded === 'undefined') { row.expanded = false; } return row; }, async loadProductVariables() { if (!this.productHasVariation) { this.tableRows = []; this.updateJson(); return; } const postId = window.formipayProductId || $('input[name="post_ID"]').val(); if (!postId) { await this.buildFromAttributes(); return; } try { const res = await $.ajax({ url: ajaxurl, method: 'POST', data: { action: 'get_product_variables', post_id: postId } }); if (res.success && Array.isArray(res.data) && res.data.length) { this.tableRows = res.data.map(row => this._ensureRowDataStructure(row)); this.deletedKeys = []; this.updateJson(); } else { await this.buildFromAttributes(); } } catch { await this.buildFromAttributes(); } }, async buildFromAttributes() { if (!this.productHasVariation) { this.tableRows = []; this.updateJson(); return; } try { const attributes = await this.getAttributeRepeaterData(); if (!attributes.length) { this.tableRows = []; this.updateJson(); return; } const combinations = this.getAllCombinations(attributes); const filtered = combinations.filter(c => !this.deletedKeys.includes(c.key)); const newRows = filtered.map(c => { const existing = this.tableRows.find(r => r.key === c.key); if (existing) { this._ensureRowDataStructure(existing); return Object.assign(existing, { name: c.label }); } let newRowData = { key: c.key, name: c.label, stock: '', weight: 0, active: true, }; return this._ensureRowDataStructure(newRowData); }); this.tableRows = newRows; this.updateJson(); } catch (e) { console.error("Error building from attributes:", e); this.tableRows = []; this.updateJson(); } }, getAttributeRepeaterData() { return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 100; const interval = setInterval(() => { const el = $('#variation-product_variation_attributes'); if (el.length && el.val()) { try { const data = JSON.parse(el.val()); clearInterval(interval); resolve(Array.isArray(data) ? data : []); } catch { clearInterval(interval); reject(new Error('Invalid JSON in attribute repeater')); } } else if (++attempts >= maxAttempts) { clearInterval(interval); reject(new Error('Attribute repeater data not found')); } }, 50); }); }, getAllCombinations(attributes) { const attrVars = attributes .map(attr => (attr.attribute_variations || []).map(v => ({ label: v.variation_label }))) .filter(arr => arr.length > 0); if (!attrVars.length) return []; const combine = (arrs) => arrs.reduce((a, b) => a.flatMap(d => b.map(e => [].concat(d, e)))); const combos = combine(attrVars); return combos.map(combo => { const labels = Array.isArray(combo) ? combo.map(c => c.label) : [combo.label]; return { key: labels.join('||'), label: labels.join(' - ') }; }); }, setupAttributeRepeaterSync() { const target = document.getElementById('variation-product_variation_attributes'); if (!target) return; const observer = new MutationObserver(() => { clearTimeout(this._debounceTimer); this._debounceTimer = setTimeout(() => this.buildFromAttributes(), 300); }); observer.observe(target, { attributes: true, attributeFilter: ['value'] }); this.attributeRepeaterWatcher = observer; }, async syncManualPrices() { const el = document.getElementById('general-product_prices'); if (el && el.value) { try { const pricesData = JSON.parse(el.value); this.manualPrices = Array.isArray(pricesData) ? pricesData : []; } catch (e) { this.manualPrices = []; } } else { this.manualPrices = []; } }, setupManualPricesSync() { const target = document.getElementById('general-product_prices'); if (!target) return; const observer = new MutationObserver(async () => { await this.syncManualPrices(); if (this.pricingMethod === 'manual') { this.tableRows.forEach(row => { const newPricesFromRepeater = this.manualPrices; let reconciledPrices = row.prices.filter(existingPrice => newPricesFromRepeater.some(newPrice => newPrice.currency === existingPrice.currency) ); newPricesFromRepeater.forEach(newPrice => { const isAlreadyThere = reconciledPrices.some(p => p.currency === newPrice.currency); if (!isAlreadyThere) { reconciledPrices.push(JSON.parse(JSON.stringify(newPrice))); } }); row.prices = reconciledPrices; }); } this.updateJson(); }); observer.observe(target, { attributes: true, attributeFilter: ['value'] }); this.manualPricesWatcher = observer; }, updateJson() { this.jsonValue = JSON.stringify(this.tableRows); }, async rebuildTable() { await this.initializeOrClearTable(); } }, watch: { tableRows: { handler() { this.updateJson(); }, deep: true } } }); } }, 250); });