Dashboard admin adalah ruang kerja yang dibuka pengelola toko setiap pagi: ringkasan penjualan di atas, tabel pesanan di tengah, dan menu navigasi yang selalu berada di tempat yang sama. Karena penggunanya bekerja berjam-jam di depan data, kualitas CSS-nya diukur dari kerapian kerangka dan konsistensi komponen, bukan dari hiasan. Pada proyek keempat ini kita membangun dasbor untuk toko daring fiktif “Toko Kita”: kerangka grid dengan sidebar penuh tinggi, bar atas, kartu statistik, tabel pesanan, dan dua breakpoint untuk tablet serta ponsel. Empat bagian berurutan memperluas satu file styles/dashboard.css, dan setiap bagian ditutup halaman uji otomatis.

Tujuan Proyek #

Target yang kita capai di akhir bagian keempat:

  • Kerangka dasbor berbasis grid-template-areas: sidebar 260px, bar atas 64px, dan area konten yang bergulir sendiri.
  • Sidebar gelap berisi logo dan menu vertikal, plus bar atas dengan judul halaman dan avatar.
  • Empat kartu statistik dalam grid repeat(4, 1fr) dan tabel pesanan dengan badge status berwarna.
  • Dua media query max-width (1023px dan 767px) yang mengubah sidebar menjadi rail 72px, lalu menyembunyikannya sepenuhnya.
  • Tinggi dasbor 100dvh dengan fallback 100vh dan tabel yang terbaca lewat gulir horizontal.

Proyek ini merangkai latihan dari tiga proyek sebelumnya — token dari Proyek: Website Portofolio , disiplin box model dari Proyek: Halaman Profil , dan kebiasaan breakpoint dari Proyek: Landing Page Responsif . Perbedaannya, dasbor hidup di layar lebar lebih lama daripada halaman publik, sehingga kerangka grid kita rancang desktop-first.

Prasyarat #

Proyek ini memakai konsep dari beberapa Section sebelumnya tanpa mengulang teorinya:

Struktur Folder Proyek #

Struktur berikut adalah kondisi akhir setelah empat bagian selesai:

text
dashboard-admin/
├── index.html              markup dasbor utuh
├── styles/
│   └── dashboard.css       satu file CSS utuh yang berkembang
├── assets/
│   ├── logo-toko.svg       logo sidebar "Toko Kita"
│   └── avatar-admin.svg    foto avatar admin di bar atas
├── tests/
│   ├── dashboard-layout.spec.js
│   ├── sidebar-dan-navigation.spec.js
│   ├── data-cards-dan-tables.spec.js
│   └── responsive-dashboard.spec.js
└── bagian/
    ├── dashboard-layout/
    ├── sidebar-dan-navigation/
    ├── data-cards-dan-tables/
    └── responsive-dashboard/

Sama seperti proyek-proyek sebelumnya, styles/dashboard.css adalah satu file yang di-update di tiap bagian, bukan diganti. Komentar penanda Bagian N di dalam file menandai asal setiap kelompok aturan, dan kedua media query ditumpuk paling akhir.

Daftar Bagian #

BagianFokus
Bagian 1: Dashboard LayoutToken, grid grid-template-areas, dan tiga area dasbor
Bagian 2: Sidebar dan NavigationSidebar gelap, menu vertikal, bar atas, dan avatar
Bagian 3: Data Cards dan TablesEmpat kartu statistik, panel, tabel pesanan, badge status
Bagian 4: Responsive Dashboard100dvh, tabel bergulir, breakpoint 1023px dan 767px

Menjalankan Halaman Uji Otomatis #

Setiap bagian memiliki halaman uji mandiri di bawah /tests/css/proyek/dashboard-admin/<topic>.html, misalnya uji Bagian 1 . Elemen probe di dalamnya membawa atribut data-expected berisi JSON pasangan properti dan nilai computed; harness membuka halaman di headless Chromium dengan viewport 1280x720 dan membandingkan hasil getComputedStyle() dengan nilai tersebut.

Jalankan seluruh uji proyek ini dengan satu perintah:

bash
node scripts/css-test/run_tests.mjs --filter dashboard-admin

