Files
portal-api/resources/views/frontend/service/index.blade.php
T
pbao9 057353a2e1 feat: Enhance product support in Cart and OrderItem models
- 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.
2026-02-09 08:04:56 +07:00

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