đŸ’ģ Pemrograman Web 2
🎓 Pertemuan
Pertemuan 14: Authentication & Transaksi Peminjaman

MODUL PEMROGRAMAN WEBSITE 2

Mata Kuliah: Pemrograman Website 2
Kode MK: INF2419
SKS: 3 (Praktikum)
Semester: Genap 2025/2026
Program Studi: Informatika
Fakultas: FEBI / Saintek
Universitas: UIN K.H. Abdurrahman Wahid Pekalongan

Dosen Pengampu: Mohammad Reza Maulana, M.Kom
NIP: 199110082025051002

Pertemuan: 14 dari 16
Durasi: 150 menit (3 × 50 menit)
Studi Kasus Berkelanjutan: Sistem Manajemen Perpustakaan


PERTEMUAN 14

AUTHENTICATION & TRANSAKSI PEMINJAMAN

A. INFORMASI PERTEMUAN

AspekKeterangan
Capaian Pembelajaran Lulusan (CPL)CPL06: Mempunyai pengetahuan dalam mengembangkan algoritma/metode yang diimplementasikan dalam perangkat lunak.
Capaian Pembelajaran Mata Kuliah (CPMK)CPMK06.1: Mampu mengimplementasikan backend web menggunakan PHP dan Laravel yang terintegrasi dengan database.
Sub-CPMKSub-CPMK06.1.3: Menerapkan autentikasi dan proteksi halaman pada aplikasi web.
Indikator PencapaianMahasiswa mampu:
1. Menginstall dan konfigurasi Laravel Breeze
2. Implementasi sistem login dan register
3. Memahami dan menggunakan middleware autentikasi
4. Membuat relasi database (hasMany, belongsTo)
5. Membuat migration tabel transaksi
6. Implementasi logika peminjaman buku
7. Handle update stok buku otomatis
8. Menampilkan data relasi di view
Alokasi Waktuâ€ĸ Teori: 60 menit
â€ĸ Praktikum: 90 menit
â€ĸ Total: 150 menit (3 × 50 menit)

B. PENDAHULUAN

1. Deskripsi Singkat

Pertemuan keempatbelas ini membahas dua topik penting: autentikasi pengguna dan transaksi peminjaman buku. Mahasiswa akan belajar mengimplementasikan sistem login/register menggunakan Laravel Breeze, melindungi halaman dengan middleware, serta membuat fitur transaksi peminjaman yang melibatkan relasi antar tabel (buku, anggota, transaksi). Transaksi peminjaman akan otomatis mengurangi stok buku dan mencatat siapa yang meminjam.

2. Keterkaitan dengan Pertemuan Lain

Pertemuan ini mengintegrasikan semua yang telah dipelajari sebelumnya:

  • Pertemuan 10: Model Buku & Anggota sebagai entitas utama
  • Pertemuan 11-13: CRUD operations untuk Buku & Anggota
  • Pertemuan 14: [SEKARANG] Authentication + Transaksi + Relasi Database
  • Pertemuan 15: Integrasi lengkap sistem + pengembalian buku

Evolusi Sistem:

P10-13: CRUD Independent
   ├─ Buku (independent)
   └─ Anggota (independent)

P14: Integration Layer
   ├─ Authentication (protect pages)
   ├─ Transaksi (relate Buku & Anggota)
   └─ Business Logic (update stok)

P15: Complete System
   └─ Dashboard, Reports, Return System

3. Manfaat Pembelajaran

  1. Mampu mengamankan aplikasi web dengan autentikasi
  2. Memahami konsep middleware untuk proteksi route
  3. Dapat membuat relasi antar tabel database
  4. Terampil implementasi business logic kompleks
  5. Siap mengembangkan sistem transaksional

4. Relevansi dengan Studi Kasus

Dalam sistem perpustakaan, authentication & transaksi digunakan untuk:

  • Authentication: Login admin/petugas perpustakaan
  • Middleware: Proteksi halaman CRUD (hanya user login yang bisa akses)
  • Transaksi: Mencatat peminjaman buku oleh anggota
  • Relasi: Menghubungkan Buku, Anggota, dan Transaksi
  • Business Logic: Update stok buku otomatis saat peminjaman

C. MATERI TEORI

1. Laravel Authentication

a. Apa itu Authentication?

Authentication (autentikasi) adalah proses memverifikasi identitas pengguna. Dalam aplikasi web, ini biasanya berupa login dengan username/email dan password.

Authentication vs Authorization:

AuthenticationAuthorization
"Siapa kamu?""Apa yang boleh kamu lakukan?"
Login/RegisterPermissions/Roles
Verifikasi identitasVerifikasi hak akses
Contoh: Email + PasswordContoh: Admin dapat hapus user

b. Laravel Breeze

Laravel Breeze adalah starter kit sederhana untuk autentikasi yang menyediakan:

  • Login
  • Register
  • Password Reset
  • Email Verification (opsional)
  • Profile Management
  • Blade views sudah jadi

Alternatif Autentikasi Laravel:

  • Laravel Breeze - Sederhana, minimal (recommended untuk belajar)
  • Laravel Jetstream - Kompleks, full-featured (teams, 2FA, dll)
  • Laravel Fortify - Backend-only (untuk API)
  • Manual - Build sendiri dari scratch

Instalasi Laravel Breeze:

# Install Breeze via Composer
composer require laravel/breeze --dev
 
# Install Breeze scaffolding (Blade stack)
php artisan breeze:install blade
 
# Install npm dependencies & compile
npm install
npm run dev
 
# Migrate database
php artisan migrate

Files yang di-generate Breeze:

app/Http/Controllers/Auth/
├── AuthenticatedSessionController.php  (Login)
├── RegisteredUserController.php        (Register)
├── PasswordResetLinkController.php     (Forgot Password)
└── ...

resources/views/auth/
├── login.blade.php
├── register.blade.php
└── ...

routes/auth.php  (Auth routes)

c. User Model & Migration

Default User Migration:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

User Model:

class User extends Authenticatable
{
    use HasFactory, Notifiable;
 
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
 