Ringkasan asersi per bagian juga tersedia sebagai tests/<topic>.spec.js yang dapat dibaca melalui bagian Berkas Sumber. Untuk pemeriksaan manual, buka halaman uji di browser, tekan F12, lalu bandingkan panel Computed dengan tabel asersi di akhir setiap bagian.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/dashboard.css

Unduh berkas
css
/* =========================================================
   Proyek: Dashboard Admin — styles/dashboard.css
   File utuh proyek yang berkembang mengikuti empat bagian:
   Bagian 1 (Dashboard Layout), Bagian 2 (Sidebar dan
   Navigation), Bagian 3 (Data Cards dan Tables), Bagian 4
   (Responsive Dashboard). Komentar "Bagian N" menandai
   kelompok aturan yang dibahas pada setiap bagian.
   ========================================================= */

/* Bagian 1: Dashboard Layout ------------------------------ */

:root {
  /* Warna semantik — dasbor toko daring Toko Kita */
  --warna-primer: #4f46e5;
  --warna-primer-tua: #4338ca;
  --warna-latar: #f5f5f4;
  --warna-permukaan: #ffffff;
  --warna-teks: #1c1917;
  --warna-teks-lembut: #78716c;
  --warna-teks-balik: #ffffff;
  --warna-garis: #e7e5e4;
  --warna-sidebar: #1c1917;
  --warna-sidebar-teks: #d6d3d1;
  --warna-sidebar-aktif: #44403c;

  /* Skala spasi */
  --spasi-1: 4px;
  --spasi-2: 8px;
  --spasi-3: 12px;
  --spasi-4: 16px;
  --spasi-6: 24px;
  --spasi-8: 32px;

  /* Bentuk */
  --radius-sedang: 10px;
  --radius-kartu: 12px;
  --radius-penuh: 999px;

  /* Dimensi kerangka */
  --font-utama: system-ui, sans-serif;
  --lebar-sidebar: 260px;
  --tinggi-atas: 64px;
}

* {
  margin: 0;
  box-sizing: border-box;
}

body {
  font-family: var(--font-utama);
  font-size: 16px;
  line-height: 1.6;
  color: var(--warna-teks);
  background-color: var(--warna-latar);
}

.dasbor {
  display: grid;
  grid-template-columns: var(--lebar-sidebar) 1fr;
  grid-template-rows: var(--tinggi-atas) 1fr;
  grid-template-areas:
    "sidebar atas"
    "sidebar utama";
  /* Bagian 4: menambah baris height: 100dvh sebagai pengganti */
  height: 100vh;
}

.sidebar {
  grid-area: sidebar;
}

.atas {
  grid-area: atas;
}

.utama {
  grid-area: utama;
  overflow-y: auto;
  padding: var(--spasi-8);
}

/* Bagian 2: Sidebar dan Navigation ------------------------ */

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--spasi-6);
  padding: var(--spasi-6);
  background-color: var(--warna-sidebar);
  color: var(--warna-sidebar-teks);
}

.logo-dasbor {
  display: flex;
  align-items: center;
  gap: var(--spasi-3);
  color: var(--warna-teks-balik);
  font-weight: 700;
  font-size: 18px;
  text-decoration: none;
}

.logo-toko {
  width: 32px;
  height: 32px;
}

.menu {
  display: flex;
  flex-direction: column;
  gap: var(--spasi-2);
  list-style: none;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: var(--spasi-3);
  padding: var(--spasi-3) var(--spasi-4);
  border-radius: var(--radius-sedang);
  color: var(--warna-sidebar-teks);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
}

.menu-item:hover {
  background-color: rgb(255 255 255 / 8%);
}

.menu-item-aktif {
  background-color: var(--warna-sidebar-aktif);
  color: var(--warna-teks-balik);
}

.menu-ikon {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-penuh);
  background-color: currentColor;
}

.atas {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spasi-6);
  padding: 0 var(--spasi-8);
  background-color: var(--warna-permukaan);
  border-bottom: 1px solid var(--warna-garis);
}

.atas-judul {
  font-size: 18px;
  font-weight: 600;
}

.atas-aksi {
  display: flex;
  align-items: center;
  gap: var(--spasi-4);
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background-color: var(--warna-primer);
  color: var(--warna-teks-balik);
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Bagian 3: Data Cards dan Tables ------------------------- */

.ringkasan {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--spasi-6);
  margin-bottom: var(--spasi-8);
}

.kartu-stat {
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-kartu);
  padding: var(--spasi-6);
}

