total + Number(input.dataset.priceModifier || 0), 0);
},
get selectedModifierPercent() {
this.modifierVersion;
return [...this.$root.querySelectorAll('input[data-modifier-option]:checked, input[data-modifier-option][type=hidden]')]
.reduce((total, input) => total + Number(input.dataset.pricePercent || 0), 0);
},
qty: 1,
designMethod: 'upload', // 'upload', 'wa'
/* Stamp Live Visualizer State */
isEmboss: false,
isHotstamp: false,
hotstampMedia: 'wood', // 'wood', 'leather', 'coconut', 'rubber', 'brass'
embossMedia: 'gold_foil', // 'gold_foil', 'silver_foil', 'white_paper'
embossOrientation: 'Bawah Dokumen',
supportsPlasticInk: false,
supportsUvInk: false,
stampInkFormula: 'paper', // 'paper', 'plastic_nonporous'
isUvLightOn: false,
stampShape: 'round',
stampColor: '#dc2626',
stampColorName: 'Merah',
stampTextTop: 'PT KARYA MAJU BERSAMA',
stampTextCenter: 'LUNAS',
stampTextBottom: 'JAKARTA PUSAT',
/* Signage Advertising & Neon Box Simulator State */
signageMode: 'letter',
letterText: 'COFFEE & ROASTERY',
letterHeightCm: 20,
/* Neon Box Specific State */
neonboxShape: 'round', // 'round', 'square', 'rect', 'capsule'
neonboxMainText: 'KOPINYA RAKYAT',
neonboxSubText: 'SPECIALTY COFFEE • EST 2024',
neonboxIcon: 'mug', // 'mug', 'store', 'utensils', 'scissors', 'tooth', 'crown', 'none'
neonboxLedColor: 'warm', // 'warm', 'white', 'ice', 'amber'
neonboxFrameColor: 'black',
isLedOn: true,
signageWallBg: 'dark',
signageFont: 'font-sans font-black',
signageFinish: 'acrylic',
get letterCount() {
const count = this.letterText.replace(/\s+/g, '').length;
return count > 0 ? count : 1;
},
get sqm() {
if (this.priceType !== 'sqm') return 1;
return ((this.widthCm / 100) * (this.heightCm / 100)).toFixed(2);
},
get rawUnitPrice() {
if (this.priceType === 'letter_cm') {
const amount = (this.letterHeightCm * this.letterCount * this.basePrice) + this.selectedModifierFlatCost;
return amount * (1 + (this.selectedModifierPercent / 100));
}
if (this.priceType === 'sqm') {
const amount = this.basePrice * ( (this.widthCm/100) * (this.heightCm/100) ) + this.selectedModifierFlatCost;
return amount * (1 + (this.selectedModifierPercent / 100));
}
const amount = this.basePrice + this.selectedModifierFlatCost;
return amount * (1 + (this.selectedModifierPercent / 100));
},
get activeDiscountTier() {
// discountTiers sudah diurutkan minQuantity DESC, jadi tier pertama
// yang cocok adalah yang min_quantity-nya paling tinggi — sama seperti
// ProductPricingService::resolveTier().
return this.discountTiers.find(tier =>
this.qty >= tier.minQuantity &&
(tier.maxQuantity === null || this.qty <= tier.maxQuantity)
) || null;
},
get discountRate() {
return this.activeDiscountTier ? this.activeDiscountTier.discountPercent / 100 : 0;
},
get unitPrice() {
// Tier dengan unitPrice adalah harga flat yang menggantikan seluruh
// hasil hitung base + modifier, persis seperti di sisi server.
const tier = this.activeDiscountTier;
if (tier && tier.unitPrice !== null) {
return tier.unitPrice;
}
return this.rawUnitPrice * (1 - this.discountRate);
},
get total() {
return Math.round(this.unitPrice * this.qty);
},
get savings() {
if (this.activeDiscountTier && this.activeDiscountTier.unitPrice !== null) {
return 0;
}
return Math.round((this.rawUnitPrice * this.qty) - this.total);
},
formatRupiah(val) {
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(val);
},
setPresetSize(w, h) {
this.widthCm = w;
this.heightCm = h;
},
/* Drag & Drop Upload State & Methods */
isDragging: false,
uploadedFileName: '',
uploadedFileSize: '',
uploadedFilePreview: null,
uploadedFileExt: '',
processFile(file) {
if (!file) {
this.removeFile();
return;
}
this.uploadedFileName = file.name;
this.uploadedFileSize = (file.size >= 1024 * 1024)
? (file.size / (1024 * 1024)).toFixed(2) + ' MB'
: (file.size / 1024).toFixed(1) + ' KB';
const ext = file.name.split('.').pop().toLowerCase();
this.uploadedFileExt = ext;
if (file.type && file.type.startsWith('image/')) {
this.uploadedFilePreview = URL.createObjectURL(file);
} else {
this.uploadedFilePreview = null;
}
},
handleFileUpload(event) {
const file = event.target.files ? event.target.files[0] : null;
this.processFile(file);
},
handleDrop(event) {
this.isDragging = false;
const files = event.dataTransfer ? event.dataTransfer.files : null;
if (files && files.length > 0) {
if (this.$refs.fileInput) {
this.$refs.fileInput.files = files;
}
this.processFile(files[0]);
}
},
removeFile() {
this.uploadedFileName = '';
this.uploadedFileSize = '';
this.uploadedFilePreview = null;
this.uploadedFileExt = '';
if (this.$refs.fileInput) {
this.$refs.fileInput.value = '';
}
}
}">
Total Tagihan