    protected $hidden = [
        'password',
        'remember_token',
    ];
 
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

2. Middleware

a. Apa itu Middleware?

Middleware adalah filter HTTP request sebelum sampai ke controller.

Analogi: Middleware seperti satpam di gedung. Sebelum masuk (controller), request harus melewati pemeriksaan (middleware).

Request → Middleware → Controller → Response
          ↑
          Check: User login? 
          - Ya: lanjut ke controller
          - Tidak: redirect ke login

b. Jenis-jenis Middleware

1. Auth Middleware (auth):

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware('auth');
 
// User harus login, jika tidak redirect ke /login

2. Guest Middleware (guest):

Route::get('/login', function () {
    return view('auth.login');
})->middleware('guest');
 
// User harus belum login, jika sudah login redirect ke /home

3. Verified Middleware (verified):

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified']);
 
// User harus login DAN email verified

c. Cara Menggunakan Middleware

Method 1: Route Middleware

// Single route
Route::get('/dashboard', [DashboardController::class, 'index'])
     ->middleware('auth');
 
// Multiple routes
Route::middleware(['auth'])->group(function () {
    Route::resource('buku', BukuController::class);
    Route::resource('anggota', AnggotaController::class);
});

Method 2: Controller Constructor

class BukuController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
        // Semua method di controller ini butuh auth
    }
}

Method 3: Route Group

Route::middleware(['auth'])->prefix('admin')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::resource('buku', BukuController::class);
    Route::resource('anggota', AnggotaController::class);
});

d. Auth Helper Functions

Check if user authenticated:

// In Controller
if (auth()->check()) {
    // User is logged in
}
 
if (auth()->guest()) {
    // User is NOT logged in
}
 
// In Blade
@auth
    <p>Welcome, {{ auth()->user()->name }}</p>
@endauth
 
@guest
    <a href="/login">Login</a>
@endguest

Get current user:

// In Controller
$user = auth()->user();
$userId = auth()->id();
 
// In Blade
{{ auth()->user()->name }}
{{ auth()->user()->email }}

Logout:

auth()->logout();
return redirect('/login');

3. Database Relationships

a. Apa itu Relationship?

Database Relationship adalah cara menghubungkan tabel-tabel dalam database.

Jenis Relasi:

  1. One to One - Satu ke satu (User → Profile)
  2. One to Many - Satu ke banyak (Anggota → Transaksi)
  3. Many to Many - Banyak ke banyak (Student → Course)

Dalam Sistem Perpustakaan:

Anggota → memiliki banyak → Transaksi (One to Many)
Buku → dipinjam dalam banyak → Transaksi (One to Many)
Transaksi → belongs to → Anggota (Many to One)
Transaksi → belongs to → Buku (Many to One)

b. One to Many Relationship

Struktur Database:

anggota                    transaksi
├─ id (PK)                 ├─ id (PK)
├─ kode_anggota            ├─ anggota_id (FK) ───┐
├─ nama                    ├─ buku_id (FK)       │
└─ ...                     ├─ tanggal_pinjam     │
                           └─ ...                 │
                                                  │
buku                                              │
├─ id (PK)                                        │
├─ kode_buku                                      │
├─ judul                                          │
└─ ...                                            │
                                                  │
        ┌─────────────────────────────────────────┘
        │
        ├─ Satu Anggota → Banyak Transaksi
        └─ Satu Buku → Banyak Transaksi

Foreign Key Convention:

  • Foreign key column: {model}_id
  • Example: anggota_id, buku_id, user_id

c. Eloquent Relationships

hasMany (One to Many - Parent Side):

// Model Anggota
class Anggota extends Model
{
    public function transaksis()  // plural
    {
        return $this->hasMany(Transaksi::class);
    }
}
 
// Model Buku
class Buku extends Model
{
    public function transaksis()
    {
        return $this->hasMany(Transaksi::class);
    }
}

belongsTo (Many to One - Child Side):

// Model Transaksi
class Transaksi extends Model
{
    public function anggota()  // singular
    {
        return $this->belongsTo(Anggota::class);
    }
    
    public function buku()
    {
        return $this->belongsTo(Buku::class);
    }
}

Usage:

// Get transaksi dari anggota
$anggota = Anggota::find(1);
$transaksis = $anggota->transaksis;  // Collection
 
foreach ($transaksis as $transaksi) {
    echo $transaksi->buku->judul;
}
 
// Get anggota dari transaksi
$transaksi = Transaksi::find(1);
echo $transaksi->anggota->nama;
echo $transaksi->buku->judul;

Eager Loading (N+1 Problem Solution):

// ❌ BAD: N+1 Query Problem
$transaksis = Transaksi::all();  // 1 query
foreach ($transaksis as $transaksi) {
    echo $transaksi->anggota->nama;  // N queries
    echo $transaksi->buku->judul;    // N queries
}
 
// ✅ GOOD: Eager Loading
$transaksis = Transaksi::with(['anggota', 'buku'])->get();  // 3 queries total
foreach ($transaksis as $transaksi) {
    echo $transaksi->anggota->nama;
    echo $transaksi->buku->judul;
}

4. Transaction Logic

a. Business Rules Peminjaman

Rules untuk Peminjaman Buku:

  1. ✅ User harus login (Authentication)
  2. ✅ Buku harus tersedia (stok > 0)
  3. ✅ Anggota harus aktif
  4. ✅ Satu peminjaman = 1 buku
  5. ✅ Maksimal peminjaman = 7 hari
  6. ✅ Stok buku berkurang otomatis
  7. ✅ Status transaksi: Dipinjam/Dikembalikan

Workflow Peminjaman:

1. User pilih Buku
   ↓
2. User pilih Anggota
   ↓
3. System check:
   - Buku tersedia?
   - Anggota aktif?
   ↓
4. Create Transaksi
   ↓
5. Update Stok Buku (-1)
   ↓
6. Redirect dengan success message

b. Migration Transaksi

Schema::create('transaksis', function (Blueprint $table) {
    $table->id();
    $table->string('kode_transaksi', 20)->unique();
    $table->foreignId('anggota_id')->constrained('anggota')->onDelete('cascade');
    $table->foreignId('buku_id')->constrained('buku')->onDelete('cascade');
    $table->date('tanggal_pinjam');
    $table->date('tanggal_kembali');
    $table->date('tanggal_dikembalikan')->nullable();
    $table->enum('status', ['Dipinjam', 'Dikembalikan'])->default('Dipinjam');
    $table->integer('denda')->default(0);
    $table->text('keterangan')->nullable();
    $table->timestamps();
});

