fix Products, Coupons, and admin pages
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
jQuery(function ($) {
|
||||
|
||||
console.log(product_details)
|
||||
|
||||
$('a[href="admin.php?page=formipay-products"]').addClass('current').closest('li').addClass('current');
|
||||
|
||||
function autoset_variation_name() {
|
||||
@@ -47,16 +49,12 @@ jQuery(function ($) {
|
||||
Vue.component('price-input', {
|
||||
// Gunakan 'value' sebagai prop, sesuai konvensi v-model Vue 2
|
||||
props: {
|
||||
value: [Number, String], // Diubah dari modelValue
|
||||
value: [Number, String],
|
||||
currencySymbol: String,
|
||||
currencyDecimalDigits: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
currencyDecimalDigits: { type: Number, default: 2 },
|
||||
disabled: { type: Boolean, default: false },
|
||||
required: { type: Boolean, default: false },
|
||||
placeholder: { type: String, default: 'Auto' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -79,13 +77,26 @@ jQuery(function ($) {
|
||||
},
|
||||
computed: {
|
||||
stepValue() {
|
||||
return Math.pow(10, -this.currencyDecimalDigits);
|
||||
const digits = this.currencyDecimalDigits || 0;
|
||||
if (!digits || digits === 0) return 1;
|
||||
return 1 / Math.pow(10, digits);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="price-input-wrapper">
|
||||
<span class="price-currency">{{ currencySymbol }}</span>
|
||||
<input type="number" :value="inputValue" @input="onInput" :step="stepValue" placeholder="0" :disabled="disabled" />
|
||||
<span class="price-currency">
|
||||
{{ currencySymbol }}
|
||||
<span v-if="required" class="required-asterisk" style="color: red; margin-left: 4px;">*</span>
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
:value="inputValue"
|
||||
@input="onInput"
|
||||
:step="stepValue"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
@@ -98,22 +109,27 @@ jQuery(function ($) {
|
||||
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 || '$'
|
||||
currencySymbol: window.product_details?.default_currency_symbol || '$',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showFlatPricing() {
|
||||
const mc = !!(window.product_details && window.product_details.multicurrency);
|
||||
const selected = (window.product_details && window.product_details.global_selected_currencies) || {};
|
||||
const count = Object.keys(selected).length;
|
||||
return !mc || count <= 1; // flat when off or only one currency selected
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.initializeOrClearTable();
|
||||
this.setupAttributeRepeaterSync();
|
||||
this.setupManualPricesSync();
|
||||
this.$nextTick(() => this.enforcePriceInputStates());
|
||||
const typeRadios = document.querySelectorAll('input[name="product_type"]');
|
||||
typeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', e => {
|
||||
@@ -121,13 +137,6 @@ jQuery(function ($) {
|
||||
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) => {
|
||||
@@ -135,43 +144,223 @@ jQuery(function ($) {
|
||||
await this.initializeOrClearTable();
|
||||
});
|
||||
}
|
||||
// Add submit validation for missing default currency price
|
||||
const postForm = document.getElementById('post');
|
||||
if (postForm) {
|
||||
postForm.addEventListener('submit', (ev) => {
|
||||
const missing = this.findFirstMissingDefault();
|
||||
if (missing) {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
const tmpl = (window.product_details
|
||||
&& window.product_details.variation_table
|
||||
&& window.product_details.variation_table.error_missing_default_price)
|
||||
|| 'Please fill Regular Price for default currency (%1$s) in variation "%2$s".';
|
||||
|
||||
const msg = tmpl.replace('%1$s', missing.currencyCode).replace('%2$s', missing.rowLabel);
|
||||
|
||||
if (typeof window.triggerSwalEmptyRequired === 'function') {
|
||||
window.triggerSwalEmptyRequired(msg);
|
||||
} else if (window.Swal && typeof window.Swal.fire === 'function') {
|
||||
window.Swal.fire({ icon: 'error', title: 'Required field empty', text: msg });
|
||||
} else {
|
||||
alert(msg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.attributeRepeaterWatcher) {
|
||||
this.attributeRepeaterWatcher.disconnect();
|
||||
}
|
||||
if (this.manualPricesWatcher) {
|
||||
this.manualPricesWatcher.disconnect();
|
||||
}
|
||||
if (this.attributeRepeaterWatcher) {
|
||||
this.attributeRepeaterWatcher.disconnect();
|
||||
}
|
||||
if (this._attrPoller) {
|
||||
clearInterval(this._attrPoller);
|
||||
this._attrPoller = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
defaultEntry(row) {
|
||||
const defCode = this.getDefaultCurrencyCode();
|
||||
const defTriple = (window.product_details && window.product_details.default_currency) || '';
|
||||
const fallback = {
|
||||
currency: defTriple || (defCode ? `${defCode}:::` : 'USD:::'),
|
||||
regular_price: '',
|
||||
sale_price: '',
|
||||
currency_decimal_digits: this.findDigitsForCode(defCode)
|
||||
};
|
||||
if (!row || !Array.isArray(row.prices) || row.prices.length === 0) {
|
||||
return fallback;
|
||||
}
|
||||
const found = row.prices.find(p => String(p.currency).split(':::')[0] === defCode);
|
||||
return found || row.prices[0] || fallback;
|
||||
},
|
||||
enforcePriceInputStates() {
|
||||
const defTriple = (window.product_details && window.product_details.default_currency) || '';
|
||||
const defCode = String(defTriple).split(':::')[0] || '';
|
||||
const REG_PLACEHOLDER = window.product_details?.variation_table?.child_placeholder_regular || 'Enter Regular Price';
|
||||
const SALE_PLACEHOLDER = window.product_details?.variation_table?.child_placeholder_sale || 'Enter Sale Price';
|
||||
const $scope = $('#product-variables-table');
|
||||
|
||||
if (!Array.isArray(this.tableRows) || this.tableRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) Reset all number inputs to optional + "Auto"
|
||||
$scope.find('input[type="number"]').each(function () {
|
||||
$(this).attr('placeholder', 'Auto').removeAttr('required');
|
||||
});
|
||||
|
||||
// 2) Flat mode: set placeholders and step for default currency inputs in main row
|
||||
if (this.showFlatPricing) {
|
||||
$scope.find('tbody > tr').each((rowIdx, tr) => {
|
||||
const row = this.tableRows[rowIdx];
|
||||
if (!row) return;
|
||||
const def = this.defaultEntry(row);
|
||||
if (!def) return;
|
||||
const code = String(def.currency || '').split(':::')[0];
|
||||
const digits = Number(this.findDigitsForCode(code) || 0);
|
||||
const step = (!digits || digits === 0) ? 1 : (1 / Math.pow(10, digits));
|
||||
const $price = $(tr).find('td[data-cell="price"] input[type="number"]');
|
||||
const $sale = $(tr).find('td[data-cell="sale"] input[type="number"]');
|
||||
if ($price.length) {
|
||||
$price.attr('placeholder', REG_PLACEHOLDER).attr('step', step).removeAttr('required');
|
||||
}
|
||||
if ($sale.length) {
|
||||
$sale.attr('placeholder', SALE_PLACEHOLDER).attr('step', step).removeAttr('required');
|
||||
}
|
||||
});
|
||||
return; // do not process inner tables in flat mode
|
||||
}
|
||||
|
||||
// 3) Multi-currency inner tables
|
||||
const detailsRows = $scope.find('.variation-details-row .inner-table tbody');
|
||||
detailsRows.each((rowIdx, tbodyEl) => {
|
||||
const vueRow = this.tableRows[rowIdx];
|
||||
if (!vueRow || !Array.isArray(vueRow.prices)) return;
|
||||
const $tbody = $(tbodyEl);
|
||||
const trs = $tbody.find('tr');
|
||||
trs.each((i, tr) => {
|
||||
const entry = vueRow.prices[i];
|
||||
if (!entry) return;
|
||||
const code = String(entry.currency).split(':::')[0];
|
||||
const digits = Number(this.findDigitsForCode(code) || 0);
|
||||
const step = (!digits || digits === 0) ? 1 : (1 / Math.pow(10, digits));
|
||||
const $inputs = $(tr).find('input[type="number"]');
|
||||
$inputs.attr('step', step);
|
||||
if (code === defCode) {
|
||||
if ($inputs.length) {
|
||||
$inputs.eq(0).removeAttr('required').attr('placeholder', REG_PLACEHOLDER);
|
||||
if ($inputs.length > 1) {
|
||||
$inputs.eq(1).attr('placeholder', SALE_PLACEHOLDER);
|
||||
}
|
||||
}
|
||||
const $firstTd = $(tr).children('td').eq(0);
|
||||
if ($firstTd.find('.required-asterisk').length === 0) {
|
||||
$firstTd.append($('<span/>', { class: 'required-asterisk', text: ' *', css: { color: 'red', marginLeft: '4px' } }));
|
||||
}
|
||||
} else {
|
||||
$(tr).children('td').eq(0).find('.required-asterisk').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
findDigitsForCode(codeOrTriple) {
|
||||
// product_details.global_currencies is the 'raw' array from PHP (with decimal_digits)
|
||||
const globalsRaw = (window.product_details && window.product_details.global_currencies) || [];
|
||||
const target = String(codeOrTriple).split(':::')[0];
|
||||
const found = globalsRaw.find(c => String(c.currency).split(':::')[0] === target);
|
||||
const dd = parseInt(found && found.decimal_digits, 10);
|
||||
return Number.isFinite(dd) ? dd : 2;
|
||||
},
|
||||
buildCurrencyPriceSkeleton() {
|
||||
const selectedMap = (window.product_details && window.product_details.global_selected_currencies) || {};
|
||||
let entries = Object.keys(selectedMap || {});
|
||||
// If multicurrency is off or nothing selected, fall back to default currency triple
|
||||
if (!entries.length) {
|
||||
const defTriple = (window.product_details && window.product_details.default_currency) || '';
|
||||
if (defTriple) entries = [defTriple];
|
||||
}
|
||||
return entries.map(val => {
|
||||
const parts = String(val).split(':::');
|
||||
const code = parts[0] || val;
|
||||
return {
|
||||
currency: val,
|
||||
regular_price: '',
|
||||
sale_price: '',
|
||||
currency_decimal_digits: this.findDigitsForCode(code)
|
||||
};
|
||||
});
|
||||
},
|
||||
async initializeOrClearTable() {
|
||||
if (!this.productHasVariation) {
|
||||
this.tableRows = [];
|
||||
this.updateJson();
|
||||
return;
|
||||
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;
|
||||
// Ensure expanded flag
|
||||
if (typeof row.expanded === 'undefined') {
|
||||
row.expanded = false;
|
||||
}
|
||||
|
||||
// Always use multi-currency child rows
|
||||
const skeleton = this.buildCurrencyPriceSkeleton();
|
||||
|
||||
const byCode = (val) => String(val).split(':::')[0];
|
||||
// Initialize or reconcile row.prices against current global selected currencies
|
||||
if (!Array.isArray(row.prices)) {
|
||||
row.prices = JSON.parse(JSON.stringify(skeleton));
|
||||
} else {
|
||||
const globalCodes = new Set(skeleton.map(p => byCode(p.currency)));
|
||||
// keep only currencies that still exist globally
|
||||
row.prices = row.prices.filter(p => p && globalCodes.has(byCode(p.currency)));
|
||||
// add newly-added global currencies
|
||||
skeleton.forEach(skel => {
|
||||
const code = byCode(skel.currency);
|
||||
const exists = row.prices.some(p => byCode(p.currency) === code);
|
||||
if (!exists) row.prices.push(JSON.parse(JSON.stringify(skel)));
|
||||
});
|
||||
}
|
||||
|
||||
// If still empty (e.g., multicurrency off), seed with skeleton
|
||||
if (!Array.isArray(row.prices) || row.prices.length === 0) {
|
||||
row.prices = JSON.parse(JSON.stringify(skeleton));
|
||||
}
|
||||
|
||||
// Normalize digits and ensure numeric defaults
|
||||
row.prices.forEach(p => {
|
||||
const code = byCode(p.currency);
|
||||
p.currency_decimal_digits = this.findDigitsForCode(code);
|
||||
if (typeof p.regular_price === 'undefined' || p.regular_price === null) p.regular_price = '';
|
||||
if (typeof p.sale_price === 'undefined' || p.sale_price === null) p.sale_price = '';
|
||||
});
|
||||
|
||||
// Sort so default currency is always first
|
||||
const defaultCode = this.getDefaultCurrencyCode();
|
||||
row.prices.sort((a, b) => {
|
||||
const byCode = val => String(val).split(':::')[0];
|
||||
if (byCode(a.currency) === defaultCode) return -1;
|
||||
if (byCode(b.currency) === defaultCode) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Remove any legacy single-currency fields if present
|
||||
delete row.price;
|
||||
delete row.sale;
|
||||
|
||||
return row;
|
||||
},
|
||||
|
||||
getDefaultCurrencyCode() {
|
||||
// Try multiple sources for robustness
|
||||
const triple = (window.product_details && window.product_details.default_currency) || '';
|
||||
const codeFromTriple = String(triple).split(':::')[0];
|
||||
const fallback = window.product_details && window.product_details.default_currency_code;
|
||||
return codeFromTriple || fallback || '';
|
||||
},
|
||||
async loadProductVariables() {
|
||||
if (!this.productHasVariation) {
|
||||
@@ -239,29 +428,29 @@ jQuery(function ($) {
|
||||
this.tableRows = newRows;
|
||||
this.updateJson();
|
||||
} catch (e) {
|
||||
console.error("Error building from attributes:", e);
|
||||
console.warn("Attributes not available; initializing empty variations.");
|
||||
this.tableRows = [];
|
||||
this.updateJson();
|
||||
}
|
||||
},
|
||||
getAttributeRepeaterData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100;
|
||||
const interval = setInterval(() => {
|
||||
const el = $('#variation-product_variation_attributes');
|
||||
const el = $('input[name="product_variation_attributes"]');
|
||||
if (el.length && el.val()) {
|
||||
try {
|
||||
const data = JSON.parse(el.val());
|
||||
clearInterval(interval);
|
||||
resolve(Array.isArray(data) ? data : []);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Invalid JSON in attribute repeater'));
|
||||
resolve([]);
|
||||
}
|
||||
} else if (++attempts >= maxAttempts) {
|
||||
clearInterval(interval);
|
||||
reject(new Error('Attribute repeater data not found'));
|
||||
resolve([]);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
@@ -284,61 +473,109 @@ jQuery(function ($) {
|
||||
});
|
||||
},
|
||||
setupAttributeRepeaterSync() {
|
||||
const target = document.getElementById('variation-product_variation_attributes');
|
||||
if (!target) return;
|
||||
const observer = new MutationObserver(() => {
|
||||
const rebuildDebounced = () => {
|
||||
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 = [];
|
||||
this._debounceTimer = setTimeout(() => {
|
||||
this.buildFromAttributes();
|
||||
this.updateJson();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// Root hidden input that contains the full JSON
|
||||
const rootInput = document.querySelector('input[name="product_variation_attributes"]');
|
||||
if (rootInput) {
|
||||
// Listen to direct input/change
|
||||
rootInput.addEventListener('input', rebuildDebounced);
|
||||
rootInput.addEventListener('change', rebuildDebounced);
|
||||
|
||||
// Observe value attribute changes (WPCFTO updates .value programmatically)
|
||||
const obs = new MutationObserver(rebuildDebounced);
|
||||
obs.observe(rootInput, { attributes: true, attributeFilter: ['value'] });
|
||||
this.attributeRepeaterWatcher = obs;
|
||||
// Fallback poller in case the repeater mutates value without firing events
|
||||
this._lastAttrJson = (rootInput && rootInput.value) || '';
|
||||
if (this._attrPoller) clearInterval(this._attrPoller);
|
||||
this._attrPoller = setInterval(() => {
|
||||
const el = document.querySelector('input[name="product_variation_attributes"]');
|
||||
if (!el) return;
|
||||
const curr = el.value || '';
|
||||
if (curr !== this._lastAttrJson) {
|
||||
this._lastAttrJson = curr;
|
||||
rebuildDebounced();
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Also watch nested variation lists to catch intermediate states before root JSON updates
|
||||
const nestedSelector = 'input[name^="product_variation_attributes_"][name$="_attribute_variations"]';
|
||||
const attachNestedObservers = () => {
|
||||
document.querySelectorAll(nestedSelector).forEach((inp) => {
|
||||
// Skip if already tagged
|
||||
if (inp.__fpObserved) return;
|
||||
inp.__fpObserved = true;
|
||||
inp.addEventListener('input', rebuildDebounced);
|
||||
inp.addEventListener('change', rebuildDebounced);
|
||||
});
|
||||
};
|
||||
attachNestedObservers();
|
||||
|
||||
// Also watch variation title inputs for changes
|
||||
const titleSelector = 'input[name^="product_variation_attributes_"][name$="_variation_label"]';
|
||||
const attachTitleObservers = () => {
|
||||
document.querySelectorAll(titleSelector).forEach(inp => {
|
||||
if (inp.__fpTitleObserved) return;
|
||||
inp.__fpTitleObserved = true;
|
||||
inp.addEventListener('input', rebuildDebounced);
|
||||
inp.addEventListener('change', rebuildDebounced);
|
||||
});
|
||||
};
|
||||
attachTitleObservers();
|
||||
|
||||
// Also watch variation value inputs for changes
|
||||
const valueSelector = 'input[name^="product_variation_attributes_"][name$="_variation_value"]';
|
||||
const attachValueObservers = () => {
|
||||
document.querySelectorAll(valueSelector).forEach(inp => {
|
||||
if (inp.__fpValueObserved) return;
|
||||
inp.__fpValueObserved = true;
|
||||
inp.addEventListener('input', rebuildDebounced);
|
||||
inp.addEventListener('change', rebuildDebounced);
|
||||
});
|
||||
};
|
||||
attachValueObservers();
|
||||
|
||||
// Trigger initial build right away if attributes already present
|
||||
this.buildFromAttributes();
|
||||
|
||||
// Re-attach on WPCFTO repeater events
|
||||
$(document).on('repeater-item-added repeater-item-removed', (ev, repeater) => {
|
||||
if (!repeater) return;
|
||||
if (repeater.field_name && String(repeater.field_name).indexOf('product_variation_attributes') === 0) {
|
||||
attachNestedObservers();
|
||||
attachTitleObservers();
|
||||
attachValueObservers();
|
||||
rebuildDebounced();
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure table rebuild when attributes repeater changes
|
||||
$(document).on('change input', '.product_variation_attributes.repeater input, .product_variation_attributes.repeater select', rebuildDebounced);
|
||||
},
|
||||
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;
|
||||
findFirstMissingDefault() {
|
||||
const defCode = this.getDefaultCurrencyCode();
|
||||
for (let i = 0; i < this.tableRows.length; i++) {
|
||||
const row = this.tableRows[i];
|
||||
if (!row || !Array.isArray(row.prices)) continue;
|
||||
const entry = row.prices.find(p => String(p.currency).split(':::')[0] === defCode);
|
||||
const reg = entry && entry.regular_price != null ? String(entry.regular_price).trim() : '';
|
||||
if (!reg) {
|
||||
return { index: i, rowLabel: row.name || `Row ${i+1}`, currencyCode: defCode };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
updateJson() {
|
||||
this.jsonValue = JSON.stringify(this.tableRows);
|
||||
this.$nextTick(() => this.enforcePriceInputStates());
|
||||
},
|
||||
async rebuildTable() {
|
||||
await this.initializeOrClearTable();
|
||||
|
||||
Reference in New Issue
Block a user