Aplikasi Booking Online Laravel 9 & Livewire #2: Blade Templating & Appointment

Aplikasi Booking Online Laravel 9 & Livewire #2: Blade Templating & Appointment

Pendahuluan

Seri ke-dua dari belajar membuat Aplikasi Booking Online menggunakan Laravel 9 & Livewire adalah bagaimana memadukan Blade templating dan Livewire. Fitur pertama yang akan kita buat adalah antar-muka dari sisi Pasien, dimana kita akan membuat tampilan beberapa fitur, diantaranya

  1. Form buat janji kunjungan.
  2. Informasi jumlah pasien yang terdaftar, dilayani, dan selesai.
  3. Informasi urutan antrian pasien
  4. Informasi lokasi klinik atau tempat praktek

Dari gambaran di atas, tampilan yang akan kita dapatkan kurang lebih seperti gambar berikut

Aplikasi Booking Online Laravel 9 & Livewire - Blade Templating

Baca Juga: Aplikasi Booking Online Laravel 9 & Livewire #1: Struktur Database

Blade Templating

Bagian pertama yang akan kita kerjakan adalah memetakan view/resource dengan blade templating. Tapi sebelum melangkah lebih jauh, install Livewire terlebih dahulu dengan command

composer require livewire/livewire

Publish config dari Livewire, karena nantinya akan ada konfigurasi yang akan kita sesuaikan.

php artisan livewire:publish --config

Tiba saatnya untuk membuat file master dari template yang akan kita gunakan, buat file frontend.blade.php di dalam folder resouces/views/layouts dan tambahkan code

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">

    <title>NadaLab - Klinik USG 3D/4D</title>
    <meta content="" name="description">
    <meta content="" name="keywords">
    <link href="{{ asset('ui/assets/img/favicon.png') }}" rel="icon">
    <link href="{{ asset('ui/assets/img/apple-touch-icon.png') }}" rel="apple-touch-icon">

    <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Raleway:300,300i,400,400i,500,500i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet">

    <link href="{{ asset('ui/assets/vendor/fontawesome-free/css/all.min.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/animate.css/animate.min.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/boxicons/css/boxicons.min.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/glightbox/css/glightbox.min.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/remixicon/remixicon.css') }}" rel="stylesheet">
    <link href="{{ asset('ui/assets/vendor/swiper/swiper-bundle.min.css') }}" rel="stylesheet">

    <link href="{{ asset('ui/assets/css/style.css') }}" rel="stylesheet">
    @livewireStyles
</head>