Penjelasan Field:

  • kode_transaksi: Unique identifier (TRX-001, TRX-002)
  • anggota_id: Foreign key ke tabel anggota
  • buku_id: Foreign key ke tabel buku
  • tanggal_pinjam: Tanggal peminjaman
  • tanggal_kembali: Tanggal harus dikembalikan (tanggal_pinjam + 7 hari)
  • tanggal_dikembalikan: Tanggal actual dikembalikan (nullable)
  • status: Dipinjam atau Dikembalikan
  • denda: Denda keterlambatan (default 0)

c. Model Transaksi

class Transaksi extends Model
{
    protected $fillable = [
        'kode_transaksi',
        'anggota_id',
        'buku_id',
        'tanggal_pinjam',
        'tanggal_kembali',
        'tanggal_dikembalikan',
        'status',
        'denda',
        'keterangan',
    ];
 
    protected $casts = [
        'tanggal_pinjam' => 'date',
        'tanggal_kembali' => 'date',
        'tanggal_dikembalikan' => 'date',
    ];
 
    // Relationships
    public function anggota()
    {
        return $this->belongsTo(Anggota::class);
    }
 
    public function buku()
    {
        return $this->belongsTo(Buku::class);
    }
 
    // Accessor untuk status badge
    public function getStatusBadgeAttribute()
    {
        return $this->status == 'Dipinjam' 
            ? '<span class="badge bg-warning">Dipinjam</span>'
            : '<span class="badge bg-success">Dikembalikan</span>';
    }
 
    // Accessor untuk durasi peminjaman
    public function getDurasiPeminjamanAttribute()
    {
        if ($this->tanggal_dikembalikan) {
            return $this->tanggal_pinjam->diffInDays($this->tanggal_dikembalikan);
        }
        return $this->tanggal_pinjam->diffInDays(now());
    }
 
    // Accessor untuk terlambat
    public function getTerlambatAttribute()
    {
        if ($this->status == 'Dikembalikan') {
            $hariTerlambat = $this->tanggal_kembali->diffInDays($this->tanggal_dikembalikan, false);
            return $hariTerlambat > 0 ? $hariTerlambat : 0;
        }
        
        if (now() > $this->tanggal_kembali) {
            return now()->diffInDays($this->tanggal_kembali);
        }
        
        return 0;
    }
}

d. Logic Update Stok

// Saat Peminjaman
public function store(Request $request)
{
    DB::transaction(function () use ($request) {
        // 1. Create transaksi
        Transaksi::create([
            'kode_transaksi' => $this->generateKodeTransaksi(),
            'anggota_id' => $request->anggota_id,
            'buku_id' => $request->buku_id,
            'tanggal_pinjam' => $request->tanggal_pinjam,
            'tanggal_kembali' => Carbon::parse($request->tanggal_pinjam)->addDays(7),
            'status' => 'Dipinjam',
        ]);
        
        // 2. Update stok buku (kurang 1)
        $buku = Buku::findOrFail($request->buku_id);
        $buku->decrement('stok');
    });
}
 
// Saat Pengembalian
public function kembalikan($id)
{
    DB::transaction(function () use ($id) {
        $transaksi = Transaksi::findOrFail($id);
        
        // 1. Update transaksi
        $transaksi->update([
            'status' => 'Dikembalikan',
            'tanggal_dikembalikan' => now(),
            'denda' => $this->hitungDenda($transaksi),
        ]);
        
        // 2. Update stok buku (tambah 1)
        $transaksi->buku->increment('stok');
    });
}

5. Session Management

a. Apa itu Session?

Session adalah cara menyimpan data user di server selama user berinteraksi dengan aplikasi.

Session vs Cookie:

SessionCookie
Data di serverData di browser
Lebih amanKurang aman
Hilang saat logoutBisa persistent
Size besar OKSize terbatas (4KB)

b. Session di Laravel

Set Session:

// Method 1: global helper
session(['key' => 'value']);
 
// Method 2: put
session()->put('key', 'value');
 
// Method 3: flash (one-time)
session()->flash('success', 'Data berhasil disimpan!');

Get Session:

$value = session('key');
$value = session('key', 'default');
$value = session()->get('key');

Check Session:

if (session()->has('key')) {
    // Session exists
}
 
if (session()->exists('key')) {
    // Session exists (even if null)
}

Delete Session:

session()->forget('key');
session()->flush(); // Clear all

c. Flash Messages

Flash messages adalah session yang auto-delete setelah di-read.

// Controller
return redirect()->route('buku.index')
                 ->with('success', 'Buku berhasil ditambahkan!');
 
// Blade
@if (session('success'))
    <div class="alert alert-success">
        {{ session('success') }}
    </div>
@endif

D. PRAKTIKUM

1. Tujuan Praktikum

  1. Install dan konfigurasi Laravel Breeze
  2. Setup authentication (login/register)
  3. Protect routes dengan middleware
  4. Membuat migration dan model Transaksi
  5. Implementasi relasi database
  6. Membuat form peminjaman buku
  7. Logic update stok otomatis

2. PRAKTIKUM 1: Install Laravel Breeze

Tujuan

Menginstall Laravel Breeze untuk sistem autentikasi.

Langkah-langkah

a. Install Laravel Breeze

# Install Breeze via Composer
composer require laravel/breeze --dev
 
# Install Breeze scaffolding (Blade)
php artisan breeze:install blade
 
# Pilih: Blade with Alpine
# Dark mode support: yes
# PHPUnit: yes

Output:

Breeze scaffolding installed successfully.
Please execute the "npm install" and "npm run dev" commands to build your assets.

b. Install Dependencies & Build Assets

# Install npm packages
npm install
 
# Build assets
npm run dev

c. Run Migration

php artisan migrate

Tables created:

  • users
  • password_reset_tokens
  • sessions

d. Test Authentication Pages

  1. Jalankan server: php artisan serve
  2. Akses: http://localhost:8000/register
  3. Verifikasi: Form register muncul dengan styling Tailwind

