feat: Implement testimonial management with dedicated UI abnd database

This commit is contained in:
Yoga Pangestu 2025-12-20 07:36:03 +07:00
parent cb9e61ee7f
commit 9bd6997359
10 changed files with 231 additions and 0 deletions

View File

@ -0,0 +1,51 @@
<?php
namespace App\Livewire\Datatable\Information;
use App\Enums\IsShow;
use App\Models\Testimonial;
use App\Traits\Datatable\WithConfiguration;
use App\Traits\Datatable\WithPrependColumn;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Str;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
class TestimonialsTable extends DataTableComponent
{
use WithConfiguration, WithPrependColumn;
protected $model = Testimonial::class;
public function columns(): array
{
return [
Column::make('Nama', 'user.customer.name')
->searchable(),
Column::make('Konten', 'content')
->searchable()
->format(fn ($value) => Str::limit($value, 50)),
Column::make('Rating', 'rating')
->sortable(),
Column::make('Visibilitas', 'is_show')
->label(fn ($row, $column) => Blade::render('<flux:switch wire:change="toggleShow('.$row->id.')" :checked="$checked" />', ['checked' => $row->is_show === IsShow::SHOW]))
->html(),
];
}
public function toggleShow(Testimonial $testimonial): void
{
$testimonial->update([
'is_show' => $testimonial->is_show === IsShow::SHOW ? IsShow::HIDDEN : IsShow::SHOW,
]);
}
public function builder(): Builder
{
return Testimonial::select('testimonials.id', 'rating', 'content', 'is_show', 'testimonials.created_at')->with(['user', 'user.customer']);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Livewire\Studio\Information;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Testimoni')]
class Testimonial extends Component
{
public function render(): View
{
return view('livewire.studio.information.testimonials', [
'pageTitle' => 'Testimoni',
]);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use App\Enums\IsShow;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Testimonial extends Model
{
protected $guarded = ['id'];
protected function casts(): array
{
return [
'is_show' => IsShow::class,
];
}
#[Scope]
public function show(Builder $query): void
{
$query->where('is_show', IsShow::SHOW);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@ -229,6 +229,13 @@ public function boot(): void
'match' => 'studio.information.faq.*',
'can' => 'view faq',
],
[
'label' => 'Testimonial',
'icon' => 'chat-bubble-left-ellipsis',
'route' => 'studio.information.testimonial.index',
'match' => 'studio.information.testimonial.*',
'can' => 'view testimonial',
],
],
],
[

View File

@ -0,0 +1,32 @@
<?php
use App\Enums\IsShow;
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('testimonials', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->text('content');
$table->integer('rating');
$table->enum('is_show', IsShow::values())->default(IsShow::SHOW)->comment(IsShow::comment());
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('testimonials');
}
};

View File

@ -32,6 +32,7 @@ public function run(): void
FaqSeeder::class,
PerfumeFeatureSeeder::class,
ChooseUsSeeder::class,
TestimonialSeeder::class,
]);
}
}

View File

@ -137,6 +137,11 @@ public function run(): void
'update faq',
'delete faq',
'view testimonial',
'create testimonial',
'update testimonial',
'delete testimonial',
'view broadcast',
'create broadcast',
'delete broadcast',

View File

@ -0,0 +1,69 @@
<?php
namespace Database\Seeders;
use App\Enums\IsShow;
use App\Models\Testimonial;
use App\Models\User;
use Illuminate\Database\Seeder;
class TestimonialSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$testimonials = [
[
'content' => 'Parfumnya wangi banget, tahan lama seharian! Recommended banget pokoknya.',
'rating' => 5,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Pengiriman cepat, packing aman. Wanginya juga sesuai ekspektasi. Terima kasih!',
'rating' => 5,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Wanginya enak, tapi sayang kurang tahan lama di luar ruangan yg panas. Tapi worth it lah harga segini.',
'rating' => 4,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Suka banget sama varian vanilla-nya. Manis tapi nggak bikin pusing. Bakal repeat order sih ini.',
'rating' => 5,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Botolnya pecah dikit pas nyampe, mungkin dari ekspedisinya. Tapi parfumnya masih oke.',
'rating' => 3,
'is_show' => IsShow::HIDDEN,
],
[
'content' => 'Pelayanan ramah, admin fast respon. Parfumnya juga original. Top marketop!',
'rating' => 5,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Aromanya mewah, cocok buat kondangan. Temen-temen pada nanyain pake parfum apa.',
'rating' => 5,
'is_show' => IsShow::SHOW,
],
[
'content' => 'Lumayan lah buat sehari-hari. Gak terlalu menyengat.',
'rating' => 4,
'is_show' => IsShow::SHOW,
],
];
foreach ($testimonials as $testimonial) {
Testimonial::create([
'user_id' => User::whereHas('customer')->inRandomOrder()->first()->id,
'content' => $testimonial['content'],
'rating' => $testimonial['rating'],
'is_show' => $testimonial['is_show'],
]);
}
}
}

View File

@ -0,0 +1,11 @@
<flux:main>
<div class="flex justify-between items-center">
<div>
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
</div>
<div class="mt-6">
<livewire:datatable.information.testimonials-table />
</div>
</flux:main>

View File

@ -24,6 +24,7 @@
use App\Livewire\Studio\Information\Broadcast as BroadcastComponent;
use App\Livewire\Studio\Information\Faq as FaqComponent;
use App\Livewire\Studio\Information\PriceRequest as PriceRequestComponent;
use App\Livewire\Studio\Information\Testimonial as TestimonialComponent;
use App\Livewire\Studio\Loyalty\Customer as CustomerComponent;
use App\Livewire\Studio\Loyalty\RedemptionVerification;
use App\Livewire\Studio\Loyalty\Reward as RewardComponent;
@ -184,6 +185,10 @@
Route::prefix('faqs')->name('faq.')->group(function () {
Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq');
});
Route::prefix('testimonials')->name('testimonial.')->group(function () {
Route::get('/', TestimonialComponent::class)->name('index')->middleware('can:view testimonial');
});
});
Route::prefix('feature')->name('studio.feature.')->group(function () {