updat
This commit is contained in:
@@ -56,7 +56,7 @@ class ProductCategoryController extends Controller
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->productCategoryService->delete($id);
|
||||
return response()->json(['success' => true]);
|
||||
return redirect()->route('products.group.index')->with('success', 'Delete Group Success');
|
||||
}
|
||||
|
||||
public function datatable(Request $request)
|
||||
|
||||
@@ -48,8 +48,11 @@ class ProductController extends Controller
|
||||
// Fetch OS versions with their group for the OS selector component
|
||||
$osVersions = \App\Models\Os::with('osGroup')->where('status', 1)->get();
|
||||
|
||||
// Fetch Control Panels for Hosting products
|
||||
$controlPanels = \App\Models\ControlPanel::active()->ordered()->get();
|
||||
|
||||
if ($step === 1) {
|
||||
return view("administrator.pages.product.create", compact('group', 'type', 'title', 'osVersions'));
|
||||
return view("administrator.pages.product.create", compact('group', 'type', 'title', 'osVersions', 'controlPanels'));
|
||||
}
|
||||
|
||||
if (!$request->has(['step', 'type'])) {
|
||||
@@ -97,7 +100,10 @@ class ProductController extends Controller
|
||||
|
||||
$view = "administrator.pages.product.edit";
|
||||
|
||||
$data = compact('product', 'title', 'group', 'type', 'osVersions');
|
||||
// Fetch Control Panels for Hosting products
|
||||
$controlPanels = \App\Models\ControlPanel::active()->ordered()->get();
|
||||
|
||||
$data = compact('product', 'title', 'group', 'type', 'osVersions', 'controlPanels');
|
||||
|
||||
return view($view, $data);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ class ProductRequest extends FormRequest
|
||||
],
|
||||
'short_description' => ['nullable', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'thumbnail' => ['nullable', 'string', 'max:255'],
|
||||
'gallery' => ['nullable', 'array'],
|
||||
'gallery.*' => ['nullable', 'string', 'max:255'],
|
||||
'stock' => ['nullable', 'integer', 'min:0'],
|
||||
'is_unlimited' => ['boolean'],
|
||||
'cpu' => ['nullable', 'string', 'max:255'],
|
||||
@@ -64,11 +67,23 @@ class ProductRequest extends FormRequest
|
||||
'is_active' => ['boolean'],
|
||||
'sort_order' => ['nullable', 'integer'],
|
||||
'published_at' => ['nullable', 'date'],
|
||||
|
||||
// OS versions (pivot table os_product)
|
||||
|
||||
// OS versions (pivot table os_product) - for VPS
|
||||
'os_ids' => ['nullable', 'array'],
|
||||
'os_ids.*' => ['exists:os,id'],
|
||||
|
||||
|
||||
// Control Panels (pivot table product_control_panel) - for Hosting
|
||||
'control_panel_ids' => ['nullable', 'array'],
|
||||
'control_panel_ids.*' => ['exists:control_panels,id'],
|
||||
'control_panel_price_modifiers' => ['nullable', 'array'],
|
||||
'control_panel_price_modifiers.*' => ['nullable', 'numeric', 'min:0'],
|
||||
'control_panel_default' => ['nullable', 'exists:control_panels,id'],
|
||||
|
||||
// Images
|
||||
'thumbnail' => ['nullable', 'string', 'max:255'],
|
||||
'gallery' => ['nullable', 'array'],
|
||||
'gallery.*' => ['nullable', 'string', 'max:255'],
|
||||
|
||||
// Pricing cycles (table product_pricings)
|
||||
'pricing' => ['nullable', 'array'],
|
||||
];
|
||||
@@ -127,7 +142,14 @@ class ProductRequest extends FormRequest
|
||||
'short_description.max' => 'Mô tả ngắn không được vượt quá 255 ký tự.',
|
||||
|
||||
'description.string' => 'Mô tả phải là chuỗi ký tự.',
|
||||
|
||||
|
||||
'thumbnail.string' => 'Đường dẫn ảnh đại diện phải là chuỗi ký tự.',
|
||||
'thumbnail.max' => 'Đường dẫn ảnh đại diện không được vượt quá 255 ký tự.',
|
||||
|
||||
'gallery.array' => 'Thư viện ảnh không hợp lệ.',
|
||||
'gallery.*.string' => 'Đường dẫn ảnh trong thư viện phải là chuỗi ký tự.',
|
||||
'gallery.*.max' => 'Đường dẫn ảnh trong thư viện không được vượt quá 255 ký tự.',
|
||||
|
||||
'stock.integer' => 'Số lượng tồn kho phải là số nguyên.',
|
||||
'stock.min' => 'Số lượng tồn kho không được âm.',
|
||||
|
||||
@@ -168,7 +190,14 @@ class ProductRequest extends FormRequest
|
||||
|
||||
'os_ids.array' => 'Danh sách hệ điều hành không hợp lệ.',
|
||||
'os_ids.*.exists' => 'Hệ điều hành được chọn không tồn tại.',
|
||||
|
||||
|
||||
'control_panel_ids.array' => 'Danh sách Control Panel không hợp lệ.',
|
||||
'control_panel_ids.*.exists' => 'Control Panel được chọn không tồn tại.',
|
||||
'control_panel_price_modifiers.array' => 'Danh sách phụ phí Control Panel không hợp lệ.',
|
||||
'control_panel_price_modifiers.*.numeric' => 'Phụ phí Control Panel phải là số.',
|
||||
'control_panel_price_modifiers.*.min' => 'Phụ phí Control Panel không được âm.',
|
||||
'control_panel_default.exists' => 'Control Panel mặc định không tồn tại.',
|
||||
|
||||
'pricing.array' => 'Thông tin giá không hợp lệ.',
|
||||
'pricing.*.price.numeric' => 'Giá phải là số.',
|
||||
'pricing.*.price.min' => 'Giá không được âm.',
|
||||
|
||||
@@ -24,11 +24,16 @@ class ProductService
|
||||
// Create product
|
||||
$product = $this->repository->create($productData);
|
||||
|
||||
// Sync OS versions if provided
|
||||
// Sync OS versions if provided (for VPS)
|
||||
if (!empty($data['os_ids'])) {
|
||||
$product->osVersions()->sync($data['os_ids']);
|
||||
}
|
||||
|
||||
// Sync Control Panels if provided (for Hosting)
|
||||
if (!empty($data['control_panel_ids'])) {
|
||||
$this->saveControlPanels($product, $data);
|
||||
}
|
||||
|
||||
// Save pricing cycles if provided
|
||||
if (!empty($data['pricing'])) {
|
||||
$this->savePricings($product, $data['pricing'], $data['currency'] ?? 'USD');
|
||||
@@ -48,11 +53,16 @@ class ProductService
|
||||
// Get the product for relationships
|
||||
$product = $this->repository->find($id);
|
||||
|
||||
// Sync OS versions if provided
|
||||
// Sync OS versions if provided (for VPS)
|
||||
if (isset($data['os_ids'])) {
|
||||
$product->osVersions()->sync($data['os_ids']);
|
||||
}
|
||||
|
||||
// Sync Control Panels if provided (for Hosting)
|
||||
if (isset($data['control_panel_ids'])) {
|
||||
$this->saveControlPanels($product, $data);
|
||||
}
|
||||
|
||||
// Save pricing cycles if provided
|
||||
if (!empty($data['pricing'])) {
|
||||
$this->savePricings($product, $data['pricing'], $data['currency'] ?? 'USD');
|
||||
@@ -95,6 +105,26 @@ class ProductService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Control Panels for product (for Hosting)
|
||||
*/
|
||||
private function saveControlPanels($product, array $data): void
|
||||
{
|
||||
$syncData = [];
|
||||
|
||||
foreach ($data['control_panel_ids'] as $panelId) {
|
||||
$priceModifier = $data['control_panel_price_modifiers'][$panelId] ?? 0;
|
||||
$isDefault = isset($data['control_panel_default']) && $data['control_panel_default'] == $panelId;
|
||||
|
||||
$syncData[$panelId] = [
|
||||
'price_modifier' => $priceModifier,
|
||||
'is_default' => $isDefault,
|
||||
];
|
||||
}
|
||||
|
||||
$product->controlPanels()->sync($syncData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data for create/update
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ControlPanel extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'logo',
|
||||
'description',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Relationship: Control Panel có nhiều Products (Hosting)
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'product_control_panel')
|
||||
->withPivot(['price_modifier', 'is_default'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Chỉ lấy active panels
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Sắp xếp theo sort_order
|
||||
*/
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('sort_order', 'asc');
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ class Product extends Model
|
||||
'slug',
|
||||
'short_description',
|
||||
'description',
|
||||
'thumbnail',
|
||||
'gallery',
|
||||
'setup_fee',
|
||||
'trial_days',
|
||||
'cpu',
|
||||
@@ -39,6 +41,7 @@ class Product extends Model
|
||||
'is_active' => 'boolean',
|
||||
'is_unlimited' => 'boolean',
|
||||
'published_at' => 'date',
|
||||
'gallery' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -58,6 +61,16 @@ class Product extends Model
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship: Product has many Control Panels (for Hosting)
|
||||
*/
|
||||
public function controlPanels()
|
||||
{
|
||||
return $this->belongsToMany(ControlPanel::class, 'product_control_panel')
|
||||
->withPivot(['price_modifier', 'is_default'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship: Product has many pricings
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,8 @@ return new class extends Migration {
|
||||
$table->string('slug')->unique();
|
||||
$table->string('short_description')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('thumbnail')->nullable();
|
||||
$table->json('gallery')->nullable();
|
||||
|
||||
// Trial / stock
|
||||
$table->integer('stock')->nullable();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('control_panels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('logo')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->integer('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('control_panels');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('product_control_panel', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('product_id');
|
||||
$table->unsignedBigInteger('control_panel_id');
|
||||
$table->decimal('price_modifier', 15, 2)->default(0)->comment('Phụ phí cho control panel này');
|
||||
$table->boolean('is_default')->default(false)->comment('Panel mặc định cho sản phẩm');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('product_id')
|
||||
->references('id')
|
||||
->on('products')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('control_panel_id')
|
||||
->references('id')
|
||||
->on('control_panels')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->unique(['product_id', 'control_panel_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('product_control_panel');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ControlPanel;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ControlPanelSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$panels = [
|
||||
[
|
||||
'name' => 'Plesk Panel',
|
||||
'slug' => 'plesk',
|
||||
'logo' => '/images/control-panels/plesk.png',
|
||||
'description' => 'Plesk là control panel thương mại hàng đầu với giao diện thân thiện, hỗ trợ cả Windows và Linux.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 1,
|
||||
],
|
||||
[
|
||||
'name' => 'DirectAdmin',
|
||||
'slug' => 'directadmin',
|
||||
'logo' => '/images/control-panels/directadmin.png',
|
||||
'description' => 'DirectAdmin là control panel nhẹ, nhanh và ổn định, phù hợp cho các server có cấu hình thấp.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 2,
|
||||
],
|
||||
[
|
||||
'name' => 'aaPanel',
|
||||
'slug' => 'aapanel',
|
||||
'logo' => '/images/control-panels/aapanel.png',
|
||||
'description' => 'aaPanel là control panel miễn phí với giao diện đơn giản, dễ sử dụng, hỗ trợ LNMP/LAMP.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 3,
|
||||
],
|
||||
[
|
||||
'name' => 'cPanel',
|
||||
'slug' => 'cpanel',
|
||||
'logo' => '/images/control-panels/cpanel.png',
|
||||
'description' => 'cPanel là control panel phổ biến nhất thế giới với đầy đủ tính năng quản lý hosting.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 4,
|
||||
],
|
||||
[
|
||||
'name' => 'CyberPanel',
|
||||
'slug' => 'cyberpanel',
|
||||
'logo' => '/images/control-panels/cyberpanel.png',
|
||||
'description' => 'CyberPanel là control panel miễn phí sử dụng OpenLiteSpeed, tối ưu hiệu suất website.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 5,
|
||||
],
|
||||
[
|
||||
'name' => 'HestiaCP',
|
||||
'slug' => 'hestiacp',
|
||||
'logo' => '/images/control-panels/hestiacp.png',
|
||||
'description' => 'HestiaCP là control panel mã nguồn mở, nhẹ và miễn phí, fork từ VestaCP.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 6,
|
||||
],
|
||||
[
|
||||
'name' => 'Không có Panel',
|
||||
'slug' => 'no-panel',
|
||||
'logo' => null,
|
||||
'description' => 'Không cài đặt control panel, quản lý server thủ công qua SSH.',
|
||||
'is_active' => true,
|
||||
'sort_order' => 99,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($panels as $panel) {
|
||||
ControlPanel::updateOrCreate(
|
||||
['slug' => $panel['slug']],
|
||||
$panel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,5 +30,6 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call(TemplateSeeder::class);
|
||||
$this->call(Setting::class);
|
||||
$this->call(MenuSeeder::class);
|
||||
$this->call(ControlPanelSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="modal__footer">
|
||||
<x-button-crm type="button" variant="secondary" size="sm" icon="x" data-micromodal-close>Cancel</x-button-crm>
|
||||
<x-button-crm type="button" variant="secondary" size="sm" icon="x"
|
||||
data-micromodal-close>Cancel</x-button-crm>
|
||||
<form id="deleteForm" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-button-crm type="submit" variant="danger" size="sm" icon="trash-2">Confirm Delete</x-button-crm>
|
||||
<x-button-crm type="submit" variant="danger" size="sm" icon="trash-2">Confirm
|
||||
Delete</x-button-crm>
|
||||
</form>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -34,10 +36,10 @@
|
||||
|
||||
@push('custom-scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
function initializeModal() {
|
||||
if (typeof ModalManager !== 'undefined') {
|
||||
window.showDeleteModal = function (id, name) {
|
||||
window.showDeleteModal = function(id, name) {
|
||||
// Update modal content
|
||||
const nameElement = document.getElementById('deleteItemName');
|
||||
const formElement = document.getElementById('deleteForm');
|
||||
@@ -60,4 +62,4 @@
|
||||
setTimeout(initializeModal, 50);
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endpush
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
<x-button-crm type="button" variant="primary" size="sm" route="products.group.edit" :params="['id' => $row->id]" icon="pencil">Edit</x-button-crm>
|
||||
<x-button-crm type="button" variant="danger" size="sm" route="products.group.destroy" :params="['id' => $row->id]" icon="trash">Delete</x-button-crm>
|
||||
<div class="d-flex gap-2">
|
||||
<x-button-crm type="button" variant="primary" size="sm" route="products.group.edit" :params="['id' => $row->id]" icon="pencil">Edit</x-button-crm>
|
||||
<x-button-crm type="button" variant="danger" size="sm" icon="trash" data-micromodal-trigger="deleteModal-{{ $row->id }}">Delete</x-button-crm>
|
||||
</div>
|
||||
|
||||
@include('administrator.pages.product.categories.datatable.modal', [
|
||||
'deleteRoute' => 'products.group.destroy',
|
||||
'itemLabel' => 'Product Group',
|
||||
'itemId' => $row->id,
|
||||
'itemName' => $row->name,
|
||||
'modalId' => 'deleteModal-' . $row->id
|
||||
])
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal micromodal-slide" id="{{ $modalId ?? 'deleteModal' }}" aria-hidden="true">
|
||||
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||
<div class="modal__container modal--sm" role="dialog" aria-modal="true" aria-labelledby="deleteModalLabel-{{ $modalId ?? 'deleteModal' }}">
|
||||
<header class="modal__header bg-primary text-white">
|
||||
<h6 class="modal__title" id="deleteModalLabel-{{ $modalId ?? 'deleteModal' }}">
|
||||
<i data-lucide="trash-2" class="w-15px h-15px me-2"></i>
|
||||
Confirm Delete
|
||||
</h6>
|
||||
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal__content">
|
||||
<div class="text-center">
|
||||
<i data-lucide="alert-triangle" class="text-warning mb-3" style="width: 48px; height: 48px;"></i>
|
||||
<h6 class="mb-2">Are you sure?</h6>
|
||||
<p class="text-muted small mb-1">Delete {{ $itemLabel }}:</p>
|
||||
<p class="fw-bold mb-2">{{ $itemName ?? '' }}</p>
|
||||
<p class="text-danger small mb-0">This action cannot be undone!</p>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="modal__footer">
|
||||
<x-button-crm type="button" variant="secondary" size="sm" icon="x" data-micromodal-close>Cancel</x-button-crm>
|
||||
<form action="{{ route($deleteRoute, ['id' => $itemId ?? 0]) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-button-crm type="submit" variant="danger" size="sm" icon="trash-2">Confirm Delete</x-button-crm>
|
||||
</form>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,4 +59,40 @@
|
||||
@foreach (config('assets.datatable.js') as $datatable)
|
||||
<script src="{{ asset($datatable) }}"></script>
|
||||
@endforeach
|
||||
@endpush
|
||||
@endpush
|
||||
@push('custom-scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Function to initialize MicroModal
|
||||
function initMicroModal() {
|
||||
if (typeof MicroModal !== 'undefined') {
|
||||
MicroModal.init({
|
||||
onShow: modal => console.info(`${modal.id} is shown`),
|
||||
onClose: modal => console.info(`${modal.id} is hidden`),
|
||||
openTrigger: 'data-micromodal-trigger',
|
||||
closeTrigger: 'data-micromodal-close',
|
||||
disableScroll: true,
|
||||
disableFocus: false,
|
||||
awaitOpenAnimation: false,
|
||||
awaitCloseAnimation: false,
|
||||
debugMode: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
initMicroModal();
|
||||
|
||||
// Re-initialize after DataTable draws (when action column renders)
|
||||
$('#productTable').on('draw.dt', function() {
|
||||
setTimeout(function() {
|
||||
initMicroModal();
|
||||
// Re-initialize Lucide icons for modal content
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -136,8 +136,7 @@
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" step="0.01" class="form-control"
|
||||
name="pricing[{{ $cycle->value }}][price_sale]"
|
||||
placeholder="0.00">
|
||||
name="pricing[{{ $cycle->value }}][price_sale]" placeholder="0.00">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -221,20 +220,6 @@
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Operating System --}}
|
||||
<div class="form-group mb-3">
|
||||
<label for="os_ids" class="mb-2">Operating System</label>
|
||||
<select class="form-control select2" name="os_ids[]" id="os_ids" multiple data-placeholder="-- Select OS versions --">
|
||||
@foreach ($osVersions as $osVersion)
|
||||
<option value="{{ $osVersion->id }}" {{ in_array($osVersion->id, old('os_ids', [])) ? 'selected' : '' }}>
|
||||
{{ $osVersion->osGroup->name ?? 'Unknown' }} - {{ $osVersion->version }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Select multiple OS versions supported by this product</small>
|
||||
</div>
|
||||
|
||||
{{-- Product Type --}}
|
||||
<div class="form-group mb-3">
|
||||
<label for="product_type" class="mb-2">Product Type</label>
|
||||
@@ -248,6 +233,59 @@
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
{{-- Operating System (for VPS) --}}
|
||||
<div class="form-group mb-3" id="os-section">
|
||||
<label for="os_ids" class="mb-2">Operating System</label>
|
||||
<select class="form-control select2" name="os_ids[]" id="os_ids" multiple
|
||||
data-placeholder="-- Select OS versions --">
|
||||
@foreach ($osVersions as $osVersion)
|
||||
<option value="{{ $osVersion->id }}"
|
||||
{{ in_array($osVersion->id, old('os_ids', [])) ? 'selected' : '' }}>
|
||||
{{ $osVersion->osGroup->name ?? 'Unknown' }} - {{ $osVersion->version }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Select multiple OS versions supported by this product</small>
|
||||
</div>
|
||||
|
||||
{{-- Control Panels (for Hosting) --}}
|
||||
<div class="form-group mb-3" id="control-panel-section">
|
||||
<label for="control_panel_ids" class="mb-2">Control Panels</label>
|
||||
<select class="form-control select2" name="control_panel_ids[]" id="control_panel_ids"
|
||||
multiple data-placeholder="-- Select Control Panels --">
|
||||
@foreach ($controlPanels as $panel)
|
||||
<option value="{{ $panel->id }}"
|
||||
{{ in_array($panel->id, old('control_panel_ids', [])) ? 'selected' : '' }}>
|
||||
{{ $panel->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Select control panels available for this hosting product</small>
|
||||
|
||||
{{-- Price modifiers for each panel --}}
|
||||
<div id="panel-price-modifiers" class="mt-3">
|
||||
@foreach ($controlPanels as $panel)
|
||||
<div class="panel-price-row mb-2" id="panel-price-{{ $panel->id }}"
|
||||
style="display: none;">
|
||||
<label class="form-label">{{ $panel->name }} - Phụ phí (VNĐ)</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control"
|
||||
name="control_panel_price_modifiers[{{ $panel->id }}]" placeholder="0"
|
||||
min="0" step="1000"
|
||||
value="{{ old('control_panel_price_modifiers.' . $panel->id, 0) }}">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<input type="radio" name="control_panel_default"
|
||||
value="{{ $panel->id }}"
|
||||
{{ old('control_panel_default') == $panel->id ? 'checked' : '' }}>
|
||||
<span class="ms-1">Mặc định</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Status --}}
|
||||
<div class="form-group mb-3">
|
||||
@@ -316,6 +354,51 @@
|
||||
if ($(".select2").length) {
|
||||
$(".select2").select2();
|
||||
}
|
||||
|
||||
// Toggle OS/Control Panel sections based on Product Type
|
||||
function toggleProductSections() {
|
||||
const productType = $('#product_type').val();
|
||||
|
||||
if (productType === 'hosting') {
|
||||
$('#os-section').hide();
|
||||
$('#control-panel-section').show();
|
||||
} else if (productType === 'vps') {
|
||||
$('#os-section').show();
|
||||
$('#control-panel-section').hide();
|
||||
} else {
|
||||
// For addon, domain, or empty
|
||||
$('#os-section').hide();
|
||||
$('#control-panel-section').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide price modifier inputs when panels are selected
|
||||
$('#control_panel_ids').on('change', function() {
|
||||
const selectedPanels = $(this).val() || [];
|
||||
|
||||
$('.panel-price-row').hide();
|
||||
selectedPanels.forEach(function(panelId) {
|
||||
$('#panel-price-' + panelId).show();
|
||||
});
|
||||
|
||||
// Clear unselected panels
|
||||
$('.panel-price-row').each(function() {
|
||||
const panelId = $(this).attr('id').replace('panel-price-', '');
|
||||
if (!selectedPanels.includes(panelId)) {
|
||||
$(this).find('input[type="number"]').val(0);
|
||||
$(this).find('input[type="radio"]').prop('checked', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initial toggle on page load
|
||||
toggleProductSections();
|
||||
|
||||
// Toggle when product type changes
|
||||
$('#product_type').on('change', toggleProductSections);
|
||||
|
||||
// Trigger initial panel price display
|
||||
$('#control_panel_ids').trigger('change');
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('lib/ckeditor/ckeditor.js') }}"></script>
|
||||
|
||||
@@ -220,8 +220,8 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Operating System --}}
|
||||
<div class="form-group mb-3">
|
||||
{{-- Operating System (for VPS) --}}
|
||||
<div class="form-group mb-3" id="os-section">
|
||||
<label for="os_ids" class="mb-2">Operating System</label>
|
||||
@php
|
||||
$selectedOsIds = old('os_ids', $product->osVersions->pluck('id')->toArray());
|
||||
@@ -237,6 +237,54 @@
|
||||
</select>
|
||||
<small class="text-muted">Select multiple OS versions supported by this product</small>
|
||||
</div>
|
||||
|
||||
{{-- Control Panels (for Hosting) --}}
|
||||
<div class="form-group mb-3" id="control-panel-section" style="display: none;">
|
||||
<label for="control_panel_ids" class="mb-2">Control Panels</label>
|
||||
@php
|
||||
$selectedPanelIds = old('control_panel_ids', $product->controlPanels->pluck('id')->toArray());
|
||||
$panelPriceModifiers = old('control_panel_price_modifiers',
|
||||
$product->controlPanels->pluck('pivot.price_modifier', 'id')->toArray());
|
||||
$defaultPanel = old('control_panel_default',
|
||||
$product->controlPanels->where('pivot.is_default', true)->first()?->id);
|
||||
@endphp
|
||||
<select class="form-control select2" name="control_panel_ids[]" id="control_panel_ids" multiple data-placeholder="-- Select Control Panels --">
|
||||
@foreach ($controlPanels as $panel)
|
||||
<option value="{{ $panel->id }}" {{ in_array($panel->id, $selectedPanelIds) ? 'selected' : '' }}>
|
||||
{{ $panel->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<small class="text-muted">Select control panels available for this hosting product</small>
|
||||
|
||||
{{-- Price modifiers for each panel --}}
|
||||
<div id="panel-price-modifiers" class="mt-3">
|
||||
@foreach ($controlPanels as $panel)
|
||||
<div class="panel-price-row mb-2" id="panel-price-{{ $panel->id }}"
|
||||
style="display: {{ in_array($panel->id, $selectedPanelIds) ? 'block' : 'none' }};">
|
||||
<label class="form-label">{{ $panel->name }} - Phụ phí (VNĐ)</label>
|
||||
<div class="input-group">
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
name="control_panel_price_modifiers[{{ $panel->id }}]"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
step="1000"
|
||||
value="{{ $panelPriceModifiers[$panel->id] ?? 0 }}">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<input type="radio"
|
||||
name="control_panel_default"
|
||||
value="{{ $panel->id }}"
|
||||
{{ $defaultPanel == $panel->id ? 'checked' : '' }}>
|
||||
<span class="ms-1">Mặc định</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Product Type --}}
|
||||
<div class="form-group mb-3">
|
||||
@@ -320,6 +368,51 @@
|
||||
if ($(".select2").length) {
|
||||
$(".select2").select2();
|
||||
}
|
||||
|
||||
// Toggle OS/Control Panel sections based on Product Type
|
||||
function toggleProductSections() {
|
||||
const productType = $('#product_type').val();
|
||||
|
||||
if (productType === 'hosting') {
|
||||
$('#os-section').hide();
|
||||
$('#control-panel-section').show();
|
||||
} else if (productType === 'vps') {
|
||||
$('#os-section').show();
|
||||
$('#control-panel-section').hide();
|
||||
} else {
|
||||
// For addon, domain, or empty
|
||||
$('#os-section').hide();
|
||||
$('#control-panel-section').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide price modifier inputs when panels are selected
|
||||
$('#control_panel_ids').on('change', function() {
|
||||
const selectedPanels = $(this).val() || [];
|
||||
|
||||
$('.panel-price-row').hide();
|
||||
selectedPanels.forEach(function(panelId) {
|
||||
$('#panel-price-' + panelId).show();
|
||||
});
|
||||
|
||||
// Clear unselected panels
|
||||
$('.panel-price-row').each(function() {
|
||||
const panelId = $(this).attr('id').replace('panel-price-', '');
|
||||
if (!selectedPanels.includes(panelId)) {
|
||||
$(this).find('input[type="number"]').val(0);
|
||||
$(this).find('input[type="radio"]').prop('checked', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initial toggle on page load
|
||||
toggleProductSections();
|
||||
|
||||
// Toggle when product type changes
|
||||
$('#product_type').on('change', toggleProductSections);
|
||||
|
||||
// Trigger initial panel price display
|
||||
$('#control_panel_ids').trigger('change');
|
||||
});
|
||||
</script>
|
||||
<script src="{{ asset('lib/ckeditor/ckeditor.js') }}"></script>
|
||||
|
||||
Reference in New Issue
Block a user