From 08005034531d313b37915990964bdf7561a8051d Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sat, 20 Dec 2025 09:44:47 +0700 Subject: [PATCH] feat: Implement dedicated perfume product pages and add a new reward tier table for loyalty features. --- .../Datatable/Loyalty/RewardsTable.php | 12 +++++++- .../Forms/Studio/Loyalty/RewardForm.php | 11 +++++++ app/Livewire/Member/RedeemPoint.php | 13 +++++++++ app/Livewire/Studio/Loyalty/Reward.php | 8 +++++ app/Models/Reward.php | 6 ++++ app/Models/Tier.php | 5 ++++ ..._12_20_092311_create_reward_tier_table.php | 29 +++++++++++++++++++ .../livewire/studio/loyalty/rewards.blade.php | 10 +++++++ 8 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2025_12_20_092311_create_reward_tier_table.php diff --git a/app/Livewire/Datatable/Loyalty/RewardsTable.php b/app/Livewire/Datatable/Loyalty/RewardsTable.php index 5aa5b09..a09ff22 100644 --- a/app/Livewire/Datatable/Loyalty/RewardsTable.php +++ b/app/Livewire/Datatable/Loyalty/RewardsTable.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Blade; use Rappasoft\LaravelLivewireTables\DataTableComponent; use Rappasoft\LaravelLivewireTables\Views\Column; +use Rappasoft\LaravelLivewireTables\Views\Columns\ArrayColumn; class RewardsTable extends DataTableComponent { @@ -42,6 +43,13 @@ public function columns(): array ->label(fn ($row, $column) => Blade::render(''.$row->is_show->label().'')) ->html(), + ArrayColumn::make('Tier') + ->data( + fn ($value, $row) => $row->tiers->pluck('name')->toArray() ?: ['Semua Tier'] + ) + ->outputFormat(fn ($index, $value) => Blade::render(''.$value.'')) + ->flexRow(['class' => 'gap-2 flex-wrap']), + Column::make('Tanggal') ->label(function ($row) { $startDate = formatDateLocalized($row->start_date); @@ -94,6 +102,8 @@ public function columns(): array public function builder(): Builder { - return Reward::select('id', 'name', 'points', 'stock', 'category', 'is_show', 'start_date', 'end_date')->withCount('redemptions'); + return Reward::select('id', 'name', 'points', 'stock', 'category', 'is_show', 'start_date', 'end_date') + ->with('tiers:id,name') + ->withCount('redemptions'); } } diff --git a/app/Livewire/Forms/Studio/Loyalty/RewardForm.php b/app/Livewire/Forms/Studio/Loyalty/RewardForm.php index 4e94c24..9a1ca28 100644 --- a/app/Livewire/Forms/Studio/Loyalty/RewardForm.php +++ b/app/Livewire/Forms/Studio/Loyalty/RewardForm.php @@ -31,6 +31,8 @@ class RewardForm extends Form public ?string $end_date = null; + public array $tier_ids = []; + public array $thumbnail = []; public function rules(): array @@ -43,6 +45,8 @@ public function rules(): array 'is_show' => ['required', Rule::in(IsShow::cases())], 'start_date' => ['required', 'date'], 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'], + 'tier_ids' => ['required', 'array', 'min:1'], + 'tier_ids.*' => Rule::exists('tiers', 'id'), 'thumbnail' => 'array', ]; } @@ -57,6 +61,8 @@ public function validationAttributes(): array 'is_show' => 'visibilitas', 'start_date' => 'tanggal mulai', 'end_date' => 'tanggal berakhir', + 'tier_ids' => 'tier', + 'tier_ids.*' => 'tier', ]; } @@ -71,6 +77,7 @@ public function setReward(Reward $reward): void $this->is_show = $reward->is_show->value; $this->start_date = $reward->start_date; $this->end_date = $reward->end_date; + $this->tier_ids = $reward->tiers->pluck('id')->toArray(); $this->thumbnail = $this->mapMediaCollection($reward->getMedia('thumbnail')); } @@ -89,6 +96,8 @@ public function store(): void 'end_date' => $this->end_date, ]); + $reward->tiers()->sync($this->tier_ids); + $this->uploadMedia($this->thumbnail, $reward, 'thumbnail'); }); } @@ -108,6 +117,8 @@ public function update(): void 'end_date' => $this->end_date, ]); + $this->reward->tiers()->sync($this->tier_ids); + $this->syncMedia($this->thumbnail, $this->reward, 'thumbnail'); $this->uploadMedia($this->thumbnail, $this->reward, 'thumbnail'); }); diff --git a/app/Livewire/Member/RedeemPoint.php b/app/Livewire/Member/RedeemPoint.php index 581dfcb..f7d10be 100644 --- a/app/Livewire/Member/RedeemPoint.php +++ b/app/Livewire/Member/RedeemPoint.php @@ -28,8 +28,13 @@ class RedeemPoint extends Component public function mount(): void { + $userTierId = auth()->user()->membership->tier_id; + $this->rewards = Reward::show() ->active() + ->whereHas('tiers', function ($query) use ($userTierId) { + $query->where('id', $userTierId); + }) ->orderBy('points') ->get() ->map(function (Reward $reward) { @@ -58,6 +63,14 @@ public function redeem(Reward $reward): void $user = auth()->user(); $membership = $user->membership; + // Check if reward is available for user's tier + if (! $reward->tiers()->where('tiers.id', $membership->tier_id)->exists()) { + $this->toast('Hadiah ini tidak tersedia untuk tier Anda.', 'Gagal', 'danger'); + Flux::modals()->close(); + + return; + } + if ($membership->reward_points < $reward->points) { $this->toast('Poin Anda tidak cukup untuk menukarkan hadiah ini.', 'Gagal', 'danger'); diff --git a/app/Livewire/Studio/Loyalty/Reward.php b/app/Livewire/Studio/Loyalty/Reward.php index a9503fb..0faef2d 100644 --- a/app/Livewire/Studio/Loyalty/Reward.php +++ b/app/Livewire/Studio/Loyalty/Reward.php @@ -4,6 +4,7 @@ use App\Livewire\Forms\Studio\Loyalty\RewardForm; use App\Models\Reward as RewardModel; +use App\Models\Tier; use App\Traits\Authorization\WithAuthorization; use App\Traits\Components\WithCloseModal; use App\Traits\Components\WithConfirmation; @@ -27,6 +28,13 @@ class Reward extends Component public string $modalTitle = ''; + public $tiers = []; + + public function mount(): void + { + $this->tiers = Tier::orderBy('min_spending')->pluck('name', 'id')->toArray(); + } + public function create(): void { $this->canOrAbort('create reward'); diff --git a/app/Models/Reward.php b/app/Models/Reward.php index 4d8bda7..12dc98e 100644 --- a/app/Models/Reward.php +++ b/app/Models/Reward.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Spatie\MediaLibrary\HasMedia; @@ -52,6 +53,11 @@ protected function active(Builder $query): void }); } + public function tiers(): BelongsToMany + { + return $this->belongsToMany(Tier::class); + } + public function redemptions(): HasMany { return $this->hasMany(Redemption::class); diff --git a/app/Models/Tier.php b/app/Models/Tier.php index 9635490..f53ab53 100644 --- a/app/Models/Tier.php +++ b/app/Models/Tier.php @@ -49,6 +49,11 @@ public function vouchers(): BelongsToMany return $this->belongsToMany(Voucher::class); } + public function rewards(): BelongsToMany + { + return $this->belongsToMany(Reward::class); + } + public static function getHighestTier() { return self::orderByDesc('min_spending')->first(); diff --git a/database/migrations/2025_12_20_092311_create_reward_tier_table.php b/database/migrations/2025_12_20_092311_create_reward_tier_table.php new file mode 100644 index 0000000..da54f5c --- /dev/null +++ b/database/migrations/2025_12_20_092311_create_reward_tier_table.php @@ -0,0 +1,29 @@ +foreignId('reward_id')->constrained()->cascadeOnDelete(); + $table->foreignId('tier_id')->constrained()->cascadeOnDelete(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('reward_tier'); + } +}; diff --git a/resources/views/livewire/studio/loyalty/rewards.blade.php b/resources/views/livewire/studio/loyalty/rewards.blade.php index 9edc4f2..efa4de3 100644 --- a/resources/views/livewire/studio/loyalty/rewards.blade.php +++ b/resources/views/livewire/studio/loyalty/rewards.blade.php @@ -88,6 +88,16 @@ placeholder="Pilih Tanggal" autocomplete="off" locale="id-ID" /> + + Tier * + + @foreach ($tiers as $key => $value) + {{ $value }} + @endforeach + + + +

Thumbnail