diff --git a/app/Http/Controllers/admin/product/ProductController.php b/app/Http/Controllers/admin/product/ProductController.php index 95ecbc0..0f589e4 100644 --- a/app/Http/Controllers/admin/product/ProductController.php +++ b/app/Http/Controllers/admin/product/ProductController.php @@ -127,23 +127,80 @@ class ProductController extends Controller } } + public function duplicate($id) + { + try { + $product = $this->productRepository->find($id); + + if (!$product) { + return back()->withErrors("Can't find product."); + } + + $product->load(['pricings', 'osVersions', 'controlPanels']); + + $newData = $product->only([ + 'short_description', 'description', 'setup_fee', 'trial_days', + 'cpu', 'ram', 'gpu', 'disk', 'bandwidth', 'stock', + 'is_unlimited', 'is_active', 'sort_order', 'product_type', + 'product_categories_id', 'published_at', + ]); + $newData['name'] = $product->name . ' (Copy)'; + + $newProduct = $this->productService->store($newData); + + if ($product->osVersions->isNotEmpty()) { + $newProduct->osVersions()->sync($product->osVersions->pluck('id')); + } + + if ($product->controlPanels->isNotEmpty()) { + $syncData = []; + foreach ($product->controlPanels as $panel) { + $syncData[$panel->id] = [ + 'price_modifier' => $panel->pivot->price_modifier, + 'is_default' => $panel->pivot->is_default, + ]; + } + $newProduct->controlPanels()->sync($syncData); + } + + if ($product->pricings->isNotEmpty()) { + foreach ($product->pricings as $pricing) { + $newProduct->pricings()->create([ + 'billing_cycle' => $pricing->billing_cycle, + 'price' => $pricing->price, + 'price_sale' => $pricing->price_sale, + 'currency' => $pricing->currency, + 'is_active' => $pricing->is_active, + ]); + } + } + + return redirect()->route('products.edit', ['id' => $newProduct->id]) + ->with('success', 'Product duplicated successfully'); + } catch (Exception $e) { + return back()->withErrors($e->getMessage()); + } + } + public function datatable(Request $request) { $query = $this->productRepository->filter( $request->all(), - ['category'], + ['category', 'pricings'], [], ['*'], true ); return DataTables::eloquent($query) - ->addColumn('category_name', function ($d) { - return $d->category?->name ?? '-'; + ->addColumn('checkbox', function ($d) { + return ''; }) - ->addColumn('product_type_label', function ($d) { - $type = ProductTypeEnums::tryFrom($d->product_type); - return $type ? $type->label() : '-'; + ->addColumn('category', function ($d) { + return view('administrator.pages.product.datatable.category', ['row' => $d])->render(); + }) + ->addColumn('price', function ($d) { + return view('administrator.pages.product.datatable.price', ['row' => $d])->render(); }) ->addColumn('specs', function ($d) { return view('administrator.pages.product.datatable.specs', ['row' => $d])->render(); @@ -157,7 +214,7 @@ class ProductController extends Controller return view('administrator.pages.product.datatable.action', ['row' => $d])->render(); } ) - ->rawColumns(['specs', 'status', 'action']) + ->rawColumns(['checkbox', 'category', 'price', 'specs', 'status', 'action']) ->make(true); } diff --git a/resources/sass/plugin-overrides/_data-tables.scss b/resources/sass/plugin-overrides/_data-tables.scss index eaf737e..d2ea171 100644 --- a/resources/sass/plugin-overrides/_data-tables.scss +++ b/resources/sass/plugin-overrides/_data-tables.scss @@ -4,6 +4,7 @@ .dt-container { &.dt-bootstrap5 { .dt-length { + @extend .d-inline-flex; @media(max-width: 767px) { text-align: left; } @@ -13,6 +14,7 @@ } } .dt-search { + @extend .d-inline-flex; @media(max-width: 767px) { text-align: left; input { diff --git a/resources/views/administrator/pages/product/datatable/action.blade.php b/resources/views/administrator/pages/product/datatable/action.blade.php index 47b56d0..d49cc0a 100644 --- a/resources/views/administrator/pages/product/datatable/action.blade.php +++ b/resources/views/administrator/pages/product/datatable/action.blade.php @@ -1,6 +1,34 @@ -
- Chỉnh sửa - Xóa -
\ No newline at end of file + diff --git a/resources/views/administrator/pages/product/datatable/category.blade.php b/resources/views/administrator/pages/product/datatable/category.blade.php new file mode 100644 index 0000000..fb7408f --- /dev/null +++ b/resources/views/administrator/pages/product/datatable/category.blade.php @@ -0,0 +1,18 @@ +@php + $type = \App\Enums\Product\ProductTypeEnums::tryFrom($row->product_type); + $categoryName = $row->category?->name; +@endphp + +
+ @if($categoryName) +
+ + {{ $categoryName }} +
+ @endif + @if($type) +
+ {{ $type->label() }} +
+ @endif +
diff --git a/resources/views/administrator/pages/product/datatable/price.blade.php b/resources/views/administrator/pages/product/datatable/price.blade.php new file mode 100644 index 0000000..c4ec83b --- /dev/null +++ b/resources/views/administrator/pages/product/datatable/price.blade.php @@ -0,0 +1,17 @@ +@php + $pricing = $row->pricings->where('is_active', 1)->sortBy('price')->first(); +@endphp + +@if($pricing) +
+ @if($pricing->price_sale && $pricing->price_sale < $pricing->price) + {{ number_format($pricing->price) }} + {{ number_format($pricing->price_sale) }} + @else + {{ number_format($pricing->price) }} + @endif + / {{ $pricing->billing_cycle ?? 'monthly' }} +
+@else +- +@endif diff --git a/resources/views/administrator/pages/product/index.blade.php b/resources/views/administrator/pages/product/index.blade.php index a7786d0..9f000fe 100644 --- a/resources/views/administrator/pages/product/index.blade.php +++ b/resources/views/administrator/pages/product/index.blade.php @@ -7,48 +7,51 @@ @section('breadcrumb') @php - $breadcrumbItems = [ - ['label' => 'Product'], - ['label' => 'Product / Services'], - ['label' => 'Product List', 'active' => true], - ]; +$breadcrumbItems = [ + ['label' => 'Product'], + ['label' => 'Product / Services'], + ['label' => 'Product List', 'active' => true], +]; @endphp @endsection @section('tab') @php - $tabs = [ - [ - 'label' => 'Product', - 'route' => 'products.index', - 'icon' => 'server', - ], - [ - 'label' => 'Product Group', - 'route' => 'products.group.index', - 'icon' => 'server', - ], - ]; +$tabs = [ + [ + 'label' => 'Product', + 'route' => 'products.index', + 'icon' => 'server', + ], + [ + 'label' => 'Product Group', + 'route' => 'products.group.index', + 'icon' => 'server', + ], +]; @endphp @endsection @section('content') @if (auth()->guard('admins')->user()->can('manager product')) -
-
+
+
@include('administrator.pages.block.product.btn')
-
- +
+ @php + $productColumns = [ + ['key' => 'checkbox', 'label' => '', 'orderable' => false, 'searchable' => false, 'className' => 'text-center'], + ['key' => 'name', 'label' => 'Product Name'], + ['key' => 'category', 'label' => 'Category / Type', 'searchable' => false], + ['key' => 'price', 'label' => 'Price', 'orderable' => false, 'searchable' => false], + ['key' => 'specs', 'label' => 'Specifications', 'orderable' => false, 'searchable' => false], + ['key' => 'status', 'label' => 'Status', 'searchable' => false], + ['key' => 'action', 'label' => '', 'orderable' => false, 'searchable' => false, 'className' => 'text-center'], + ]; + @endphp +
-
@else
diff --git a/resources/views/components/data-table.blade.php b/resources/views/components/data-table.blade.php index cc5aa0c..983ef7f 100644 --- a/resources/views/components/data-table.blade.php +++ b/resources/views/components/data-table.blade.php @@ -1,35 +1,33 @@ @props(['id', 'columns', 'ajax', 'bulkDeleteUrl' => null]) @php - $dtColumns = collect($columns) - ->map(function ($c) { - return [ - 'data' => $c['key'], - 'name' => $c['key'], - 'orderable' => $c['orderable'] ?? true, - 'searchable' => $c['searchable'] ?? true, - 'className' => $c['className'] ?? '', - ]; - }) - ->values(); +$dtColumns = collect($columns) + ->map(function ($c) { + return [ + 'data' => $c['key'], + 'name' => $c['key'], + 'orderable' => $c['orderable'] ?? true, + 'searchable' => $c['searchable'] ?? true, + 'className' => $c['className'] ?? '', + ]; + }) + ->values(); @endphp @if ($bulkDeleteUrl) -
-
-
- - 0 items selected +
+
+
+ + 0 items selected +
+
+ + Delete Selected +
-
- - Delete Selected -
-
- - {{-- Bulk Delete Confirmation Modal (Bootstrap 5) --}}