248 lines
12 KiB
PHP
248 lines
12 KiB
PHP
@php
|
|
$basePrice = $product->price_sale ?: $product->price;
|
|
@endphp
|
|
|
|
<div x-data="cartHandler({{ json_encode($addons) }}, {{ $basePrice }}, {{ $months }})" data-skeleton-id="addons-summary">
|
|
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-3">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-4">{{ __('Thời hạn') }}</h3>
|
|
<div class="relative">
|
|
<!-- Alpine.js Dropdown -->
|
|
<div class="custom-dropdown" @click.away="open = false">
|
|
|
|
<!-- Dropdown Selected -->
|
|
<div class="dropdown-selected" :class="{ 'active': open }" @click="open = !open">
|
|
<span class="selected-text"
|
|
x-text="formatPrice(periodTotalPrice) + ' / ' + selectedPeriod + ' {{ __('tháng') }}'"></span>
|
|
<i class="dropdown-arrow" :class="{ 'rotate-180': open }">▼</i>
|
|
</div>
|
|
|
|
<!-- Dropdown Options -->
|
|
<div class="dropdown-options" :class="{ 'show': open }" x-show="open" x-transition>
|
|
<template x-for="period in periods" :key="period.value">
|
|
<div class="dropdown-option" :class="{ 'selected': period.months === selectedPeriod }"
|
|
@click="selectPeriod(period)">
|
|
<div class="flex justify-between items-center">
|
|
<span
|
|
x-text="formatPrice(basePrice * period.months - (period.months >= 12 ? Math.round(basePrice * period.months * 0.1) : 0)) + ' / ' + period.text"></span>
|
|
<span x-show="period.months >= 12"
|
|
class="text-xs text-green-600 bg-green-100 px-2 py-1 rounded">{{ __('Tiết kiệm 10%') }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" name="payment_period" :value="selectedPeriod">
|
|
</div>
|
|
|
|
<!-- Total Price Display -->
|
|
<div class="mt-4 p-4 bg-gradient-to-r from-orange-50 to-indigo-50 rounded-lg border border-orange-200">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-lg font-semibold text-orange-800">{{ __('Tổng tiền:') }}</span>
|
|
<span class="text-2xl font-bold text-orange-900" x-text="formatPrice(periodTotalPrice)"></span>
|
|
</div>
|
|
<div class="text-sm text-orange-600 mt-1">
|
|
<span x-text="selectedPeriod + ' {{ __('tháng') }}'"></span> •
|
|
<span x-text="formatPrice(basePrice) + '/{{ __('tháng') }}'"></span>
|
|
</div>
|
|
<div class="mt-2 pt-2 border-t border-orange-200" x-show="discount > 0">
|
|
<div class="flex justify-between text-xs text-orange-700">
|
|
<span>{{ __('Giá gốc:') }}</span>
|
|
<span x-text="formatPrice(originalPrice)"></span>
|
|
</div>
|
|
<div class="flex justify-between text-xs text-green-600 font-medium">
|
|
<span>{{ __('Tiết kiệm:') }}</span>
|
|
<span x-text="formatPrice(discount)"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card chọn addon -->
|
|
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-3">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-4">{{ __('Cấu hình tùy chọn') }}</h3>
|
|
<template x-if="loading">
|
|
<div class="space-y-4">
|
|
@for ($i = 0; $i < 3; $i++)
|
|
<div class="p-4 bg-gray-50 rounded-lg animate-pulse">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<div class="h-4 bg-gray-200 rounded w-24"></div>
|
|
<div class="h-4 bg-gray-200 rounded w-20"></div>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden bg-white">
|
|
<div class="w-10 h-10 bg-gray-200"></div>
|
|
<div class="w-16 h-10 bg-gray-200"></div>
|
|
<div class="w-10 h-10 bg-gray-200"></div>
|
|
</div>
|
|
<div class="h-4 bg-gray-200 rounded w-16"></div>
|
|
</div>
|
|
</div>
|
|
@endfor
|
|
</div>
|
|
</template>
|
|
<template x-if="!loading">
|
|
<template x-for="(addon, key) in addons" :key="key">
|
|
<div class="p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="font-bold" x-text="addon.name"></span>
|
|
<span class="text-gray-700" x-text="formatPrice(addon.price) + ' đ/' + addon.prefix"></span>
|
|
</div>
|
|
<div class="flex flex-row items-center gap-3">
|
|
<div class="flex items-center border border-gray-300 rounded-lg overflow-hidden bg-white">
|
|
<button type="button" @click="decrement(key)"
|
|
class="w-10 h-10 flex items-center justify-center bg-gray-50 hover:bg-orange-100 text-gray-600 hover:text-orange-600 transition-colors duration-200 border-r border-gray-300">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M20 12H4">
|
|
</path>
|
|
</svg>
|
|
</button>
|
|
|
|
<input type="number" :min="addon.min" :max="addon.max"
|
|
:step="addon.step" x-model.number="addon.qty"
|
|
class="w-16 h-10 text-center border-0 focus:ring-0 focus:outline-none bg-white text-gray-800 font-medium">
|
|
|
|
<button type="button" @click="increment(key)"
|
|
class="w-10 h-10 flex items-center justify-center bg-gray-50 hover:bg-orange-100 text-gray-600 hover:text-orange-600 transition-colors duration-200 border-l border-gray-300">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M12 4v16m8-8H4"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<span class="ml-auto font-medium text-orange-600"
|
|
x-text="formatPrice(addon.qty * addon.price) + ' đ'"></span>
|
|
|
|
<!-- Input hidden để gửi dữ liệu -->
|
|
<template x-if="addon.qty > 0">
|
|
<div>
|
|
<input type="hidden" :name="`addons[${addon.id}][qty]`" :value="addon.qty">
|
|
<input type="hidden" :name="`addons[${addon.id}][price]`" :value="addon.price">
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Card tóm tắt đơn hàng -->
|
|
<x-card class="mb-4">
|
|
<x-card-header title="{{ __('Tóm tắt đơn hàng') }}" />
|
|
<x-card-content>
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th class="py-2 px-4 text-left font-medium text-gray-700">{{ __('Mục') }}</th>
|
|
<th class="py-2 px-4 text-center font-medium text-gray-700">{{ __('Số lượng') }}</th>
|
|
<th class="py-2 px-4 text-right font-medium text-gray-700">{{ __('Giá tiền') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Sản phẩm chính -->
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-3 px-4 font-medium">{{ $product->name }} (<span x-text="selectedPeriod"></span> {{ __('tháng') }})</td>
|
|
<td class="py-3 px-4 text-center">1</td>
|
|
<td class="py-3 px-4 text-right" x-text="formatPrice(periodTotalPrice) + ' đ'"></td>
|
|
</tr>
|
|
|
|
<!-- Các addon được chọn -->
|
|
<template x-for="addon in addons.filter(a => a.qty > 0)" :key="addon.id">
|
|
<tr class="border-b border-gray-100">
|
|
<td class="py-3 px-4" x-text="addon.name + ' (' + selectedPeriod + ' {{ __('tháng') }})'"></td>
|
|
<td class="py-3 px-4 text-center" x-text="addon.qty + ' ' + addon.prefix"></td>
|
|
<td class="py-3 px-4 text-right"
|
|
x-text="formatPrice(addon.qty * addon.price * selectedPeriod) + ' đ'">
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
|
|
<!-- Tổng cộng -->
|
|
<tr class="font-bold bg-green-50">
|
|
<td class="py-3 px-4" colspan="2">{{ __('Tổng cộng') }}</td>
|
|
<td class="py-3 px-4 text-right" x-text="formatPrice(totalPrice) + ' đ'"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</x-card-content>
|
|
</x-card>
|
|
|
|
<!-- Input hidden gửi tổng giá -->
|
|
<input type="hidden" name="totalPrice" :value="totalPrice">
|
|
<input type="hidden" name="selectedPeriod" :value="selectedPeriod">
|
|
</div>
|
|
|
|
|
|
<script>
|
|
function cartHandler(initialAddons, basePrice, initialMonths) {
|
|
return {
|
|
loading: true,
|
|
open: false,
|
|
selectedPeriod: initialMonths,
|
|
basePrice: basePrice,
|
|
addons: [],
|
|
periods: [
|
|
{value: 6, months: 6, text: '6 {{ __('tháng') }}'},
|
|
{value: 12, months: 12, text: '1 {{ __('năm') }}'},
|
|
{value: 24, months: 24, text: '2 {{ __('năm') }}'},
|
|
{value: 36, months: 36, text: '3 {{ __('năm') }}'},
|
|
],
|
|
|
|
formatPrice(val) {
|
|
return new Intl.NumberFormat('vi-VN').format(val);
|
|
},
|
|
|
|
get periodTotalPrice() {
|
|
const originalPrice = this.basePrice * this.selectedPeriod;
|
|
const discount = this.selectedPeriod >= 12 ? Math.round(originalPrice * 0.1) : 0;
|
|
return originalPrice - discount;
|
|
},
|
|
|
|
get originalPrice() {
|
|
return this.basePrice * this.selectedPeriod;
|
|
},
|
|
|
|
get discount() {
|
|
return this.selectedPeriod >= 12 ? Math.round(this.originalPrice * 0.1) : 0;
|
|
},
|
|
|
|
get totalPrice() {
|
|
const addonTotal = this.addons.reduce((sum, addon) => {
|
|
return sum + addon.qty * addon.price * this.selectedPeriod;
|
|
}, 0);
|
|
return this.periodTotalPrice + addonTotal;
|
|
},
|
|
|
|
selectPeriod(period) {
|
|
this.selectedPeriod = period.months;
|
|
this.open = false;
|
|
},
|
|
|
|
increment(key) {
|
|
const addon = this.addons[key];
|
|
if (addon.qty < addon.max) addon.qty += addon.step;
|
|
},
|
|
|
|
decrement(key) {
|
|
const addon = this.addons[key];
|
|
if (addon.qty > addon.min) addon.qty -= addon.step;
|
|
},
|
|
|
|
init() {
|
|
setTimeout(() => {
|
|
this.addons = initialAddons.map(addon => ({
|
|
...addon,
|
|
qty: 0,
|
|
min: addon.min || 0,
|
|
max: addon.max || 999,
|
|
step: addon.step || 1,
|
|
}));
|
|
this.loading = false;
|
|
}, 1500); // Sync with other components
|
|
}
|
|
};
|
|
}
|
|
</script>
|