e. Register User Pertama

  1. Buka: http://localhost:8000/register
  2. Isi form:
    • Name: Admin Perpustakaan
    • Email: admin@perpustakaan.com
    • Password: password123
    • Confirm Password: password123
  3. Klik "Register"
  4. Verifikasi: Redirect ke /dashboard

f. Test Login

  1. Logout: Klik profile → Logout
  2. Akses: http://localhost:8000/login
  3. Login dengan credential tadi
  4. Verifikasi: Berhasil login ke dashboard

3. PRAKTIKUM 2: Customize Breeze untuk Sistem Perpustakaan

Tujuan

Menyesuaikan tampilan dan navigation Breeze dengan sistem perpustakaan.

Langkah-langkah

a. Update Navigation

File: resources/views/layouts/navigation.blade.php

Ganti konten dengan navigation kita:

<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex justify-between h-16">
            <div class="flex">
                <!-- Logo -->
                <div class="shrink-0 flex items-center">
                    <a href="{{ route('dashboard') }}">
                        <x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
                        <span class="ml-2 text-xl font-bold text-gray-800">Perpustakaan</span>
                    </a>
                </div>
 
                <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                        {{ __('Dashboard') }}
                    </x-nav-link>
                    <x-nav-link :href="route('buku.index')" :active="request()->routeIs('buku.*')">
                        {{ __('Buku') }}
                    </x-nav-link>
                    <x-nav-link :href="route('anggota.index')" :active="request()->routeIs('anggota.*')">
                        {{ __('Anggota') }}
                    </x-nav-link>
                    <x-nav-link :href="route('transaksi.index')" :active="request()->routeIs('transaksi.*')">
                        {{ __('Transaksi') }}
                    </x-nav-link>
                </div>
            </div>
 
            <!-- Settings Dropdown -->
            <div class="hidden sm:flex sm:items-center sm:ml-6">
                <x-dropdown align="right" width="48">
                    <x-slot name="trigger">
                        <button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
                            <div>{{ Auth::user()->name }}</div>
 
                            <div class="ml-1">
                                <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                                    <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                </svg>
                            </div>
                        </button>
                    </x-slot>
 
                    <x-slot name="content">
                        <x-dropdown-link :href="route('profile.edit')">
                            {{ __('Profile') }}
                        </x-dropdown-link>
 
                        <!-- Authentication -->
                        <form method="POST" action="{{ route('logout') }}">
                            @csrf
 
                            <x-dropdown-link :href="route('logout')"
                                    onclick="event.preventDefault();
                                                this.closest('form').submit();">
                                {{ __('Log Out') }}
                            </x-dropdown-link>
                        </form>
                    </x-slot>
                </x-dropdown>
            </div>
 
            <!-- Hamburger -->
            <div class="-mr-2 flex items-center sm:hidden">
                <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
                    <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                        <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
        </div>
    </div>
 
    <!-- Responsive Navigation Menu -->
    <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
        <div class="pt-2 pb-3 space-y-1">
            <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                {{ __('Dashboard') }}
            </x-responsive-nav-link>
            <x-responsive-nav-link :href="route('buku.index')" :active="request()->routeIs('buku.*')">
                {{ __('Buku') }}
            </x-responsive-nav-link>
            <x-responsive-nav-link :href="route('anggota.index')" :active="request()->routeIs('anggota.*')">
                {{ __('Anggota') }}
            </x-responsive-nav-link>
            <x-responsive-nav-link :href="route('transaksi.index')" :active="request()->routeIs('transaksi.*')">
                {{ __('Transaksi') }}
            </x-responsive-nav-link>
        </div>
 
        <!-- Responsive Settings Options -->
        <div class="pt-4 pb-1 border-t border-gray-200">
            <div class="px-4">
                <div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
            </div>
 
            <div class="mt-3 space-y-1">
                <x-responsive-nav-link :href="route('profile.edit')">
                    {{ __('Profile') }}
                </x-responsive-nav-link>
 
                <!-- Authentication -->
                <form method="POST" action="{{ route('logout') }}">
                    @csrf
 
                    <x-responsive-nav-link :href="route('logout')"
                            onclick="event.preventDefault();
                                        this.closest('form').submit();">
                        {{ __('Log Out') }}
                    </x-responsive-nav-link>
                </form>
            </div>
        </div>
    </div>
</nav>

b. Update Dashboard

