Gratis update & dukungan instalasi

Tutorial Website 28 Oct 2026

Cara Membuat Dashboard Statistik Penjualan di Laravel dengan Chart.js (Panduan Lengkap 2026)

R Oleh renz mobellgnd
Cara Membuat Dashboard Statistik Penjualan di Laravel dengan Chart.js (Panduan Lengkap 2026)

Dashboard statistik penjualan di Laravel dibuat dengan mengambil data agregasi dari database lewat Eloquent, mengirimkannya ke Blade, lalu menampilkannya sebagai grafik interaktif memakai Chart.js. Dalam panduan ini Anda akan membangun dashboard lengkap: kartu omzet, grafik omzet 30 hari, produk terlaris, dan filter periode — tanpa package admin panel, hanya Laravel + Chart.js.

Kenapa Dashboard Penjualan Perlu Grafik, Bukan Sekadar Tabel?

Tabel memang akurat, tapi lambat dibaca. Pemilik toko perlu tahu dalam 5 detik: apakah omzet naik atau turun, produk apa yang paling laku, dan hari apa penjualan paling ramai. Grafik menjawab tiga pertanyaan itu sekejap, sementara tabel memaksa mata memindai puluhan baris angka.

Itulah alasan dashboard menjadi fitur standar di hampir semua source code toko online — termasuk yang dijual di KiosKoding. Bedanya, tutorial ini tidak memakai data dummy. Semua angka dihitung langsung dari tabel orders dan order_items lewat query agregasi, sehingga grafik selalu mencerminkan penjualan sungguhan.

Apa Saja yang Dibangun di Tutorial Ini?

Satu halaman dashboard admin berisi empat komponen:

  1. Kartu KPI — omzet bulan ini, jumlah pesanan, rata-rata nilai pesanan, dan produk terjual.
  2. Grafik garis omzet 30 hari — tren harian memakai Chart.js.
  3. Grafik batang produk terlaris — 5 produk dengan penjualan tertinggi.
  4. Filter periode — 7 hari, 30 hari, dan 90 hari lewat query string.

Asumsi struktur datanya sederhana dan umum dipakai source code toko: tabel orders punya kolom total_amount, status, dan created_at, sedangkan order_items punya order_id, product_id, quantity, dan price. Sesuaikan nama kolom dengan skema Anda sendiri bila berbeda.

Langkah 1: Buat Controller Dashboard

Buat controller khusus agar logika agregasi tidak menumpuk di route:

php artisan make:controller Admin/DashboardController

Di dalam method index, hitung KPI dan siapkan data grafik. Kuncinya: agregasi dilakukan di database (SUM, COUNT, GROUP BY), bukan dengan memuat semua order ke memori lalu menghitung di PHP. Untuk toko dengan ribuan order, perbedaannya terasa langsung di kecepatan halaman.

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\OrderItem;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class DashboardController extends Controller
{
public function index(Request $request)
{
$days = (int) $request->query('days', 30);
$days = in_array($days, [7, 30, 90]) ? $days : 30;

$start = now()->subDays($days)->startOfDay();

// Kartu KPI: hanya order berstatus selesai yang dihitung sebagai omzet.
$completed = Order::where('status', 'completed')
->where('created_at', '>=', $start);

$kpi = [
'omzet' => (clone $completed)->sum('total_amount'),
'pesanan' => (clone $completed)->count(),
'rata_rata' => (clone $completed)->avg('total_amount'),
'terjual' => OrderItem::whereHas('order', function ($q) use ($start) {
$q->where('status', 'completed')
->where('created_at', '>=', $start);
})->sum('quantity'),
];

// Grafik omzet harian: satu baris per tanggal, termasuk hari tanpa penjualan.
$daily = (clone $completed)
->selectRaw('DATE(created_at) as tanggal, SUM(total_amount) as omzet')
->groupBy('tanggal')
->orderBy('tanggal')
->pluck('omzet', 'tanggal');

$labels = [];
$values = [];
for ($i = $days - 1; $i >= 0; $i--) {
$tanggal = now()->subDays($i)->format('Y-m-d');
$labels[] = now()->subDays($i)->format('d M');
$values[] = (int) ($daily[$tanggal] ?? 0);
}

// Produk terlaris: gabungkan order_items dengan products.
$terlaris = OrderItem::select('products.name')
->selectRaw('SUM(order_items.quantity) as terjual')
->join('products', 'products.id', '=', 'order_items.product_id')
->whereHas('order', function ($q) use ($start) {
$q->where('status', 'completed')
->where('created_at', '>=', $start);
})
->groupBy('products.id', 'products.name')
->orderByDesc('terjual')
->limit(5)
->get();

return view('admin.dashboard', [
'kpi' => $kpi,
'days' => $days,
'chartLabels' => $labels,
'chartValues' => $values,
'terlaris' => $terlaris,
]);
}
}

