From cde9091f542f32850cdc38ab2f5077da32aab007 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 30 Sep 2025 22:29:26 +0700 Subject: [PATCH] refactor(membership): mengubah label membership menjadi tier dan menyesuaikan skema db nya --- .../{MembershipsTable.php => TiersTable.php} | 22 ++---- app/Livewire/Forms/MembershipForm.php | 73 ------------------- app/Livewire/Forms/TierForm.php | 58 +++++++++++++++ .../Loyalty/{Membership.php => Tier.php} | 26 +++---- app/Models/{Membership.php => Tier.php} | 6 +- database/factories/MembershipFactory.php | 18 ----- database/factories/TierFactory.php | 17 +++++ ... 2025_09_24_141358_create_tiers_table.php} | 9 +-- database/seeders/DatabaseSeeder.php | 2 +- database/seeders/MembershipSeeder.php | 39 ---------- database/seeders/TierSeeder.php | 39 ++++++++++ .../partials/studio/_sidebar.blade.php | 4 +- .../studio/loyalty/memberships.blade.php | 64 ---------------- .../livewire/studio/loyalty/tiers.blade.php | 42 +++++++++++ routes/web.php | 8 +- .../Studio/Loyalty/MembershipTest.php | 71 ------------------ .../Livewire/Studio/Loyalty/TierTest.php | 69 ++++++++++++++++++ 17 files changed, 260 insertions(+), 307 deletions(-) rename app/Livewire/Datatable/{MembershipsTable.php => TiersTable.php} (60%) delete mode 100644 app/Livewire/Forms/MembershipForm.php create mode 100644 app/Livewire/Forms/TierForm.php rename app/Livewire/Studio/Loyalty/{Membership.php => Tier.php} (71%) rename app/Models/{Membership.php => Tier.php} (73%) delete mode 100644 database/factories/MembershipFactory.php create mode 100644 database/factories/TierFactory.php rename database/migrations/{2025_09_24_141358_create_memberships_table.php => 2025_09_24_141358_create_tiers_table.php} (63%) delete mode 100644 database/seeders/MembershipSeeder.php create mode 100644 database/seeders/TierSeeder.php delete mode 100644 resources/views/livewire/studio/loyalty/memberships.blade.php create mode 100644 resources/views/livewire/studio/loyalty/tiers.blade.php delete mode 100644 tests/Feature/Livewire/Studio/Loyalty/MembershipTest.php create mode 100644 tests/Feature/Livewire/Studio/Loyalty/TierTest.php diff --git a/app/Livewire/Datatable/MembershipsTable.php b/app/Livewire/Datatable/TiersTable.php similarity index 60% rename from app/Livewire/Datatable/MembershipsTable.php rename to app/Livewire/Datatable/TiersTable.php index 31e9fa6..6c3db99 100644 --- a/app/Livewire/Datatable/MembershipsTable.php +++ b/app/Livewire/Datatable/TiersTable.php @@ -2,7 +2,7 @@ namespace App\Livewire\Datatable; -use App\Models\Membership; +use App\Models\Tier; use App\Traits\Datatable\WithAppendColumn; use App\Traits\Datatable\WithConfiguration; use App\Traits\Datatable\WithPrependColumn; @@ -10,26 +10,20 @@ use Rappasoft\LaravelLivewireTables\DataTableComponent; use Rappasoft\LaravelLivewireTables\Views\Column; -class MembershipsTable extends DataTableComponent +class TiersTable extends DataTableComponent { use WithAppendColumn, WithConfiguration, WithPrependColumn; - protected $model = Membership::class; + protected $model = Tier::class; public function columns(): array { return [ Column::make('Nama', 'name')->searchable(), - Column::make('Min. Belanja', 'min_purchase') - ->format(fn ($value) => currency($value, 'Rp')) - ->searchable() - ->sortable(), + Column::make('Min. Poin', 'min_points')->searchable()->sortable(), - Column::make('Diskon', 'discount_percentage') - ->format(fn ($value) => percentage($value)) - ->searchable() - ->sortable(), + Column::make('Maks. Poin', 'max_points')->searchable()->sortable(), Column::make('Aksi') ->label(function ($row) { @@ -38,12 +32,12 @@ public function columns(): array $actions .= view('components.datatables.edit-modal', [ 'id' => $row->id, 'method' => 'update', - 'modalTitle' => 'Ubah Membership', + 'modalTitle' => 'Ubah Tier', ])->render(); $actions .= view('components.datatables.delete', [ 'id' => $row->id, - 'deleteRoute' => route('studio.loyalty.membership.delete', $row->id), + 'deleteRoute' => route('studio.loyalty.tier.delete', $row->id), ])->render(); return $actions; @@ -54,6 +48,6 @@ public function columns(): array public function builder(): Builder { - return Membership::select('id', 'name', 'discount_percentage', 'min_purchase'); + return Tier::select('id', 'name', 'min_points', 'max_points'); } } diff --git a/app/Livewire/Forms/MembershipForm.php b/app/Livewire/Forms/MembershipForm.php deleted file mode 100644 index db3c16c..0000000 --- a/app/Livewire/Forms/MembershipForm.php +++ /dev/null @@ -1,73 +0,0 @@ - ['required', 'string', 'max:50'], - 'min_purchase' => ['required', 'regex:/^[0-9.]+$/', 'max:99999999999'], - 'discount_percentage' => ['required', 'numeric', 'between:0,100'], - 'terms_conditions' => ['nullable', 'string'], - ]; - } - - public function validationAttributes(): array - { - return [ - 'name' => 'nama', - 'min_purchase' => 'min. belanja', - 'discount_percentage' => 'persentase diskon', - 'terms_conditions' => 'syarat dan ketentuan', - ]; - } - - public function setMembership(Membership $membership) - { - $this->membership = $membership; - - $this->name = $membership->name; - $this->min_purchase = currency($membership->min_purchase); - $this->terms_conditions = $membership->terms_conditions; - $this->discount_percentage = $membership->discount_percentage; - } - - public function store() - { - $this->validate(); - - Membership::create([ - 'name' => $this->name, - 'min_purchase' => replaceCurrency($this->min_purchase), - 'discount_percentage' => $this->discount_percentage, - 'terms_conditions' => $this->terms_conditions, - ]); - } - - public function update() - { - $this->validate(); - - $this->membership->update([ - 'name' => $this->name, - 'min_purchase' => replaceCurrency($this->min_purchase), - 'discount_percentage' => $this->discount_percentage, - 'terms_conditions' => $this->terms_conditions, - ]); - } -} diff --git a/app/Livewire/Forms/TierForm.php b/app/Livewire/Forms/TierForm.php new file mode 100644 index 0000000..7cff2dd --- /dev/null +++ b/app/Livewire/Forms/TierForm.php @@ -0,0 +1,58 @@ + ['required', 'string', 'max:50'], + 'min_points' => ['required', 'numeric'], + 'max_points' => ['nullable', 'numeric'], + ]; + } + + public function validationAttributes(): array + { + return [ + 'name' => 'nama', + 'min_points' => 'min. poin', + 'max_points' => 'maks. poin', + ]; + } + + public function setTier(Tier $tier) + { + $this->tier = $tier; + + $this->name = $tier->name; + $this->min_points = $tier->min_points; + $this->max_points = $tier->max_points; + } + + public function store() + { + $this->validate(); + + Tier::create($this->pull()); + } + + public function update() + { + $this->validate(); + + $this->tier->update($this->pull()); + } +} diff --git a/app/Livewire/Studio/Loyalty/Membership.php b/app/Livewire/Studio/Loyalty/Tier.php similarity index 71% rename from app/Livewire/Studio/Loyalty/Membership.php rename to app/Livewire/Studio/Loyalty/Tier.php index 4f9b55b..f69a837 100644 --- a/app/Livewire/Studio/Loyalty/Membership.php +++ b/app/Livewire/Studio/Loyalty/Tier.php @@ -2,8 +2,8 @@ namespace App\Livewire\Studio\Loyalty; -use App\Livewire\Forms\MembershipForm; -use App\Models\Membership as MembershipModel; +use App\Livewire\Forms\TierForm; +use App\Models\Tier as TierModel; use App\Traits\WithCloseModal; use App\Traits\WithConfirmation; use App\Traits\WithUpdatedData; @@ -12,12 +12,12 @@ use Livewire\Attributes\Title; use Livewire\Component; -#[Title('Membership')] -class Membership extends Component +#[Title('Tier')] +class Tier extends Component { use WithCloseModal, WithConfirmation, WithUpdatedData; - public MembershipForm $form; + public TierForm $form; public string $method = 'create'; @@ -33,7 +33,7 @@ public function openModal(string $method, string $modalTitle, ?string $id = null $this->modalTitle = $modalTitle; if ($id) { - $this->form->setMembership(MembershipModel::findOrFail($id)); + $this->form->setTier(TierModel::findOrFail($id)); } } @@ -45,7 +45,7 @@ public function create() Flux::toast( heading: 'Berhasil', - text: 'Membership berhasil ditambahkan.', + text: 'Tier berhasil ditambahkan.', variant: 'success', duration: 3000 ); @@ -61,7 +61,7 @@ public function update() Flux::toast( heading: 'Berhasil', - text: 'Membership berhasil diperbarui.', + text: 'Tier berhasil diperbarui.', variant: 'success', duration: 3000 ); @@ -69,15 +69,15 @@ public function update() Flux::modals()->close(); } - public function delete(MembershipModel $membership) + public function delete(TierModel $tier) { - $membership->delete(); + $tier->delete(); $this->dispatch('refreshDatatable'); Flux::toast( heading: 'Berhasil', - text: 'Membership berhasil dihapus.', + text: 'Tier berhasil dihapus.', variant: 'success', ); @@ -86,8 +86,8 @@ public function delete(MembershipModel $membership) public function render() { - return view('livewire.studio.loyalty.memberships', [ - 'pageTitle' => 'Membership', + return view('livewire.studio.loyalty.tiers', [ + 'pageTitle' => 'Tier', ]); } } diff --git a/app/Models/Membership.php b/app/Models/Tier.php similarity index 73% rename from app/Models/Membership.php rename to app/Models/Tier.php index f41246b..78ad9e1 100644 --- a/app/Models/Membership.php +++ b/app/Models/Tier.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; -class Membership extends Model +class Tier extends Model { use HasFactory, SoftDeletes; @@ -15,8 +15,8 @@ class Membership extends Model protected function casts(): array { return [ - 'min_purchase' => 'int', - 'discount_percent' => 'decimal:2', + 'min_points' => 'int', + 'max_points' => 'int', ]; } } diff --git a/database/factories/MembershipFactory.php b/database/factories/MembershipFactory.php deleted file mode 100644 index 63a0c86..0000000 --- a/database/factories/MembershipFactory.php +++ /dev/null @@ -1,18 +0,0 @@ - $this->faker->name(), - 'min_purchase' => $this->faker->randomNumber(2), - 'discount_percentage' => $this->faker->randomFloat(2, 0, 100), - 'terms_conditions' => $this->faker->optional()->text(), - ]; - } -} diff --git a/database/factories/TierFactory.php b/database/factories/TierFactory.php new file mode 100644 index 0000000..26725b2 --- /dev/null +++ b/database/factories/TierFactory.php @@ -0,0 +1,17 @@ + $this->faker->name(), + 'min_points' => $this->faker->randomNumber(), + 'max_points' => $this->faker->optional()->randomNumber(), + ]; + } +} diff --git a/database/migrations/2025_09_24_141358_create_memberships_table.php b/database/migrations/2025_09_24_141358_create_tiers_table.php similarity index 63% rename from database/migrations/2025_09_24_141358_create_memberships_table.php rename to database/migrations/2025_09_24_141358_create_tiers_table.php index 3c7b132..48c70a7 100644 --- a/database/migrations/2025_09_24_141358_create_memberships_table.php +++ b/database/migrations/2025_09_24_141358_create_tiers_table.php @@ -11,12 +11,11 @@ */ public function up(): void { - Schema::create('memberships', function (Blueprint $table) { + Schema::create('tiers', function (Blueprint $table) { $table->id(); $table->string('name', 50); - $table->unsignedInteger('min_purchase'); - $table->decimal('discount_percentage', 5, 2); - $table->text('terms_conditions')->nullable(); + $table->unsignedInteger('min_points'); + $table->unsignedInteger('max_points')->nullable(); $table->timestamps(); $table->softDeletes(); }); @@ -27,6 +26,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('memberships'); + Schema::dropIfExists('tiers'); } }; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 5bc56b3..739ab8b 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -11,7 +11,7 @@ public function run(): void $this->call([ OutletSeeder::class, UserSeeder::class, - MembershipSeeder::class, + TierSeeder::class, VoucherSeeder::class, CategorySeeder::class, BrandSeeder::class, diff --git a/database/seeders/MembershipSeeder.php b/database/seeders/MembershipSeeder.php deleted file mode 100644 index 6686d5b..0000000 --- a/database/seeders/MembershipSeeder.php +++ /dev/null @@ -1,39 +0,0 @@ -insert([ - [ - 'name' => 'Silver', - 'min_purchase' => 500000, - 'discount_percentage' => 5, - 'terms_conditions' => 'Minimal belanja Rp500.000 dalam 1 bulan.', - ], - [ - 'name' => 'Gold', - 'min_purchase' => 1000000, - 'discount_percentage' => 10, - 'terms_conditions' => 'Minimal belanja Rp1.000.000 dalam 1 bulan.', - ], - [ - 'name' => 'Platinum', - 'min_purchase' => 2500000, - 'discount_percentage' => 15, - 'terms_conditions' => 'Minimal belanja Rp2.500.000 dalam 1 bulan.', - ], - [ - 'name' => 'Diamond', - 'min_purchase' => 5000000, - 'discount_percentage' => 20, - 'terms_conditions' => 'Minimal belanja Rp5.000.000 dalam 1 bulan.', - ], - ]); - } -} diff --git a/database/seeders/TierSeeder.php b/database/seeders/TierSeeder.php new file mode 100644 index 0000000..e5cb6bf --- /dev/null +++ b/database/seeders/TierSeeder.php @@ -0,0 +1,39 @@ + 'Silver', + 'min_points' => 0, + 'max_points' => 99, + ], + [ + 'name' => 'Gold', + 'min_points' => 100, + 'max_points' => 499, + ], + [ + 'name' => 'Platinum', + 'min_points' => 500, + 'max_points' => 1999, + ], + [ + 'name' => 'Diamond', + 'min_points' => 2000, + 'max_points' => null, + ], + ]; + + foreach ($tiers as $tier) { + Tier::create($tier); + } + } +} diff --git a/resources/views/components/partials/studio/_sidebar.blade.php b/resources/views/components/partials/studio/_sidebar.blade.php index 17c0606..0073510 100644 --- a/resources/views/components/partials/studio/_sidebar.blade.php +++ b/resources/views/components/partials/studio/_sidebar.blade.php @@ -24,8 +24,8 @@ class="bg-zinc-50 dark:bg-zinc-900 border-r rtl:border-r-0 rtl:border-l border-z
Loyalty
- Membership + Tier -
-
- {{ $pageTitle }} -
-
- - Tambah - - -
-
- -
- -
- - @include('components.confirmation.delete') - -
- {{ $modalTitle }} - - - - - Min. Belanja - - Rp - - - - - - - - - Persentase Diskon - - - - % - - - - - - - -
- - - Simpan - -
-
-
- - diff --git a/resources/views/livewire/studio/loyalty/tiers.blade.php b/resources/views/livewire/studio/loyalty/tiers.blade.php new file mode 100644 index 0000000..b6142b4 --- /dev/null +++ b/resources/views/livewire/studio/loyalty/tiers.blade.php @@ -0,0 +1,42 @@ + +
+
+ {{ $pageTitle }} +
+
+ + Tambah + + +
+
+ +
+ +
+ + @include('components.confirmation.delete') + +
+ {{ $modalTitle }} + + + + + + + +
+ + + Simpan + +
+
+
+ +
diff --git a/routes/web.php b/routes/web.php index 88f59d4..74d4ec2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,7 +6,7 @@ use App\Livewire\Studio\Catalog\Category as CategoryComponent; use App\Livewire\Studio\Dashboard\Overview; use App\Livewire\Studio\Loyalty\Customer as CustomerComponent; -use App\Livewire\Studio\Loyalty\Membership as MembershipComponent; +use App\Livewire\Studio\Loyalty\Tier as TierComponent; 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; @@ -57,10 +57,10 @@ }); Route::prefix('loyalty') - ->as('studio.loyalty.membership.') + ->as('studio.loyalty.tier.') ->group(function () { - Route::get('memberships', MembershipComponent::class)->name('index'); - Route::delete('memberships/{membership}/delete', MembershipComponent::class)->name('delete'); + Route::get('tiers', TierComponent::class)->name('index'); + Route::delete('tiers/{tier}/delete', TierComponent::class)->name('delete'); }); Route::prefix('loyalty') diff --git a/tests/Feature/Livewire/Studio/Loyalty/MembershipTest.php b/tests/Feature/Livewire/Studio/Loyalty/MembershipTest.php deleted file mode 100644 index 1e0f6b6..0000000 --- a/tests/Feature/Livewire/Studio/Loyalty/MembershipTest.php +++ /dev/null @@ -1,71 +0,0 @@ -assertViewIs('livewire.studio.loyalty.memberships') - ->assertViewHas('pageTitle', 'Membership'); -}); - -it('displays all memberships', function () { - MembershipModel::factory()->count(10)->create(); - - $membership = MembershipModel::first(); - - Livewire::test(Membership::class)->assertSee($membership->name); -}); - -it('can create a membership', function () { - $data = MembershipModel::factory()->raw(); - - Livewire::test(Membership::class) - ->set('form.name', $data['name']) - ->set('form.min_purchase', $data['min_purchase']) - ->set('form.discount_percentage', $data['discount_percentage']) - ->set('form.terms_conditions', $data['terms_conditions']) - ->call('create') - ->assertHasNoErrors() - ->assertDispatched('refreshDatatable'); - - expect(MembershipModel::count())->toBe(1); - expect(MembershipModel::first()->name)->toBe($data['name']); -}); - -it('can update a membership', function () { - $membership = MembershipModel::factory()->create(); - - $data = MembershipModel::factory()->raw(); - - Livewire::test(Membership::class) - ->call('openModal', 'update', 'Edit Membership', $membership->id) - ->set('form.name', $data['name']) - ->set('form.min_purchase', $data['min_purchase']) - ->set('form.discount_percentage', $data['discount_percentage']) - ->set('form.terms_conditions', $data['terms_conditions']) - ->call('update') - ->assertHasNoErrors() - ->assertDispatched('refreshDatatable'); - - $membership->refresh(); - - expect($membership->name)->toBe($data['name']); - expect($membership->min_purchase)->toBe($data['min_purchase']); - expect($membership->discount_percentage)->toBe($data['discount_percentage']); -}); - -it('can delete an membership', function () { - $membership = MembershipModel::factory()->create(); - - Livewire::test(Membership::class) - ->call('delete', $membership) - ->assertHasNoErrors() - ->assertDispatched('refreshDatatable'); - - expect(MembershipModel::count())->toBe(0); -}); diff --git a/tests/Feature/Livewire/Studio/Loyalty/TierTest.php b/tests/Feature/Livewire/Studio/Loyalty/TierTest.php new file mode 100644 index 0000000..aebe9ae --- /dev/null +++ b/tests/Feature/Livewire/Studio/Loyalty/TierTest.php @@ -0,0 +1,69 @@ +assertViewIs('livewire.studio.loyalty.tiers') + ->assertViewHas('pageTitle', 'Tier'); +}); + +it('displays all tiers', function () { + TierModel::factory()->count(10)->create(); + + $tier = TierModel::first(); + + Livewire::test(Tier::class)->assertSee($tier->name); +}); + +it('can create a tier', function () { + $data = TierModel::factory()->raw(); + + Livewire::test(Tier::class) + ->set('form.name', $data['name']) + ->set('form.min_points', $data['min_points']) + ->set('form.max_points', $data['max_points']) + ->call('create') + ->assertHasNoErrors() + ->assertDispatched('refreshDatatable'); + + expect(TierModel::count())->toBe(1); + expect(TierModel::first()->name)->toBe($data['name']); +}); + +it('can update a tier', function () { + $tier = TierModel::factory()->create(); + + $data = TierModel::factory()->raw(); + + Livewire::test(Tier::class) + ->call('openModal', 'update', 'Edit Tier', $tier->id) + ->set('form.name', $data['name']) + ->set('form.min_points', $data['min_points']) + ->set('form.max_points', $data['max_points']) + ->call('update') + ->assertHasNoErrors() + ->assertDispatched('refreshDatatable'); + + $tier->refresh(); + + expect($tier->name)->toBe($data['name']); + expect($tier->min_points)->toBe($data['min_points']); + expect($tier->max_points)->toBe($data['max_points']); +}); + +it('can delete an tier', function () { + $tier = TierModel::factory()->create(); + + Livewire::test(Tier::class) + ->call('delete', $tier) + ->assertHasNoErrors() + ->assertDispatched('refreshDatatable'); + + expect(TierModel::count())->toBe(0); +});