<body>
    <div id="topbar" class="d-flex align-items-center fixed-top">
        <div class="container d-flex justify-content-between">
            <div class="contact-info d-flex align-items-center">
                <i class="bi bi-envelope"></i> <a href="mailto:[email protected]">[email protected]</a>
                <i class="bi bi-phone"></i> +6285-343-966-997
            </div>
            <div class="d-none d-lg-flex social-links align-items-center">
                <a href="#" class="twitter"><i class="bi bi-twitter"></i></a>
                <a href="#" class="facebook"><i class="bi bi-facebook"></i></a>
                <a href="#" class="instagram"><i class="bi bi-instagram"></i></a>
                <a href="#" class="linkedin"><i class="bi bi-linkedin"></i></i></a>
            </div>
        </div>
    </div>

    <header id="header" class="fixed-top">
        <div class="container d-flex align-items-center">
            <h1 class="logo me-auto"><a href="/">NadaLab</a></h1>
            <nav id="navbar" class="navbar order-last order-lg-0">
                <ul>
                    <li><a class="nav-link scrollto active" href="#hero">Home</a></li>
                    <li><a class="nav-link scrollto" href="#contact">Contact</a></li>
                </ul>
                <i class="bi bi-list mobile-nav-toggle"></i>
            </nav>
            <a href="#appointment" class="appointment-btn scrollto"><span class="d-none d-md-inline">Buat</span> Janji</a>
        </div>
    </header>

    <section id="hero" class="d-flex align-items-center">
        <div class="container">
            <h1>Welcome to NadaLab</h1>
            <h2>Klinik dengan layanan USG 3D/4D</h2>
            <a href="#about" class="btn-get-started scrollto">Get Started</a>
        </div>
    </section>

    <main id="main">
        {{ $slot }}
    
        <section id="contact" class="contact">
            <div class="container">
                <div class="section-title">
                    <h2>Hubungi Kami</h2>
                    <p>Temukan kami di alamat berikut ini untuk konsultasi lebih lanjut</p>
                </div>
            </div>
            <div>
                <iframe style="border:0; width: 100%; height: 350px;" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3973.3815422675316!2d119.43419941459196!3d-5.202578296224953!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dbee3467b3aef3d%3A0xf290fdd4187a977e!2sDaeng%20Web!5e0!3m2!1sid!2sid!4v1662085476813!5m2!1sid!2sid" frameborder="0" allowfullscreen></iframe>
            </div>
            </div>
        </section>
    </main>
    <footer id="footer">
        <div class="container d-md-flex py-4">
            <div class="me-md-auto text-center text-md-start">
                <div class="copyright">
                    &copy; Copyright <strong><span>NadaLab</span></strong>. All Rights Reserved
                </div>
                <div class="credits">
                    Designed by <a href="https://daengweb.id">DaengWeb.id</a>
                </div>
            </div>
            <div class="social-links text-center text-md-right pt-3 pt-md-0">
                <a href="#" class="twitter"><i class="bx bxl-twitter"></i></a>
                <a href="#" class="facebook"><i class="bx bxl-facebook"></i></a>
                <a href="#" class="instagram"><i class="bx bxl-instagram"></i></a>
                <a href="#" class="google-plus"><i class="bx bxl-skype"></i></a>
                <a href="#" class="linkedin"><i class="bx bxl-linkedin"></i></a>
            </div>
        </div>
    </footer>
    <div id="preloader"></div>
    <a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i class="bi bi-arrow-up-short"></i></a>
    
    <script src="{{ asset('ui/assets/vendor/purecounter/purecounter_vanilla.js') }}"></script>
    <script src="{{ asset('ui/assets/vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
    <script src="{{ asset('ui/assets/vendor/glightbox/js/glightbox.min.js') }}"></script>
    <script src="{{ asset('ui/assets/vendor/swiper/swiper-bundle.min.js') }}"></script>
    <script src="{{ asset('ui/assets/vendor/php-email-form/validate.js') }}"></script>

    <script src="{{ asset('ui/assets/js/main.js') }}"></script>
    @livewireScripts
</body>
</html>

Note: Dua bagian penting yang harus Anda tambahkan sebelum menggunakan Livewire, pertama adalah tag untuk me-load assets @livewireStyles & @livewireScripts dan bagian kedua adalah variable $slot, karena component Livewire akan mengisi bagian tersebut secara default.

-Download asset dari template diatas: Booking Online Asset-

Generate sebuah Livewire component dengan command

php artisan make:livewire BookingOnline

Perintah di atas akan menghasilkan dua buah output

CLASS: app/Http/Livewire/BookingOnline.php
VIEW:  resources/views/livewire/booking-online.blade.php

Class BookingOnline akan kita hubungkan dengan route, buka file routes/web.php dan modifikasi menjadi

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Livewire\BookingOnline;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', BookingOnline::class);

