diff --git a/app/Livewire/Datatable/Studio/Feature/ChooseUsTable.php b/app/Livewire/Datatable/Studio/Feature/ChooseUsTable.php new file mode 100644 index 0000000..806621e --- /dev/null +++ b/app/Livewire/Datatable/Studio/Feature/ChooseUsTable.php @@ -0,0 +1,97 @@ +searchable()->sortable(), + + Column::make('Keterangan', 'description')->searchable()->sortable(), + + Column::make('Aksi') + ->label(function ($row) { + $actions = ''; + $maxSortOrder = ChooseUs::max('sort_order'); + + if (auth()->user()->can('update choose us')) { + if ($row->sort_order > 1) { + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'chooseUs', + 'direction' => 'up', + 'tooltip' => 'Naik', + 'color' => 'cyan', + 'icon' => 'arrow-up', + ])->render(); + + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'chooseUs', + 'direction' => 'first', + 'tooltip' => 'Pertama', + 'color' => 'emerald', + 'icon' => 'bars-arrow-up', + ])->render(); + } + + if ($row->sort_order < $maxSortOrder) { + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'chooseUs', + 'direction' => 'down', + 'tooltip' => 'Turun', + 'color' => 'orange', + 'icon' => 'arrow-down', + ])->render(); + + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'chooseUs', + 'direction' => 'last', + 'tooltip' => 'Terakhir', + 'color' => 'rose', + 'icon' => 'bars-arrow-down', + ])->render(); + } + } + + if (auth()->user()->can('update choose us')) { + $actions .= view('components.datatables.edit-modal', [ + 'id' => $row->hash, + 'method' => 'update', + 'modalTitle' => 'Ubah Kategori', + ])->render(); + } + + if (auth()->user()->can('delete choose us')) { + $actions .= view('components.datatables.delete', [ + 'id' => $row->hash, + 'deleteRoute' => route('studio.feature.choose_us.delete', $row->hash), + ])->render(); + } + + return $actions; + }) + ->html(), + ]; + } + + public function builder(): Builder + { + return ChooseUs::select('id', 'name', 'description', 'sort_order')->orderBy('sort_order'); + } +} diff --git a/app/Livewire/Datatable/Studio/Feature/PerfumeFeaturesTable.php b/app/Livewire/Datatable/Studio/Feature/PerfumeFeaturesTable.php new file mode 100644 index 0000000..62096c8 --- /dev/null +++ b/app/Livewire/Datatable/Studio/Feature/PerfumeFeaturesTable.php @@ -0,0 +1,97 @@ +searchable()->sortable(), + + Column::make('Keterangan', 'description')->searchable()->sortable(), + + Column::make('Aksi') + ->label(function ($row) { + $actions = ''; + $maxSortOrder = PerfumeFeature::max('sort_order'); + + if (auth()->user()->can('update perfume feature')) { + if ($row->sort_order > 1) { + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'perfumeFeature', + 'direction' => 'up', + 'tooltip' => 'Naik', + 'color' => 'cyan', + 'icon' => 'arrow-up', + ])->render(); + + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'perfumeFeature', + 'direction' => 'first', + 'tooltip' => 'Pertama', + 'color' => 'emerald', + 'icon' => 'bars-arrow-up', + ])->render(); + } + + if ($row->sort_order < $maxSortOrder) { + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'perfumeFeature', + 'direction' => 'down', + 'tooltip' => 'Turun', + 'color' => 'orange', + 'icon' => 'arrow-down', + ])->render(); + + $actions .= view('components.datatables.sort-button', [ + 'id' => $row->hash, + 'module' => 'perfumeFeature', + 'direction' => 'last', + 'tooltip' => 'Terakhir', + 'color' => 'rose', + 'icon' => 'bars-arrow-down', + ])->render(); + } + } + + if (auth()->user()->can('update perfume feature')) { + $actions .= view('components.datatables.edit-modal', [ + 'id' => $row->hash, + 'method' => 'update', + 'modalTitle' => 'Ubah Kategori', + ])->render(); + } + + if (auth()->user()->can('delete perfume feature')) { + $actions .= view('components.datatables.delete', [ + 'id' => $row->hash, + 'deleteRoute' => route('studio.feature.perfume_feature.delete', $row->hash), + ])->render(); + } + + return $actions; + }) + ->html(), + ]; + } + + public function builder(): Builder + { + return PerfumeFeature::select('id', 'name', 'description', 'sort_order')->orderBy('sort_order'); + } +} diff --git a/app/Livewire/Forms/Studio/Feature/ChooseUsForm.php b/app/Livewire/Forms/Studio/Feature/ChooseUsForm.php new file mode 100644 index 0000000..6b809d4 --- /dev/null +++ b/app/Livewire/Forms/Studio/Feature/ChooseUsForm.php @@ -0,0 +1,123 @@ + ['required', 'string', 'max:50'], + 'description' => ['required', 'string'], + 'color' => ['required', 'string', 'max:20'], + 'icon' => ['required', 'string', 'max:20'], + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'description' => 'keterangan', + 'color' => 'warna', + 'icon' => 'ikon', + ]; + } + + public function setChooseUs(ChooseUs $chooseUs) + { + $this->chooseUs = $chooseUs; + + $this->name = $chooseUs->name; + $this->description = $chooseUs->description; + $this->color = $chooseUs->color; + $this->icon = $chooseUs->icon; + } + + public function store() + { + $this->validate(); + + ChooseUs::create([ + 'name' => $this->name, + 'description' => $this->description, + 'color' => $this->color, + 'icon' => $this->icon, + 'sort_order' => ChooseUs::count() + 1, + ]); + } + + public function update() + { + $this->validate(); + + $this->chooseUs->update([ + 'name' => $this->name, + 'description' => $this->description, + 'color' => $this->color, + 'icon' => $this->icon, + ]); + } + + public function delete() + { + $this->chooseUs->delete(); + + ChooseUs::where('sort_order', '>', $this->chooseUs->sort_order) + ->orderBy('sort_order') + ->get() + ->each(function ($cat) { + $cat->update(['sort_order' => $cat->sort_order - 1]); + }); + } + + public function sortOrder(string $direction) + { + if (in_array($direction, ['up', 'down'])) { + $swap = null; + + if ($direction === 'up') { + $swap = ChooseUs::where('sort_order', '<', $this->chooseUs->sort_order) + ->orderByDesc('sort_order') + ->first(); + } else { + $swap = ChooseUs::where('sort_order', '>', $this->chooseUs->sort_order) + ->orderBy('sort_order') + ->first(); + } + + if ($swap) { + $temp = $this->chooseUs->sort_order; + $this->chooseUs->update(['sort_order' => $swap->sort_order]); + $swap->update(['sort_order' => $temp]); + } + } elseif ($direction === 'first') { + $this->chooseUs->update(['sort_order' => ChooseUs::min('sort_order') - 1]); + $this->normalizeSortOrder(); + } elseif ($direction === 'last') { + $this->chooseUs->update(['sort_order' => ChooseUs::max('sort_order') + 1]); + $this->normalizeSortOrder(); + } + } + + protected function normalizeSortOrder() + { + $chooseUss = ChooseUs::orderBy('sort_order')->get(); + foreach ($chooseUss as $index => $chooseUs) { + $chooseUs->update(['sort_order' => $index + 1]); + } + } +} diff --git a/app/Livewire/Forms/Studio/Feature/PerfumeFeatureForm.php b/app/Livewire/Forms/Studio/Feature/PerfumeFeatureForm.php new file mode 100644 index 0000000..c37a302 --- /dev/null +++ b/app/Livewire/Forms/Studio/Feature/PerfumeFeatureForm.php @@ -0,0 +1,109 @@ + ['required', 'string', 'max:50'], + 'description' => ['required', 'string'], + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'description' => 'keterangan', + ]; + } + + public function setPerfumeFeature(PerfumeFeature $perfumeFeature) + { + $this->perfumeFeature = $perfumeFeature; + + $this->name = $perfumeFeature->name; + $this->description = $perfumeFeature->description; + } + + public function store() + { + $this->validate(); + + PerfumeFeature::create([ + 'name' => $this->name, + 'description' => $this->description, + 'sort_order' => PerfumeFeature::count() + 1, + ]); + } + + public function update() + { + $this->validate(); + + $this->perfumeFeature->update([ + 'name' => $this->name, + 'description' => $this->description, + ]); + } + + public function delete() + { + $this->perfumeFeature->delete(); + + PerfumeFeature::where('sort_order', '>', $this->perfumeFeature->sort_order) + ->orderBy('sort_order') + ->get() + ->each(function ($cat) { + $cat->update(['sort_order' => $cat->sort_order - 1]); + }); + } + + public function sortOrder(string $direction) + { + if (in_array($direction, ['up', 'down'])) { + $swap = null; + + if ($direction === 'up') { + $swap = PerfumeFeature::where('sort_order', '<', $this->perfumeFeature->sort_order) + ->orderByDesc('sort_order') + ->first(); + } else { + $swap = PerfumeFeature::where('sort_order', '>', $this->perfumeFeature->sort_order) + ->orderBy('sort_order') + ->first(); + } + + if ($swap) { + $temp = $this->perfumeFeature->sort_order; + $this->perfumeFeature->update(['sort_order' => $swap->sort_order]); + $swap->update(['sort_order' => $temp]); + } + } elseif ($direction === 'first') { + $this->perfumeFeature->update(['sort_order' => PerfumeFeature::min('sort_order') - 1]); + $this->normalizeSortOrder(); + } elseif ($direction === 'last') { + $this->perfumeFeature->update(['sort_order' => PerfumeFeature::max('sort_order') + 1]); + $this->normalizeSortOrder(); + } + } + + protected function normalizeSortOrder() + { + $perfumeFeatures = PerfumeFeature::orderBy('sort_order')->get(); + foreach ($perfumeFeatures as $index => $perfumeFeature) { + $perfumeFeature->update(['sort_order' => $index + 1]); + } + } +} diff --git a/app/Livewire/Studio/Feature/ChooseUs.php b/app/Livewire/Studio/Feature/ChooseUs.php new file mode 100644 index 0000000..c3173ec --- /dev/null +++ b/app/Livewire/Studio/Feature/ChooseUs.php @@ -0,0 +1,99 @@ +resetValidation(); + $this->resetErrorBag(); + + $this->method = $method; + $this->modalTitle = $modalTitle; + + if ($id) { + $this->form->setChooseUs(ChooseUsModel::byHashOrFail($id)); + } + } + + public function create() + { + $this->canOrAbort('create choose us'); + + $this->form->store(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Pilih kami berhasil ditambahkan.'); + + Flux::modals()->close(); + } + + public function update() + { + $this->canOrAbort('update choose us'); + + $this->form->update(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Pilih kami berhasil diperbarui.'); + + Flux::modals()->close(); + } + + public function delete(ChooseUsModel $chooseUs) + { + $this->form->chooseUs = $chooseUs; + + $this->form->delete(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Pilih kami berhasil dihapus.'); + + Flux::modals()->close(); + } + + #[On('fn:sortOrder')] + public function sortOrder(ChooseUsModel $chooseUs, string $direction) + { + $this->canOrAbort('update choose us'); + + $this->form->chooseUs = $chooseUs; + + $this->form->sortOrder($direction); + + $this->dispatch('refreshDatatable'); + } + + public function render() + { + return view('livewire.studio.feature.choose-us', [ + 'pageTitle' => 'Pilih Kami', + ]); + } +} diff --git a/app/Livewire/Studio/Feature/PerfumeFeature.php b/app/Livewire/Studio/Feature/PerfumeFeature.php new file mode 100644 index 0000000..162445e --- /dev/null +++ b/app/Livewire/Studio/Feature/PerfumeFeature.php @@ -0,0 +1,99 @@ +resetValidation(); + $this->resetErrorBag(); + + $this->method = $method; + $this->modalTitle = $modalTitle; + + if ($id) { + $this->form->setPerfumeFeature(PerfumeFeatureModel::byHashOrFail($id)); + } + } + + public function create() + { + $this->canOrAbort('create perfume feature'); + + $this->form->store(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Keunggulan parfum berhasil ditambahkan.'); + + Flux::modals()->close(); + } + + public function update() + { + $this->canOrAbort('update perfume feature'); + + $this->form->update(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Keunggulan parfum berhasil diperbarui.'); + + Flux::modals()->close(); + } + + public function delete(PerfumeFeatureModel $perfumeFeature) + { + $this->form->perfumeFeature = $perfumeFeature; + + $this->form->delete(); + + $this->dispatch('refreshDatatable'); + + $this->toast('Keunggulan parfum berhasil dihapus.'); + + Flux::modals()->close(); + } + + #[On('fn:sortOrder')] + public function sortOrder(PerfumeFeatureModel $perfumeFeature, string $direction) + { + $this->canOrAbort('update perfume feature'); + + $this->form->perfumeFeature = $perfumeFeature; + + $this->form->sortOrder($direction); + + $this->dispatch('refreshDatatable'); + } + + public function render() + { + return view('livewire.studio.feature.perfume-features', [ + 'pageTitle' => 'Keunggulan Parfum', + ]); + } +} diff --git a/app/Models/ChooseUs.php b/app/Models/ChooseUs.php new file mode 100644 index 0000000..1545492 --- /dev/null +++ b/app/Models/ChooseUs.php @@ -0,0 +1,21 @@ + 'int', + ]; + } +} diff --git a/app/Models/PerfumeFeature.php b/app/Models/PerfumeFeature.php new file mode 100644 index 0000000..f108b0e --- /dev/null +++ b/app/Models/PerfumeFeature.php @@ -0,0 +1,21 @@ + 'int', + ]; + } +} diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index 0af0580..b104872 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -167,6 +167,25 @@ public function boot(): void ], ], ], + [ + 'heading' => 'Keunggulan', + 'items' => [ + [ + 'label' => 'Keunggulan Parfum', + 'icon' => 'rocket-launch', + 'route' => 'studio.feature.perfume_feature.index', + 'match' => 'studio.feature.perfume_feature.*', + 'can' => 'view perfume feature', + ], + [ + 'label' => 'Pilih Kami', + 'icon' => 'trophy', + 'route' => 'studio.feature.choose_us.index', + 'match' => 'studio.feature.choose_us.*', + 'can' => 'view choose us', + ], + ], + ], [ 'heading' => 'Pengaturan', 'items' => [ diff --git a/database/migrations/2025_11_06_094639_create_perfume_features_table.php b/database/migrations/2025_11_06_094639_create_perfume_features_table.php new file mode 100644 index 0000000..46e3207 --- /dev/null +++ b/database/migrations/2025_11_06_094639_create_perfume_features_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name', 50); + $table->text('description'); + $table->unsignedInteger('sort_order'); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('perfume_features'); + } +}; diff --git a/database/migrations/2025_11_06_100555_create_choose_us_table.php b/database/migrations/2025_11_06_100555_create_choose_us_table.php new file mode 100644 index 0000000..45125b8 --- /dev/null +++ b/database/migrations/2025_11_06_100555_create_choose_us_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 50); + $table->text('description'); + $table->string('icon', 20); + $table->string('color', 20); + $table->unsignedInteger('sort_order'); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('choose_us'); + } +}; diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index a41221a..8fe3c75 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -120,6 +120,16 @@ public function run(): void 'update faq', 'delete faq', + 'view perfume feature', + 'create perfume feature', + 'update perfume feature', + 'delete perfume feature', + + 'view choose us', + 'create choose us', + 'update choose us', + 'delete choose us', + 'view application settings', 'view whatsapp settings', diff --git a/resources/views/livewire/studio/feature/choose-us.blade.php b/resources/views/livewire/studio/feature/choose-us.blade.php new file mode 100644 index 0000000..0a298ea --- /dev/null +++ b/resources/views/livewire/studio/feature/choose-us.blade.php @@ -0,0 +1,66 @@ + + + + {{ $pageTitle }} + + @can('create choose us') + + + + Tambah + + + + @endcan + + + + + + + @include('components.confirmation.delete') + + + {{ $modalTitle }} + + + Nama * + + + + + + Keterangan * + + + + + + + Warna * + + + + + + Icon * + + + + + + + + + + Simpan + + + + + diff --git a/resources/views/livewire/studio/feature/perfume-features.blade.php b/resources/views/livewire/studio/feature/perfume-features.blade.php new file mode 100644 index 0000000..e869a5d --- /dev/null +++ b/resources/views/livewire/studio/feature/perfume-features.blade.php @@ -0,0 +1,56 @@ + + + + {{ $pageTitle }} + + @can('create perfume feature') + + + + Tambah + + + + @endcan + + + + + Informasi + + Untuk tampilan yang lebih bagus dan rapi, disarankan hanya mengisi 4 data saja. + + + + + + + @include('components.confirmation.delete') + + + {{ $modalTitle }} + + + Nama * + + + + + + Keterangan * + + + + + + + Simpan + + + + + diff --git a/routes/pages/studio.php b/routes/pages/studio.php index 416b346..6726f04 100644 --- a/routes/pages/studio.php +++ b/routes/pages/studio.php @@ -16,6 +16,8 @@ use App\Livewire\Studio\Catalog\Product\Edit as ProductEdit; use App\Livewire\Studio\Catalog\Product\Index as ProductIndex; use App\Livewire\Studio\Dashboard\Overview as OverviewComponent; +use App\Livewire\Studio\Feature\ChooseUs as ChooseUsComponent; +use App\Livewire\Studio\Feature\PerfumeFeature as PerfumeFeatureComponent; use App\Livewire\Studio\Finance\Expense as ExpenseComponent; use App\Livewire\Studio\Finance\Payroll as PayrollComponent; use App\Livewire\Studio\Loyalty\Customer as CustomerComponent; @@ -174,6 +176,18 @@ }); }); + Route::prefix('feature')->name('studio.feature.')->group(function () { + Route::prefix('perfume-features')->name('perfume_feature.')->group(function () { + Route::get('/', PerfumeFeatureComponent::class)->name('index')->middleware('can:view perfume feature'); + Route::delete('/{perfume_feature}/delete', PerfumeFeatureComponent::class)->name('delete')->middleware('can:delete perfume feature'); + }); + + Route::prefix('choose-us')->name('choose_us.')->group(function () { + Route::get('/', ChooseUsComponent::class)->name('index')->middleware('can:view choose us'); + Route::delete('/{choose_us}/delete', ChooseUsComponent::class)->name('delete')->middleware('can:delete choose us'); + }); + }); + Route::get('/settings/applications', ApplicationComponent::class)->name('studio.setting.application')->middleware('can:view application settings'); Route::get('/settings/whatsapp', WhatsappComponent::class)->name('studio.setting.whatsapp')->middleware('can:view whatsapp settings'); });