File: resources/views/dashboard.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard Perpustakaan') }}
        </h2>
    </x-slot>
 
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <!-- Statistics Cards -->
            <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div class="p-6">
                        <div class="flex items-center">
                            <div class="flex-shrink-0 bg-blue-500 rounded-md p-3">
                                <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
                                </svg>
                            </div>
                            <div class="ml-4">
                                <p class="text-sm font-medium text-gray-600">Total Buku</p>
                                <p class="text-2xl font-semibold text-gray-900">{{ \App\Models\Buku::count() }}</p>
                            </div>
                        </div>
                    </div>
                </div>
 
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div class="p-6">
                        <div class="flex items-center">
                            <div class="flex-shrink-0 bg-green-500 rounded-md p-3">
                                <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
                                </svg>
                            </div>
                            <div class="ml-4">
                                <p class="text-sm font-medium text-gray-600">Total Anggota</p>
                                <p class="text-2xl font-semibold text-gray-900">{{ \App\Models\Anggota::count() }}</p>
                            </div>
                        </div>
                    </div>
                </div>
 
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div class="p-6">
                        <div class="flex items-center">
                            <div class="flex-shrink-0 bg-yellow-500 rounded-md p-3">
                                <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
                                </svg>
                            </div>
                            <div class="ml-4">
                                <p class="text-sm font-medium text-gray-600">Dipinjam</p>
                                <p class="text-2xl font-semibold text-gray-900">{{ \App\Models\Transaksi::where('status', 'Dipinjam')->count() }}</p>
                            </div>
                        </div>
                    </div>
                </div>
 
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div class="p-6">
                        <div class="flex items-center">
                            <div class="flex-shrink-0 bg-purple-500 rounded-md p-3">
                                <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                                </svg>
                            </div>
                            <div class="ml-4">
                                <p class="text-sm font-medium text-gray-600">Transaksi Hari Ini</p>
                                <p class="text-2xl font-semibold text-gray-900">{{ \App\Models\Transaksi::whereDate('created_at', today())->count() }}</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
 
            <!-- Quick Actions -->
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg mb-6">
                <div class="p-6">
                    <h3 class="text-lg font-semibold mb-4">Aksi Cepat</h3>
                    <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
                        <a href="{{ route('buku.create') }}" class="flex items-center p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition">
                            <svg class="h-8 w-8 text-blue-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
                            </svg>
                            <span class="font-medium text-blue-900">Tambah Buku</span>
                        </a>
                        
                        <a href="{{ route('anggota.create') }}" class="flex items-center p-4 bg-green-50 rounded-lg hover:bg-green-100 transition">
                            <svg class="h-8 w-8 text-green-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
                            </svg>
                            <span class="font-medium text-green-900">Tambah Anggota</span>
                        </a>
                        
                        <a href="{{ route('transaksi.create') }}" class="flex items-center p-4 bg-yellow-50 rounded-lg hover:bg-yellow-100 transition">
                            <svg class="h-8 w-8 text-yellow-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
                            </svg>
                            <span class="font-medium text-yellow-900">Pinjam Buku</span>
                        </a>
                        
                        <a href="{{ route('transaksi.index') }}" class="flex items-center p-4 bg-purple-50 rounded-lg hover:bg-purple-100 transition">
                            <svg class="h-8 w-8 text-purple-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
                            </svg>
                            <span class="font-medium text-purple-900">Lihat Transaksi</span>
                        </a>
                    </div>
                </div>
            </div>
 
            <!-- Recent Transactions -->
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6">
                    <h3 class="text-lg font-semibold mb-4">Transaksi Terbaru</h3>
                    <div class="overflow-x-auto">
                        <table class="min-w-full divide-y divide-gray-200">
                            <thead class="bg-gray-50">
                                <tr>
                                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode</th>
                                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Anggota</th>
                                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Buku</th>
                                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tanggal Pinjam</th>
                                    <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
                                </tr>
                            </thead>
                            <tbody class="bg-white divide-y divide-gray-200">
                                @forelse(\App\Models\Transaksi::with(['anggota', 'buku'])->latest()->take(5)->get() as $transaksi)
                                <tr>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
                                        {{ $transaksi->kode_transaksi }}
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                        {{ $transaksi->anggota->nama }}
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                        {{ $transaksi->buku->judul }}
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                                        {{ $transaksi->tanggal_pinjam->format('d M Y') }}
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap">
                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ $transaksi->status == 'Dipinjam' ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800' }}">
                                            {{ $transaksi->status }}
                                        </span>
                                    </td>
                                </tr>
                                @empty
                                <tr>
                                    <td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">
                                        Belum ada transaksi
                                    </td>
                                </tr>
                                @endforelse
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

4. PRAKTIKUM 3: Protect Routes dengan Middleware

Tujuan

Melindungi route CRUD dengan middleware auth.

Langkah-langkah

a. Update routes/web.php

<?php
 
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\BukuController;
use App\Http\Controllers\AnggotaController;
use App\Http\Controllers\TransaksiController;
use Illuminate\Support\Facades\Route;
 
// Public routes (tanpa auth)
Route::get('/', function () {
    return redirect()->route('login');
});
 
// Protected routes (dengan auth middleware)
Route::middleware(['auth'])->group(function () {
    // Dashboard
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
 
    // Profile
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
 
    // Buku - CRUD
    Route::resource('buku', BukuController::class);
 
    // Anggota - CRUD
    Route::resource('anggota', AnggotaController::class);
 
    // Transaksi - CRUD + Custom routes
    Route::resource('transaksi', TransaksiController::class);
    Route::put('/transaksi/{id}/kembalikan', [TransaksiController::class, 'kembalikan'])->name('transaksi.kembalikan');
});
 
require __DIR__.'/auth.php';

b. Test Protected Routes

  1. Logout dari aplikasi
  2. Coba akses: http://localhost:8000/buku
  3. Verifikasi: Redirect otomatis ke /login
  4. Login kembali
  5. Akses: http://localhost:8000/buku
  6. Verifikasi: Halaman buku bisa diakses

5. PRAKTIKUM 4: Migration & Model Transaksi

Tujuan

Membuat tabel transaksi dengan relasi ke buku dan anggota.

Langkah-langkah

a. Generate Migration

php artisan make:migration create_transaksis_table

b. Edit Migration

File: database/migrations/xxxx_create_transaksis_table.php

<?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('transaksis', function (Blueprint $table) {
            $table->id();
            $table->string('kode_transaksi', 20)->unique();
            $table->foreignId('anggota_id')->constrained('anggota')->onDelete('cascade');
            $table->foreignId('buku_id')->constrained('buku')->onDelete('cascade');
            $table->date('tanggal_pinjam');
            $table->date('tanggal_kembali');
            $table->date('tanggal_dikembalikan')->nullable();
            $table->enum('status', ['Dipinjam', 'Dikembalikan'])->default('Dipinjam');
            $table->integer('denda')->default(0);
            $table->text('keterangan')->nullable();
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('transaksis');
    }
};

c. Run Migration

php artisan migrate

d. Generate Model

php artisan make:model Transaksi

e. Edit Model Transaksi

File: app/Models/Transaksi.php

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
 
class Transaksi extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'kode_transaksi',
        'anggota_id',
        'buku_id',
        'tanggal_pinjam',
        'tanggal_kembali',
        'tanggal_dikembalikan',
        'status',
        'denda',
        'keterangan',
    ];
 
    protected $casts = [
        'tanggal_pinjam' => 'date',
        'tanggal_kembali' => 'date',
        'tanggal_dikembalikan' => 'date',
    ];
 
    // Relationship ke Anggota (belongsTo)
    public function anggota()
    {
        return $this->belongsTo(Anggota::class);
    }
 
    // Relationship ke Buku (belongsTo)
    public function buku()
    {
        return $this->belongsTo(Buku::class);
    }
 
    // Accessor untuk durasi peminjaman (hari)
    public function getDurasiPeminjamanAttribute()
    {
        if ($this->tanggal_dikembalikan) {
            return $this->tanggal_pinjam->diffInDays($this->tanggal_dikembalikan);
        }
        return $this->tanggal_pinjam->diffInDays(now());
    }
 
    // Accessor untuk cek terlambat (hari)
    public function getTerlambatAttribute()
    {
        if ($this->status == 'Dikembalikan') {
            if ($this->tanggal_dikembalikan > $this->tanggal_kembali) {
                return $this->tanggal_kembali->diffInDays($this->tanggal_dikembalikan);
            }
            return 0;
        }
        
        if (now() > $this->tanggal_kembali) {
            return $this->tanggal_kembali->diffInDays(now());
        }
        
        return 0;
    }
 
    // Accessor untuk status badge HTML
    public function getStatusBadgeAttribute()
    {
        return $this->status == 'Dipinjam' 
            ? '<span class="badge bg-warning text-dark">Dipinjam</span>'
            : '<span class="badge bg-success">Dikembalikan</span>';
    }
}

