My Course — Membangun Website Food Store Dengan Laravel Filament, Livewire dan Payment Gateway

Membuat Component Modal Rating


Sekarang kita akan membuat sebuah component livewire yang nanti digunakan untuk menampilkan sebuah modal rating.

Langkah 1 - Membuat Component Rating

Silahkan teman-teman jalankan perintah berikut ini di dalam terminal/CMD dan pastikan berada di dalam project Laravel-nya.

go
php artisan make:livewire Account/MyOrders/ModalRating

Jika perintah di atas berhasil dijalankan, maka kita akan mendapatkan 2 file baru, yaitu:

  1. Class Component: app/Livewire/Account/MyOrders/ModalRating.php
  2. View Component: resources/views/livewire/account/my-orders/modal-rating.blade.php

Langkah 2 - Membuat Fungsi Store Rating

Silahkan teman-teman buka file app/Livewire/Account/MyOrders/ModalRating.php, kemudian ubah semua kode-nya menjadi seperti berikut ini.

php
<?php namespace App\Livewire\Account\MyOrders; use App\Models\Rating; use Livewire\Component; class ModalRating extends Component { public $transaction; public $item; public $rating; public $review; /** * rules * * @return void */ public function rules() { return [ 'rating' => 'required|in:1,2,3,4,5', 'review' => 'required', ]; } public function setRating($value) { $this->rating = $value; } /** * storeRating * * @return void */ public function storeRating() { // validate $this->validate(); //check rating already $check_rating = Rating::query() ->where('product_id', $this->item->product->id) ->where('customer_id', auth()->guard('customer')->user()->id) ->first(); if($check_rating) { session()->flash('warning', 'Anda sudah pernah memberikan rating untuk produk ini'); return $this->redirect('/account/my-orders/'.$this->transaction->snap_token, navigate: true); } // create rating Rating::create([ 'transaction_detail_id' => $this->item->id, 'product_id' => $this->item->product->id, 'rating' => $this->rating, 'review' => $this->review, 'customer_id' => auth()->guard('customer')->user()->id ]); //session flash session()->flash('success', 'Rating Berhasil Disimpan'); //redirect return $this->redirect('/account/my-orders/'.$this->transaction->snap_token, navigate: true); } public function render() { return view('livewire.account.my-orders.modal-rating'); } }

Dari perubahan kode di atas, pertama kita import Model Rating.

perl
use App\Models\Rating;

Kemudian kita membuat 4 public properti, yaitu:

  1. $transaction - akan berisi detail data transaction.
  2. $item - akan berisi object data item / product yang dibeli.
  3. $rating - akan berisi jumlah rating yang akan diberikan (1-5).
  4. $review - akan berisi ulasan terkait item / product yang dibeli.
php
public $transaction; public $item; public $rating; public $review;

Setelah itu, kita membuat method baru dengan nama rules, dimaan method ini digunakan untuk medefinisikan sebuah validasi.

csharp
public function rules() { return [ 'rating' => 'required|in:1,2,3,4,5', 'review' => 'required', ]; }

Kemudian kita buat method lagi dengan nama setRating, dimana isinya adalah melakukan assign nilai yang dikirimkan oleh form nantin-nya ke properti $rating secara reactive / realtime.

php
public function setRating($value) { $this->rating = $value; }

Selanjutnya, yaitu membuat method untuk menyimpan data rating dan ulasan ke dalam database, yaitu kita beri nama storeRating.

csharp
public function storeRating() { //... }

Di dalamnya, pertama kita panggil validasi.

scss
// validate $this->validate();

Selanjutnya kita membuat pengecekan apakah si customer sudah pernah memberikan rating dan ulasan, jika iya maka akan di redirect ke halaman detail order dengan memberikan pesan sudah pernah memberikan sebuah rating pada item / product ini.

php
//check rating already $check_rating = Rating::query() ->where('product_id', $this->item->product->id) ->where('customer_id', auth()->guard('customer')->user()->id) ->first(); if($check_rating) { session()->flash('warning', 'Anda sudah pernah memberikan rating untuk produk ini'); return $this->redirect('/account/my-orders/'.$this->transaction->snap_token, navigate: true); }

Jika belum, maka kita akan melakukan proses insert data rating ke dalam database menggunakan Model.

php
// create rating Rating::create([ 'transaction_detail_id' => $this->item->id, 'product_id' => $this->item->product->id, 'rating' => $this->rating, 'review' => $this->review, 'customer_id' => auth()->guard('customer')->user()->id ]);

Setelah itu, kita buat session flash dengan status success dan kita redirect ke halaman detail order lagi.

php
//session flash session()->flash('success', 'Rating Berhasil Disimpan'); //redirect return $this->redirect('/account/my-orders/'.$this->transaction->snap_token, navigate: true);

Langkah 3 - Membuat Form Modal Rating

Silahkan teman-teman buka file resources/views/livewire/account/my-orders/modal-rating.blade.php, kemudian ubah semua kode-nya menjadi seperti berikut ini.

bash
<div class="modal fade" wire:ignore.self ref="modal" id="modal-{{ $item->id }}" tabindex="-1" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content rounded border-0 shadow-sm"> <div class="modal-header"> <h1 class="modal-title fs-5" id="exampleModalLabel">Rating</h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div class="d-flex justify-content-center"> <div class="d-flex gap-2 rating"> @for ($i = 1; $i <= 5; $i++) <label for="star{{ $i }}" class="mb-0 position-relative"> <input wire:click="setRating({{ $i }})" type="radio" id="star{{ $i }}" value="{{ $i }}" class="position-absolute start-0 top-0 w-100 h-100 opacity-0" style="cursor: pointer"/> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" style="cursor: pointer" class="cursor-pointer @if($rating >= $i) text-orange @else text-secondary @endif" viewBox="0 0 20 20"> <path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z" /> </svg> </label> @endfor </div> </div> @error('rating') <div class="alert alert-danger mt-2 rounded border-0"> {{ $message }} </div> @enderror <div class="d-flex justify-content-center mt-3"> <textarea class="form-control @error('review') is-invalid @enderror" placeholder="Tulis ulasan disini..." rows="3" wire:model="review"></textarea> </div> @error('review') <div class="alert alert-danger mt-2 rounded border-0"> {{ $message }} </div> @enderror </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary rounded border-0 shadow-sm" data-bs-dismiss="modal">Close</button> <button type="button" wire:click="storeRating" class="btn btn-primary rounded border-0 shadow-sm" data-bs-dismiss="modal">Send</button> </div> </div> </div> </div>

Di atas, kita membuat sebuah star atau bintang dengan melakukan perulangan biar kita tidak menulisnya secara berulang-ulang dan di dalamnya kita berikan event wire:click yang mengarah ke method setRating yang sudah kita buat sebelumnya di dalam class component dan di dalam parameternya kita berikan value index dari perulangan.

perl
@for ($i = 1; $i <= 5; $i++) <label for="star{{ $i }}" class="mb-0 position-relative"> <input wire:click="setRating({{ $i }})" type="radio" id="star{{ $i }}" value="{{ $i }}" class="position-absolute start-0 top-0 w-100 h-100 opacity-0" style="cursor: pointer"/> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" style="cursor: pointer" class="cursor-pointer @if($rating >= $i) text-orange @else text-secondary @endif" viewBox="0 0 20 20"> <path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z" /> </svg> </label> @endfor

Dan pada bagian button Send kita berikan event wire:click juga dan kita arahkan ke dalam method storeRating yang ada di dalam class component.

bash
<button type="button" wire:click="storeRating" class="btn btn-primary rounded border-0 shadow-sm" data-bs-dismiss="modal">Send</button>

Langkah 4 - Memanggil Component Modal Rating

Selah component rating berhasil dibuat, maka selanjutnya adalah memanggil component tersebut pada halaman detail order, kususnya di dalam perulangan transactionDetails.

Silahkan buka file resources/views/livewire/account/my-orders/show.blade.php, kemudian ubah semua kode-nya menjadi seperti berikut ini.

javascript
<div class="container"> <div class="row justify-content-center mt-0" style="margin-bottom: 150px;"> <div class="col-md-6"> <x-menus.customer /> <div class="card border-0 shadow-sm rounded"> <div class="card-body p-4"> <h6> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-basket3 mb-1 me-2" viewBox="0 0 16 16"> <path d="M5.757 1.071a.5.5 0 0 1 .172.686L3.383 6h9.234L10.07 1.757a.5.5 0 1 1 .858-.514L13.783 6H15.5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5H.5a.5.5 0 0 1-.5-.5v-1A.5.5 0 0 1 .5 6h1.717L5.07 1.243a.5.5 0 0 1 .686-.172zM3.394 15l-1.48-6h-.97l1.525 6.426a.75.75 0 0 0 .729.574h9.606a.75.75 0 0 0 .73-.574L15.056 9h-.972l-1.479 6z" /> </svg> Detail Order </h6> <hr /> <div class="table-responsive"> <table class="table "> <tbody> <tr> <td style="width: 25%;">No. Invoice</td> <td style="width: 5%;">:</td> <td>{{ $transaction->invoice }}</td> </tr> <tr> <td>Order Date</td> <td>:</td> <td>{{ $transaction->created_at }}</td> </tr> <tr> <td>Status</td> <td>:</td> <td> @if($transaction->status == 'pending') <button onclick="payment('{{ $transaction->snap_token }}')" class="btn btn-sm btn-warning rounded border-0"> BAYAR SEKARANG </button> @elseif($transaction->status == 'success') <button class="btn btn-sm btn-success rounded border-0"> <i class="fa fa-check-circle"></i> {{ $transaction->status }} </button> @elseif($transaction->status == 'expired') <button class="btn btn-sm btn-warning rounded border-0"> <i class="fa fa-exclamation-triangle"></i> {{ $transaction->status }} </button> @elseif($transaction->status == 'failed') <button class="btn btn-sm btn-danger rounded border-0"> <i class="fa fa-times-circle"></i> {{ $transaction->status }} </button> @endif </td> </tr> <tr> <td>Grand Total</td> <td>:</td> <td>Rp. {{ number_format($transaction->total) }}</td> </tr> </tbody> </table> </div> <h6 class="mt-3"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-truck mb-1" viewBox="0 0 16 16"> <path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h9A1.5 1.5 0 0 1 12 3.5V5h1.02a1.5 1.5 0 0 1 1.17.563l1.481 1.85a1.5 1.5 0 0 1 .329.938V10.5a1.5 1.5 0 0 1-1.5 1.5H14a2 2 0 1 1-4 0H5a2 2 0 1 1-3.998-.085A1.5 1.5 0 0 1 0 10.5zm1.294 7.456A2 2 0 0 1 4.732 11h5.536a2 2 0 0 1 .732-.732V3.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .294.456M12 10a2 2 0 0 1 1.732 1h.768a.5.5 0 0 0 .5-.5V8.35a.5.5 0 0 0-.11-.312l-1.48-1.85A.5.5 0 0 0 13.02 6H12zm-9 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2m9 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2" /> </svg> Shipping </h6> <hr /> <div class="table-responsive"> <table class="table"> <tbody> <tr> <td style="width: 25%;">Courier</td> <td style="width: 5%;">:</td> <td>{{ strtoupper($transaction->shipping->shipping_courier) }}</td> </tr> <tr> <td>Service</td> <td>:</td> <td>{{ strtoupper($transaction->shipping->shipping_service) }}</td> </tr> <tr> <td>Cost</td> <td>:</td> <td>Rp. {{ number_format($transaction->shipping->shipping_cost) }}</td> </tr> </tbody> </table> </div> <h6 class="mt-3"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-bag mb-1" viewBox="0 0 16 16"> <path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1m3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4zM2 5h12v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z" /> </svg> Items Details </h6> <hr /> <div class="row"> @foreach ($transaction->transactionDetails()->get() as $item) <div class="col-12 col-md-12 mb-4"> <div class="card rounded border"> <div class="row g-0"> <div class="col-5 col-md-4"> <img src="{{ asset('/storage/' . $item->product->image) }}" class="img-fluid w-100 h-100 object-fit-cover rounded-start"> </div> <div class="col-7 col-md-8"> <div class="card-body"> <div class="d-flex justify-content-between mt-4"> <div class="text-start"> <h6 class="card-title">{{ $item->product->title }}</h6> </div> @if($transaction->status == 'success') <div class="text-end"> <a href="#" data-bs-toggle="modal" data-bs-target="#modal-{{ $item->id }}" class="btn-rating me-2 shadow-sm"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16"> <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325" /> </svg> </a> </div> @endif </div> <div class="d-flex justify-content-between mt-2"> <div class="text-start"> <p class="text-muted">Qty: <strong>{{ $item->qty }}</strong></p> <span class="text-success fw-bold">Rp. {{ number_format($item->product->price * $item->qty) }}</span> </div> </div> </div> </div> </div> </div> <!-- component modal rating --> <livewire:account.my-orders.modal-rating :item="$item" :transaction="$transaction" /> <!-- end component modal rating --> </div> @endforeach </div> </div> </div> </div> </div> </div> <script src="https://app.sandbox.midtrans.com/snap/snap.js" data-client-key="{{ config('midtrans.client_key') }}"></script> <script> const payment = async (snap_token) => { window.snap.pay(snap_token, { onSuccess: function() { window.location = "/account/my-orders/" + snap_token } , onPending: function() { window.location = "/account/my-orders/" + snap_token } , onError: function() { window.location = "/account/my-orders/" + snap_token } }) }; </script>

Di atas, kita panggil component modal rating dan kita kirimkan 2 props di dalamnya, yaitu :item dan :transaction.

bash
<livewire:account.my-orders.modal-rating :item="$item" :transaction="$transaction" />

Langkah 5 - Uji Coba Mmeberikan Rating

INFORMASI : Untuk sekarang kamu belum bisa melihat hasilnya, karena belum memiliki data order apapun di dalam database.

ketika status di order sudah success, maka akan menampilkan button di dalam item product yang nantinya bisa digunakan untuk mengirim sebuah rating dan review. Kurang lebih seperti berikut ini :

Jika kita klik button tersebut, maka akan menampilkan popup seperti berikut ini :

Kita bisa memberitan rating dan ulasan / review sesuai dengan product yang kita beli dan jika kita klik Send, maka akan mendapatkaan hasil seperti berikut ini :