Membangun Dashboard Analitik yang Berguna

Dashboard sudah dibuat, grafiknya warna-warni, tetapi pemilik usaha tetap menelepon Anda untuk bertanya "bulan ini penjualan naik atau turun?". Itu tanda dashboard-nya menampilkan...

Membangun Dashboard Analitik yang Berguna

Dashboard sudah dibuat, grafiknya warna-warni, tetapi pemilik usaha tetap menelepon Anda untuk bertanya "bulan ini penjualan naik atau turun?". Itu tanda dashboard-nya menampilkan angka, bukan jawaban. Masalah kedua biasanya muncul belakangan: halaman dashboard makin lambat seiring data transaksi bertambah.

Artikel ini membahas dua sisi tersebut. Pertama, cara memilih metrik yang memandu keputusan. Kedua, cara membangunnya di Laravel 11 dengan query agregasi yang efisien, grafik Chart.js, cache, dan tabel ringkasan harian untuk data besar.

1. Mulai dari Pertanyaan, Bukan dari Grafik

Sebelum menulis query, duduklah dengan pemakai dashboard dan catat pertanyaan yang mereka ajukan setiap minggu. Untuk toko retail atau kafe, daftarnya biasanya seperti ini:

  • Apakah pendapatan 30 hari terakhir lebih tinggi dari 30 hari sebelumnya?
  • Produk apa yang paling laku, dan mana yang tidak bergerak?
  • Jam dan hari apa yang paling ramai, untuk mengatur jadwal karyawan?
  • Berapa rata-rata nilai satu transaksi (AOV)?

Aturan praktisnya: sebuah metrik layak dipasang jika ada keputusan yang berubah ketika angkanya berubah. Total pengunjung sepanjang masa tidak lolos tes ini karena angkanya selalu naik dan tidak memicu tindakan apa pun.

Vanity metricPengganti yang bisa ditindaklanjutiKeputusan yang dipandu
Total pengunjung sepanjang masaKonversi pengunjung ke pembeli minggu iniPerbaiki halaman produk atau promosi
Jumlah produk di katalogProduk tanpa penjualan 60 hariDiskon, bundling, atau stop restock
Total omzet tahun berjalanPendapatan 30 hari vs periode sebelumnyaTambah atau kurangi anggaran iklan
Jumlah user terdaftarPelanggan yang belanja ulang dalam 90 hariProgram loyalitas atau follow-up

Batasi tampilan utama pada empat sampai enam angka. Detail lain bisa diletakkan di halaman terpisah yang dibuka saat diperlukan.

2. Kartu Ringkasan dengan Pembanding

Angka tanpa pembanding sulit dimaknai. Rp12 juta itu bagus atau buruk? Tergantung bulan lalu. Biarkan database yang menghitung; jangan mengambil ribuan baris lalu menjumlahkannya di PHP.

use App\Models\Order;

$now   = now();
$start = $now->copy()->subDays(30);
$prev  = $now->copy()->subDays(60);

$current = Order::where('created_at', '>=', $start)
    ->selectRaw('COUNT(*) as orders, COALESCE(SUM(total), 0) as revenue')
    ->first();

$previousRevenue = Order::whereBetween('created_at', [$prev, $start])->sum('total');

$aov    = $current->orders > 0 ? $current->revenue / $current->orders : 0;
$growth = $previousRevenue > 0
    ? round(($current->revenue - $previousRevenue) / $previousRevenue * 100, 1)
    : null; // null = belum ada data pembanding

Perhatikan dua hal. Jumlah order dan pendapatan diambil dalam satu query, bukan dua. Lalu pertumbuhan bernilai null saat periode sebelumnya kosong, sehingga tampilan bisa menulis "belum ada pembanding" alih-alih "0%" yang menyesatkan.

3. Deret Waktu Tanpa Tanggal Bolong

$daily = Order::where('created_at', '>=', $start)
    ->selectRaw('DATE(created_at) as day, SUM(total) as revenue')
    ->groupBy('day')
    ->orderBy('day')
    ->pluck('revenue', 'day');

$labels = [];
$values = [];
for ($i = 29; $i >= 0; $i--) {
    $date     = now()->subDays($i)->toDateString();
    $labels[] = $date;
    $values[] = (int) ($daily[$date] ?? 0);
}

Hari tanpa transaksi tidak muncul di hasil GROUP BY. Tanpa loop pengisi nol, Chart.js akan menarik garis langsung dari Senin ke Kamis dan grafik terlihat seolah tidak ada hari libur. Pastikan juga zona waktu aplikasi di config/app.php sesuai lokasi usaha, misalnya Asia/Jakarta, agar transaksi pukul 23.30 tidak tercatat di hari berikutnya.

4. Produk Terlaris dan Jam Ramai

$topProducts = OrderItem::query()
    ->join('orders', 'orders.id', '=', 'order_items.order_id')
    ->where('orders.created_at', '>=', $start)
    ->selectRaw('order_items.product_name, SUM(order_items.qty) as sold')
    ->groupBy('order_items.product_name')
    ->orderByDesc('sold')
    ->limit(5)
    ->get();

$busyHours = Order::where('created_at', '>=', $start)
    ->selectRaw('HOUR(created_at) as hour, COUNT(*) as orders')
    ->groupBy('hour')
    ->orderBy('hour')
    ->pluck('orders', 'hour');