f. Update Model Anggota - Tambah Relasi

File: app/Models/Anggota.php

// Tambahkan method ini di class Anggota
public function transaksis()
{
    return $this->hasMany(Transaksi::class);
}

g. Update Model Buku - Tambah Relasi

File: app/Models/Buku.php

// Tambahkan method ini di class Buku
public function transaksis()
{
    return $this->hasMany(Transaksi::class);
}

6. PRAKTIKUM 5: Form Peminjaman Buku

Tujuan

Membuat form peminjaman buku dengan logic update stok.

Langkah-langkah

a. Generate TransaksiController

php artisan make:controller TransaksiController --resource

b. Edit TransaksiController

File: app/Http/Controllers/TransaksiController.php

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\Transaksi;
use App\Models\Buku;
use App\Models\Anggota;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
 
class TransaksiController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $transaksis = Transaksi::with(['anggota', 'buku'])
                               ->latest()
                               ->get();
        
        return view('transaksi.index', compact('transaksis'));
    }
 
    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        // Get only anggota aktif
        $anggotas = Anggota::where('status', 'Aktif')->orderBy('nama')->get();
        
        // Get only buku yang tersedia (stok > 0)
        $bukus = Buku::where('stok', '>', 0)->orderBy('judul')->get();
        
        return view('transaksi.create', compact('anggotas', 'bukus'));
    }
 
    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate([
            'anggota_id' => 'required|exists:anggota,id',
            'buku_id' => 'required|exists:buku,id',
            'tanggal_pinjam' => 'required|date',
            'keterangan' => 'nullable|string',
        ], [
            'anggota_id.required' => 'Anggota wajib dipilih.',
            'buku_id.required' => 'Buku wajib dipilih.',
            'tanggal_pinjam.required' => 'Tanggal pinjam wajib diisi.',
        ]);
        
        try {
            DB::transaction(function () use ($request) {
                // 1. Check stok buku
                $buku = Buku::findOrFail($request->buku_id);
                if ($buku->stok <= 0) {
                    throw new \Exception('Stok buku habis!');
                }
                
                // 2. Generate kode transaksi
                $kodeTransaksi = $this->generateKodeTransaksi();
                
                // 3. Calculate tanggal kembali (7 hari dari tanggal pinjam)
                $tanggalKembali = Carbon::parse($request->tanggal_pinjam)->addDays(7);
                
                // 4. Create transaksi
                Transaksi::create([
                    'kode_transaksi' => $kodeTransaksi,
                    'anggota_id' => $request->anggota_id,
                    'buku_id' => $request->buku_id,
                    'tanggal_pinjam' => $request->tanggal_pinjam,
                    'tanggal_kembali' => $tanggalKembali,
                    'status' => 'Dipinjam',
                    'keterangan' => $request->keterangan,
                ]);
                
                // 5. Update stok buku (kurang 1)
                $buku->decrement('stok');
            });
            
            return redirect()->route('transaksi.index')
                             ->with('success', 'Transaksi peminjaman berhasil dibuat!');
                             
        } catch (\Exception $e) {
            return redirect()->back()
                             ->withInput()
                             ->with('error', 'Gagal membuat transaksi: ' . $e->getMessage());
        }
    }
 
    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        $transaksi = Transaksi::with(['anggota', 'buku'])->findOrFail($id);
        return view('transaksi.show', compact('transaksi'));
    }
 
    /**
     * Kembalikan buku (update status transaksi).
     */
    public function kembalikan(string $id)
    {
        try {
            DB::transaction(function () use ($id) {
                $transaksi = Transaksi::findOrFail($id);
                
                // 1. Update transaksi
                $tanggalDikembalikan = now();
                $denda = $this->hitungDenda($transaksi, $tanggalDikembalikan);
                
                $transaksi->update([
                    'status' => 'Dikembalikan',
                    'tanggal_dikembalikan' => $tanggalDikembalikan,
                    'denda' => $denda,
                ]);
                
                // 2. Update stok buku (tambah 1)
                $transaksi->buku->increment('stok');
            });
            
            return redirect()->route('transaksi.show', $id)
                             ->with('success', 'Buku berhasil dikembalikan!');
                             
        } catch (\Exception $e) {
            return redirect()->back()
                             ->with('error', 'Gagal mengembalikan buku: ' . $e->getMessage());
        }
    }
 
    /**
     * Generate kode transaksi otomatis.
     */
    private function generateKodeTransaksi()
    {
        $lastTransaksi = Transaksi::latest()->first();
        
        if ($lastTransaksi) {
            $lastNumber = intval(substr($lastTransaksi->kode_transaksi, -3));
            $newNumber = $lastNumber + 1;
        } else {
            $newNumber = 1;
        }
        
        return 'TRX-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT);
    }
 
    /**
     * Hitung denda keterlambatan.
     */
    private function hitungDenda($transaksi, $tanggalDikembalikan)
    {
        $hariTerlambat = $transaksi->tanggal_kembali->diffInDays($tanggalDikembalikan, false);
        
        if ($hariTerlambat > 0) {
            // Denda Rp 5.000 per hari
            return $hariTerlambat * 5000;
        }
        
        return 0;
    }
}

c. Buat View Create Transaksi

File: resources/views/transaksi/create.blade.php

@extends('layouts.app')
 
@section('title', 'Transaksi Peminjaman')
 
