diff --git a/app/Livewire/Datatable/BottlesTable.php b/app/Livewire/Datatable/BottlesTable.php new file mode 100644 index 0000000..861459a --- /dev/null +++ b/app/Livewire/Datatable/BottlesTable.php @@ -0,0 +1,86 @@ +label(function ($row) { + return << +
{$row->name}
+ {$row->size} ml + + HTML; + }) + ->searchable(function ($query, $searchTerm) { + $query->where('name', 'like', "%{$searchTerm}%") + ->orWhere('size', 'like', "%{$searchTerm}%"); + }) + ->html(), + + Column::make('Harga Beli', 'cost_price') + ->label(fn ($row, $column) => currency($row->cost_price, 'Rp')) + ->searchable() + ->sortable(), + + Column::make('Harga Jual', 'sale_price') + ->label(fn ($row, $column) => currency($row->sale_price, 'Rp')) + ->searchable() + ->sortable(), + + ImageColumn::make('Gambar') + ->location(fn ($row) => optional($row->getMedia('image')->first())->getUrl() ?? asset('assets/images/logo.png')) + ->attributes(fn ($row) => [ + 'style' => 'width: 50px; height: 50px;', + 'alt' => $row->name, + ]), + + ArrayColumn::make('Outlet') + ->data(fn ($value, $row) => $row->outlets->pluck('name')->toArray()) + ->outputFormat(fn ($index, $value) => Blade::render(''.$value.'')) + ->flexRow(['class' => 'gap-2 flex-wrap']), + + Column::make('Aksi') + ->label(function ($row) { + $actions = ''; + + $actions .= view('components.datatables.edit', [ + 'id' => $row->id, + 'editRoute' => route('studio.catalog.bottle.edit', $row->id), + ])->render(); + + $actions .= view('components.datatables.delete', [ + 'id' => $row->id, + 'deleteRoute' => route('studio.catalog.bottle.delete', $row->id), + ])->render(); + + return $actions; + }) + ->html(), + ]; + } + + public function builder(): Builder + { + return Bottle::select('id', 'name', 'size', 'cost_price', 'sale_price')->with('outlets'); + } +} diff --git a/app/Livewire/Forms/BottleForm.php b/app/Livewire/Forms/BottleForm.php new file mode 100644 index 0000000..1b523cd --- /dev/null +++ b/app/Livewire/Forms/BottleForm.php @@ -0,0 +1,100 @@ + ['required', 'string', 'max:50'], + 'size' => ['required', 'numeric', new UnsignedInteger], + 'cost_price' => ['required', 'numeric', new UnsignedInteger], + 'sale_price' => ['required', 'numeric', new UnsignedInteger], + 'description' => ['nullable', 'string'], + 'image' => ['nullable', 'array'], + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'size' => 'ukuran', + 'cost_price' => 'harga beli', + 'sale_price' => 'harga jual', + 'description' => 'deskripsi', + 'image' => 'gambar', + ]; + } + + public function setBottle(Bottle $bottle) + { + $this->bottle = $bottle; + + $this->name = $bottle->name; + $this->size = $bottle->size; + $this->cost_price = $bottle->cost_price; + $this->sale_price = $bottle->sale_price; + $this->description = $bottle->description; + + $this->image = $this->mapMediaCollection($bottle->getMedia('image')); + } + + public function store() + { + $this->validate(); + + DB::transaction(function () { + $bottle = Bottle::create([ + 'name' => $this->name, + 'size' => $this->size, + 'cost_price' => $this->cost_price, + 'sale_price' => $this->sale_price, + 'description' => $this->description, + ]); + + $this->uploadMedia($this->image, $bottle, 'image'); + }); + } + + public function update() + { + $this->validate(); + + DB::transaction(function () { + $this->bottle->update([ + 'name' => $this->name, + 'size' => $this->size, + 'cost_price' => $this->cost_price, + 'sale_price' => $this->sale_price, + 'description' => $this->description, + ]); + + $this->syncMedia($this->image, $this->bottle, 'image'); + $this->uploadMedia($this->image, $this->bottle, 'image'); + }); + } +} diff --git a/app/Livewire/Studio/Catalog/Bottle/Create.php b/app/Livewire/Studio/Catalog/Bottle/Create.php new file mode 100644 index 0000000..62e1803 --- /dev/null +++ b/app/Livewire/Studio/Catalog/Bottle/Create.php @@ -0,0 +1,49 @@ +outlets = Outlet::pluck('name', 'id')->toArray(); + } + + public function save() + { + $this->form->store(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Botol berhasil ditambahkan.', + variant: 'success', + duration: 3000 + ); + + $this->redirectRoute('studio.catalog.bottle.index', navigate: true); + } + + public function render() + { + return view('livewire.studio.catalog.bottle.form', [ + 'pageTitle' => 'Tambah Botol', + ]); + } +} diff --git a/app/Livewire/Studio/Catalog/Bottle/Edit.php b/app/Livewire/Studio/Catalog/Bottle/Edit.php new file mode 100644 index 0000000..2e34aaa --- /dev/null +++ b/app/Livewire/Studio/Catalog/Bottle/Edit.php @@ -0,0 +1,52 @@ +form->setBottle($bottle); + + $this->outlets = Outlet::pluck('name', 'id')->toArray(); + } + + public function save() + { + $this->form->update(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Botol berhasil diperbarui.', + variant: 'success', + duration: 3000 + ); + + $this->redirectRoute('studio.catalog.bottle.index', navigate: true); + } + + public function render() + { + return view('livewire.studio.catalog.bottle.form', [ + 'pageTitle' => 'Ubah Botol', + ]); + } +} diff --git a/app/Livewire/Studio/Catalog/Bottle/Index.php b/app/Livewire/Studio/Catalog/Bottle/Index.php new file mode 100644 index 0000000..ec466ea --- /dev/null +++ b/app/Livewire/Studio/Catalog/Bottle/Index.php @@ -0,0 +1,37 @@ +delete(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Botol berhasil dihapus.', + variant: 'success', + ); + + Flux::modals()->close(); + } + + public function render() + { + return view('livewire.studio.catalog.bottle.index', [ + 'pageTitle' => 'Botol', + ]); + } +} diff --git a/app/Models/Bottle.php b/app/Models/Bottle.php new file mode 100644 index 0000000..bcea0e4 --- /dev/null +++ b/app/Models/Bottle.php @@ -0,0 +1,39 @@ + 'int', + 'sale_price' => 'int', + ]; + } + + public function getSlugOptions(): SlugOptions + { + return SlugOptions::create() + ->generateSlugsFrom('name') + ->saveSlugsTo('slug'); + } + + public function outlets(): BelongsToMany + { + return $this->belongsToMany(Outlet::class); + } +} diff --git a/app/Models/BottleOutlet.php b/app/Models/BottleOutlet.php new file mode 100644 index 0000000..2ab838a --- /dev/null +++ b/app/Models/BottleOutlet.php @@ -0,0 +1,12 @@ +belongsToMany(Product::class); } + + public function bottles(): BelongsToMany + { + return $this->belongsToMany(Bottle::class); + } } diff --git a/database/migrations/2025_10_02_020223_create_bottles_table.php b/database/migrations/2025_10_02_020223_create_bottles_table.php new file mode 100644 index 0000000..f5cdf1d --- /dev/null +++ b/database/migrations/2025_10_02_020223_create_bottles_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 50); + $table->string('slug', 70)->unique(); + $table->unsignedInteger('size'); + $table->unsignedInteger('cost_price')->default(0); + $table->unsignedInteger('sale_price')->default(0); + $table->text('description')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bottles'); + } +}; diff --git a/database/migrations/2025_10_02_020233_create_bottle_outlet_table.php b/database/migrations/2025_10_02_020233_create_bottle_outlet_table.php new file mode 100644 index 0000000..c11d17c --- /dev/null +++ b/database/migrations/2025_10_02_020233_create_bottle_outlet_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('bottle_id')->constrained()->cascadeOnDelete(); + $table->foreignId('outlet_id')->constrained()->cascadeOnDelete(); + $table->unsignedInteger('stock')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bottle_outlet'); + } +}; diff --git a/resources/views/components/partials/studio/_sidebar.blade.php b/resources/views/components/partials/studio/_sidebar.blade.php index 1a637c1..c23af4d 100644 --- a/resources/views/components/partials/studio/_sidebar.blade.php +++ b/resources/views/components/partials/studio/_sidebar.blade.php @@ -56,6 +56,10 @@ class="bg-zinc-50 dark:bg-zinc-900 border-r rtl:border-r-0 rtl:border-l border-z Produk + + Botol + diff --git a/resources/views/livewire/studio/catalog/bottle/form.blade.php b/resources/views/livewire/studio/catalog/bottle/form.blade.php new file mode 100644 index 0000000..10ff950 --- /dev/null +++ b/resources/views/livewire/studio/catalog/bottle/form.blade.php @@ -0,0 +1,107 @@ + +
+
+ {{ $pageTitle }} +
+
+ + Kembali + +
+
+ +
+
+
+
+
+
+ +
+ + + + + + Harga Beli + + Rp + + + + + + + Harga Jual + + Rp + + + + + +
+ +
+
+
+
+
+
+ +
+ + + Pilih Semua + + Hapus Semua + + @foreach ($outlets as $key => $value) + {{ $value }} + + @endforeach + + + + +
+