Fungsi HOUR() dan DATE() di atas berlaku untuk MySQL dan MariaDB. Di PostgreSQL gunakan EXTRACT(HOUR FROM created_at) dan created_at::date.

5. Mengirim Data ke Chart.js

<canvas id="revenueChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('revenueChart'), {
    type: 'line',
    data: {
        labels: @json($labels),
        datasets: [{ label: 'Pendapatan harian', data: @json($values), tension: 0.3 }]
    }
});
</script>

Direktif @json meng-encode array dengan aman sehingga tanda kutip di nama produk tidak merusak JavaScript.

6. Menjaga Dashboard Tetap Cepat

Pilih strategi sesuai ukuran data. Angka di bawah hanya perkiraan kasar untuk server VPS kecil; ukur sendiri dengan data Anda.

Ukuran tabel order (perkiraan)Strategi
Puluhan ribu barisQuery langsung + index pada created_at
Ratusan ribu barisTambahkan Cache::remember 5–15 menit
Jutaan barisTabel ringkasan harian yang diisi cron

Index adalah langkah pertama yang paling murah. Tambahkan lewat migration, lalu ikuti tips lain di artikel optimasi performa query Eloquent.

Schema::table('orders', function (Blueprint $table) {
    $table->index('created_at');
});

Untuk data jutaan baris, simpan agregat per hari di tabel daily_sales (kolom date unik, orders, revenue), lalu isi dengan command terjadwal:

// routes/console.php
use App\Models\Order;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    $day = now()->subDay()->toDateString();

    $row = Order::whereDate('created_at', $day)
        ->selectRaw('COUNT(*) as orders, COALESCE(SUM(total), 0) as revenue')
        ->first();

    DB::table('daily_sales')->updateOrInsert(
        ['date' => $day],
        ['orders' => $row->orders, 'revenue' => $row->revenue, 'updated_at' => now()]
    );
})->dailyAt('00:15');

Dashboard kemudian cukup membaca 30 baris dari daily_sales ditambah data hari ini secara langsung. Jika pekerjaan agregasinya berat, pindahkan ke job antrean seperti dibahas di Queue dan Job di Laravel.

7. Tata Letak yang Membantu Membaca

Mata pemakai bergerak dari kiri atas ke kanan bawah. Manfaatkan urutan itu:

  1. Baris pertama: kartu KPI utama, masing-masing dengan panah naik atau turun dan persentase perubahan.
  2. Baris kedua: grafik tren pendapatan 30 hari, cukup satu garis.
  3. Baris ketiga: tabel produk terlaris dan grafik batang jam ramai berdampingan.
  4. Paling bawah: tautan ke laporan detail, filter rentang tanggal, dan tombol ekspor.

Gunakan warna dengan hemat. Hijau dan merah cukup untuk naik dan turun; jangan memberi warna berbeda pada setiap kartu hanya demi variasi. Di layar HP, susun kartu menjadi satu kolom karena pemilik usaha sering mengecek dashboard dari ponsel di sela kesibukan.

8. Batasi Siapa yang Bisa Melihat

Angka omzet dan margin bukan informasi untuk semua karyawan. Definisikan Gate di AppServiceProvider, lalu pasang di route:

// app/Providers/AppServiceProvider.php, di method boot()
Gate::define('view-dashboard', fn (User $user) => in_array($user->role, ['owner', 'manager']));

// routes/web.php
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware(['auth', 'can:view-dashboard']);

Kolom role di contoh ini hanyalah asumsi; sesuaikan dengan struktur tabel user Anda.

Kesalahan Umum dan Perbaikannya

  • Terlalu banyak grafik di satu layar. Pemakai tidak tahu harus melihat ke mana. Pangkas menjadi empat sampai enam KPI, sisanya pindah ke halaman detail.
  • Jenis grafik salah. Tren waktu pakai garis, perbandingan antar-produk pakai batang horizontal. Pie chart dengan sepuluh potongan nyaris tidak terbaca.
  • Menghitung transaksi batal. Filter status, misalnya hanya paid, agar omzet tidak menggelembung.
  • Cache tidak pernah diperbarui. Panggil Cache::forget() di event pembuatan order bila angka harus segar.
  • Angka tidak pernah dicocokkan. Satu selisih kecil dengan laporan kasir membuat pemilik berhenti percaya pada seluruh dashboard. Cocokkan sampel sebelum serah terima.

Checklist Sebelum Dashboard Diserahkan

  • Setiap KPI punya pembanding periode sebelumnya atau target.
  • Rentang tanggal dan zona waktu tertulis jelas di layar.
  • Tanggal tanpa transaksi muncul sebagai nol.
  • Halaman termuat di bawah dua detik dengan data nyata.
  • Angka dicocokkan manual dengan laporan kasir untuk satu hari sampel.
  • Pemilik usaha bisa menjawab pertanyaan mingguannya tanpa bertanya ke developer.

Dashboard yang berguna bukan yang paling ramai grafiknya, melainkan yang membuat keputusan lebih cepat diambil. Konsep yang sama juga berlaku saat Anda mendigitalisasi bisnis kecil dengan sistem informasi: data baru bernilai ketika dipakai untuk bertindak.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Dashboard Analitik Sistem Informasi
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →