diff --git a/app/Enums/VoucherType.php b/app/Enums/VoucherType.php new file mode 100644 index 0000000..8fffc41 --- /dev/null +++ b/app/Enums/VoucherType.php @@ -0,0 +1,38 @@ + 'Nominal', + self::PERCENTAGE => 'Persentase', + }; + } + + public function color() + { + return match ($this) { + self::FIXED => 'blue', + self::PERCENTAGE => 'pink', + }; + } + + public function icon() + { + return match ($this) { + self::FIXED => 'currency-dollar', + self::PERCENTAGE => 'percent-badge', + }; + } +} diff --git a/app/Livewire/Datatable/MembershipsTable.php b/app/Livewire/Datatable/MembershipsTable.php index b1d7b26..31e9fa6 100644 --- a/app/Livewire/Datatable/MembershipsTable.php +++ b/app/Livewire/Datatable/MembershipsTable.php @@ -21,7 +21,7 @@ public function columns(): array return [ Column::make('Nama', 'name')->searchable(), - Column::make('Min. Pembelian', 'min_purchase') + Column::make('Min. Belanja', 'min_purchase') ->format(fn ($value) => currency($value, 'Rp')) ->searchable() ->sortable(), diff --git a/app/Livewire/Datatable/VouchersTable.php b/app/Livewire/Datatable/VouchersTable.php new file mode 100644 index 0000000..643d132 --- /dev/null +++ b/app/Livewire/Datatable/VouchersTable.php @@ -0,0 +1,110 @@ +label(function ($row) { + return << +
{$row->name}
+ {$row->code} + + HTML; + }) + ->searchable(function ($query, $searchTerm) { + $query->where('name', 'like', "%{$searchTerm}%") + ->orWhere('code', 'like', "%{$searchTerm}%"); + }) + ->html(), + + Column::make('Min. Belanja', 'min_purchase') + ->format(fn ($value) => currency($value, 'Rp')) + ->searchable() + ->sortable(), + + Column::make('Jumlah Diskon', 'discount_amount') + ->label(fn ($row, $column) => formatDiscount($row->discount_amount, $row->type)) + ->searchable() + ->sortable(), + + Column::make('Maks. Diskon') + ->label(fn ($row, $column) => currency($row->max_discount, 'Rp')) + ->searchable() + ->sortable(), + + Column::make('Kuota', 'quota')->searchable()->sortable(), + + Column::make('Batas Per Pembali', 'limit_per_user')->searchable()->sortable(), + + Column::make('Tanggal') + ->label(function ($row) { + $startDate = formatDate($row->start_date); + $startAgo = timeAgo($row->start_date); + + $endDate = formatDate($row->end_date); + $endAgo = timeAgo($row->end_date); + + return << +
+ Tgl Dimulai: + {$startDate} + ({$startAgo}) +
+
+ Tgl Berakhir: + {$endDate} + ({$endAgo}) +
+ + HTML; + }) + ->html(), + + Column::make('Akitf?', 'is_active') + ->format(fn ($value) => Blade::render(''.$value->label().'')) + ->html(), + + Column::make('Aksi') + ->label(function ($row) { + $actions = ''; + + $actions .= view('components.datatables.edit', [ + 'id' => $row->id, + 'editRoute' => route('studio.loyalty.voucher.edit', $row->id), + ])->render(); + + $actions .= view('components.datatables.delete', [ + 'id' => $row->id, + 'deleteRoute' => route('studio.loyalty.voucher.delete', $row->id), + ])->render(); + + return $actions; + }) + ->html(), + ]; + } + + public function builder(): Builder + { + return Voucher::select('id', 'name', 'code', 'min_purchase', 'discount_amount', 'max_discount', 'quota', 'limit_per_user', 'start_date', 'end_date', 'is_active', 'type'); + } +} diff --git a/app/Livewire/Forms/MembershipForm.php b/app/Livewire/Forms/MembershipForm.php index 7957642..db3c16c 100644 --- a/app/Livewire/Forms/MembershipForm.php +++ b/app/Livewire/Forms/MembershipForm.php @@ -31,7 +31,7 @@ public function validationAttributes(): array { return [ 'name' => 'nama', - 'min_purchase' => 'min. pembelian', + 'min_purchase' => 'min. belanja', 'discount_percentage' => 'persentase diskon', 'terms_conditions' => 'syarat dan ketentuan', ]; diff --git a/app/Livewire/Forms/VoucherForm.php b/app/Livewire/Forms/VoucherForm.php new file mode 100644 index 0000000..9e3c989 --- /dev/null +++ b/app/Livewire/Forms/VoucherForm.php @@ -0,0 +1,147 @@ + ['required', 'string', 'max:50'], + 'code' => [ + 'required', + 'string', + 'max:20', + 'alpha_num', + Rule::unique('vouchers', 'code')->ignore($this->voucher), + ], + 'type' => ['required', Rule::in(VoucherType::values())], + 'discount_amount' => [ + 'required', + Rule::when( + fn () => $this->type == VoucherType::PERCENTAGE->value, + ['integer', 'between:0,100'], + ['numeric'], + ), + ], + 'min_purchase' => ['nullable', 'numeric'], + 'max_discount' => [ + Rule::when( + fn () => $this->type == VoucherType::PERCENTAGE->value, + ['required', 'numeric', 'between:0,100'], + ['nullable'], + ), + ], + 'quota' => ['nullable', 'numeric'], + 'limit_per_user' => ['nullable', 'numeric'], + 'start_date' => ['required', 'date'], + 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'], + 'terms_conditions' => ['nullable', 'string'], + 'is_active' => ['required', Rule::in(IsActive::values())], + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'code' => 'kode', + 'min_purchase' => 'min. belanja', + 'max_discount' => 'maks. diskon', + 'quota' => 'kuota', + 'limit_per_user' => 'limit per user', + 'terms_conditions' => 'syarat dan ketentuan', + 'type' => 'Tipe', + 'discount_amount' => 'Jumlah Diskon', + 'start_date' => 'tanggal mulai', + 'end_date' => 'tanggal selesai', + 'is_active' => 'aktif?', + ]; + } + + public function setVoucher(Voucher $voucher) + { + $this->voucher = $voucher; + + $this->name = $this->voucher->name; + $this->code = $this->voucher->code; + $this->type = $this->voucher->type->value; + $this->discount_amount = replaceCurrency($this->voucher->discount_amount); + $this->min_purchase = replaceCurrency($this->voucher->min_purchase); + $this->max_discount = replaceCurrency($this->voucher->max_discount); + $this->quota = $this->voucher->quota; + $this->limit_per_user = $this->voucher->limit_per_user; + $this->terms_conditions = $this->voucher->terms_conditions; + $this->start_date = $this->voucher->start_date; + $this->end_date = $this->voucher->end_date; + $this->is_active = $this->voucher->is_active->value; + } + + public function store() + { + $this->validate(); + + $data = $this->prepareSavedData(); + + Voucher::create($data); + } + + public function update() + { + $this->validate(); + + $data = $this->prepareSavedData(); + + $this->voucher->update($data); + } + + private function prepareSavedData() + { + return [ + 'name' => $this->name, + 'code' => $this->code, + 'type' => $this->type, + 'discount_amount' => replaceCurrency($this->discount_amount), + 'min_purchase' => replaceCurrency($this->min_purchase), + 'max_discount' => $this->type == VoucherType::PERCENTAGE->value ? replaceCurrency($this->max_discount) : null, + 'quota' => $this->quota, + 'limit_per_user' => $this->limit_per_user, + 'terms_conditions' => $this->terms_conditions, + 'start_date' => $this->start_date, + 'end_date' => $this->end_date, + 'is_active' => $this->is_active, + ]; + } +} diff --git a/app/Livewire/Studio/Loyalty/Voucher/Create.php b/app/Livewire/Studio/Loyalty/Voucher/Create.php new file mode 100644 index 0000000..1693f45 --- /dev/null +++ b/app/Livewire/Studio/Loyalty/Voucher/Create.php @@ -0,0 +1,40 @@ +form->store(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Voucher berhasil ditambahkan.', + variant: 'success', + duration: 3000 + ); + + $this->redirectRoute('studio.loyalty.voucher.index', navigate: true); + } + + public function render() + { + return view('livewire.studio.loyalty.voucher.form', [ + 'pageTitle' => 'Tambah Voucher', + ]); + } +} diff --git a/app/Livewire/Studio/Loyalty/Voucher/Edit.php b/app/Livewire/Studio/Loyalty/Voucher/Edit.php new file mode 100644 index 0000000..f47c82e --- /dev/null +++ b/app/Livewire/Studio/Loyalty/Voucher/Edit.php @@ -0,0 +1,46 @@ +form->setVoucher($voucher); + } + + public function save() + { + $this->form->update(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Voucher berhasil diperbarui.', + variant: 'success', + duration: 3000 + ); + + $this->redirectRoute('studio.loyalty.voucher.index', navigate: true); + } + + public function render() + { + return view('livewire.studio.loyalty.voucher.form', [ + 'pageTitle' => 'Ubah Voucher', + ]); + } +} diff --git a/app/Livewire/Studio/Loyalty/Voucher/Index.php b/app/Livewire/Studio/Loyalty/Voucher/Index.php new file mode 100644 index 0000000..a16d084 --- /dev/null +++ b/app/Livewire/Studio/Loyalty/Voucher/Index.php @@ -0,0 +1,37 @@ +delete(); + + $this->dispatch('refreshDatatable'); + + Flux::toast( + heading: 'Berhasil', + text: 'Voucher berhasil dihapus.', + variant: 'success', + ); + + Flux::modals()->close(); + } + + public function render() + { + return view('livewire.studio.loyalty.voucher.index', [ + 'pageTitle' => 'Voucher', + ]); + } +} diff --git a/app/Models/Voucher.php b/app/Models/Voucher.php new file mode 100644 index 0000000..b3a1573 --- /dev/null +++ b/app/Models/Voucher.php @@ -0,0 +1,29 @@ + VoucherType::class, + 'discount_amount' => 'int', + 'min_purchase' => 'int', + 'max_discount' => 'int', + 'quota' => 'int', + 'limit_per_user' => 'int', + 'is_active' => IsActive::class, + ]; + } +} diff --git a/database/migrations/2025_09_25_125559_create_vouchers_table.php b/database/migrations/2025_09_25_125559_create_vouchers_table.php new file mode 100644 index 0000000..29cc6cc --- /dev/null +++ b/database/migrations/2025_09_25_125559_create_vouchers_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('name', 50); + $table->string('code', 20)->unique(); + $table->text('terms_conditions')->nullable(); + $table->enum('type', [VoucherType::values()])->default(VoucherType::PERCENTAGE)->comment(VoucherType::comment()); + $table->unsignedInteger('discount_amount'); + $table->unsignedInteger('min_purchase')->nullable(); + $table->unsignedInteger('max_discount')->nullable(); + $table->unsignedInteger('quota')->nullable(); + $table->unsignedInteger('limit_per_user')->nullable(); + $table->date('start_date'); + $table->date('end_date')->nullable(); + $table->enum('is_active', [IsActive::values()])->default(IsActive::ACTIVE)->comment(IsActive::comment()); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('vouchers'); + } +}; diff --git a/resources/views/components/partials/studio/_sidebar.blade.php b/resources/views/components/partials/studio/_sidebar.blade.php index c8b89be..203c904 100644 --- a/resources/views/components/partials/studio/_sidebar.blade.php +++ b/resources/views/components/partials/studio/_sidebar.blade.php @@ -11,8 +11,7 @@ class="px-2 hidden dark:flex" /> Ringkasan
-
Master -
+
Master
Outlet @@ -25,12 +24,15 @@ class="px-2 hidden dark:flex" />
-
Loyalty -
+
Loyalty
Membership + + Voucher +
diff --git a/resources/views/livewire/studio/loyalty/voucher/form.blade.php b/resources/views/livewire/studio/loyalty/voucher/form.blade.php new file mode 100644 index 0000000..03a59f3 --- /dev/null +++ b/resources/views/livewire/studio/loyalty/voucher/form.blade.php @@ -0,0 +1,138 @@ + +
+
+ {{ $pageTitle }} +
+
+ + Kembali + +
+
+ +
+
+
+
+
+
+ +
+ + + + +
+
+ + Min. Belanja + + Rp + + + + + + + + +
+
+ + + + + +
+ +
+
+
+
+
+
+ +
+ + + @foreach (\App\Enums\VoucherType::cases() as $item) + + {{ $item->label() }} + + @endforeach + + + + + @if ($form->type == \App\Enums\VoucherType::FIXED->value) + Rp + + + @else + + + % + @endif + + + + + + @if ($form->type == \App\Enums\VoucherType::PERCENTAGE->value) + + Maks. Diskon + + Rp + + + + + + + @endif + + + + + @foreach (\App\Enums\IsActive::cases() as $item) + + {{ $item->label() }} + + @endforeach + + +
+
+ +
+ + Simpan + +
+
+
+
diff --git a/resources/views/livewire/studio/loyalty/voucher/index.blade.php b/resources/views/livewire/studio/loyalty/voucher/index.blade.php new file mode 100644 index 0000000..340055a --- /dev/null +++ b/resources/views/livewire/studio/loyalty/voucher/index.blade.php @@ -0,0 +1,19 @@ + +
+
+ {{ $pageTitle }} +
+
+ + Tambah + +
+
+ +
+ +
+ + @include('components.confirmation.delete') +
diff --git a/routes/web.php b/routes/web.php index 0dd863f..66fe4a2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,6 +4,9 @@ use App\Livewire\Auth\Logout; use App\Livewire\Studio\Dashboard\Overview; use App\Livewire\Studio\Loyalty\Membership as MembershipComponent; +use App\Livewire\Studio\Loyalty\Voucher\Create as VoucherCreate; +use App\Livewire\Studio\Loyalty\Voucher\Edit as VoucherEdit; +use App\Livewire\Studio\Loyalty\Voucher\Index as VoucherIndex; use App\Livewire\Studio\Master\Outlet\Create as OutletCreate; use App\Livewire\Studio\Master\Outlet\Edit as OutletEdit; use App\Livewire\Studio\Master\Outlet\Index as OutletIndex; @@ -56,4 +59,13 @@ Route::get('memberships', MembershipComponent::class)->name('index'); Route::delete('memberships/{membership}/delete', MembershipComponent::class)->name('delete'); }); + + Route::prefix('loyalty') + ->as('studio.loyalty.voucher.') + ->group(function () { + Route::get('vouchers', VoucherIndex::class)->name('index'); + Route::get('vouchers/create', VoucherCreate::class)->name('create'); + Route::get('vouchers/{voucher}/edit', VoucherEdit::class)->name('edit'); + Route::get('vouchers/{voucher}/delete', VoucherCreate::class)->name('delete'); + }); });