feat: Implement dedicated perfume product pages and add a new reward tier table for loyalty features.
This commit is contained in:
parent
d2578fff34
commit
0800503453
@ -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('<flux:badge color="'.$row->is_show->color().'">'.$row->is_show->label().'</flux:badge>'))
|
||||
->html(),
|
||||
|
||||
ArrayColumn::make('Tier')
|
||||
->data(
|
||||
fn ($value, $row) => $row->tiers->pluck('name')->toArray() ?: ['Semua Tier']
|
||||
)
|
||||
->outputFormat(fn ($index, $value) => Blade::render('<span class="text-xs text-gray-400 border border-gray-400 rounded px-1">'.$value.'</span>'))
|
||||
->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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('reward_tier', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@ -88,6 +88,16 @@
|
||||
placeholder="Pilih Tanggal" autocomplete="off" locale="id-ID" />
|
||||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>Tier <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
<flux:select placeholder="Pilih Tier" wire:model="form.tier_ids" multiple searchable variant="listbox">
|
||||
@foreach ($tiers as $key => $value)
|
||||
<flux:select.option value="{{ $key }}">{{ $value }}</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:error name="form.tier_ids" />
|
||||
</flux:field>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-sm font-medium">Thumbnail</h3>
|
||||
<div class="dropzone-wrapper">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user