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
| Aspek | Keterangan |
|---|---|
| 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-CPMK | Sub-CPMK06.1.3: Menerapkan autentikasi dan proteksi halaman pada aplikasi web. |
| Indikator Pencapaian | Mahasiswa 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 System3. Manfaat Pembelajaran
- Mampu mengamankan aplikasi web dengan autentikasi
- Memahami konsep middleware untuk proteksi route
- Dapat membuat relasi antar tabel database
- Terampil implementasi business logic kompleks
- 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:
| Authentication | Authorization |
|---|---|
| "Siapa kamu?" | "Apa yang boleh kamu lakukan?" |
| Login/Register | Permissions/Roles |
| Verifikasi identitas | Verifikasi hak akses |
| Contoh: Email + Password | Contoh: 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 migrateFiles 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 loginb. Jenis-jenis Middleware
1. Auth Middleware (auth):
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware('auth');
// User harus login, jika tidak redirect ke /login2. Guest Middleware (guest):
Route::get('/login', function () {
return view('auth.login');
})->middleware('guest');
// User harus belum login, jika sudah login redirect ke /home3. Verified Middleware (verified):
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified']);
// User harus login DAN email verifiedc. 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>
@endguestGet 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:
- One to One - Satu ke satu (User â Profile)
- One to Many - Satu ke banyak (Anggota â Transaksi)
- 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 TransaksiForeign 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:
- â User harus login (Authentication)
- â Buku harus tersedia (stok > 0)
- â Anggota harus aktif
- â Satu peminjaman = 1 buku
- â Maksimal peminjaman = 7 hari
- â Stok buku berkurang otomatis
- â 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 messageb. 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 anggotabuku_id: Foreign key ke tabel bukutanggal_pinjam: Tanggal peminjamantanggal_kembali: Tanggal harus dikembalikan (tanggal_pinjam + 7 hari)tanggal_dikembalikan: Tanggal actual dikembalikan (nullable)status: Dipinjam atau Dikembalikandenda: 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:
| Session | Cookie |
|---|---|
| Data di server | Data di browser |
| Lebih aman | Kurang aman |
| Hilang saat logout | Bisa persistent |
| Size besar OK | Size 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 allc. 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>
@endifD. PRAKTIKUM
1. Tujuan Praktikum
- Install dan konfigurasi Laravel Breeze
- Setup authentication (login/register)
- Protect routes dengan middleware
- Membuat migration dan model Transaksi
- Implementasi relasi database
- Membuat form peminjaman buku
- 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: yesOutput:
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 devc. Run Migration
php artisan migrateTables created:
userspassword_reset_tokenssessions
d. Test Authentication Pages
- Jalankan server:
php artisan serve - Akses:
http://localhost:8000/register - Verifikasi: Form register muncul dengan styling Tailwind
e. Register User Pertama
- Buka:
http://localhost:8000/register - Isi form:
- Name:
Admin Perpustakaan - Email:
admin@perpustakaan.com - Password:
password123 - Confirm Password:
password123
- Name:
- Klik "Register"
- Verifikasi: Redirect ke
/dashboard
f. Test Login
- Logout: Klik profile â Logout
- Akses:
http://localhost:8000/login - Login dengan credential tadi
- 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
- Logout dari aplikasi
- Coba akses:
http://localhost:8000/buku - Verifikasi: Redirect otomatis ke
/login - Login kembali
- Akses:
http://localhost:8000/buku - 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_tableb. 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 migrated. Generate Model
php artisan make:model Transaksie. 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 --resourceb. 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>
@endsectiond. 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>
@endsectione. Test Transaksi Peminjaman
- Login ke sistem
- Akses:
http://localhost:8000/transaksi/create - Isi form:
- Pilih anggota
- Pilih buku (yang ada stoknya)
- Tanggal pinjam: hari ini
- Klik "Proses Peminjaman"
- 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:
-
View Detail Transaksi dengan button "Kembalikan Buku"
-
Method
kembalikan()di Controller (sudah ada template) -
Perhitungan Denda:
- Denda Rp 5.000/hari
- Hanya jika terlambat
- Tampilkan total denda di detail
-
Update Stok: Stok buku bertambah 1 saat dikembalikan
Tugas 2: Laporan Transaksi (30%)
Instruksi: Buat halaman laporan transaksi dengan filter.
Spesifikasi:
- Route:
/transaksi/laporan - Filter:
- Range tanggal (dari-sampai)
- Status (Semua/Dipinjam/Dikembalikan)
- Anggota (dropdown)
- Tampilan:
- Tabel transaksi
- Total transaksi
- Total denda (jika ada)
- Export PDF: Button untuk export laporan ke PDF
Tugas 3: Notifikasi Terlambat (30%)
Instruksi: Tambah fitur notifikasi untuk buku yang terlambat dikembalikan.
Spesifikasi:
-
Dashboard Widget:
- Card "Buku Terlambat"
- Jumlah transaksi yang terlambat
- List anggota yang terlambat
-
Badge Terlambat:
- Di index transaksi, tambah badge "Terlambat" warna merah
- Tampilkan berapa hari terlambat
-
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:
-
Laravel Breeze menyediakan:
- A. Only backend auth
- B. Login & Register views
- C. Only API auth
- D. Database only
-
Middleware
authberfungsi untuk:- A. Proteksi route agar hanya user login yang bisa akses
- B. Enkripsi password
- C. Generate token
- D. Membuat session
-
Relasi "One to Many" di Laravel menggunakan:
- A.
hasOne() - B.
hasMany() - C.
belongsTo() - D.
manyToMany()
- A.
-
Eager loading untuk mencegah N+1 problem:
- A.
Transaksi::all() - B.
Transaksi::get() - C.
Transaksi::with(['anggota', 'buku'])->get() - D.
Transaksi::load()
- A.
-
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
-
Method untuk mengurangi stok buku:
- A.
$buku->stok-- - B.
$buku->decrement('stok') - C.
$buku->reduce('stok') - D.
$buku->minus('stok')
- A.
-
DB::transaction() digunakan untuk:
- A. Membuat transaksi baru
- B. Query database
- C. Rollback otomatis jika ada error
- D. Commit manual
-
Helper untuk cek user login:
- A.
user()->check() - B.
auth()->check() - C.
login()->check() - D.
session()->check()
- A.
-
Blade directive untuk cek autentikasi:
- A.
@login - B.
@auth - C.
@user - D.
@logged
- A.
-
belongsTo relationship ada di:
- A. Parent table
- B. Child table
- C. Pivot table
- D. Semua table
Essay:
-
Jelaskan perbedaan Authentication dan Authorization! Berikan contoh! (15 poin)
-
Apa itu N+1 Query Problem? Bagaimana cara mengatasinya di Laravel? (15 poin)
-
Jelaskan alur lengkap peminjaman buku dari form sampai update stok! (15 poin)
-
Buatlah code untuk relasi hasMany di Model Anggota ke Transaksi! (10 poin)
-
Mengapa perlu menggunakan DB::transaction() saat peminjaman buku? (10 poin)
2. Checklist Kompetensi
| No | Kompetensi | Belum | Cukup | Mahir |
|---|---|---|---|---|
| 1 | Install Laravel Breeze | â | â | â |
| 2 | Implementasi login/register | â | â | â |
| 3 | Menggunakan middleware auth | â | â | â |
| 4 | Membuat relasi database | â | â | â |
| 5 | hasMany & belongsTo | â | â | â |
| 6 | Eager loading | â | â | â |
| 7 | Database transaction | â | â | â |
| 8 | Logic update stok | â | â | â |
| 9 | Form peminjaman | â | â | â |
| 10 | Business logic implementation | â | â | â |
G. REFERENSI
1. Dokumentasi Laravel 12
- Authentication: https://laravel.com/docs/12.x/authentication (opens in a new tab)
- Laravel Breeze: https://laravel.com/docs/12.x/starter-kits#laravel-breeze (opens in a new tab)
- Middleware: https://laravel.com/docs/12.x/middleware (opens in a new tab)
- Eloquent Relationships: https://laravel.com/docs/12.x/eloquent-relationships (opens in a new tab)
- Database Transactions: https://laravel.com/docs/12.x/database#database-transactions (opens in a new tab)
2. Security
- OWASP Authentication: https://owasp.org/www-project-web-security-testing-guide/ (opens in a new tab)
- Laravel Security Best Practices
H. PERSIAPAN PERTEMUAN 15
Topik: Integrasi Sistem & Finalisasi
Preview:
- Dashboard dengan grafik
- Fitur pencarian global
- Pengembalian buku
- Laporan transaksi
- Bug fixing
- UI/UX enhancement
Yang Perlu Disiapkan:
- Authentication sudah jalan
- CRUD semua modul lengkap
- Transaksi peminjaman berfungsi
- 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