.kartu-label {
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

.kartu-nilai {
  margin-top: var(--spasi-2);
  font-size: 28px;
  font-weight: 700;
}

.kartu-perubahan {
  margin-top: var(--spasi-2);
  font-size: 13px;
  color: #166534;
}

.kartu-perubahan-turun {
  color: #991b1b;
}

.panel {
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-kartu);
  padding: var(--spasi-6);
}

.judul-panel {
  font-size: 18px;
  font-weight: 600;
}

.tabel {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--spasi-4);
}

.tabel th {
  text-align: left;
  padding: var(--spasi-3) var(--spasi-4);
  border-bottom: 1px solid var(--warna-garis);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--warna-teks-lembut);
}

.tabel td {
  padding: var(--spasi-4);
  border-bottom: 1px solid var(--warna-garis);
  font-size: 14px;
}

.status {
  display: inline-block;
  padding: var(--spasi-1) var(--spasi-3);
  border-radius: var(--radius-penuh);
  font-size: 12px;
  font-weight: 500;
}

.status-lunas {
  background-color: #dcfce7;
  color: #166534;
}

.status-diproses {
  background-color: #fef3c7;
  color: #92400e;
}

.status-dibatalkan {
  background-color: #fee2e2;
  color: #991b1b;
}

/* Bagian 4: Responsive Dashboard -------------------------- */

/* Pembungkus tabel agar baris data lebar tetap terbaca
   dengan gulir horizontal, bukan terpotong */
.tabel-gulir {
  overflow-x: auto;
}

.dasbor {
  /* Fallback 100vh diikuti 100dvh untuk browser yang
     mendukung dynamic viewport height (Baseline 2023) */
  height: 100vh;
  height: 100dvh;
}

@media (max-width: 1023px) {
  .dasbor {
    grid-template-columns: 72px 1fr;
  }

  .logo-teks,
  .menu-teks {
    display: none;
  }

  .menu-item {
    justify-content: center;
  }

  .ringkasan {
    grid-template-columns: repeat(2, 1fr);
  }

  .utama {
    padding: var(--spasi-6);
  }
}

@media (max-width: 767px) {
  .dasbor {
    grid-template-columns: 1fr;
    grid-template-areas:
      "atas"
      "utama";
  }

  .sidebar {
    display: none;
  }

  .ringkasan {
    grid-template-columns: 1fr;
  }

  .atas-judul {
    font-size: 16px;
  }
}

tests/dashboard-layout.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 1: Dashboard Layout
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/dashboard-admin/dashboard-layout.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // body: tipografi dasar dan warna dari token semantik.
  { elemen: "body", properti: "font-size", diharapkan: "16px" },
  { elemen: "body", properti: "line-height", diharapkan: "25.6px" },
  { elemen: "body", properti: "color", diharapkan: "rgb(28, 25, 23)" },
  { elemen: "body", properti: "background-color", diharapkan: "rgb(245, 245, 244)" },

  // .dasbor: grid dua kolom dua baris dengan grid-template-areas.
  { elemen: ".dasbor", properti: "display", diharapkan: "grid" },
  { elemen: ".dasbor", properti: "height", diharapkan: "720px" },
  { elemen: ".dasbor", properti: "grid-template-columns", diharapkan: "260px 1020px" },
  { elemen: ".dasbor", properti: "grid-template-rows", diharapkan: "64px 656px" },
  { elemen: ".dasbor", properti: "grid-template-areas", diharapkan: "\"sidebar atas\" \"sidebar utama\"" },

  // .sidebar: kolom pertama selebar token --lebar-sidebar.
  { elemen: ".sidebar", properti: "grid-area", diharapkan: "sidebar" },
  { elemen: ".sidebar", properti: "width", diharapkan: "260px" },

  // .atas: baris pertama kolom kedua, tinggi token --tinggi-atas.
  { elemen: ".atas", properti: "grid-area", diharapkan: "atas" },
  { elemen: ".atas", properti: "width", diharapkan: "1020px" },

  // .utama: area konten yang bergulir sendiri dengan padding token.
  { elemen: ".utama", properti: "grid-area", diharapkan: "utama" },
  { elemen: ".utama", properti: "overflow-y", diharapkan: "auto" },
  { elemen: ".utama", properti: "padding-left", diharapkan: "32px" },
];

