From 4febd74674fa2bb86ffff65dce5339db60fc8828 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 19 Dec 2025 20:41:04 +0700 Subject: [PATCH] feat: Implement member point redemption system with studio verification and enhanced reward management. --- app/Enums/RedemptionStatus.php | 33 +++++ app/Enums/RewardCategory.php | 39 +++++ .../Datatable/Loyalty/RewardsTable.php | 38 ++++- .../Forms/Studio/Loyalty/RewardForm.php | 38 ++++- app/Livewire/Member/RedeemPoint.php | 134 +++++++++++++++++ .../Studio/Loyalty/RedemptionVerification.php | 76 ++++++++++ app/Livewire/Studio/Loyalty/Reward.php | 7 + app/Models/Redemption.php | 59 ++++++++ app/Models/Reward.php | 34 +++++ app/Models/User.php | 10 ++ app/Providers/ViewServiceProvider.php | 7 + ...2025_12_18_193031_create_rewards_table.php | 9 +- ..._12_18_202206_create_redemptions_table.php | 37 +++++ database/seeders/RolePermissionSeeder.php | 10 ++ .../sections/ui/member/_sidebar.blade.php | 3 + .../views/livewire/member/rewards.blade.php | 136 ++++++++++++++++++ .../views/livewire/member/voucher.blade.php | 3 +- .../studio/loyalty/redemption.blade.php | 114 +++++++++++++++ .../livewire/studio/loyalty/rewards.blade.php | 50 +++++-- routes/pages/member.php | 2 + routes/pages/studio.php | 3 + 21 files changed, 820 insertions(+), 22 deletions(-) create mode 100644 app/Enums/RedemptionStatus.php create mode 100644 app/Enums/RewardCategory.php create mode 100644 app/Livewire/Member/RedeemPoint.php create mode 100644 app/Livewire/Studio/Loyalty/RedemptionVerification.php create mode 100644 app/Models/Redemption.php create mode 100644 database/migrations/2025_12_18_202206_create_redemptions_table.php create mode 100644 resources/views/livewire/member/rewards.blade.php create mode 100644 resources/views/livewire/studio/loyalty/redemption.blade.php diff --git a/app/Enums/RedemptionStatus.php b/app/Enums/RedemptionStatus.php new file mode 100644 index 0000000..cd2f337 --- /dev/null +++ b/app/Enums/RedemptionStatus.php @@ -0,0 +1,33 @@ + 'Menunggu Pengambilan', + self::PICKED_UP => 'Sudah Diambil', + self::EXPIRED => 'Kedaluwarsa', + }; + } + + public function color(): string + { + return match ($this) { + self::WAITING_PICKUP => 'yellow', + self::PICKED_UP => 'emerald', + self::EXPIRED => 'red', + }; + } +} diff --git a/app/Enums/RewardCategory.php b/app/Enums/RewardCategory.php new file mode 100644 index 0000000..33e689c --- /dev/null +++ b/app/Enums/RewardCategory.php @@ -0,0 +1,39 @@ + 'Eksklusif', + self::REGULAR => 'Regular', + self::LIMITED_EDITION => 'Limited Edition', + self::SEASONAL => 'Seasonal', + self::SIGNATURE => 'Signature', + }; + } + + public function color(): string + { + return match ($this) { + self::EXCLUSIVE => 'amber', + self::REGULAR => 'slate', + self::LIMITED_EDITION => 'rose', + self::SEASONAL => 'indigo', + self::SIGNATURE => 'emerald', + }; + } +} diff --git a/app/Livewire/Datatable/Loyalty/RewardsTable.php b/app/Livewire/Datatable/Loyalty/RewardsTable.php index eac28d6..5aa5b09 100644 --- a/app/Livewire/Datatable/Loyalty/RewardsTable.php +++ b/app/Livewire/Datatable/Loyalty/RewardsTable.php @@ -7,6 +7,7 @@ use App\Traits\Datatable\WithConfiguration; use App\Traits\Datatable\WithPrependColumn; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\Blade; use Rappasoft\LaravelLivewireTables\DataTableComponent; use Rappasoft\LaravelLivewireTables\Views\Column; @@ -33,11 +34,44 @@ public function columns(): array ->searchable() ->sortable(), + Column::make('Kategori', 'category') + ->label(fn ($row, $column) => Blade::render(''.$row->category->label().'')) + ->html(), + + Column::make('Visibilitas', 'is_show') + ->label(fn ($row, $column) => Blade::render(''.$row->is_show->label().'')) + ->html(), + + Column::make('Tanggal') + ->label(function ($row) { + $startDate = formatDateLocalized($row->start_date); + $startAgo = formatRelativeTime($row->start_date); + + $endDate = formatDateLocalized($row->end_date); + $endAgo = formatRelativeTime($row->end_date); + + return << +
+ Tgl Dimulai: + {$startDate} + ({$startAgo}) +
+
+ Tgl Berakhir: + {$endDate} + ({$endAgo}) +
+ + HTML; + }) + ->html(), + Column::make('Aksi') ->label(function ($row) { $actions = ''; - if (auth()->user()->can('update reward')) { + if (auth()->user()->can('update reward') && $row->redemptions_count === 0) { $actions .= view('components.actions.table.edit-modal', [ 'id' => $row->hash, 'method' => 'update', @@ -60,6 +94,6 @@ public function columns(): array public function builder(): Builder { - return Reward::select('id', 'name', 'points', 'stock'); + return Reward::select('id', 'name', 'points', 'stock', 'category', 'is_show', 'start_date', 'end_date')->withCount('redemptions'); } } diff --git a/app/Livewire/Forms/Studio/Loyalty/RewardForm.php b/app/Livewire/Forms/Studio/Loyalty/RewardForm.php index 6fe759b..4e94c24 100644 --- a/app/Livewire/Forms/Studio/Loyalty/RewardForm.php +++ b/app/Livewire/Forms/Studio/Loyalty/RewardForm.php @@ -2,10 +2,13 @@ namespace App\Livewire\Forms\Studio\Loyalty; +use App\Enums\IsShow; +use App\Enums\RewardCategory; use App\Models\Reward; use App\Rules\UnsignedInteger; use App\Traits\Media\WithMediaHandler; use Illuminate\Support\Facades\DB; +use Illuminate\Validation\Rule; use Livewire\Form; class RewardForm extends Form @@ -20,17 +23,26 @@ class RewardForm extends Form public ?string $stock = null; - public string $how_to_get = ''; + public int $category = RewardCategory::REGULAR->value; + + public int $is_show = IsShow::SHOW->value; + + public string $start_date = ''; + + public ?string $end_date = null; public array $thumbnail = []; public function rules(): array { return [ - 'name' => ['required', 'string', 'max:100'], + 'name' => ['required', 'string', 'max:30'], 'points' => ['required', new UnsignedInteger], 'stock' => ['nullable', new UnsignedInteger], - 'how_to_get' => ['required', 'string'], + 'category' => ['required', Rule::in(RewardCategory::cases())], + 'is_show' => ['required', Rule::in(IsShow::cases())], + 'start_date' => ['required', 'date'], + 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'], 'thumbnail' => 'array', ]; } @@ -41,7 +53,10 @@ public function validationAttributes(): array 'name' => 'nama', 'points' => 'poin', 'stock' => 'stok', - 'how_to_get' => 'cara mendapatkan', + 'category' => 'kategori', + 'is_show' => 'visibilitas', + 'start_date' => 'tanggal mulai', + 'end_date' => 'tanggal berakhir', ]; } @@ -52,7 +67,10 @@ public function setReward(Reward $reward): void $this->name = $reward->name; $this->points = formatCurrencyNumber($reward->points); $this->stock = formatCurrencyNumber($reward->stock); - $this->how_to_get = $reward->how_to_get ?? ''; + $this->category = $reward->category->value; + $this->is_show = $reward->is_show->value; + $this->start_date = $reward->start_date; + $this->end_date = $reward->end_date; $this->thumbnail = $this->mapMediaCollection($reward->getMedia('thumbnail')); } @@ -65,7 +83,10 @@ public function store(): void 'name' => $this->name, 'points' => parseRupiahToInt($this->points), 'stock' => parseRupiahToInt($this->stock), - 'how_to_get' => $this->how_to_get, + 'category' => $this->category, + 'is_show' => $this->is_show, + 'start_date' => $this->start_date, + 'end_date' => $this->end_date, ]); $this->uploadMedia($this->thumbnail, $reward, 'thumbnail'); @@ -81,7 +102,10 @@ public function update(): void 'name' => $this->name, 'points' => parseRupiahToInt($this->points), 'stock' => $this->stock ? parseRupiahToInt($this->stock) : null, - 'how_to_get' => $this->how_to_get, + 'category' => $this->category, + 'is_show' => $this->is_show, + 'start_date' => $this->start_date, + 'end_date' => $this->end_date, ]); $this->syncMedia($this->thumbnail, $this->reward, 'thumbnail'); diff --git a/app/Livewire/Member/RedeemPoint.php b/app/Livewire/Member/RedeemPoint.php new file mode 100644 index 0000000..581dfcb --- /dev/null +++ b/app/Livewire/Member/RedeemPoint.php @@ -0,0 +1,134 @@ +rewards = Reward::show() + ->active() + ->orderBy('points') + ->get() + ->map(function (Reward $reward) { + $reward->thumbnail = $reward->getFirstMediaUrl('thumbnail'); + $reward->category_label = $reward->category->label(); + + return $reward; + }) + ->toArray(); + + $this->userRedemptions = Redemption::with('reward') + ->where('user_id', auth()->id()) + ->latest() + ->get() + ->map(function (Redemption $redemption) { + $redemption->status_label = $redemption->status->label(); + $redemption->status_color = $redemption->status->color(); + + return $redemption; + }) + ->toArray(); + } + + public function redeem(Reward $reward): void + { + $user = auth()->user(); + $membership = $user->membership; + + if ($membership->reward_points < $reward->points) { + $this->toast('Poin Anda tidak cukup untuk menukarkan hadiah ini.', 'Gagal', 'danger'); + + Flux::modals()->close(); + + return; + } + + if ($reward->stock !== null && $reward->stock <= 0) { + $this->toast('Stok hadiah ini sudah habis.', 'Gagal', 'danger'); + Flux::modals()->close(); + + return; + } + + $redemption = DB::transaction(function () use ($user, $membership, $reward) { + // Deduct points from membership + $membership->decrement('reward_points', $reward->points); + + // Create point record + $user->pointRecords()->create([ + 'change' => $reward->points, + 'type' => PointRecordType::REWARD, + 'is_addition' => false, + 'description' => Str::limit("Redeem: {$reward->name}", 50), + ]); + + // Create redemption + $redemption = Redemption::create([ + 'user_id' => $user->id, + 'reward_id' => $reward->id, + 'code' => 'RD-'.Str::upper(Str::random(8)), + 'points_spent' => $reward->points, + 'status' => RedemptionStatus::WAITING_PICKUP, + 'expires_at' => now()->addWeek(), + ]); + + // Decrement stock if applicable + if ($reward->stock !== null) { + $reward->decrement('stock'); + } + + return $redemption; + }); + + // Update rewards list in real-time + $this->rewards = collect($this->rewards)->map(function ($item) use ($reward) { + if ($item['id'] === $reward->getRouteKey() && $item['stock'] !== null) { + $item['stock']--; + } + + return $item; + })->toArray(); + + // Update user redemptions history in real-time + $redemption->load('reward'); + array_unshift($this->userRedemptions, [ + ...$redemption->toArray(), + 'status_label' => $redemption->status->label(), + 'status_color' => $redemption->status->color(), + ]); + + $this->toast('Hadiah berhasil ditukarkan! Silakan cek tab Riwayat untuk melihat kode penukaran Anda.'); + + Flux::modals()->close(); + } + + public function render(): View + { + return view('livewire.member.rewards', [ + 'pageTitle' => 'Penukaran Poin', + ]); + } +} diff --git a/app/Livewire/Studio/Loyalty/RedemptionVerification.php b/app/Livewire/Studio/Loyalty/RedemptionVerification.php new file mode 100644 index 0000000..443523a --- /dev/null +++ b/app/Livewire/Studio/Loyalty/RedemptionVerification.php @@ -0,0 +1,76 @@ +canOrAbort('view redemption'); + + $this->foundRedemption = Redemption::where('code', Str::upper(Str::trim($this->searchCode))) + ->with(['user', 'user.customer', 'reward']) + ->first(); + + if (! $this->foundRedemption) { + $this->toast('Kode penukaran tidak ditemukan.', 'Gagal', 'danger'); + } else { + // Force refresh status if expired + $this->foundRedemption->isExpired(); + } + } + + public function verify($id = null): void + { + $this->canOrAbort('update redemption'); + + if ($this->foundRedemption->status !== RedemptionStatus::WAITING_PICKUP) { + $this->toast('Penukaran ini sudah diproses sebelumnya.', 'warning'); + + return; + } + + if ($this->foundRedemption->isExpired()) { + $this->toast('Kode penukaran ini sudah kadaluarsa.', 'error'); + + return; + } + + $this->foundRedemption->update([ + 'status' => RedemptionStatus::PICKED_UP, + 'verified_at' => now(), + 'verified_by' => auth()->id(), + ]); + + $this->toast('Penukaran hadiah berhasil diverifikasi!'); + + $this->foundRedemption->refresh(); + + Flux::modals()->close(); + } + + public function render(): View + { + return view('livewire.studio.loyalty.redemption', [ + 'pageTitle' => 'Verifikasi Penukaran', + ]); + } +} diff --git a/app/Livewire/Studio/Loyalty/Reward.php b/app/Livewire/Studio/Loyalty/Reward.php index 1015f5a..a9503fb 100644 --- a/app/Livewire/Studio/Loyalty/Reward.php +++ b/app/Livewire/Studio/Loyalty/Reward.php @@ -44,6 +44,13 @@ public function update(): void { $this->canOrAbort('update reward'); + if ($this->form->reward->redemptions()->exists()) { + $this->toast('Hadiah tidak dapat diubah karena sudah ada penukaran.', 'error'); + Flux::modals()->close(); + + return; + } + $this->form->update(); $this->dispatch('refreshDatatable'); diff --git a/app/Models/Redemption.php b/app/Models/Redemption.php new file mode 100644 index 0000000..af95cab --- /dev/null +++ b/app/Models/Redemption.php @@ -0,0 +1,59 @@ + RedemptionStatus::class, + 'points_spent' => 'int', + 'expires_at' => 'datetime', + 'verified_at' => 'datetime', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function reward(): BelongsTo + { + return $this->belongsTo(Reward::class)->withTrashed(); + } + + public function verifier(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by'); + } + + public function isExpired(): bool + { + if ($this->status === RedemptionStatus::EXPIRED) { + return true; + } + + if ($this->expires_at->isPast()) { + if ($this->status === RedemptionStatus::WAITING_PICKUP) { + $this->update(['status' => RedemptionStatus::EXPIRED]); + } + + return true; + } + + return false; + } +} diff --git a/app/Models/Reward.php b/app/Models/Reward.php index 8a8730e..4d8bda7 100644 --- a/app/Models/Reward.php +++ b/app/Models/Reward.php @@ -2,8 +2,13 @@ namespace App\Models; +use App\Enums\IsShow; +use App\Enums\RewardCategory; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; @@ -20,6 +25,35 @@ protected function casts(): array return [ 'points' => 'int', 'stock' => 'int', + 'category' => RewardCategory::class, + 'is_show' => IsShow::class, ]; } + + #[Scope] + protected function show(Builder $query): void + { + $query->where('is_show', IsShow::SHOW); + } + + #[Scope] + protected function hide(Builder $query): void + { + $query->where('is_show', IsShow::HIDDEN); + } + + #[Scope] + protected function active(Builder $query): void + { + $query->whereDate('start_date', '<=', now()) + ->where(function ($q) { + $q->whereNull('end_date') + ->orWhereDate('end_date', '>=', now()); + }); + } + + public function redemptions(): HasMany + { + return $this->hasMany(Redemption::class); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 1a4e1ff..a8abd9d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -150,6 +150,11 @@ public function pushNotifications(): HasMany return $this->hasMany(PushNotification::class); } + public function redemptions(): HasMany + { + return $this->hasMany(Redemption::class); + } + public function salaryHistories(): HasMany { return $this->hasMany(SalaryHistory::class); @@ -159,4 +164,9 @@ public function stockOpnameItems(): HasMany { return $this->hasMany(StockOpnameItem::class); } + + public function verifiedRedemptions(): HasMany + { + return $this->hasMany(Redemption::class, 'verified_by'); + } } diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index 1b4ac96..d5dfe54 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -103,6 +103,13 @@ public function boot(): void 'match' => 'studio.loyalty.customer.*', 'can' => 'view customer', ], + [ + 'label' => 'Verifikasi Penukaran', + 'icon' => 'check-badge', + 'route' => 'studio.loyalty.redemption.index', + 'match' => 'studio.loyalty.redemption.*', + 'can' => 'view redemption', + ], ], ], [ diff --git a/database/migrations/2025_12_18_193031_create_rewards_table.php b/database/migrations/2025_12_18_193031_create_rewards_table.php index ef7a0e3..2f9e243 100644 --- a/database/migrations/2025_12_18_193031_create_rewards_table.php +++ b/database/migrations/2025_12_18_193031_create_rewards_table.php @@ -1,5 +1,7 @@ id(); - $table->string('name'); - $table->text('how_to_get'); + $table->string('name', 30); $table->unsignedInteger('points'); $table->unsignedInteger('stock')->nullable(); + $table->enum('category', RewardCategory::values())->default(RewardCategory::REGULAR)->comment(RewardCategory::comment()); + $table->enum('is_show', IsShow::values())->default(IsShow::SHOW)->comment(IsShow::comment()); + $table->date('start_date'); + $table->date('end_date')->nullable(); $table->timestamps(); $table->softDeletes(); }); diff --git a/database/migrations/2025_12_18_202206_create_redemptions_table.php b/database/migrations/2025_12_18_202206_create_redemptions_table.php new file mode 100644 index 0000000..65963c5 --- /dev/null +++ b/database/migrations/2025_12_18_202206_create_redemptions_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('reward_id')->constrained()->cascadeOnDelete(); + $table->string('code')->unique(); + $table->unsignedInteger('points_spent'); + $table->enum('status', RedemptionStatus::values())->default(RedemptionStatus::WAITING_PICKUP)->comment(RedemptionStatus::comment()); + $table->timestamp('expires_at'); + $table->timestamp('verified_at')->nullable(); + $table->foreignId('verified_by')->nullable()->constrained('users')->nullOnDelete(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('redemptions'); + } +}; diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index f1a7e4e..8f124af 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -165,6 +165,9 @@ public function run(): void 'view social settings', 'update social settings', + 'view redemption', + 'update redemption', + // member 'view member overview', @@ -172,6 +175,8 @@ public function run(): void 'view member membership', + 'view member reward', + // partner 'view partner overview', ]; @@ -194,6 +199,7 @@ public function run(): void 'view member voucher', 'view member membership', + 'view member reward', ])); $leader->syncPermissions(array_diff($permissions, [ @@ -202,6 +208,7 @@ public function run(): void 'view member voucher', 'view member membership', + 'view member reward', 'create outlet', 'update outlet', @@ -298,6 +305,7 @@ public function run(): void 'view member voucher', 'view member membership', + 'view member reward', 'view outlet', 'create outlet', @@ -436,6 +444,8 @@ public function run(): void 'view member voucher', 'view member membership', + + 'view member reward', ]); } } diff --git a/resources/views/components/sections/ui/member/_sidebar.blade.php b/resources/views/components/sections/ui/member/_sidebar.blade.php index dff4da8..b714ca0 100644 --- a/resources/views/components/sections/ui/member/_sidebar.blade.php +++ b/resources/views/components/sections/ui/member/_sidebar.blade.php @@ -15,6 +15,9 @@ class="bg-zinc-50 dark:bg-zinc-900 border-r rtl:border-r-0 rtl:border-l border-z Membership + + Penukaran Poin @if (auth()->user()->permission('view studio overview'))
diff --git a/resources/views/livewire/member/rewards.blade.php b/resources/views/livewire/member/rewards.blade.php new file mode 100644 index 0000000..23870fd --- /dev/null +++ b/resources/views/livewire/member/rewards.blade.php @@ -0,0 +1,136 @@ + + {{ $pageTitle }} + +
+
+ +
+ +
+
+
Poin Tersedia
+
+ {{ formatCurrencyNumber(auth()->user()->membership?->reward_points) }} + Poin +
+
+
+ + + + Tukar Poin + Riwayat + + + +
+ @foreach ($rewards as $reward) +
+ +
+ @if ($reward['thumbnail']) + {{ $reward['name'] }} + @else +
+ +
+ @endif +
+
+
+ +
+
+
+ {{ $reward['category_label'] }} +
+
+ {{ formatCurrencyNumber($reward['points']) }} + Poin +
+
+ {{ $reward['name'] }} +
+
+ +
+
+ + + Tukarkan Sekarang + + +
+
+ + @if ($reward['stock'] !== null) +
+ + Stok: {{ $reward['stock'] }} + +
+ @endif +
+
+ @endforeach +
+
+ + + @if (!empty($userRedemptions)) + + + + Hadiah + Kode + Poin Dipakai + Status + Kadaluarsa + + + + @foreach ($userRedemptions as $redemption) + + {{ $redemption['reward']['name'] }} + {{ $redemption['code'] }} + {{ formatCurrencyNumber($redemption['points_spent']) }} + + + + {{ $redemption['status_label'] }} + + + + {{ formatDateLocalized($redemption['expires_at'], 'l, d M Y H:i') }} + + + @endforeach + + + + @endif + +
+
+ + @include('components.modals.confirmation', [ + 'modalName' => 'redeem-confirmation', + 'modalTitle' => 'Tukarkan Hadiah?', + 'modalMessage' => 'Apakah Anda yakin ingin menukarkan poin Anda dengan hadiah ini?', + 'buttonVariant' => 'primary', + 'buttonColor' => 'zinc', + 'buttonText' => 'Ya, Tukarkan', + ]) +
diff --git a/resources/views/livewire/member/voucher.blade.php b/resources/views/livewire/member/voucher.blade.php index 4b2c71b..ca10cd0 100644 --- a/resources/views/livewire/member/voucher.blade.php +++ b/resources/views/livewire/member/voucher.blade.php @@ -30,7 +30,7 @@ class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap - Min. Belanja {{ $voucher['min_purchase'] }} • {{ $voucher['tier_name'] }} + Min. Belanja {{ $voucher['min_purchase'] }}
@endassets diff --git a/resources/views/livewire/studio/loyalty/redemption.blade.php b/resources/views/livewire/studio/loyalty/redemption.blade.php new file mode 100644 index 0000000..d38727e --- /dev/null +++ b/resources/views/livewire/studio/loyalty/redemption.blade.php @@ -0,0 +1,114 @@ + +
+
+ {{ $pageTitle }} +
+
+ +
+ +
+ +
+ Cari +
+
+
+ + @if ($foundRedemption) + +
+
+
+ Detail Penukaran + Informasi lengkap terkait kode {{ $foundRedemption->code }} +
+ + {{ $foundRedemption->status->label() }} + +
+ +
+
+
Member +
+
+ +
+
{{ $foundRedemption?->user?->customer?->name }} +
+
{{ $foundRedemption?->user?->customer?->email }} +
+
+
+
+
+
Hadiah
+
{{ $foundRedemption->reward->name }}
+
{{ $foundRedemption->points_spent }} Poin
+
+
+
Waktu + Penukaran
+
{{ formatDateLocalized($foundRedemption->created_at, 'l, d M Y H:i') }} +
+
+
+
Batas Waktu +
+
+ {{ formatDateLocalized($foundRedemption->expires_at, 'l, d M Y H:i') }} + @if ($foundRedemption->isExpired()) + (Kadaluarsa) + @endif +
+
+
+ + @if ($foundRedemption->status === \App\Enums\RedemptionStatus::VERIFIED) +
+
+
+
+ Diverifikasi Oleh
+
{{ $foundRedemption->verifier?->employee?->full_name ?? 'Sistem' }}
+
+
+
Waktu + Verifikasi
+
+ {{ formatDateLocalized($foundRedemption->verified_at, 'l, d M Y H:i') ?? '-' }} +
+
+
+
+ @endif + + @if ($foundRedemption->status === \App\Enums\RedemptionStatus::WAITING_PICKUP && !$foundRedemption->isExpired()) +
+ + + Verifikasi Sekarang + + +
+ @endif +
+
+ @endif +
+ + @include('components.modals.confirmation', [ + 'modalName' => 'verify-confirmation', + 'modalTitle' => 'Verifikasi Penukaran?', + 'modalMessage' => 'Apakah Anda yakin ingin memverifikasi penukaran hadiah ini?', + 'buttonVariant' => 'primary', + 'buttonColor' => 'zinc', + 'buttonText' => 'Ya, Verifikasi', + ]) +
diff --git a/resources/views/livewire/studio/loyalty/rewards.blade.php b/resources/views/livewire/studio/loyalty/rewards.blade.php index f98e5b9..9edc4f2 100644 --- a/resources/views/livewire/studio/loyalty/rewards.blade.php +++ b/resources/views/livewire/studio/loyalty/rewards.blade.php @@ -36,12 +36,25 @@ - - Poin * - - - +
+ + + Poin * + + + + + Kategori * + + @foreach (\App\Enums\RewardCategory::cases() as $category) + {{ $category->label() }} + + @endforeach + + + +
Stok @@ -52,12 +65,29 @@ - Cara Mendapatkan * - - + Visibilitas * + + @foreach (\App\Enums\IsShow::cases() as $item) + + {{ $item->label() }} + + @endforeach + + +
+ + Tanggal Mulai * + + + + + +
+

Thumbnail

diff --git a/routes/pages/member.php b/routes/pages/member.php index d71cc32..7b9382d 100644 --- a/routes/pages/member.php +++ b/routes/pages/member.php @@ -2,6 +2,7 @@ use App\Livewire\Member\Membership; use App\Livewire\Member\Overview; +use App\Livewire\Member\RedeemPoint; use App\Livewire\Member\Voucher; use Illuminate\Support\Facades\Route; @@ -12,4 +13,5 @@ Route::get('overview', Overview::class)->name('overview'); Route::get('vouchers', Voucher::class)->name('voucher'); Route::get('memberships', Membership::class)->name('membership'); + Route::get('redeem-points', RedeemPoint::class)->name('redeem_point'); }); diff --git a/routes/pages/studio.php b/routes/pages/studio.php index 28a528f..96c80a3 100644 --- a/routes/pages/studio.php +++ b/routes/pages/studio.php @@ -25,6 +25,7 @@ use App\Livewire\Studio\Information\Faq as FaqComponent; use App\Livewire\Studio\Information\PriceRequest as PriceRequestComponent; use App\Livewire\Studio\Loyalty\Customer as CustomerComponent; +use App\Livewire\Studio\Loyalty\RedemptionVerification; use App\Livewire\Studio\Loyalty\Reward as RewardComponent; use App\Livewire\Studio\Loyalty\Tier as TierComponent; use App\Livewire\Studio\Loyalty\Voucher\Create as VoucherCreate; @@ -99,6 +100,8 @@ Route::prefix('rewards')->name('reward.')->group(function () { Route::get('/', RewardComponent::class)->name('index')->middleware('can:view reward'); }); + + Route::get('/redemptions', RedemptionVerification::class)->name('redemption.index')->middleware('can:view redemption'); }); Route::prefix('catalog')->name('studio.catalog.')->group(function () {