Gambar

+
+ + @error('form.image') + + @enderror +
+
+
+
+
+ +
+ + Simpan + +
+
+
+
diff --git a/resources/views/livewire/studio/catalog/bottle/index.blade.php b/resources/views/livewire/studio/catalog/bottle/index.blade.php new file mode 100644 index 0000000..3c23062 --- /dev/null +++ b/resources/views/livewire/studio/catalog/bottle/index.blade.php @@ -0,0 +1,19 @@ + +
+
+ {{ $pageTitle }} +
+
+ + Tambah + +
+
+ +
+ +
+ + @include('components.confirmation.delete') +
diff --git a/routes/web.php b/routes/web.php index 0ef5948..dfb3c25 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,9 @@ use App\Livewire\Auth\Login; use App\Livewire\Auth\Logout; use App\Livewire\Auth\Register; +use App\Livewire\Studio\Catalog\Bottle\Create as BottleCreate; +use App\Livewire\Studio\Catalog\Bottle\Edit as BottleEdit; +use App\Livewire\Studio\Catalog\Bottle\Index as BottleIndex; use App\Livewire\Studio\Catalog\Brand as BrandComponent; use App\Livewire\Studio\Catalog\Category as CategoryComponent; use App\Livewire\Studio\Catalog\Perfume\Create as PerfumeCreate; @@ -139,4 +142,13 @@ Route::get('products/{product}/edit', ProductEdit::class)->name('edit'); Route::get('products/{product}/delete', ProductCreate::class)->name('delete'); }); + + Route::prefix('catalog') + ->as('studio.catalog.bottle.') + ->group(function () { + Route::get('bottles', BottleIndex::class)->name('index'); + Route::get('bottles/create', BottleCreate::class)->name('create'); + Route::get('bottles/{bottle}/edit', BottleEdit::class)->name('edit'); + Route::get('bottles/{bottle}/delete', BottleCreate::class)->name('delete'); + }); });