@section('content')
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header bg-primary text-white">
                <h4 class="mb-0">
                    <i class="bi bi-plus-circle"></i>
                    Form Peminjaman Buku
                </h4>
            </div>
            <div class="card-body">
                <form action="{{ route('transaksi.store') }}" method="POST">
                    @csrf
                    
                    {{-- Pilih Anggota --}}
                    <div class="mb-3">
                        <label for="anggota_id" class="form-label">
                            Pilih Anggota <span class="text-danger">*</span>
                        </label>
                        <select name="anggota_id" 
                                id="anggota_id" 
                                class="form-select @error('anggota_id') is-invalid @enderror">
                            <option value="">-- Pilih Anggota --</option>
                            @foreach($anggotas as $anggota)
                                <option value="{{ $anggota->id }}" {{ old('anggota_id') == $anggota->id ? 'selected' : '' }}>
                                    {{ $anggota->kode_anggota }} - {{ $anggota->nama }}
                                </option>
                            @endforeach
                        </select>
                        @error('anggota_id')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                        <small class="text-muted">Hanya anggota dengan status Aktif yang dapat meminjam</small>
                    </div>
                    
                    {{-- Pilih Buku --}}
                    <div class="mb-3">
                        <label for="buku_id" class="form-label">
                            Pilih Buku <span class="text-danger">*</span>
                        </label>
                        <select name="buku_id" 
                                id="buku_id" 
                                class="form-select @error('buku_id') is-invalid @enderror">
                            <option value="">-- Pilih Buku --</option>
                            @foreach($bukus as $buku)
                                <option value="{{ $buku->id }}" {{ old('buku_id') == $buku->id ? 'selected' : '' }}>
                                    {{ $buku->judul }} - (Stok: {{ $buku->stok }})
                                </option>
                            @endforeach
                        </select>
                        @error('buku_id')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                        <small class="text-muted">Hanya buku dengan stok tersedia yang dapat dipinjam</small>
                    </div>
                    
                    {{-- Tanggal Pinjam --}}
                    <div class="mb-3">
                        <label for="tanggal_pinjam" class="form-label">
                            Tanggal Pinjam <span class="text-danger">*</span>
                        </label>
                        <input type="date" 
                               name="tanggal_pinjam" 
                               id="tanggal_pinjam" 
                               class="form-control @error('tanggal_pinjam') is-invalid @enderror"
                               value="{{ old('tanggal_pinjam', date('Y-m-d')) }}"
                               max="{{ date('Y-m-d') }}">
                        @error('tanggal_pinjam')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                        <small class="text-muted">Tanggal kembali otomatis 7 hari dari tanggal pinjam</small>
                    </div>
                    
                    {{-- Keterangan --}}
                    <div class="mb-3">
                        <label for="keterangan" class="form-label">Keterangan</label>
                        <textarea name="keterangan" 
                                  id="keterangan" 
                                  rows="3" 
                                  class="form-control @error('keterangan') is-invalid @enderror"
                                  placeholder="Keterangan tambahan (opsional)">{{ old('keterangan') }}</textarea>
                        @error('keterangan')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    
                    {{-- Info Box --}}
                    <div class="alert alert-info">
                        <i class="bi bi-info-circle"></i>
                        <strong>Informasi Peminjaman:</strong>
                        <ul class="mb-0 mt-2">
                            <li>Durasi peminjaman: <strong>7 hari</strong></li>
                            <li>Denda keterlambatan: <strong>Rp 5.000/hari</strong></li>
                            <li>Stok buku akan berkurang otomatis setelah peminjaman</li>
                        </ul>
                    </div>
                    
                    <hr>
                    
                    {{-- Buttons --}}
                    <div class="d-flex justify-content-between">
                        <a href="{{ route('transaksi.index') }}" class="btn btn-secondary">
                            <i class="bi bi-arrow-left"></i> Kembali
                        </a>
                        <button type="submit" class="btn btn-primary">
                            <i class="bi bi-save"></i> Proses Peminjaman
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection

d. Buat View Index Transaksi

File: resources/views/transaksi/index.blade.php

@extends('layouts.app')
 
@section('title', 'Daftar Transaksi')
 
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1>
        <i class="bi bi-arrow-left-right"></i>
        Daftar Transaksi Peminjaman
    </h1>
    <a href="{{ route('transaksi.create') }}" class="btn btn-primary">
        <i class="bi bi-plus-circle"></i> Pinjam Buku
    </a>
</div>
 
{{-- Statistik --}}
<div class="row mb-4">
    <div class="col-md-4">
        <div class="card border-primary">
            <div class="card-body">
                <h6 class="text-muted">Total Transaksi</h6>
                <h2>{{ $transaksis->count() }}</h2>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card border-warning">
            <div class="card-body">
                <h6 class="text-muted">Sedang Dipinjam</h6>
                <h2>{{ $transaksis->where('status', 'Dipinjam')->count() }}</h2>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card border-success">
            <div class="card-body">
                <h6 class="text-muted">Sudah Dikembalikan</h6>
                <h2>{{ $transaksis->where('status', 'Dikembalikan')->count() }}</h2>
            </div>
        </div>
    </div>
</div>
 
{{-- Tabel Transaksi --}}
<div class="card">
    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-hover">
                <thead class="table-light">
                    <tr>
                        <th>No</th>
                        <th>Kode Transaksi</th>
                        <th>Anggota</th>
                        <th>Buku</th>
                        <th>Tanggal Pinjam</th>
                        <th>Tanggal Kembali</th>
                        <th>Status</th>
                        <th>Aksi</th>
                    </tr>
                </thead>
                <tbody>
                    @forelse($transaksis as $transaksi)
                    <tr>
                        <td>{{ $loop->iteration }}</td>
                        <td><code>{{ $transaksi->kode_transaksi }}</code></td>
                        <td>{{ $transaksi->anggota->nama }}</td>
                        <td>{{ $transaksi->buku->judul }}</td>
                        <td>{{ $transaksi->tanggal_pinjam->format('d M Y') }}</td>
                        <td>{{ $transaksi->tanggal_kembali->format('d M Y') }}</td>
                        <td>
                            @if($transaksi->status == 'Dipinjam')
                                <span class="badge bg-warning text-dark">Dipinjam</span>
                            @else
                                <span class="badge bg-success">Dikembalikan</span>
                            @endif
                        </td>
                        <td>
                            <a href="{{ route('transaksi.show', $transaksi->id) }}" 
                               class="btn btn-sm btn-info text-white">
                                <i class="bi bi-eye"></i>
                            </a>
                        </td>
                    </tr>
                    @empty
                    <tr>
                        <td colspan="8" class="text-center text-muted">
                            Belum ada transaksi
                        </td>
                    </tr>
                    @endforelse
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection

