From 9bd699735958b2967fb3ef85148a6bad7888aa2d Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sat, 20 Dec 2025 07:36:03 +0700 Subject: [PATCH] feat: Implement testimonial management with dedicated UI abnd database --- .../Information/TestimonialsTable.php | 51 ++++++++++++++ .../Studio/Information/Testimonial.php | 18 +++++ app/Models/Testimonial.php | 32 +++++++++ app/Providers/ViewServiceProvider.php | 7 ++ ...12_19_214248_create_testimonials_table.php | 32 +++++++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/RolePermissionSeeder.php | 5 ++ database/seeders/TestimonialSeeder.php | 69 +++++++++++++++++++ .../studio/information/testimonials.blade.php | 11 +++ routes/pages/studio.php | 5 ++ 10 files changed, 231 insertions(+) create mode 100644 app/Livewire/Datatable/Information/TestimonialsTable.php create mode 100644 app/Livewire/Studio/Information/Testimonial.php create mode 100644 app/Models/Testimonial.php create mode 100644 database/migrations/2025_12_19_214248_create_testimonials_table.php create mode 100644 database/seeders/TestimonialSeeder.php create mode 100644 resources/views/livewire/studio/information/testimonials.blade.php diff --git a/app/Livewire/Datatable/Information/TestimonialsTable.php b/app/Livewire/Datatable/Information/TestimonialsTable.php new file mode 100644 index 0000000..f78f3df --- /dev/null +++ b/app/Livewire/Datatable/Information/TestimonialsTable.php @@ -0,0 +1,51 @@ +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('', ['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']); + } +} diff --git a/app/Livewire/Studio/Information/Testimonial.php b/app/Livewire/Studio/Information/Testimonial.php new file mode 100644 index 0000000..76972e6 --- /dev/null +++ b/app/Livewire/Studio/Information/Testimonial.php @@ -0,0 +1,18 @@ + 'Testimoni', + ]); + } +} diff --git a/app/Models/Testimonial.php b/app/Models/Testimonial.php new file mode 100644 index 0000000..026c30c --- /dev/null +++ b/app/Models/Testimonial.php @@ -0,0 +1,32 @@ + IsShow::class, + ]; + } + + #[Scope] + public function show(Builder $query): void + { + $query->where('is_show', IsShow::SHOW); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index d5dfe54..817ddd4 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -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', + ], ], ], [ diff --git a/database/migrations/2025_12_19_214248_create_testimonials_table.php b/database/migrations/2025_12_19_214248_create_testimonials_table.php new file mode 100644 index 0000000..e6256f0 --- /dev/null +++ b/database/migrations/2025_12_19_214248_create_testimonials_table.php @@ -0,0 +1,32 @@ +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'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index df93d3f..ac69966 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -32,6 +32,7 @@ public function run(): void FaqSeeder::class, PerfumeFeatureSeeder::class, ChooseUsSeeder::class, + TestimonialSeeder::class, ]); } } diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 8f124af..ffc3ebc 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -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', diff --git a/database/seeders/TestimonialSeeder.php b/database/seeders/TestimonialSeeder.php new file mode 100644 index 0000000..52841c1 --- /dev/null +++ b/database/seeders/TestimonialSeeder.php @@ -0,0 +1,69 @@ + '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'], + ]); + } + } +} diff --git a/resources/views/livewire/studio/information/testimonials.blade.php b/resources/views/livewire/studio/information/testimonials.blade.php new file mode 100644 index 0000000..32f7591 --- /dev/null +++ b/resources/views/livewire/studio/information/testimonials.blade.php @@ -0,0 +1,11 @@ + +
+
+ {{ $pageTitle }} +
+
+ +
+ +
+
diff --git a/routes/pages/studio.php b/routes/pages/studio.php index 96c80a3..3d8d503 100644 --- a/routes/pages/studio.php +++ b/routes/pages/studio.php @@ -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 () {