Proyek pencarian dan filter menutup satu era dalam course ini: seluruh datanya — 22 buku fiktif — sudah tinggal di dalam browser, tinggal disaring. Dashboard data membuka era berikutnya: angka yang ditampilkan tidak lagi berasal dari satu berkas, melainkan dari tiga sumber yang berbeda. Dashboard sebuah toko biasanya merangkum statistik kunjungan, daftar transaksi terbaru, dan alur aktivitas; ketiganya hidup di endpoint yang berbeda, datang dengan kecepatan yang tidak sama, dan bisa saja salah satunya gagal di tengah jalan. Ada pula tuntutan yang khas dashboard: angka mentah seperti 3420 dan 34500000 harus berubah menjadi 3,4 rb, 34,5 jt, dan bar yang bisa dibaca sekilas.
Empat tantangan itu dijawab bertahap dalam satu produk utuh: dashboard toko dengan tiga kartu ringkas, bar teks, tabel transaksi, dan daftar aktivitas. Pendekatan utamanya sama dengan keempat proyek sebelumnya — pemisahan logika dari tampilan. Seluruh keputusan data hidup di src/dashboard.mjs, module murni tanpa satu pun document: memuat data secara paralel, mengelola state dengan aliran satu arah, memformat angka, dan melaporkan kegagalan per sumber. Pemuat data disuntikkan dari luar — pola dependency injection — sehingga seluruh logika jaringan teruji lewat node --test tanpa satu pun permintaan internet. Berkas HTML hanya menampilkan state dan meneruskan peristiwa ke logika.
Proyek dikerjakan dalam empat bagian berurutan:
- Bagian 1: Mengambil Data — menulis
muatDashboard(pemuat)denganPromise.all: tiga pemuat dipanggil sekaligus, hasilnya dikumpulkan sesuai urutan permintaan. Demo membandingkan pemuatan berurutan dan paralel lewat urutan peristiwa, bukan angka waktu, dan tiga uji pertama mengunci perilakunya — termasuk sifatPromise.allyang menolak keseluruhan muatan ketika satu sumber gagal. Kita belajar dependency injection: pemuat nyata boleh memanggilfetch, uji memasang pemuat tiruan. - Bagian 2: Mengelola State — menyimpan seluruh wujud dashboard dalam satu object yang dibuat oleh
buatState, dengan tiga pintu perubahan murni:setState,patchState, danterapkanData. Setiap perubahan menghasilkan state baru; state lama tidak pernah tersentuh — aliran satu arah sebagaimana dibahas pada chapter State Management Fundamentals. Uji kumulatif bertambah menjadi sembilan. - Bagian 3: Visualisasi Data — merapikan angka mentah dengan formatter murni:
ringkasAngkaberbasisIntl.NumberFormatcompact untuk localeid-ID,formatPersen, danbuatBaryang menggambar bar teks ASCII. Antarmuka lengkapnya dirakit dari kartu ringkas, tabel transaksi, dan daftar aktivitas yang dirender sekali tempel lewatDocumentFragment. Uji kumulatif mencapai tujuh belas. - Bagian 4: Error Handling dan Testing — mengganti kegagalan “semua atau tidak sama sekali” dengan laporan per sumber:
ApiErroryang membawa nama sumbernya,muatDashboardToleranberbasisPromise.allSettled, danterapkanLaporanyang memilih keadaansiap,sebagian, ataugalat. Bagian ini juga meninjau seluruh strategi pengujian proyek dan menutupnya dengan keluarannode --testlengkap: dua puluh empat uji dalam sebelas suite.
Struktur Berkas Proyek #
Seluruh kode proyek tinggal di tiga folder pada content/javascript/proyek/dashboard-data/:
dashboard-data/
├── src/
│ ├── dashboard.mjs # logika inti: muat paralel, state, formatter, laporan galat
│ └── index.html.txt # contoh antarmuka (salin ulang sebagai index.html)
├── styles/
│ └── styles.css # tampilan dashboard: kartu, bar, tabel, panel
└── tests/
└── dashboard.test.mjs # uji otomatis untuk dashboard.mjsPeran tiap berkas:
| Berkas | Peran |
|---|---|
src/dashboard.mjs | Otak dashboard: muatDashboard paralel, state satu pintu, formatter Intl, dan laporan kegagalan per sumber. Tidak menyentuh DOM. |
src/index.html.txt | Wajah aplikasi: kartu ringkas dengan bar, tabel transaksi, daftar aktivitas, dan skrip penghubung ke logika. |
styles/styles.css | Penampil: grid kartu, bar monospace, tabel, serta warna status dan daftar kegagalan. |
tests/dashboard.test.mjs | Penjaga: dua puluh empat uji otomatis dalam sebelas suite yang dijalankan test runner bawaan Node. |
Setiap bagian menampilkan kondisi berkas pada akhir bagian tersebut: blok kode yang membahas sebuah berkas menyediakan tautan unduhan salinannya di header blok. Versi lengkap dan terakhir dari seluruh berkas selalu ada di folder src/, styles/, dan tests/ di atas — jika Anda mengikuti proyek dari Bagian 1, berkas-berkas itu tumbuh bertahap mengikuti pembahasan.
Menjalankan Pengujian #
Uji otomatis dijalankan dari folder proyek dengan test runner bawaan Node.js:
cd dashboard-data
node --testNode mencari berkas uji dengan pola nama seperti *.test.mjs di seluruh pohon folder proyek. Folder course ini memuat berkas uji yang sama persis di dua tempat — tests/dashboard.test.mjs dan salinan unduhannya di folder Bagian 4 — sehingga rangkaian dua puluh empat uji dijalankan dua kali. Ringkasan di ujung keluarannya berbunyi:
ℹ tests 48
ℹ suites 22
ℹ pass 48
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 316.662852Angka pass 48 adalah dua kali dua puluh empat uji dari dua salinan berkas uji yang identik — keduanya lulus semua. Bila folder Anda hanya berisi src/, styles/, dan tests/ seperti struktur di atas, perintah yang sama melaporkan tests 24, suites 11, pass 24; selisihnya murni berapa banyak salinan berkas uji yang ditemukan, bukan perbedaan perilaku. duration_ms akan berbeda-beda di mesin Anda karena mengukur waktu eksekusi. Bila ingin menyasar satu berkas uji secara eksplisit, tulis nama berkasnya utuh: node --test tests/dashboard.test.mjs.
Mulailah dari Bagian 1 dan ikuti urutannya — setiap bagian dibangun di atas bagian sebelumnya. Pada bagian akhir, dashboard kita berdiri utuh: datanya dimuat paralel, keadaannya bergerak lewat satu pintu, angkanya siap dibaca, dan kegagalannya dilaporkan per sumber, bukan ditelan bersama semuanya.
Memuat beberapa sumber data dashboard secara paralel dan bertahap.
Buka → # Bagian 2: Mengelola StateMenyimpan state dashboard di satu tempat dengan update satu arah.
Buka → # Bagian 3: Visualisasi DataMenyajikan data dalam bentuk kartu ringkas dan grafik sederhana.
Buka → # Bagian 4: Error Handling dan TestingMenangani galat data dan menguji logika dashboard.
Buka →Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
styles/styles.css
Unduh berkas/* styles.css — tampilan dashboard data */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 24px 16px;
background-color: #f3f4f6;
font-family: system-ui, sans-serif;
color: #111827;
}
.dashboard {
max-width: 880px;
margin: 0 auto;
}
.dashboard-atas {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.dashboard-atas h1 {
margin: 0;
font-size: 1.5rem;
}
.tombol-muat {
padding: 8px 16px;
border: none;
border-radius: 8px;
background-color: #2563eb;
color: #ffffff;
font-size: 0.95rem;
cursor: pointer;
}
.tombol-muat:hover {
background-color: #1d4ed8;
}
.status {
margin: 0 0 16px;
padding: 10px 12px;
background-color: #e5e7eb;
border-radius: 8px;
}
.daftar-gagal {
margin: 0 0 16px;
padding: 10px 12px 10px 28px;
background-color: #fee2e2;
color: #991b1b;
border-radius: 8px;
}
.daftar-gagal:empty {
display: none;
}
.kartu {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.kartu-item {
padding: 14px;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.kartu-item h2 {
margin: 0 0 8px;
font-size: 0.85rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.kartu-angka {
margin: 0 0 8px;
font-size: 1.6rem;
font-weight: 700;
}
.kartu-bar {
margin: 0;
font-family: monospace;
font-size: 0.9rem;
color: #2563eb;
letter-spacing: 1px;
}
.konversi {
margin: 0 0 16px;
color: #6b7280;
}
.panel {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.panel-item {
padding: 14px;
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.panel-item h2 {
margin: 0 0 10px;
font-size: 1rem;
}
.tabel-transaksi {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.tabel-transaksi th,
.tabel-transaksi td {
padding: 6px 8px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.tabel-transaksi th {
color: #6b7280;
font-weight: 600;
}
.daftar-aktivitas {
margin: 0;
padding-left: 18px;
font-size: 0.9rem;
}
.daftar-aktivitas li {
margin-bottom: 6px;
color: #374151;
}