Jika kita akses halaman root (http://localhost:3000) akan menghasilkan blank page atau white screen, hal ini disebabkan karena class BookingOnline me-render file booking-online.blade.php, dimana file tersebut masih kosong.

Make Appointment

Section di atas, kita telah melakukan setup templating yang akan digunakan sepanjang UI untuk pasien, maka pada section ini, kita akan melengkapi white screen tersebut.

Buka file booking-online.blade.php dan modifikasi menjadi

<div>
    <section id="why-us" class="why-us">
        <div class="container">
            <div class="row">
                <div class="col-lg-4 d-flex align-items-stretch">
                    <div class="content">
                        <h3>Kenapa Memilih NadaLab?</h3>
                        <p>
                            
                        </p>
                        <div class="text-center">
                            <a href="#" class="more-btn">Learn More <i class="bx bx-chevron-right"></i></a>
                        </div>
                    </div>
                </div>
                <div class="col-lg-8 d-flex align-items-stretch">
                    <div class="icon-boxes d-flex flex-column justify-content-center">
                        <div class="row">
                            <div class="col-xl-4 d-flex align-items-stretch">
                                <div class="icon-box mt-4 mt-xl-0">
                                    <i class="bx bx-receipt"></i>
                                    <h4>Pendaftaran Online</h4>
                                    <p>Daftarkan diri Anda tanpa harus datang ke klinik</p>
                                </div>
                            </div>
                            <div class="col-xl-4 d-flex align-items-stretch">
                                <div class="icon-box mt-4 mt-xl-0">
                                    <i class="bx bx-cube-alt"></i>
                                    <h4>Monitoring Antrian</h4>
                                    <p>Hemat waktu dengan datang ke klinik saat antrian Anda hampir tiba</p>
                                </div>
                            </div>
                            <div class="col-xl-4 d-flex align-items-stretch">
                                <div class="icon-box mt-4 mt-xl-0">
                                    <i class="bx bx-images"></i>
                                    <h4>Layanan Prima</h4>
                                    <p>Pantau perkembangan kehamilan Anda dengan layanan kami</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
    <section id="counts" class="counts">
        <div class="container">
            <div class="row">
                <div class="col-lg-3 col-md-6">
                    <div class="count-box">
                        <i class="fas fa-user-md"></i>
                        <span data-purecounter-start="0" data-purecounter-end="0" data-purecounter-duration="1" class="purecounter"></span>
                        <p>Pasien (Hari Ini)</p>
                    </div>
                </div>
                <div class="col-lg-3 col-md-6 mt-5 mt-md-0">
                    <div class="count-box">
                        <i class="far fa-hospital"></i>
                        <span data-purecounter-start="0" data-purecounter-end="0" data-purecounter-duration="1" class="purecounter"></span>
                        <p>Antrian (Hari Ini)</p>
                    </div>
                </div>
                <div class="col-lg-3 col-md-6 mt-5 mt-lg-0">
                    <div class="count-box">
                        <i class="fas fa-flask"></i>
                        <span data-purecounter-start="0" data-purecounter-end="0" data-purecounter-duration="1" class="purecounter"></span>
                        <p>Tertangani (Hari Ini)</p>
                    </div>
                </div>
                <div class="col-lg-3 col-md-6 mt-5 mt-lg-0">
                    <div class="count-box">
                        <i class="fas fa-award"></i>
                        <span data-purecounter-start="0" data-purecounter-end="0" data-purecounter-duration="1" class="purecounter"></span>
                        <p>Total Pasien</p>
                    </div>
                </div>
            </div>
        </div>
    </section>
  
  	<!-- PERHATIKAN FORM INI -->
    <section id="appointment" class="appointment section-bg">
      	<!-- LOAD COMPONENT APPOINTMENT DARI FOLDER RESOURCES/VIEWS/LIVEWIRE/ORDER -->
        @livewire('order.appointment')
    </section>
</div>

Note: Informasi tentang jumlah pasien, antrian dan lain-lain, akan kita masukkan datanya pada artikel berikutnya. Fokus utama kita adalah form pasien mengambil antrian.

Component appointment yang kita load di atas belum tersedia, tapi sebelum melanjutkan, kita sesuaikan konfigurasi dari Livewire terlebih dahulu. Buka file config/livewire.php dan modifikasi key layout

'layout' => 'layouts.frontend',

Note: Hal ini dilakukan untuk menyesuaikan master layout kita, karena secara default, Livewire mengenali master layout dengan nama app.

Lanjut, buat component baru dengan command

php artisan make:livewire Order.Appointment

Perintah di atas akan menghasilkan output

CLASS: app/Http/Livewire/Order/Appointment.php
VIEW:  resources/views/livewire/order/appointment.blade.php

Buka file Livewire/Order/Appointment.php dan modifikasi method render() menjadi

public function render()
{
    $days = [
        [
            'day' => Carbon::now()->addDays(1)->format('Y-m-d'),
            'label' => Carbon::now()->addDays(1)->format('l, d F Y')
        ],
        [
            'day' => Carbon::now()->addDays(2)->format('Y-m-d'),
            'label' => Carbon::now()->addDays(2)->format('l, d F Y')
        ]
    ];
    return view('livewire.order.appointment', compact('days'));
}

Note: Pasien hanya bisa mengambil antrian atau membuat janji temu maksimal 2 hari sebelum jadwal kunjungan.

Jangan lupa tambahkan use statement

use Carbon\Carbon;

Buka file resources/views/livewire/order/appointment.blade.php dan tambahkan code

<div>
    <div class="container">
        <div class="section-title">
            <h2>Buat Janji</h2>
            <p>Daftarkan kunjungan Anda dengan mengambil antrian online</p>
        </div>

      	<!-- KETIKA FORM DISUBMIT, MAKA AKAN MENJALANKAN METHOD STORE -->
        <form wire:submit.prevent="store" role="form" class="php-email-form">
          
          	<!-- MENAMPILKAN NOTIFIKASI BERHASIL/GAGAL VIA FLASH MESSAGE -->
            @if (session()->has('success'))
                <div class="alert alert-success">
                    {{ session('success') }}
                </div>
            @endif
            @if (session()->has('error'))
                <div class="alert alert-danger">
                    {{ session('error') }}
                </div>
            @endif

            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="">Tanggal/Hari</label>
                        <select wire:model="day" wire:change="getTimeSlot($event.target.value)" class="form-control">
                            <option value="">Pilih</option>
                            @foreach ($days as $row)
                            <option value="{{ $row['day'] }}">{{ $row['label'] }}</option>
                            @endforeach
                        </select>
                        @error('day') <p class="text-danger">{{ $message }}</p> @enderror
                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="">Waktu Kedatangan</label>
                        <select wire:model="slot" class="form-control">
                            <option value="">Pilih</option>
                            @foreach ($timeSlot as $val)
                                <option value="{{ $val->id }}" {{ ($val->orders_count >= $val->quota) ? 'disabled="disabled"':'' }}>
                                    {{ $val->name }} ({{ $val->orders_count }}/{{ $val->quota }} Kuota)
                                </option>
                            @endforeach
                        </select>
                        @error('slot') <p class="text-danger">{{ $message }}</p> @enderror
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="">Nama Lengkap</label>
                <input type="text" wire:model="name" class="form-control">
                @error('name') <p class="text-danger">{{ $message }}</p> @enderror
            </div>
            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="">Umur</label>
                        <input type="text"  wire:model="age" class="form-control">
                        @error('age') <p class="text-danger">{{ $message }}</p> @enderror
                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="">Whatsapp</label>
                        <input type="text" wire:model="phone_number" class="form-control">
                        @error('phone_number') <p class="text-danger">{{ $message }}</p> @enderror
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="">Keperluan</label>
                <select wire:model="note" onchange="showHideNote()" id="note" class="form-control">
                    <option value="">Pilih</option>
                    <option value="Kontrol Hamil">Kontrol Hamil</option>
                    <option value="Promil">Promil</option>
                    <option value="Lain-Lain">Lain-Lain</option>
                </select>
                @error('note') <p class="text-danger">{{ $message }}</p> @enderror
            </div>
            <div class="form-group" id="note_form" style="display: none">
                <label for="">Catatan</label>
                <textarea wire:model="note_additional" class="form-control" id="note_additional" cols="5" rows="5"></textarea>
                @error('note_additional') <p class="text-danger">{{ $message }}</p> @enderror
            </div>
            
            <div class="text-center">
                <button type="submit">Buat Janji</button>
            </div>
        </form>
    </div>

    <script>
        function showHideNote() {
            getValue = document.getElementById("note").value;
            if (getValue == 'Lain-Lain') {
                document.getElementById('note_form').style.display = 'block';
            } else {
                document.getElementById('note_form').style.display = 'none';
            }
        }
    </script>
</div>

Penjelasan: Ada beberapa hal yang menjadi catatan, diantaranya:

  1. wire:model: Jika Anda pengguna Vue.js, maka directive ini cara kerjanya hampir sama dengan v-model. Intinya, Livewire akan mengenali input-an setiap form input ke dalam variable yang kita sebut sebagai class property.
  2. wire:change="getTimeSlot($event.target.value)": ketika tanggal dipilih dari select option, maka fungsi getTimeSlot() akan dijalankan untuk me-load kategori slot (waktu pelayanan yang tersedia).
  3. Custom js dengan method showHideNote() bertugas untuk hide/show form catatan berdasarkan pilihan Keperluan dari Pasien.

Masing-masing wire:model terhubung dengan property-nya, maka buka file Livewire/Order/Appointment.php dan tambahkan code berikut di atas method render()

<?php

namespace App\Http\Livewire\Order;

use Livewire\Component;
use Carbon\Carbon;

class Appointment extends Component
{
  	//TAMBAHKAN CODE INI
    public $timeSlot = []; //UNTUK MENYIMPAN DATA KATEGORI SLOT YANG TERSEDIA BERDASARKAN PILIHAN HARI/TANGGAL

    public $day;
    public $slot;
    public $name;
    public $age;
    public $phone_number;
    public $note;
    public $note_additional;
    //TAMBAHKAN CODE INI

    public function render()
    {
        $days = [
            [
                'day' => Carbon::now()->addDays(1)->format('Y-m-d'),
                'label' => Carbon::now()->addDays(1)->format('l, d F Y')
            ],
            [
                'day' => Carbon::now()->addDays(2)->format('Y-m-d'),
                'label' => Carbon::now()->addDays(2)->format('l, d F Y')
            ]
        ];
        return view('livewire.order.appointment', compact('days'));
    }

Masih dengan file yang sama, tambahkan method getTimeSlot() untuk mengolah data kategori slot

public function getTimeSlot($value)
{
  	//QUERY UNTUK MENGAMBIL WAKTU LAYANAN YANG TERSEDIA
    //WITHCOUNT UNTUK MENGAMBIL TOTAL PASIEN YANG SUDAH MENDAFTAR
    $getTimeSlot = DailySlot::withCount(['orders' => function($query) use($value) {
            //DENGAN FILTER HARI YANG DIPILIH PASIEN
            $query->where('day', $value);
        }])->where('is_active', 1)
        ->orderBy('created_at', 'DESC')
        ->get();

    //HASIL QUERY DI ATAS AKAN DISIMPAN KEDALAM PROPERTY timeSlot YANG TELAH KITA LOOPING PADA FORM Waktu Kedatangan
    $this->timeSlot = $getTimeSlot;
}

Jangan lupa tambahkan use statement

use App\Models\DailySlot;

Tugas terakhir adalah meng-handle data ketika pasien klik submit. Dengan yang file yang sama, tambahkan method store()

//RULES VALIDASI
protected $rules = [
    'day' => 'required|string',
    'slot' => 'required|exists:daily_slots,id',
    'name' => 'required|string|max:100',
    'age' => 'required|numeric|digits:2',
    'phone_number' => 'required|numeric',
    'note' => 'required|string',
    'note_additional' => 'nullable|string|max:200'
];

public function store()
{
    //MENJALANKAN VALIDASI
    $this->validate();

    try {
        //MEMBUAT QUERY BERDASARKAN WAKTU KEDATANGAN YANG DIPLIH BESERTA TOTAL PENDAFTAR PADA HARI YANG TELAH DIPILIH
        $dailySlot = DailySlot::withCount(['orders' => function($query) {
                $query->where('day', $this->day);
            }])
            ->where('id', $this->slot)
            ->first();
      
        //JIKA PENDAFTAR TELAH MELEBIHI ATAU SAMA DENGAN QUOTA YANG TELAH DITETAPKAN
        if ($dailySlot->orders_count >= $dailySlot->quota) {
            //MAKA KOSONGKAN 3 PROPERTY DI BAWAH INI
            $this->timeSlot = [];
            $this->day = '';
            $this->slot = '';

            //LALU BERIKAN NOTIFIKASI KEPADA PASIEN
            return session()->flash('error', 'Kuota pasien telah mencapai batas maksimum');
        };

        //JIKA QUOTA MASIH TERSEDIA, MAKA SIMPAN KE DALAM DATABASE
        $order = Order::create([
            'daily_slot_id' => $this->slot,
            'day' => $this->day,
            'name' => $this->name,
            'age' => $this->age,
            'phone_number' => $this->phone_number,
            'note' => $this->note . ' ' . $this->note_additional,
            'status' => 0
        ]);

        //LALU KOSONGKAN SEMUA PROPERTY
        $this->timeSlot = [];
        $this->day = '';
        $this->slot = '';
        $this->name = '';
        $this->age = '';
        $this->phone_number = '';
        $this->note = '';
        $this->note_additional = '';

        //BERIKAN NOTIFIKASI BAHWA PASIEN TELAH MENDAPATKAN NOMOR ANTRIAN
        return session()->flash('success', 'Nomor Antrian Anda Adalah: ' . $order->order_id);
    } catch (\Exception $e) {
        //JIKA ERROR, TAMPILKAN NOTIFIKASI ERROR
        return session()->flash('error', $e->getMessage());
    }
}

Jangan lupa tambahkan use statement

use App\Models\Order;

Model DailySlot me-load relationships dengan nama orders, maka kita harus mendefinisikan relasi tersebut. Buka file DailySlot.php dan modifikasi menjadi

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class DailySlot extends Model
{
    use HasFactory;
    protected $fillable = ['name', 'quota', 'is_active'];

    //TAMBAHKAN CODE INI
    public function orders()
    {
        return $this->hasMany(Order::class);
    }
}

Format order_id akan kita ambil dari 4 digit terakhir nomor Telp dan 2 digit pertama Nama Pasien. Karena data yang butuhkan terpenuhi, maka kita buat Accessor tanpa harus menambahkan field order_id ke dalam table orders. Adapun nomor Telp akan menggunakan format 62xxxxx. Buka file app/Models/Order.php dan modifikasi menjadi

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    use HasFactory;
    protected $guarded = [];
    protected $appends = ['order_id'];

    //ACCESSOR UNTUK FORMAT ORDER_ID
    public function getOrderIdAttribute()
    {
        return substr($this->phone_number, -4) . '-' . strtoupper($this->name[0] . $this->name[1]);
    }

    //MUTATORS UNTUK MENGUBAH FORMAT NOMOR TELP
    public function setPhoneNumberAttribute($value)
    {
        //JIKA KARAKTER PERTAMA ADALAH 0 MAKA AKAN DIREPLACE DENGA 62
        $value = $value[0] == 0 ? '62' . substr($value, 1):$value;
        $this->attributes['phone_number'] = $value;
    }
}

Ada yang ketinggalan, filed day belum ada di dalam table orders, dari command line, generate migration baru dengan command

php artisan make:migration add_field_day_to_orders_table

Buka file migration yang baru saja dibuat dan modifikasi menjadi

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->date('day')->nullable()->after('daily_slot_id');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->dropColumn('day');
        });
    }
};

Eksekusi migration di atas dengan command php artisan migrate.

Aplikasi Booking Online Laravel 9 & Livewire - Livewire Store

Kesimpulan

Sepanjang artikel ini kita telah belajar beberapa hal tentang Laravel 9 & Livewire, diantaranya:

  1. Blade templating & Livewire
  2. Membuat Livewire Component
  3. Rendering Component
  4. Menggunakan event submit dan change dari Livewire
  5. Livewire validation
  6. Livewire properties
  7. Livewire flash message
  8. Handle user input dan menyimpan ke database
  9. Membuat relationships dari table daily_slot ke table orders
  10. Accessor dan Mutator

Adapun source code dari serial ini bisa Anda temukan di Github.

Category:
Share:

Comments