From 245f5ade3788092f09946755bb59af1cd3ed66dd Mon Sep 17 00:00:00 2001 From: pbao9 Date: Fri, 13 Feb 2026 11:31:54 +0700 Subject: [PATCH] feat: Refactor addon management to use `AddonType` and `AddonInputType` enums, update the database schema, and implement a grouped datatable display for addons. --- app/Enums/AddonInputType.php | 28 ++ app/Enums/AddonType.php | 33 +++ .../Controllers/admin/AddonController.php | 65 ++++- app/Models/Addon.php | 9 +- ...type_id_to_type_in_product_addon_table.php | 22 ++ database/seeders/AddonSeeder.php | 121 ++++++++- database/seeders/OsSeeder.php | 8 +- .../administrator/pages/addon/add.blade.php | 255 +++++++++++------- .../pages/addon/datatable/children.blade.php | 51 ++++ .../administrator/pages/addon/edit.blade.php | 222 ++++++++++----- .../administrator/pages/addon/index.blade.php | 86 +++++- 11 files changed, 684 insertions(+), 216 deletions(-) create mode 100644 app/Enums/AddonInputType.php create mode 100644 app/Enums/AddonType.php create mode 100644 database/migrations/2026_02_13_104842_change_type_id_to_type_in_product_addon_table.php create mode 100644 resources/views/administrator/pages/addon/datatable/children.blade.php diff --git a/app/Enums/AddonInputType.php b/app/Enums/AddonInputType.php new file mode 100644 index 0000000..db7f1d3 --- /dev/null +++ b/app/Enums/AddonInputType.php @@ -0,0 +1,28 @@ + 'Số', + self::SLIDER => 'Thanh trượt', + self::SELECT => 'Chọn', + }; + } + + public static function getOptions(): array + { + return [ + self::NUMBER->value => self::NUMBER->label(), + self::SLIDER->value => self::SLIDER->label(), + self::SELECT->value => self::SELECT->label(), + ]; + } +} diff --git a/app/Enums/AddonType.php b/app/Enums/AddonType.php new file mode 100644 index 0000000..51e2eae --- /dev/null +++ b/app/Enums/AddonType.php @@ -0,0 +1,33 @@ + 'VPS', + self::HOSTING => 'Hosting', + self::DOMAIN => 'Domain', + }; + } + + public static function getOptions(): array + { + return [ + self::VPS->value => self::VPS->label(), + self::HOSTING->value => self::HOSTING->label(), + self::DOMAIN->value => self::DOMAIN->label(), + ]; + } + + public static function values(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/app/Http/Controllers/admin/AddonController.php b/app/Http/Controllers/admin/AddonController.php index ec80bd4..2c69241 100644 --- a/app/Http/Controllers/admin/AddonController.php +++ b/app/Http/Controllers/admin/AddonController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\admin; +use App\Enums\AddonType; use App\Http\Controllers\Controller; use App\Http\Requests\AddOnRequest; use App\Models\Addon; @@ -102,22 +103,58 @@ class AddonController extends Controller public function datatable(Request $request) { - $query = $this->addonRepository->filter( - $request->all(), - [], - [], - ['*'], - true - ); + $addons = Addon::orderBy('sort_order')->get(); - return DataTables::eloquent($query) - ->addColumn( - 'action', - function ($d) { - return view('administrator.pages.addon.datatable.action', ['row' => $d])->render(); + $grouped = $addons->groupBy(function ($item) { + return $item->type instanceof AddonType + ? $item->type->value + : $item->type; + }); + + $types = AddonType::values(); + $data = []; + + foreach ($types as $type) { + $typeEnum = AddonType::tryFrom($type); + $typeLabel = $typeEnum ? $typeEnum->label() : $type; + $typeAddons = $grouped->get($type, collect()); + + $childrenHtml = view('administrator.pages.addon.datatable.children', [ + 'children' => $typeAddons + ])->render(); + + $data[] = [ + 'id' => $type, + 'type' => $type, + 'name' => $typeLabel, + 'code' => $type, + 'children' => $typeAddons->toArray(), + 'children_html' => $childrenHtml, + 'status' => true, + ]; + } + + return DataTables::of(collect($data)) + ->addColumn('details_control', function ($d) { + $hasChildren = !empty($d['children']) && count($d['children']) > 0; + if ($hasChildren) { + return ''; } - ) - ->rawColumns(['status', 'action']) + return ''; + }) + ->addColumn('name', function ($d) { + return $d['name']; + }) + ->addColumn('slug', function ($d) { + return $d['code']; + }) + ->addColumn('is_active', function ($d) { + return 'Active'; + }) + ->addColumn('action', function ($d) { + return ''; + }) + ->rawColumns(['details_control', 'is_active', 'children_html']) ->make(true); } } diff --git a/app/Models/Addon.php b/app/Models/Addon.php index e735eb3..27189ee 100644 --- a/app/Models/Addon.php +++ b/app/Models/Addon.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\AddonType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -12,7 +13,7 @@ class Addon extends Model protected $table = 'product_addon'; protected $fillable = [ - 'type_id', + 'type', 'code', 'name', 'price', @@ -20,7 +21,13 @@ class Addon extends Model 'max', 'step', 'prefix', + 'input_type', 'status', 'sort_order' ]; + + protected $casts = [ + 'type' => AddonType::class, + 'status' => 'boolean', + ]; } diff --git a/database/migrations/2026_02_13_104842_change_type_id_to_type_in_product_addon_table.php b/database/migrations/2026_02_13_104842_change_type_id_to_type_in_product_addon_table.php new file mode 100644 index 0000000..2f4dd4b --- /dev/null +++ b/database/migrations/2026_02_13_104842_change_type_id_to_type_in_product_addon_table.php @@ -0,0 +1,22 @@ +renameColumn('type_id', 'type'); + }); + } + + public function down(): void + { + Schema::table('product_addon', function (Blueprint $table) { + $table->renameColumn('type', 'type_id'); + }); + } +}; diff --git a/database/seeders/AddonSeeder.php b/database/seeders/AddonSeeder.php index f482b31..79ec78a 100644 --- a/database/seeders/AddonSeeder.php +++ b/database/seeders/AddonSeeder.php @@ -10,42 +10,139 @@ class AddonSeeder extends Seeder public function run(): void { $addons = [ + // VPS Addons [ - 'type_id' => 'vps', + 'type' => 'vps', 'code' => 'cpu', 'name' => 'CPU', - 'price' => 100000, - 'min' => 0, - 'max' => 30, + 'price' => 20000, + 'min' => 1, + 'max' => 32, 'step' => 1, 'prefix' => 'vCore', + 'input_type' => 'number', 'status' => true, 'sort_order' => 0, ], [ - 'type_id' => 'vps', + 'type' => 'vps', 'code' => 'ram', 'name' => 'RAM', - 'price' => 50000, - 'min' => 0, - 'max' => 32, + 'price' => 100000, + 'min' => 1, + 'max' => 64, 'step' => 1, 'prefix' => 'GB', + 'input_type' => 'number', 'status' => true, 'sort_order' => 1, ], [ - 'type_id' => 'vps', + 'type' => 'vps', 'code' => 'disk', 'name' => 'SSD', 'price' => 4000, - 'min' => 0, - 'max' => 1500, - 'step' => 10, + 'min' => 25, + 'max' => 2000, + 'step' => 25, 'prefix' => 'GB', + 'input_type' => 'number', 'status' => true, 'sort_order' => 2, ], + [ + 'type' => 'vps', + 'code' => 'gpu', + 'name' => 'GPU', + 'price' => 500000, + 'min' => 0, + 'max' => 4, + 'step' => 1, + 'prefix' => 'GB', + 'input_type' => 'select', + 'status' => true, + 'sort_order' => 3, + ], + [ + 'type' => 'vps', + 'code' => 'bandwidth', + 'name' => 'Bandwidth', + 'price' => 50000, + 'min' => 100, + 'max' => 10000, + 'step' => 100, + 'prefix' => 'Mbps', + 'input_type' => 'slider', + 'status' => true, + 'sort_order' => 4, + ], + + // Hosting Addons + [ + 'type' => 'hosting', + 'code' => 'hosting_cpu', + 'name' => 'CPU', + 'price' => 15000, + 'min' => 1, + 'max' => 8, + 'step' => 1, + 'prefix' => 'Core', + 'input_type' => 'number', + 'status' => true, + 'sort_order' => 10, + ], + [ + 'type' => 'hosting', + 'code' => 'hosting_ram', + 'name' => 'RAM', + 'price' => 25000, + 'min' => 512, + 'max' => 16384, + 'step' => 512, + 'prefix' => 'MB', + 'input_type' => 'number', + 'status' => true, + 'sort_order' => 11, + ], + [ + 'type' => 'hosting', + 'code' => 'hosting_disk', + 'name' => 'Disk', + 'price' => 2000, + 'min' => 5, + 'max' => 500, + 'step' => 5, + 'prefix' => 'GB', + 'input_type' => 'number', + 'status' => true, + 'sort_order' => 12, + ], + [ + 'type' => 'hosting', + 'code' => 'hosting_bandwidth', + 'name' => 'Bandwidth', + 'price' => 10000, + 'min' => 10, + 'max' => 1000, + 'step' => 10, + 'prefix' => 'GB', + 'input_type' => 'slider', + 'status' => true, + 'sort_order' => 13, + ], + [ + 'type' => 'hosting', + 'code' => 'hosting_ip', + 'name' => 'IP Address', + 'price' => 30000, + 'min' => 0, + 'max' => 5, + 'step' => 1, + 'prefix' => 'IP', + 'input_type' => 'number', + 'status' => true, + 'sort_order' => 14, + ], ]; Addon::upsert($addons, ['code']); diff --git a/database/seeders/OsSeeder.php b/database/seeders/OsSeeder.php index 2a144c0..45e4919 100644 --- a/database/seeders/OsSeeder.php +++ b/database/seeders/OsSeeder.php @@ -17,7 +17,7 @@ class OsSeeder extends Seeder [ 'name' => 'Ubuntu', 'thumbnail' => 'ubuntu.png', - 'module_host_id' => 1, + 'module_host_id' => null, 'status' => 1, 'versions' => [ '20.04 LTS', @@ -28,7 +28,7 @@ class OsSeeder extends Seeder [ 'name' => 'CentOS', 'thumbnail' => 'centos.png', - 'module_host_id' => 1, + 'module_host_id' => null, 'status' => 1, 'versions' => [ '7', @@ -38,7 +38,7 @@ class OsSeeder extends Seeder [ 'name' => 'Debian', 'thumbnail' => 'debian.png', - 'module_host_id' => 1, + 'module_host_id' => null, 'status' => 1, 'versions' => [ '10', @@ -49,7 +49,7 @@ class OsSeeder extends Seeder [ 'name' => 'Windows Server', 'thumbnail' => 'windows.png', - 'module_host_id' => 1, + 'module_host_id' => null, 'status' => 1, 'versions' => [ '2016', diff --git a/resources/views/administrator/pages/addon/add.blade.php b/resources/views/administrator/pages/addon/add.blade.php index 9253af3..be432b0 100644 --- a/resources/views/administrator/pages/addon/add.blade.php +++ b/resources/views/administrator/pages/addon/add.blade.php @@ -7,124 +7,171 @@ @section('breadcrumb') @php -$breadcrumbItems = [ - ['label' => 'Product'], - ['label' => 'Product Addons'], - ['label' => 'Product Addons List', 'url' => route('addon.index')], - ['label' => 'Add new addon', 'active' => true], -]; + $breadcrumbItems = [ + ['label' => 'Product'], + ['label' => 'Product Addons', 'url' => route('addon.index')], + ['label' => 'Add New Addon', 'active' => true], + ]; @endphp @endsection @php -use App\Enums\Status; + use App\Enums\Status; + use App\Enums\AddonType; + use App\Enums\AddonInputType; @endphp @section('content') @if (auth()->guard('admins')->user()->can('manager product')) -
-
- Create New Addon +
+ @csrf +
+
+
+
+
Add New Addon
+
+
+
+
Basic Information
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + + Đơn vị hiển thị (VD: GB, vCore, Mbps) +
+
+ +
+
Configuration
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ Step là bước nhảy: VD RAM step=1 nhảy 1GB, step=512 nhảy 512MB + +
+
+
+ +
+ +
+ VND +
+
+ Giá cho mỗi step (VD: 1 Core = 20,000đ) +
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+
+
Publish
+
+
+
+ + +
+
+ +
+
- - @csrf -
-
-
- {{-- Name --}} -
- -
- -
-
- - {{-- Code --}} -
- -
- -
-
- - {{-- Price --}} -
- -
- -
-
- - {{-- Min --}} -
- -
- -
-
- - {{-- Max --}} -
- -
- -
-
- - {{-- Step --}} -
- -
- -
-
- - {{-- Prefix --}} -
- -
- -
-
- {{-- Status --}} -
- -
- -
-
-
-
-
- - Cancel - Create - Addon - -
-
+ + Cancel + + Create Addon + + @else
-
Access Denied - You are not assigned to any Product - management department
+
Access Denied - You are not assigned to any Product management department
@endif @endsection + @push('plugin-scripts') @foreach (config('assets.datatable.js') as $datatable) @@ -134,4 +181,4 @@ use App\Enums\Status; $('.select2').select2(); }); -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/administrator/pages/addon/datatable/children.blade.php b/resources/views/administrator/pages/addon/datatable/children.blade.php new file mode 100644 index 0000000..7a4b66e --- /dev/null +++ b/resources/views/administrator/pages/addon/datatable/children.blade.php @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + @forelse($children as $child) + + + + + + + + + + + @empty + + + + @endforelse + +
NamePrefixMinMaxStepPriceStatusAction
{{ $child['name'] }}{{ $child['prefix'] }}{{ $child['min'] }}{{ $child['max'] }}{{ $child['step'] }}{{ number_format($child['price']) }} VND + @if($child['status']) + Active + @else + Inactive + @endif + +
+ + + +
+ @csrf + @method('DELETE') + +
+
+
No addons in this group
diff --git a/resources/views/administrator/pages/addon/edit.blade.php b/resources/views/administrator/pages/addon/edit.blade.php index 071c5bd..d18c7ba 100644 --- a/resources/views/administrator/pages/addon/edit.blade.php +++ b/resources/views/administrator/pages/addon/edit.blade.php @@ -9,9 +9,8 @@ @php $breadcrumbItems = [ ['label' => 'Product'], - ['label' => 'Product Addons'], - ['label' => 'Product Addons List', 'url' => route('addon.index')], - ['label' => 'Edit addon', 'active' => true], + ['label' => 'Product Addons', 'url' => route('addon.index')], + ['label' => 'Edit Addon', 'active' => true], ]; @endphp @@ -19,89 +18,176 @@ @php use App\Enums\Status; + use App\Enums\AddonType; + use App\Enums\AddonInputType; @endphp -@section("content") -
-
- @csrf - @method('put') -
-
-
- {{--Product name--}} -
- -
- +@section('content') + + @csrf + @method('put') +
+
+
+
+
Edit Addon: {{ $addon->name }}
+
+
+
+
Basic Information
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + + Đơn vị hiển thị (VD: GB, vCore, Mbps)
- {{--Price--}} -
- -
- +
+
Configuration
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
-
+ Step là bước nhảy: VD RAM step=1 nhảy 1GB, step=512 nhảy + 512MB - {{--Price Sales--}} -
- -
- -
-
- - {{--Renewal--}} -
- -
- -
-
- - {{--Transfer--}} -
- -
- -
-
- - - {{--Transfer--}} -
- -
- +
+
+
+ +
+ +
+ VND +
+
+ Giá cho mỗi step (VD: 1 Core = 20,000đ) +
+
+
+
+ + +
+
- - Cancel - Edit - Addon - - -
+ +
+
+
+
Publish
+
+
+
+ + +
+
+ +
+
+
+ + Cancel + + Update Addon + + @endsection + @push('plugin-scripts') @foreach (config('assets.datatable.js') as $datatable) @endforeach -@endpush \ No newline at end of file +@endpush diff --git a/resources/views/administrator/pages/addon/index.blade.php b/resources/views/administrator/pages/addon/index.blade.php index ac124ca..1beb4a4 100644 --- a/resources/views/administrator/pages/addon/index.blade.php +++ b/resources/views/administrator/pages/addon/index.blade.php @@ -15,35 +15,95 @@ @endphp @endsection + +@php + use App\Enums\AddonType; +@endphp + @section("content") @if (auth()->guard('admins')->user()->can('manager product'))
@include('administrator.pages.block.addon.btn')
-
+
-
@else
-
Access Denied - You are not assigned to any Product - management department
+
Access Denied - You are not assigned to any Product management + department
@endif @endsection + @push('plugin-scripts') @foreach (config('assets.datatable.js') as $datatable) @endforeach -@endpush \ No newline at end of file +@endpush + +@push('custom-styles') + +@endpush + +@push('custom-scripts') + +@endpush +