Day 18 — Dashboard Analytics & Widget Statistik
Fase: 2 — Mini Project Asset Management | Minggu: 4 | Hari: Rabu
🎯 Tujuan Hari Ini
Tim membangun halaman Dashboard yang informatif dengan widget statistik menggunakan aggregasi data dari semua tabel: total aset, total peminjaman aktif, aset sering dipinjam, dan peringatan aset terlambat dikembalikan.
📋 Task Wajib
1. Rancang Konten Dashboard
Dashboard harus menampilkan:
| Widget | Data |
|---|---|
| 📦 Total Kategori | Category::count() |
| 🗂️ Total Jenis Aset | Asset::count() |
| 🔄 Peminjaman Aktif | Loan::where('status', 'dipinjam')->count() |
| ✅ Dikembalikan | Loan::where('status', 'dikembalikan')->count() |
| ⚠️ Terlambat | Pinjaman aktif yang melewati expected_return_date |
| 📊 Tabel Aset Stok Menipis | Aset dengan stock <= 2 |
| 📋 Pinjaman Terbaru | 5 pinjaman terakhir |
2. Update Route Dashboard
Di routes/web.php, update route /:
php
use App\Models\Category;
use App\Models\Asset;
use App\Models\Loan;
Route::get('/', function () {
$stats = [
'total_kategori' => Category::count(),
'total_aset' => Asset::count(),
'total_unit' => Asset::sum('stock'),
'pinjaman_aktif' => Loan::where('status', 'dipinjam')->count(),
'pinjaman_selesai' => Loan::where('status', 'dikembalikan')->count(),
'pinjaman_terlambat' => Loan::where('status', 'dipinjam')
->where('expected_return_date', '<', today())
->count(),
];
$asetMenipis = Asset::where('stock', '<=', 2)
->with('category')
->orderBy('stock')
->get();
$pinjamanTerbaru = Loan::with('asset')
->where('status', 'dipinjam')
->orderBy('created_at', 'desc')
->take(5)
->get();
$pinjamanTerlambat = Loan::with('asset')
->where('status', 'dipinjam')
->where('expected_return_date', '<', today())
->orderBy('expected_return_date')
->get();
return view('dashboard', compact(
'stats', 'asetMenipis', 'pinjamanTerbaru', 'pinjamanTerlambat'
));
})->name('dashboard');3. Buat View Dashboard yang Lengkap
Ganti seluruh isi resources/views/dashboard.blade.php:
html
@extends('layouts.app')
@section('title', 'Dashboard')
@section('page-title', 'Dashboard')
@section('content')
{{-- Baris 1: Statistik Utama (6 Widget) --}}
<div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:20px; margin-bottom:28px;">
<div style="background:white; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">📂</span>
<div>
<p style="color:#999; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Kategori</p>
<p style="font-size:32px; font-weight:bold; color:#1a1a2e;">{{ $stats['total_kategori'] }}</p>
</div>
</div>
</div>
<div style="background:white; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">🗂️</span>
<div>
<p style="color:#999; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Jenis Aset</p>
<p style="font-size:32px; font-weight:bold; color:#1a1a2e;">{{ $stats['total_aset'] }}</p>
</div>
</div>
</div>
<div style="background:white; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">📦</span>
<div>
<p style="color:#999; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Total Unit</p>
<p style="font-size:32px; font-weight:bold; color:#28a745;">{{ $stats['total_unit'] }}</p>
</div>
</div>
</div>
<div style="background:#fff3cd; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">🔄</span>
<div>
<p style="color:#856404; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Sedang Dipinjam</p>
<p style="font-size:32px; font-weight:bold; color:#856404;">{{ $stats['pinjaman_aktif'] }}</p>
</div>
</div>
</div>
<div style="background:#d4edda; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">✅</span>
<div>
<p style="color:#155724; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Selesai Dikembalikan</p>
<p style="font-size:32px; font-weight:bold; color:#155724;">{{ $stats['pinjaman_selesai'] }}</p>
</div>
</div>
</div>
<div style="background:{{ $stats['pinjaman_terlambat'] > 0 ? '#f8d7da' : 'white' }}; padding:24px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<div style="display:flex; align-items:center; gap:16px;">
<span style="font-size:36px;">⚠️</span>
<div>
<p style="color:{{ $stats['pinjaman_terlambat'] > 0 ? '#721c24' : '#999' }}; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Terlambat Dikembalikan</p>
<p style="font-size:32px; font-weight:bold; color:{{ $stats['pinjaman_terlambat'] > 0 ? '#dc3545' : '#1a1a2e' }};">{{ $stats['pinjaman_terlambat'] }}</p>
</div>
</div>
</div>
</div>
{{-- Baris 2: Tabel Pinjaman Aktif & Aset Menipis --}}
<div style="display:grid; grid-template-columns:2fr 1fr; gap:24px; margin-bottom:28px;">
{{-- Pinjaman Aktif Terbaru --}}
<div style="background:white; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07); overflow:hidden;">
<div style="padding:20px; border-bottom:1px solid #f0f0f0; display:flex; justify-content:space-between; align-items:center;">
<h2 style="font-size:16px; color:#1a1a2e;">📋 Pinjaman Aktif Terbaru</h2>
<a href="{{ route('loans.index') }}" style="color:#4f46e5; font-size:13px; text-decoration:none;">Lihat semua →</a>
</div>
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="background:#f8f9fa;">
<th style="padding:10px 16px; text-align:left; font-size:13px; color:#666;">Peminjam</th>
<th style="padding:10px 16px; text-align:left; font-size:13px; color:#666;">Aset</th>
<th style="padding:10px 16px; text-align:center; font-size:13px; color:#666;">Qty</th>
<th style="padding:10px 16px; text-align:center; font-size:13px; color:#666;">Kembali</th>
</tr>
</thead>
<tbody>
@forelse($pinjamanTerbaru as $p)
<tr style="border-bottom:1px solid #f5f5f5;">
<td style="padding:12px 16px; font-size:14px;">{{ $p->borrower_name }}</td>
<td style="padding:12px 16px; font-size:14px; color:#666;">{{ \Illuminate\Support\Str::limit($p->asset->name, 25) }}</td>
<td style="padding:12px 16px; text-align:center; font-size:14px; font-weight:bold;">{{ $p->quantity }}</td>
<td style="padding:12px 16px; text-align:center; font-size:13px;
color:{{ $p->isTerlambat() ? '#dc3545' : '#666' }};">
{{ $p->expected_return_date->format('d M') }}
@if($p->isTerlambat()) ⚠️ @endif
</td>
</tr>
@empty
<tr>
<td colspan="4" style="text-align:center; padding:30px; color:#999; font-size:14px;">
Tidak ada pinjaman aktif saat ini 🎉
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Aset Stok Menipis --}}
<div style="background:white; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.07); overflow:hidden;">
<div style="padding:20px; border-bottom:1px solid #f0f0f0;">
<h2 style="font-size:16px; color:#1a1a2e;">📉 Stok Aset Menipis</h2>
<p style="color:#999; font-size:12px; margin-top:4px;">Aset dengan stok ≤ 2 unit</p>
</div>
@forelse($asetMenipis as $a)
<div style="padding:14px 20px; border-bottom:1px solid #f5f5f5; display:flex; justify-content:space-between; align-items:center;">
<div>
<p style="font-size:14px; font-weight:bold; color:#1a1a2e;">{{ $a->name }}</p>
<p style="font-size:12px; color:#999;">{{ $a->category->name }}</p>
</div>
<span style="background:{{ $a->stock == 0 ? '#f8d7da' : '#fff3cd' }};
color:{{ $a->stock == 0 ? '#721c24' : '#856404' }};
padding:4px 12px; border-radius:20px; font-size:13px; font-weight:bold;">
{{ $a->stock }} unit
</span>
</div>
@empty
<div style="padding:30px; text-align:center; color:#28a745;">
✅ Semua stok aset aman!
</div>
@endforelse
</div>
</div>
{{-- Baris 3: Peringatan Terlambat (tampil hanya jika ada) --}}
@if($pinjamanTerlambat->count() > 0)
<div style="background:#f8d7da; border:1px solid #f5c6cb; border-radius:12px; padding:20px; margin-bottom:28px;">
<h2 style="color:#721c24; margin-bottom:16px; font-size:16px;">⚠️ Peringatan: Pinjaman Terlambat Dikembalikan</h2>
@foreach($pinjamanTerlambat as $p)
<div style="background:white; padding:14px 16px; border-radius:8px; margin-bottom:8px; display:flex; justify-content:space-between; align-items:center;">
<div>
<strong>{{ $p->borrower_name }}</strong>
— {{ $p->asset->name }} ({{ $p->quantity }} unit)
</div>
<div style="text-align:right;">
<span style="color:#dc3545; font-size:13px;">
Seharusnya kembali: {{ $p->expected_return_date->format('d M Y') }}
({{ $p->expected_return_date->diffForHumans() }})
</span>
<a href="{{ route('loans.show', $p->id) }}" style="display:block; color:#4f46e5; font-size:13px; margin-top:4px;">
Lihat detail →
</a>
</div>
</div>
@endforeach
</div>
@endif
@endsection📝 Laporan ke Mentor
📌 LAPORAN HARIAN — Day 18
Nama : [Nama Lengkap]
Role : [Role Kamu]
✅ Yang saya kerjakan hari ini:
- [ ] Dashboard menampilkan 6 widget statistik dari database
- [ ] Tabel "Pinjaman Aktif Terbaru" berjalan
- [ ] Widget "Stok Menipis" menampilkan aset dengan stok ≤ 2
- [ ] Alert merah "Pinjaman Terlambat" muncul jika ada data yang terlambat
📸 Screenshot Wajib:
1. Dashboard lengkap dengan 6 widget statistik
2. Widget peringatan terlambat (buat data test: isi tanggal kembali kemarin)
3. Widget stok menipis (buat data test: edit stok aset jadi 1 atau 0)
🧠 Pertanyaan Pemahaman:
Q: Apa perbedaan antara `today()` dan `now()` di Laravel?
A: [Jawaban kamu]
Q: Kenapa kita menggunakan `->take(5)->get()` bukan `->paginate(5)` di dashboard?
A: [Jawaban kamu]
❓ Kendala:
[Tulis jika ada]Catatan Mentor
Jelaskan kepada peserta konsep aggregation functions (COUNT, SUM) yang sudah mereka gunakan tanpa sadar. Query seperti Loan::where('status', 'dipinjam')->count() diterjemahkan menjadi SELECT COUNT(*) FROM loans WHERE status = 'dipinjam' di database. Ini adalah fondasi penting untuk ke depannya mempelajari laporan dan reporting system.