tests/data-cards-dan-tables.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 3: Data Cards dan Tables
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/dashboard-admin/data-cards-dan-tables.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // .ringkasan: grid empat kolom sama lebar dengan gap token spasi-6.
  { elemen: ".ringkasan", properti: "display", diharapkan: "grid" },
  { elemen: ".ringkasan", properti: "grid-template-columns", diharapkan: "221px 221px 221px 221px" },
  { elemen: ".ringkasan", properti: "gap", diharapkan: "24px" },
  { elemen: ".ringkasan", properti: "margin-bottom", diharapkan: "32px" },

  // .kartu-stat: permukaan putih, border token, radius kartu 12px.
  { elemen: ".kartu-stat", properti: "padding-top", diharapkan: "24px" },
  { elemen: ".kartu-stat", properti: "border-radius", diharapkan: "12px" },
  { elemen: ".kartu-stat", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".kartu-stat", properti: "border-top-color", diharapkan: "rgb(231, 229, 228)" },

  // Tiga baris isi kartu: label lembut, nilai besar, perubahan naik.
  { elemen: ".kartu-label", properti: "font-size", diharapkan: "14px" },
  { elemen: ".kartu-label", properti: "color", diharapkan: "rgb(120, 113, 108)" },
  { elemen: ".kartu-nilai", properti: "font-size", diharapkan: "28px" },
  { elemen: ".kartu-nilai", properti: "font-weight", diharapkan: "700" },
  { elemen: ".kartu-perubahan", properti: "font-size", diharapkan: "13px" },
  { elemen: ".kartu-perubahan", properti: "color", diharapkan: "rgb(22, 101, 52)" },

  // .panel dan .judul-panel: wadah tabel dengan judul 18px.
  { elemen: ".panel", properti: "padding-top", diharapkan: "24px" },
  { elemen: ".panel", properti: "border-radius", diharapkan: "12px" },
  { elemen: ".judul-panel", properti: "font-size", diharapkan: "18px" },
  { elemen: ".judul-panel", properti: "font-weight", diharapkan: "600" },

  // .tabel: border antarsel dilebur; deklarasi width 100% terresolusi
  // menjadi lebar terpakai 906px di dalam panel (956px dikurangi
  // padding 24px dua sisi dan 2px border).
  { elemen: ".tabel", properti: "border-collapse", diharapkan: "collapse" },
  { elemen: ".tabel", properti: "width", diharapkan: "906px" },

  // .tabel th: header kapital dengan pelacakan huruf longgar.
  { elemen: ".tabel th", properti: "text-align", diharapkan: "left" },
  { elemen: ".tabel th", properti: "text-transform", diharapkan: "uppercase" },
  { elemen: ".tabel th", properti: "font-size", diharapkan: "13px" },
  { elemen: ".tabel th", properti: "letter-spacing", diharapkan: "0.4px" },
  { elemen: ".tabel th", properti: "padding-top", diharapkan: "12px" },
  { elemen: ".tabel th", properti: "color", diharapkan: "rgb(120, 113, 108)" },

  // .tabel td: sel data berpadding 16px dengan garis bawah tipis.
  { elemen: ".tabel td", properti: "padding", diharapkan: "16px" },
  { elemen: ".tabel td", properti: "font-size", diharapkan: "14px" },
  { elemen: ".tabel td", properti: "border-bottom-width", diharapkan: "1px" },

  // .status-lunas: pill hijau latar muda dengan teks hijau tua.
  { elemen: ".status-lunas", properti: "background-color", diharapkan: "rgb(220, 252, 231)" },
  { elemen: ".status-lunas", properti: "color", diharapkan: "rgb(22, 101, 52)" },
  { elemen: ".status-lunas", properti: "border-radius", diharapkan: "999px" },
  { elemen: ".status-lunas", properti: "font-size", diharapkan: "12px" },
];

tests/responsive-dashboard.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 4: Responsive Dashboard
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/dashboard-admin/responsive-dashboard.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan. Harness membuka halaman
// pada viewport 1280x720, sehingga kedua media query
// (max-width: 1023px dan max-width: 767px) berstatus tidak
// aktif — asersi di bawah membuktikan kondisi desktop sebelum
// breakpoint bekerja.

