- Added new fields 'billing_cycle' and 'product_pricing_id' to Cart and OrderItem models. - Updated product retrieval logic to support both new and legacy systems. - Introduced relationships for Product and ProductPricings in Cart and OrderItem models. - Created migration to add new fields to the carts and order_item tables. - Updated frontend service index to handle new pricing structure and billing cycles. - Improved UI components for better user experience in product selection and pricing display. - Refactored action buttons in various views for consistency and improved styling.
339 lines
23 KiB
PHP
339 lines
23 KiB
PHP
@extends('frontend.layouts.master')
|
|
|
|
@section('content')
|
|
@if (!empty($category))
|
|
<!-- Products Grid -->
|
|
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2 xl:grid-cols-4" x-data>
|
|
@forelse ($products as $index => $item)
|
|
<form action="{{ route('cart.add') }}" method="post">
|
|
@csrf
|
|
<input name="product_id" value="{{ $item->id }}" type="hidden" />
|
|
{{-- Remove type_id for new Product system --}}
|
|
@php
|
|
$defaultPricingForCart =
|
|
$item->pricings->firstWhere('billing_cycle', 'semi_annually') ?? $item->pricings->first();
|
|
$cartPrice = $defaultPricingForCart
|
|
? floatval($defaultPricingForCart->price_sale ?: $defaultPricingForCart->price)
|
|
: 0;
|
|
@endphp
|
|
<input name="product_price" value="{{ $cartPrice }}" type="hidden" class="hiddenProductPrice" />
|
|
|
|
<div class="group relative transform overflow-hidden rounded-md border border-gray-100 bg-white transition-all duration-500 hover:shadow-2xl"
|
|
x-init="$el.style.opacity = 0;
|
|
setTimeout(() => { $el.style.opacity = 1 }, {{ (int) $index * 150 }})" style="opacity: 0">
|
|
|
|
<!-- Header with gradient -->
|
|
<div class="border-b border-dashed border-gray-200 p-2">
|
|
<h3 class="text-primary mb-2 text-center text-xl font-bold">
|
|
{{ $item->name }}
|
|
</h3>
|
|
</div>
|
|
|
|
@php
|
|
// Get pricing for different billing cycles
|
|
// Note: If billing_cycle is 'onetime', treat it as monthly
|
|
$monthlyPricing =
|
|
$item->pricings->firstWhere('billing_cycle', 'monthly') ??
|
|
$item->pricings->firstWhere('billing_cycle', 'onetime');
|
|
$semiAnnuallyPricing = $item->pricings->firstWhere('billing_cycle', 'semi_annually');
|
|
$annuallyPricing = $item->pricings->firstWhere('billing_cycle', 'annually');
|
|
$bienniallyPricing = $item->pricings->firstWhere('billing_cycle', 'biennially');
|
|
$trienniallyPricing = $item->pricings->firstWhere('billing_cycle', 'triennially');
|
|
|
|
// Use monthly or first available pricing for display
|
|
$displayPricing = $monthlyPricing ?? $item->pricings->first();
|
|
$basePrice = $displayPricing
|
|
? floatval($displayPricing->price_sale ?: $displayPricing->price)
|
|
: 0;
|
|
@endphp
|
|
|
|
<div class="p-6" data-product-id="{{ $item->id }}" data-base-price="{{ $basePrice }}">
|
|
|
|
<!-- Price Display -->
|
|
<div class="text-center">
|
|
<div
|
|
class="mb-3 inline-flex w-full items-center justify-center gap-2 rounded-md bg-orange-50 p-2">
|
|
<span class="text-primary text-sm font-bold">
|
|
≈
|
|
{{ $displayPricing ? format_price($displayPricing->price_sale ?: $displayPricing->price) : 'Liên hệ' }}
|
|
</span>
|
|
@if ($displayPricing)
|
|
<span class="text-primary font-medium">/ tháng</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<!-- Duration Selector -->
|
|
|
|
<div class="mb-6">
|
|
<!-- Custom Dropdown -->
|
|
<div class="custom-dropdown" id="durationDropdown{{ $item->id }}">
|
|
@php
|
|
// Default to semi_annually (6 months) if available, otherwise first available
|
|
$defaultPricing = $semiAnnuallyPricing ?? $item->pricings->first();
|
|
$defaultPrice = $defaultPricing
|
|
? floatval($defaultPricing->price_sale ?: $defaultPricing->price)
|
|
: 0;
|
|
$defaultMonths = match ($defaultPricing?->billing_cycle) {
|
|
'semi_annually' => 6,
|
|
'annually' => 12,
|
|
'biennially' => 24,
|
|
'triennially' => 36,
|
|
default => 1,
|
|
};
|
|
@endphp
|
|
<div class="dropdown-selected">
|
|
<span class="selected-text">
|
|
@if ($defaultPricing)
|
|
{{ format_price($defaultPrice) }} / {{ $defaultMonths }}
|
|
{{ $defaultMonths > 1 ? 'tháng' : 'tháng' }}
|
|
@else
|
|
Chọn thời gian
|
|
@endif
|
|
</span>
|
|
<i class="dropdown-arrow">▼</i>
|
|
</div>
|
|
<div class="dropdown-options">
|
|
{{-- Monthly --}}
|
|
<div class="dropdown-option {{ !$monthlyPricing ? 'disabled opacity-50 cursor-not-allowed' : '' }} {{ $defaultPricing?->billing_cycle === 'monthly' ? 'selected' : '' }}"
|
|
@if ($monthlyPricing) data-value="monthly"
|
|
data-months="1"
|
|
data-price="{{ floatval($monthlyPricing->price_sale ?: $monthlyPricing->price) }}" @endif>
|
|
<div class="flex items-center justify-between">
|
|
<span>
|
|
@if ($monthlyPricing)
|
|
{{ format_price($monthlyPricing->price_sale ?: $monthlyPricing->price) }}
|
|
/ 1 tháng
|
|
@else
|
|
<span class="text-gray-400">1 tháng - Liên hệ</span>
|
|
@endif
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Semi-annually --}}
|
|
<div class="dropdown-option {{ !$semiAnnuallyPricing ? 'disabled opacity-50 cursor-not-allowed' : '' }} {{ $defaultPricing?->billing_cycle === 'semi_annually' ? 'selected' : '' }}"
|
|
@if ($semiAnnuallyPricing) data-value="semi_annually"
|
|
data-months="6"
|
|
data-price="{{ floatval($semiAnnuallyPricing->price_sale ?: $semiAnnuallyPricing->price) }}" @endif>
|
|
<div class="flex items-center justify-between">
|
|
<span>
|
|
@if ($semiAnnuallyPricing)
|
|
{{ format_price($semiAnnuallyPricing->price_sale ?: $semiAnnuallyPricing->price) }}
|
|
/ 6 tháng
|
|
@else
|
|
<span class="text-gray-400">6 tháng - Liên hệ</span>
|
|
@endif
|
|
</span>
|
|
@if ($monthlyPricing && $semiAnnuallyPricing && $semiAnnuallyPricing->price < $monthlyPricing->price * 6)
|
|
<span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">Tiết
|
|
kiệm</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Annually --}}
|
|
<div class="dropdown-option {{ !$annuallyPricing ? 'disabled opacity-50 cursor-not-allowed' : '' }} {{ $defaultPricing?->billing_cycle === 'annually' ? 'selected' : '' }}"
|
|
@if ($annuallyPricing) data-value="annually"
|
|
data-months="12"
|
|
data-price="{{ floatval($annuallyPricing->price_sale ?: $annuallyPricing->price) }}" @endif>
|
|
<div class="flex items-center justify-between">
|
|
<span>
|
|
@if ($annuallyPricing)
|
|
{{ format_price($annuallyPricing->price_sale ?: $annuallyPricing->price) }}
|
|
/ 1 năm
|
|
@else
|
|
<span class="text-gray-400">1 năm - Liên hệ</span>
|
|
@endif
|
|
</span>
|
|
@if ($semiAnnuallyPricing && $annuallyPricing && $annuallyPricing->price < $semiAnnuallyPricing->price * 2)
|
|
<span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">Tiết
|
|
kiệm</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Biennially --}}
|
|
<div class="dropdown-option {{ !$bienniallyPricing ? 'disabled opacity-50 cursor-not-allowed' : '' }} {{ $defaultPricing?->billing_cycle === 'biennially' ? 'selected' : '' }}"
|
|
@if ($bienniallyPricing) data-value="biennially"
|
|
data-months="24"
|
|
data-price="{{ floatval($bienniallyPricing->price_sale ?: $bienniallyPricing->price) }}" @endif>
|
|
<div class="flex items-center justify-between">
|
|
<span>
|
|
@if ($bienniallyPricing)
|
|
{{ format_price($bienniallyPricing->price_sale ?: $bienniallyPricing->price) }}
|
|
/ 2 năm
|
|
@else
|
|
<span class="text-gray-400">2 năm - Liên hệ</span>
|
|
@endif
|
|
</span>
|
|
@if ($annuallyPricing && $bienniallyPricing && $bienniallyPricing->price < $annuallyPricing->price * 2)
|
|
<span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">Tiết
|
|
kiệm</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Triennially --}}
|
|
<div class="dropdown-option {{ !$trienniallyPricing ? 'disabled opacity-50 cursor-not-allowed' : '' }} {{ $defaultPricing?->billing_cycle === 'triennially' ? 'selected' : '' }}"
|
|
@if ($trienniallyPricing) data-value="triennially"
|
|
data-months="36"
|
|
data-price="{{ floatval($trienniallyPricing->price_sale ?: $trienniallyPricing->price) }}" @endif>
|
|
<div class="flex items-center justify-between">
|
|
<span>
|
|
@if ($trienniallyPricing)
|
|
{{ format_price($trienniallyPricing->price_sale ?: $trienniallyPricing->price) }}
|
|
/ 3 năm
|
|
@else
|
|
<span class="text-gray-400">3 năm - Liên hệ</span>
|
|
@endif
|
|
</span>
|
|
@if ($bienniallyPricing && $trienniallyPricing && $trienniallyPricing->price < $bienniallyPricing->price * 1.5)
|
|
<span class="rounded bg-green-100 px-2 py-1 text-xs text-green-600">Tiết
|
|
kiệm</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" name="months" class="hiddenMonths" value="{{ $defaultMonths }}">
|
|
<input type="hidden" name="billing_cycle" class="hiddenBillingCycle"
|
|
value="{{ $defaultPricing?->billing_cycle ?? 'semi_annually' }}">
|
|
</div>
|
|
<!-- Specifications -->
|
|
<div class="mb-6 space-y-4">
|
|
<ul class="space-y-1">
|
|
<!-- CPU -->
|
|
@if ($item->cpu)
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-microchip text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">CPU:</span>
|
|
<span class="ml-2 font-bold text-gray-800">{{ $item->cpu }} vCore</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
|
|
<!-- RAM -->
|
|
@if ($item->ram)
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-memory text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">RAM:</span>
|
|
<span class="ml-2 font-bold text-gray-800">{{ $item->ram }} GB</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
|
|
<!-- SSD -->
|
|
@if ($item->disk)
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-hdd text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">SSD:</span>
|
|
<span class="ml-2 font-bold text-gray-800">{{ $item->disk }} GB</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
|
|
<!-- GPU -->
|
|
@if ($item->gpu)
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-tv text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">GPU:</span>
|
|
<span class="ml-2 font-bold text-gray-800">{{ $item->gpu }} GB</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
|
|
<!-- Bandwidth -->
|
|
@if ($item->bandwidth)
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-tachometer-alt text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">Băng thông:</span>
|
|
<span class="ml-2 font-bold text-gray-800">{{ $item->bandwidth }} MB</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
|
|
<!-- OS -->
|
|
@if ($item->osVersions && $item->osVersions->isNotEmpty())
|
|
<li class="flex items-center justify-center gap-3">
|
|
<div
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fab fa-linux os-icon text-orange-600"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<span class="text-sm text-gray-600">Hệ điều hành:</span>
|
|
<span class="guest-os ml-2 font-bold text-gray-800">
|
|
{{ $item->osVersions->pluck('name')->unique()->implode(', ') }}
|
|
</span>
|
|
</div>
|
|
</li>
|
|
@endif
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Action Button -->
|
|
<flux:button variant="primary" color="orange" type="submit" class="w-full" icon="shopping-bag">
|
|
Đăng ký ngay</flux:button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
@empty
|
|
<div class="col-span-full">
|
|
<div class="py-16 text-center">
|
|
<div class="mx-auto mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-box-open text-4xl text-orange-500"></i>
|
|
</div>
|
|
<h3 class="mb-2 text-2xl font-bold text-gray-800">Không có sản phẩm</h3>
|
|
<p class="text-gray-600">Danh mục này hiện chưa có sản phẩm nào.</p>
|
|
</div>
|
|
</div>
|
|
{{ $products->links() }}
|
|
@endforelse
|
|
</div>
|
|
@else
|
|
<div class="flex min-h-screen items-center justify-center">
|
|
<div class="text-center">
|
|
<div class="mx-auto mb-6 flex h-32 w-32 items-center justify-center rounded-full bg-orange-100">
|
|
<i class="fas fa-exclamation-triangle text-5xl text-orange-500"></i>
|
|
</div>
|
|
<h2 class="mb-4 text-3xl font-bold text-gray-800">Không tìm thấy</h2>
|
|
<p class="text-lg text-gray-600">Danh mục này không tồn tại hoặc đã bị xóa.</p>
|
|
<a href="{{ route('home') }}"
|
|
class="mt-6 inline-block rounded-xl bg-orange-500 px-6 py-3 font-bold text-white transition-colors hover:bg-orange-600">
|
|
Về trang chủ
|
|
</a>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
@endsection
|
|
|
|
@push('viewStyles')
|
|
<link href="{{ asset('lib/tom-select/tom-select.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('css/service/dropdown.css') }}" rel="stylesheet">
|
|
@endpush
|
|
|
|
@push('viewScripts')
|
|
<script src="{{ asset('lib/tom-select/tom-select.complete.min.js') }}" defer></script>
|
|
<script src="{{ asset('js/service/vminfo.min.js') }}" defer></script>
|
|
@endpush
|