Dashboard eksekutif idealnya menampilkan maksimal 8–12 KPI — cukup untuk mencerminkan kesehatan bisnis secara keseluruhan dan memungkinkan pengambilan keputusan tanpa membebani pengguna. Menampilkan terlalu banyak metrik adalah kesalahan paling umum. Untuk perusahaan dagang, KPI inti meliputi pendapatan vs. target, margin laba kotor %, DSO, DPO, perputaran inventaris, posisi kas, pesanan terbuka, dan faktur jatuh tempo.
Mengkueri tabel transaksi mentah setiap kali halaman dimuat terlalu lambat untuk dashboard eksekutif yang harus merespons dalam milidetik. Sebagai gantinya, tabel daily_kpi_snapshot dihitung terlebih dahulu setiap malam oleh job cron NestJS pukul 01.00 WIB, menyimpan nilai penutupan tiap hari bisnis. Dashboard kemudian menjalankan SELECT sederhana tanpa join pada tabel snapshot ini, sementara metrik yang benar-benar real-time seperti posisi kas menggunakan materialized view yang diperbarui setiap 15 menit.
Angka saja tidak pernah cukup sebagai konteks pengambilan keputusan. Setiap kartu KPI harus menampilkan nilai saat ini, target atau tolok ukur, varians (absolut dan persentase), serta sparkline 12 bulan terakhir. Misalnya, 'Rp 2,4M vs. target Rp 3M (80%) — turun 12% vs. bulan yang sama tahun lalu' memungkinkan keputusan, sedangkan angka pendapatan tanpa konteks tidak memberi makna apa pun.
Pemangku kepentingan yang berbeda sering mendefinisikan KPI yang sama secara berbeda — misalnya, 'pendapatan' bisa berarti penjualan bruto bagi direktur penjualan dan bersih setelah pengembalian bagi CFO. Solusinya adalah mendokumentasikan formula tepat untuk setiap KPI sebelum membangun, meminta persetujuan dari CFO dan pemilik bisnis, lalu menampilkan definisi formula di tooltip dashboard. Dokumentasi yang eksplisit ini — bukan data yang lebih baik — yang menyelesaikan perselisihan ketika eksekutif menghitung angka secara berbeda.
Eksekutif Indonesia lebih mengutamakan mobile, sehingga route mobile terpisah menciutkan 12 KPI penuh menjadi 5 yang paling kritis, menampilkan angka besar dengan label minimal, dan menggunakan kode warna hijau/kuning/merah untuk status instan. Bot WhatsApp mengirimkan ringkasan KPI harian pukul 07.00 sebelum eksekutif tiba di kantor. Alert berbasis threshold — yang dapat dikonfigurasi admin tanpa deployment kode — mengirimkan notifikasi email, WhatsApp, atau SMS ketika KPI melampaui batas yang ditentukan, seperti DSO AR melebihi 60 hari atau faktur jatuh tempo melampaui Rp 500M.
Bisnis yang memanfaatkan KPI real-time di lingkungan ERP mereka memiliki keunggulan kompetitif melalui kemampuan untuk melihat, memahami, dan bertindak berdasarkan data secara real-time. Untuk tim eksekutif di perusahaan Indonesia, dashboard ERP sering menjadi deliverable paling terlihat dari seluruh proyek ERP — yang dilihat CEO setiap pagi. Mendapatkannya dengan benar berarti memilih metrik yang tepat (bukan setiap metrik), merancang untuk kecepatan, dan membuatnya actionable.
Kesalahan paling umum dalam dashboard eksekutif adalah menampilkan terlalu banyak metrik. Dashboard eksekutif harus memiliki maksimal 8-12 KPI — yang mencerminkan kesehatan bisnis dan memungkinkan keputusan. Untuk perusahaan dagang di Indonesia, KPI intinya adalah: Pendapatan (bulan ini vs. target, vs. bulan yang sama tahun lalu), Margin Laba Kotor %, DSO AR, DPO AP, Perputaran Inventaris, Posisi Kas, Pesanan Terbuka, dan Faktur Jatuh Tempo.
Untuk performa, dashboard eksekutif tidak boleh langsung mengkueri tabel transaksi mentah. Saya pra-menghitung tabel daily_kpi_snapshot yang menyimpan nilai metrik utama per hari bisnis. Job cron NestJS berjalan pukul 01.00 WIB setiap malam dan mengisi tabel ini dengan nilai penutupan kemarin. Dashboard kemudian mengkueri tabel snapshot — SELECT sederhana tanpa join — kembali dalam milidetik.
Executive KPI Dashboard Architecture
Nightly cron (01:00 WIB)
→ Queries raw tables: journal_lines, ar_invoices, stock_movements, ...
→ Computes 12 KPIs for yesterday
→ Writes to daily_kpi_snapshot table
→ Invalidates Redis cache for dashboard
Dashboard API endpoint: GET /dashboard/kpis
→ Reads daily_kpi_snapshot (fast: single row lookup)
→ Merges with real-time queries for cash/orders (15min cache)
→ Returns single JSON payload (not 12 separate calls)
KPI Card structure (React + Recharts):
┌─────────────────────────────────────┐
│ Revenue (Month-to-Date) │
│ │
│ Rp 2.4B ▲ +8% vs target │
│ Target: Rp 3B ▼ -12% vs LY │
│ │
│ [sparkline: last 12 months] │
│ ▁▂▃▃▄▄▅▆▆▇▇▇ │
└─────────────────────────────────────┘
12 KPI Cards:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Revenue │ │ GP Margin│ │ AR DSO │ │ AP DPO │
│ 🟢 80% │ │ 🟡 36% │ │ 🔴 68d │ │ 🟢 42d │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Inv Turn │ │ Cash Pos │ │Open Ord │ │ Overdue │
│ 🟢 4.2x │ │ 🟢 2.1B │ │ 🟡 45 │ │ 🔴 23 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘Dari pengalaman saya membangun sistem ERP sebagai konsultan ERP freelance: selalu tampilkan metrik, target, dan tren bersama-sama. Angka pendapatan tidak berarti apa-apa tanpa konteks. 'Rp 2,4M pendapatan bulan ini vs. Rp 3M target (80%) — turun 12% vs. bulan yang sama tahun lalu' memungkinkan keputusan. Rancang setiap kartu KPI dengan: nilai saat ini, target atau tolok ukur, varians (absolut dan %), dan sparkline yang menunjukkan 12 bulan terakhir.
API dashboard mengembalikan semua 12 KPI dalam satu panggilan endpoint — bukan 12 panggilan API terpisah. Saya membatch komputasi KPI dan mengembalikan satu payload JSON. Ini mengurangi jumlah round-trip dan mencegah efek 'waterfall loading' di mana setiap kartu muncul pada waktu yang berbeda.
Setiap kartu KPI harus dapat diklik dan menelusuri ke transaksi yang mendasarinya. Klik 'Faktur Jatuh Tempo: 23' dan Anda mendapatkan daftar 23 faktur dengan nama pelanggan, jumlah, hari jatuh tempo, dan pemilik AR. Kapabilitas drill-down ini yang membedakan dashboard yang berguna dari layar perhiasan.
// NestJS: KPI service with Redis caching
@Injectable()
export class KpiService {
constructor(
private financeService: FinanceService,
private arService: ArService,
private inventoryService: InventoryService,
@InjectRedis() private redis: Redis,
) {}
async getDashboardKpis(): Promise<DashboardKpis> {
const cacheKey = 'dashboard:kpis:' + new Date().toISOString().slice(0, 13);
const cached = await this.redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// Fetch all KPIs in parallel
const [revenue, margin, dso, dpo, cashPos, overdueInvoices] =
await Promise.all([
this.financeService.getRevenueKpi(),
this.financeService.getGrossMarginKpi(),
this.arService.getDsoKpi(),
this.financeService.getDpoKpi(),
this.financeService.getCashPositionKpi(),
this.arService.getOverdueKpi(),
]);
const kpis = { revenue, margin, dso, dpo, cashPos, overdueInvoices };
// Cache for 15 minutes
await this.redis.setex(cacheKey, 900, JSON.stringify(kpis));
return kpis;
}
// Threshold alert job — runs Mon morning 07:00 WIB
@Cron('0 7 * * 1', { timeZone: 'Asia/Jakarta' })
async checkKpiAlerts() {
const kpis = await this.getDashboardKpis();
const alerts = this.alertConfig;
if (kpis.dso.value > alerts.dso.threshold) {
await this.notifier.sendEmail(alerts.dso.recipients, {
subject: `⚠️ AR DSO Alert: ${kpis.dso.value} days`,
body: `DSO has exceeded ${alerts.dso.threshold} day threshold.`,
});
}
// ... additional alert checks
}
}Backend dashboard yang saya bangun adalah NestJS DashboardModule dengan KpiService tunggal. Service mengorkestrasikan query di seluruh FinanceService, InventoryService, ARService, dan APService, merakit payload KPI, dan meng-cache-nya di Redis dengan TTL 15 menit. Frontend adalah dashboard React menggunakan Recharts untuk sparkline.
Pemangku kepentingan yang berbeda sering mendefinisikan KPI yang sama secara berbeda. 'Pendapatan' mungkin berarti penjualan bruto bagi direktur penjualan dan bersih setelah pengembalian bagi CFO. Sebelum membangun dashboard, dokumentasikan formula tepat untuk setiap KPI, minta CFO dan pemilik bisnis menyetujuinya, dan tampilkan definisi formula dalam tooltip dashboard. Saya memiliki dua situasi di mana eksekutif memperdebatkan angka dashboard karena mereka menghitung secara berbeda. Solusinya bukan data yang lebih baik — ini dokumentasi formula yang eksplisit.
Eksekutif Indonesia lebih mengutamakan mobile. CEO meninjau dashboard di ponsel mereka saat bepergian antar rapat. Saya membangun tampilan dashboard yang dioptimalkan untuk mobile yang menciutkan 12 KPI menjadi 5 yang paling kritis, menampilkan angka besar dengan label minimal, dan menggunakan kode warna hijau/kuning/merah untuk status instan. Saya juga membangun bot WhatsApp yang mengirimkan ringkasan KPI harian pukul 07.00 — fitur favorit CEO.
Dashboard bersifat reaktif — eksekutif melihatnya ketika mereka melihat. Alert bersifat proaktif. Saya menambahkan alert berbasis threshold: jika DSO AR melebihi 60 hari, email ke CFO. Jika posisi kas turun di bawah minimum yang dikonfigurasi, WhatsApp ke direktur. Jika faktur jatuh tempo melebihi Rp 500M, SMS ke manajer penagihan. Alert ini berjalan sebagai job cron NestJS yang memeriksa nilai KPI setiap hari.