Skip to content

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:

WidgetData
📦 Total KategoriCategory::count()
🗂️ Total Jenis AsetAsset::count()
🔄 Peminjaman AktifLoan::where('status', 'dipinjam')->count()
✅ DikembalikanLoan::where('status', 'dikembalikan')->count()
⚠️ TerlambatPinjaman aktif yang melewati expected_return_date
📊 Tabel Aset Stok MenipisAset dengan stock <= 2
📋 Pinjaman Terbaru5 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.

Program Magang SMK RPL — Rekayasa Perangkat Lunak