Files
sst_site/public/js/price-island.js
2025-10-26 14:44:10 +09:00

289 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Dynamic Price Island Controller
class PriceIsland {
constructor() {
this.island = document.getElementById('priceIsland');
this.container = document.getElementById('islandContainer');
this.content = document.getElementById('islandContent');
this.totalPrice = document.getElementById('totalPrice');
this.selectedServices = [];
this.projectDetails = {};
this.appliedPromo = null;
this.isExpanded = false;
this.init();
}
init() {
// Listen for calculator changes
document.addEventListener('calculatorStateChange', (e) => {
this.updateIsland(e.detail);
});
// Initialize promo code functionality
this.initPromoCode();
// Show island initially as compact
this.showCompact();
}
updateIsland(calculatorData) {
const { selectedServices, complexity, timeline, totalPrice } = calculatorData;
this.selectedServices = selectedServices || [];
this.projectDetails = { complexity, timeline };
// Update total price
this.updateTotalPrice(totalPrice || 0);
// Update content
this.updateContent();
// Expand if there's data to show
if (this.selectedServices.length > 0 || complexity || timeline) {
this.expand();
} else {
this.collapse();
}
}
updateTotalPrice(price) {
if (this.totalPrice) {
this.totalPrice.textContent = this.formatPrice(price);
this.totalPrice.classList.add('price-update');
setTimeout(() => {
this.totalPrice.classList.remove('price-update');
}, 300);
}
}
updateContent() {
this.updateSelectedServices();
this.updateProjectDetails();
this.updatePriceBreakdown();
}
updateSelectedServices() {
const container = document.getElementById('selectedServices');
const list = document.getElementById('servicesList');
if (this.selectedServices.length > 0) {
container.classList.remove('hidden');
list.innerHTML = this.selectedServices.map(service => `
<div class="flex justify-between items-center py-1">
<span class="text-sm text-gray-600 dark:text-gray-400">${service.name}</span>
<span class="text-sm font-medium text-gray-900 dark:text-white">${this.formatPrice(service.price)}</span>
</div>
`).join('');
} else {
container.classList.add('hidden');
}
}
updateProjectDetails() {
const container = document.getElementById('projectDetails');
const list = document.getElementById('detailsList');
const details = [];
if (this.projectDetails.complexity) {
details.push(`Сложность: ${this.projectDetails.complexity.name} (×${this.projectDetails.complexity.multiplier})`);
}
if (this.projectDetails.timeline) {
details.push(`Сроки: ${this.projectDetails.timeline.name} (×${this.projectDetails.timeline.multiplier})`);
}
if (details.length > 0) {
container.classList.remove('hidden');
list.innerHTML = details.map(detail => `
<div class="text-sm text-gray-600 dark:text-gray-400">${detail}</div>
`).join('');
} else {
container.classList.add('hidden');
}
}
updatePriceBreakdown() {
const container = document.getElementById('priceBreakdown');
const list = document.getElementById('breakdownList');
if (this.selectedServices.length > 0) {
container.classList.remove('hidden');
const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0);
const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1;
const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1;
const afterComplexity = basePrice * complexityMultiplier;
const final = afterComplexity * timelineMultiplier;
let breakdown = [
{ label: 'Базовая стоимость', value: basePrice }
];
if (complexityMultiplier !== 1) {
breakdown.push({
label: `Сложность (×${complexityMultiplier})`,
value: afterComplexity
});
}
if (timelineMultiplier !== 1) {
breakdown.push({
label: `Сроки (×${timelineMultiplier})`,
value: final
});
}
if (this.appliedPromo) {
const discount = final * (this.appliedPromo.discount / 100);
breakdown.push({
label: `Скидка ${this.appliedPromo.code} (-${this.appliedPromo.discount}%)`,
value: final - discount,
isDiscount: true
});
}
list.innerHTML = breakdown.map(item => `
<div class="flex justify-between items-center py-1 ${item.isDiscount ? 'text-green-600 dark:text-green-400' : ''}">
<span class="text-sm">${item.label}</span>
<span class="text-sm font-medium">${this.formatPrice(item.value)}</span>
</div>
`).join('');
} else {
container.classList.add('hidden');
}
}
initPromoCode() {
const promoBtn = document.getElementById('applyPromoBtn');
const promoInput = document.getElementById('promoInput');
const promoStatus = document.getElementById('promoStatus');
if (promoBtn && promoInput) {
promoBtn.addEventListener('click', () => {
const code = promoInput.value.trim().toUpperCase();
this.applyPromoCode(code);
});
promoInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const code = promoInput.value.trim().toUpperCase();
this.applyPromoCode(code);
}
});
}
}
applyPromoCode(code) {
const promoStatus = document.getElementById('promoStatus');
// Simulate promo code validation
const validCodes = {
'WELCOME10': { discount: 10, name: 'Скидка новичка' },
'SAVE20': { discount: 20, name: 'Экономия 20%' },
'VIP15': { discount: 15, name: 'VIP скидка' }
};
if (validCodes[code]) {
this.appliedPromo = { code, ...validCodes[code] };
promoStatus.textContent = `✓ Применен: ${this.appliedPromo.name}`;
promoStatus.className = 'text-xs mt-1 text-green-600 dark:text-green-400';
// Trigger recalculation
this.updateContent();
this.recalculateTotal();
} else if (code) {
promoStatus.textContent = '✗ Неверный промокод';
promoStatus.className = 'text-xs mt-1 text-red-600 dark:text-red-400';
} else {
promoStatus.textContent = '';
}
}
recalculateTotal() {
if (this.selectedServices.length > 0) {
const basePrice = this.selectedServices.reduce((sum, service) => sum + service.price, 0);
const complexityMultiplier = this.projectDetails.complexity?.multiplier || 1;
const timelineMultiplier = this.projectDetails.timeline?.multiplier || 1;
let total = basePrice * complexityMultiplier * timelineMultiplier;
if (this.appliedPromo) {
total = total * (1 - this.appliedPromo.discount / 100);
}
this.updateTotalPrice(total);
// Also update mobile price if exists
const mobilePrice = document.getElementById('mobilePriceValue');
if (mobilePrice) {
mobilePrice.textContent = this.formatPrice(total);
}
}
}
expand() {
if (!this.isExpanded) {
this.isExpanded = true;
this.content.style.maxHeight = this.content.scrollHeight + 'px';
// Show promo section when expanded
const promoSection = document.getElementById('promoSection');
if (promoSection) {
promoSection.classList.remove('hidden');
}
}
}
collapse() {
if (this.isExpanded) {
this.isExpanded = false;
this.content.style.maxHeight = '0';
// Hide promo section when collapsed
const promoSection = document.getElementById('promoSection');
if (promoSection) {
promoSection.classList.add('hidden');
}
}
}
showCompact() {
this.island.classList.remove('hidden');
}
hide() {
this.island.classList.add('hidden');
}
formatPrice(price) {
return new Intl.NumberFormat('ko-KR', {
style: 'currency',
currency: 'KRW',
maximumFractionDigits: 0
}).format(price);
}
}
// CSS for price update animation
const style = document.createElement('style');
style.textContent = `
.price-update {
animation: priceUpdate 0.3s ease-out;
}
@keyframes priceUpdate {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
`;
document.head.appendChild(style);
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.priceIsland = new PriceIsland();
});
// Export for use in calculator
window.PriceIsland = PriceIsland;