Perhatikan tiga keputusan penting di kode ini. Pertama, status order difilter ke completed saja — order yang dibatalkan atau masih menunggu pembayaran tidak boleh menggelembungkan omzet. Kedua, hari tanpa penjualan tetap dimasukkan dengan nilai 0, supaya garis grafik tidak melompat dan tanggalnya berurutan. Ketiga, parameter days dibatasi ke 7, 30, atau 90, agar pengguna tidak bisa memasukkan angka sembarangan lewat URL.

Tambahkan route-nya di routes/web.php, di dalam grup admin yang sudah memakai middleware autentikasi:

use App\Http\Controllers\Admin\DashboardController;

Route::middleware(['auth', 'admin'])->group(function () {
Route::get('/admin/dashboard', [DashboardController::class, 'index'])
->name('admin.dashboard');
});

Langkah 2: Pasang Chart.js di Blade

Chart.js adalah library grafik open-source berlisensi MIT yang merender grafik di elemen <canvas> (dokumentasi resmi: chartjs.org/docs). Untuk dashboard internal seperti ini, memuatnya lewat CDN sudah cukup — tidak perlu instalasi npm dan build Vite.

Pendekatan Cocok untuk Kekurangan
CDN (dipakai di tutorial ini)Dashboard admin internal, prototipe cepatPerlu koneksi internet saat memuat halaman
npm + ViteAplikasi produksi dengan build pipelineButuh konfigurasi build dan proses deploy
Package admin panel (mis. Filament)Dashboard lengkap siap pakaiKurang fleksibel bila desainnya sudah ditentukan

Di view resources/views/admin/dashboard.blade.php, tampilkan kartu KPI, kanvas grafik, dan tabel produk terlaris:

<div class="kpi-grid">
<div class="card">Omzet {{ $days }} hari: Rp {{ number_format($kpi['omzet'], 0, ',', '.') }}</div>
<div class="card">Pesanan: {{ $kpi['pesanan'] }}</div>
<div class="card">Rata-rata order: Rp {{ number_format($kpi['rata_rata'] ?? 0, 0, ',', '.') }}</div>
<div class="card">Produk terjual: {{ $kpi['terjual'] }}</div>
</div>

<div class="filter">
<a href="?days=7">7 hari</a>
<a href="?days=30">30 hari</a>
<a href="?days=90">90 hari</a>
</div>

<canvas id="grafikOmzet" height="120"></canvas>
<canvas id="grafikTerlaris" height="120"></canvas>

Lalu inisialisasi kedua grafik. Data dikirim dari controller ke JavaScript memakai @json, cara resmi Laravel yang aman dari karakter berbahaya — jangan mencetak variabel Blade mentah ke dalam skrip:

<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
new Chart(document.getElementById('grafikOmzet'), {
type: 'line',
data: {
labels: @json($chartLabels),
datasets: [{
label: 'Omzet (Rp)',
data: @json($chartValues),
tension: 0.3,
fill: true,
}],
},
options: {
responsive: true,
scales: {
y: {
ticks: {
callback: value => 'Rp ' + value.toLocaleString('id-ID'),
},
},
},
},
});

new Chart(document.getElementById('grafikTerlaris'), {
type: 'bar',
data: {
labels: @json($terlaris->pluck('name')),
datasets: [{
label: 'Terjual',
data: @json($terlaris->pluck('terjual')),
}],
},
options: { indexAxis: 'y', responsive: true },
});
</script>

Grafik batang produk terlaris memakai indexAxis: 'y' sehingga batangnya horizontal — nama produk yang panjang tetap terbaca, tidak terpotong seperti pada batang vertikal.

Langkah 3: Jaga Dashboard Tetap Cepat Saat Data Membesar

Dashboard yang cepat dengan 500 order bisa melambat saat order menyentuh puluhan ribu. Tiga langkah ini menunda masalah itu jauh-jauh hari:

  1. Indeks kolom yang difilter. Pastikan orders.status dan orders.created_at terindeks, begitu juga order_items.order_id dan order_items.product_id. Tanpa indeks, setiap buka dashboard berarti full table scan.
  2. Cache hasil agregasi. Data dashboard tidak harus real-time per detik. Simpan hasil query selama 10–15 menit dengan Cache::remember, dan hapus cache saat ada order baru yang berstatus selesai. Beban database turun drastis karena ratusan kunjungan admin hanya membaca satu cache.
  3. Hindari N+1 query. Kalau Anda menambahkan relasi (mis. kategori produk terlaris), muat dengan eager loading. Panduan lengkapnya ada di artikel Cara Mengatasi N+1 Query di Laravel.
// Contoh: cache agregasi 15 menit, kunci dibedakan per periode.
$cacheKey = "dashboard:{$days}";