const asersi = [
  // .tabel-gulir: pembungkus tabel dengan gulir horizontal sendiri.
  { elemen: ".tabel-gulir", properti: "overflow-x", diharapkan: "auto" },

  // .dasbor: 100dvh terresolusi menjadi tinggi viewport penuh.
  { elemen: ".dasbor", properti: "height", diharapkan: "720px" },

  // Kondisi desktop: sidebar 260px dan grid tetap dua kolom.
  { elemen: ".dasbor", properti: "grid-template-columns", diharapkan: "260px 1020px" },
  { elemen: ".sidebar", properti: "display", diharapkan: "flex" },
  { elemen: ".sidebar", properti: "width", diharapkan: "260px" },

  // .menu-teks: label menu terlihat (flex item) di desktop,
  // disembunyikan media query 1023px menjadi rail 72px.
  { elemen: ".menu-teks", properti: "display", diharapkan: "block" },

  // Kondisi desktop yang berubah di breakpoint: empat kartu
  // 221px, padding konten 32px, judul bar atas 18px.
  { elemen: ".ringkasan", properti: "grid-template-columns", diharapkan: "221px 221px 221px 221px" },
  { elemen: ".utama", properti: "padding-top", diharapkan: "32px" },
  { elemen: ".atas-judul", properti: "font-size", diharapkan: "18px" },
];

tests/sidebar-dan-navigation.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Dashboard Admin
// Bagian 2: Sidebar dan Navigation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/dashboard-admin/sidebar-dan-navigation.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // .sidebar: flex column dengan latar gelap dan jarak antarblok 24px.
  { elemen: ".sidebar", properti: "display", diharapkan: "flex" },
  { elemen: ".sidebar", properti: "flex-direction", diharapkan: "column" },
  { elemen: ".sidebar", properti: "gap", diharapkan: "24px" },
  { elemen: ".sidebar", properti: "padding-top", diharapkan: "24px" },
  { elemen: ".sidebar", properti: "background-color", diharapkan: "rgb(28, 25, 23)" },

  // .logo-dasbor: nama toko dengan teks putih tebal 18px.
  { elemen: ".logo-dasbor", properti: "font-size", diharapkan: "18px" },
  { elemen: ".logo-dasbor", properti: "font-weight", diharapkan: "700" },
  { elemen: ".logo-dasbor", properti: "color", diharapkan: "rgb(255, 255, 255)" },

  // .menu: daftar vertikal berjarak 8px antartautan.
  { elemen: ".menu", properti: "display", diharapkan: "flex" },
  { elemen: ".menu", properti: "flex-direction", diharapkan: "column" },
  { elemen: ".menu", properti: "gap", diharapkan: "8px" },

  // .menu-item-aktif: pill terang dengan teks putih di atas menu gelap.
  { elemen: ".menu-item", properti: "gap", diharapkan: "12px" },
  { elemen: ".menu-item", properti: "padding-top", diharapkan: "12px" },
  { elemen: ".menu-item", properti: "border-radius", diharapkan: "10px" },
  { elemen: ".menu-item", properti: "font-size", diharapkan: "14px" },
  { elemen: ".menu-item-aktif", properti: "color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".menu-item-aktif", properti: "background-color", diharapkan: "rgb(68, 64, 60)" },
  { elemen: ".menu-item-aktif .menu-ikon", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },

  // .atas: baris atas setinggi 64px dengan ruas kiri-kanan terpisah.
  { elemen: ".atas", properti: "height", diharapkan: "64px" },
  { elemen: ".atas", properti: "justify-content", diharapkan: "space-between" },
  { elemen: ".atas", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".atas", properti: "border-bottom-color", diharapkan: "rgb(231, 229, 228)" },

  // .atas-judul: judul halaman 18px ketebalan 600.
  { elemen: ".atas-judul", properti: "font-size", diharapkan: "18px" },
  { elemen: ".atas-judul", properti: "font-weight", diharapkan: "600" },

  // .avatar: lingkaran primer 36px di ruas kanan bar atas.
  { elemen: ".avatar", properti: "width", diharapkan: "36px" },
  { elemen: ".avatar", properti: "height", diharapkan: "36px" },
  { elemen: ".avatar", properti: "border-radius", diharapkan: "50%" },
  { elemen: ".avatar", properti: "background-color", diharapkan: "rgb(79, 70, 229)" },
];