e. Test Transaksi Peminjaman

  1. Login ke sistem
  2. Akses: http://localhost:8000/transaksi/create
  3. Isi form:
    • Pilih anggota
    • Pilih buku (yang ada stoknya)
    • Tanggal pinjam: hari ini
  4. Klik "Proses Peminjaman"
  5. Verifikasi:
    • Redirect ke index transaksi
    • Flash message muncul
    • Transaksi baru muncul di list
    • Stok buku berkurang 1 (cek di halaman buku)

E. TUGAS

Tugas 1: Fitur Pengembalian Buku (40%)

Instruksi: Implementasi lengkap fitur pengembalian buku dengan perhitungan denda.

Spesifikasi:

  1. View Detail Transaksi dengan button "Kembalikan Buku"

  2. Method kembalikan() di Controller (sudah ada template)

  3. Perhitungan Denda:

    • Denda Rp 5.000/hari
    • Hanya jika terlambat
    • Tampilkan total denda di detail
  4. Update Stok: Stok buku bertambah 1 saat dikembalikan


Tugas 2: Laporan Transaksi (30%)

Instruksi: Buat halaman laporan transaksi dengan filter.

Spesifikasi:

  1. Route: /transaksi/laporan
  2. Filter:
    • Range tanggal (dari-sampai)
    • Status (Semua/Dipinjam/Dikembalikan)
    • Anggota (dropdown)
  3. Tampilan:
    • Tabel transaksi
    • Total transaksi
    • Total denda (jika ada)
  4. Export PDF: Button untuk export laporan ke PDF

Tugas 3: Notifikasi Terlambat (30%)

Instruksi: Tambah fitur notifikasi untuk buku yang terlambat dikembalikan.

Spesifikasi:

  1. Dashboard Widget:

    • Card "Buku Terlambat"
    • Jumlah transaksi yang terlambat
    • List anggota yang terlambat
  2. Badge Terlambat:

    • Di index transaksi, tambah badge "Terlambat" warna merah
    • Tampilkan berapa hari terlambat
  3. Reminder:

    • Di detail transaksi, tampilkan warning jika sudah melewati tanggal kembali

Submission:

  • Format: Link repository GitHub (sertakan screenshot di README)
  • Deadline: Pertemuan 15
  • Upload ke: Ngaji UIN Gusdur (submit link repository GitHub)

F. EVALUASI

1. Kuis Singkat

Pilihan Ganda:

  1. Laravel Breeze menyediakan:

    • A. Only backend auth
    • B. Login & Register views
    • C. Only API auth
    • D. Database only
  2. Middleware auth berfungsi untuk:

    • A. Proteksi route agar hanya user login yang bisa akses
    • B. Enkripsi password
    • C. Generate token
    • D. Membuat session
  3. Relasi "One to Many" di Laravel menggunakan:

    • A. hasOne()
    • B. hasMany()
    • C. belongsTo()
    • D. manyToMany()
  4. Eager loading untuk mencegah N+1 problem:

    • A. Transaksi::all()
    • B. Transaksi::get()
    • C. Transaksi::with(['anggota', 'buku'])->get()
    • D. Transaksi::load()
  5. Foreign key constraint onDelete('cascade') artinya:

    • A. Tidak boleh hapus parent
    • B. Hapus parent, child juga terhapus
    • C. Hapus child dulu
    • D. Tidak ada efek
  6. Method untuk mengurangi stok buku:

    • A. $buku->stok--
    • B. $buku->decrement('stok')
    • C. $buku->reduce('stok')
    • D. $buku->minus('stok')
  7. DB::transaction() digunakan untuk:

    • A. Membuat transaksi baru
    • B. Query database
    • C. Rollback otomatis jika ada error
    • D. Commit manual
  8. Helper untuk cek user login:

    • A. user()->check()
    • B. auth()->check()
    • C. login()->check()
    • D. session()->check()
  9. Blade directive untuk cek autentikasi:

    • A. @login
    • B. @auth
    • C. @user
    • D. @logged
  10. belongsTo relationship ada di:

    • A. Parent table
    • B. Child table
    • C. Pivot table
    • D. Semua table

Essay:

  1. Jelaskan perbedaan Authentication dan Authorization! Berikan contoh! (15 poin)

  2. Apa itu N+1 Query Problem? Bagaimana cara mengatasinya di Laravel? (15 poin)

  3. Jelaskan alur lengkap peminjaman buku dari form sampai update stok! (15 poin)

  4. Buatlah code untuk relasi hasMany di Model Anggota ke Transaksi! (10 poin)

  5. Mengapa perlu menggunakan DB::transaction() saat peminjaman buku? (10 poin)


2. Checklist Kompetensi

NoKompetensiBelumCukupMahir
1Install Laravel Breeze○○○
2Implementasi login/register○○○
3Menggunakan middleware auth○○○
4Membuat relasi database○○○
5hasMany & belongsTo○○○
6Eager loading○○○
7Database transaction○○○
8Logic update stok○○○
9Form peminjaman○○○
10Business logic implementation○○○

G. REFERENSI

1. Dokumentasi Laravel 12

2. Security


H. PERSIAPAN PERTEMUAN 15

Topik: Integrasi Sistem & Finalisasi

Preview:

  1. Dashboard dengan grafik
  2. Fitur pencarian global
  3. Pengembalian buku
  4. Laporan transaksi
  5. Bug fixing
  6. UI/UX enhancement

Yang Perlu Disiapkan:

  1. Authentication sudah jalan
  2. CRUD semua modul lengkap
  3. Transaksi peminjaman berfungsi
  4. Pahami keseluruhan sistem

Pre-reading:

  • Chart.js for Laravel
  • Search functionality best practices

Selamat Belajar! 🚀🔐

End of Module - Pertemuan 14

Next: Pertemuan 15 - Integrasi Sistem & Finalisasi