$data = Cache::remember($cacheKey, now()->addMinutes(15), function () use ($start) {
// ... query KPI, grafik harian, dan produk terlaris ...
return compact('kpi', 'labels', 'values', 'terlaris');
});

Bagaimana Cara Menguji Dashboard Ini?

Sebelum diserahkan ke pemilik toko, uji empat hal berikut dengan data nyata, bukan sekadar satu-dua order contoh:

  1. Order batal tidak masuk omzet. Buat satu order cancelled bernilai besar, lalu pastikan KPI dan grafik tidak berubah.
  2. Hari tanpa penjualan tampil nol. Cari rentang yang bolong, pastikan grafik tetap berurutan tanpa tanggal hilang.
  3. Filter periode bekerja. Klik 7, 30, dan 90 hari, lalu cocokkan totalnya dengan SUM manual di database.
  4. Produk terlaris terurut benar. Bandingkan lima besar di grafik dengan query SUM(quantity) langsung di tabel order_items.

Kalau dashboard ini dipasang di toko yang sudah berjalan, dua fitur lanjutan yang paling sering diminta berikutnya adalah tombol export laporan dan halaman keranjang-checkout yang rapi: Cara Export Data ke Excel di Laravel dan Cara Membuat Keranjang Belanja & Checkout di Laravel. Untuk toko yang memakai kupon, grafik omzet juga sebaiknya dibedakan antara omzet kotor dan omzet setelah diskon — lihat Cara Membuat Sistem Voucher Diskon di Laravel.

Kapan Cara Ini Tidak Cocok?

Jujur saja, pendekatan ini punya batas. Kalau dashboard harus diperbarui tiap detik (mis. monitor flash sale live), polling halaman biasa tidak cukup — pakai Livewire atau WebSocket. Kalau datanya sudah jutaan baris dan filternya kompleks (per cabang, per kategori, per kurir sekaligus), agregasi on-the-fly akan terasa berat meski sudah di-cache; saat itu pindahkan agregasi ke tabel ringkasan harian yang diisi lewat scheduled job. Dan kalau tim Anda sudah memakai Filament atau Nova, membangun dashboard dari nol seperti ini justru membuang waktu — pakai widget bawaan package tersebut.

Untuk mayoritas toko online UMKM dan source code toko yang dijual di KiosKoding, pendekatan tutorial ini sudah lebih dari cukup: sederhana, mudah dipahami pembeli source code, dan tidak menambah dependensi berat.

FAQ

Apa itu dashboard statistik penjualan di Laravel?

Dashboard statistik penjualan adalah halaman admin yang merangkum performa toko — omzet, jumlah pesanan, dan produk terlaris — dalam bentuk kartu angka dan grafik. Di Laravel, datanya dihitung lewat query agregasi Eloquent, lalu divisualisasikan dengan library seperti Chart.js.

Apakah Chart.js gratis untuk dipakai di source code komersial?

Ya. Chart.js berlisensi MIT, sehingga bebas dipakai di proyek komersial termasuk source code yang dijual, selama lisensinya tetap dicantumkan. Library ini merender grafik di elemen <canvas> dan tidak memerlukan lisensi berbayar.

Bagaimana cara menampilkan grafik Chart.js di Laravel?

Pasang Chart.js lewat CDN atau npm, kirim data dari controller ke Blade, lalu teruskan ke JavaScript dengan @json. Buat objek new Chart() dengan tipe line untuk tren omzet atau bar untuk produk terlaris, dan arahkan ke elemen <canvas> di halaman.

Kenapa omzet di dashboard berbeda dengan total di tabel order?

Penyebab paling umum: order berstatus dibatalkan, menunggu pembayaran, atau dikembalikan ikut terjumlah. Filter agregasi hanya ke status completed (atau status setara di sistem Anda), dan pastikan zona waktu created_at sama dengan yang dipakai laporan.

Bagaimana cara mempercepat dashboard Laravel yang lambat?

Indeks kolom yang difilter (status, created_at), agregasi di database bukan di PHP, cache hasil agregasi 10–15 menit, dan hindari N+1 query dengan eager loading. Bila data sudah sangat besar, pindahkan agregasi ke tabel ringkasan harian.

Apakah dashboard ini bisa menampilkan data per kategori atau per cabang?

Bisa. Tambahkan where pada kategori atau cabang di query agregasi, lalu jadikan filternya parameter seperti days pada tutorial ini. Pastikan kolom filternya terindeks agar query tetap cepat.

Apakah tutorial ini cocok untuk pemula Laravel?

Ya, selama sudah memahami dasar route, controller, dan Blade. Semua query memakai Eloquent standar tanpa package tambahan, dan Chart.js dipasang lewat CDN sehingga tidak perlu konfigurasi build.