5025251096_Gabriel Mesly Managam Siahaan_Tugas CSS

 


Tampilan Web , telah ditambahkan saya sendiri ke tabel


Index.html

<!doctype html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="Kelola data mahasiswa dengan mudah menggunakan Student Management."
    />
    <title>Student Management</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="app.js" defer></script>
  </head>
  <body>
    <header class="topbar">
      <a class="brand" href="#home" aria-label="Student Management — Home">
        <span class="brand-icon" aria-hidden="true">
          <svg viewBox="0 0 48 48" fill="none">
            <path d="M3 17.5 24 7l21 10.5L24 28 3 17.5Z" fill="currentColor" />
            <path d="M11 22.5V33c7.5 6 18.5 6 26 0V22.5L24 29l-13-6.5Z" fill="currentColor" />
            <path d="M43 20v12" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
          </svg>
        </span>
        <span>Student Management</span>
      </a>
      <nav class="main-nav" aria-label="Navigasi utama">
        <a href="#home">Home</a>
        <a href="#students">Students</a>
        <a href="#about">About</a>
      </nav>
      <button class="profile-button" type="button" aria-label="Profil pengguna">
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 12a4.3 4.3 0 1 0 0-8.6 4.3 4.3 0 0 0 0 8.6Zm0 2c-4.4 0-8 2.2-8 5v1h16v-1c0-2.8-3.6-5-8-5Z" fill="currentColor" />
        </svg>
      </button>
    </header>

    <main class="dashboard" id="home">
      <section class="panel form-panel" aria-labelledby="form-heading">
        <h1 id="form-heading">Form Student</h1>
        <form id="student-form">
          <label for="nim">NIM</label>
          <input
            id="nim"
            name="nim"
            type="text"
            placeholder="Masukkan NIM"
            autocomplete="off"
            required
          />

          <label for="name">Nama Lengkap</label>
          <input
            id="name"
            name="name"
            type="text"
            placeholder="Masukkan nama lengkap"
            autocomplete="name"
            required
          />

          <label for="major">Jurusan</label>
          <select id="major" name="major" required>
            <option value="" disabled selected>Pilih Jurusan</option>
            <option>Informatika</option>
            <option>Sistem Informasi</option>
            <option>Teknik Komputer</option>
            <option>Manajemen</option>
            <option>Akuntansi</option>
            <option>Teknik Elektro</option>
          </select>

          <label for="email">Email</label>
          <input
            id="email"
            name="email"
            type="email"
            placeholder="Masukkan email"
            autocomplete="email"
            required
          />

          <div class="form-actions">
            <button class="button button-primary" type="submit">
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 3h12l4 4v14H3V3h2Zm2 2v5h10V5H7Zm0 14h10v-6H7v6Zm8-13h2v3h-2V6Z" fill="currentColor" /></svg>
              <span id="submit-label">Simpan</span>
            </button>
            <button class="button button-muted" id="cancel-button" type="button">Batal</button>
            <button class="button button-danger" id="reset-button" type="button">
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a8 8 0 0 1 7.5 5H17l4 4 4-4h-3.3A10 10 0 0 0 4.1 7.3l2.1 2.1A7.9 7.9 0 0 1 12 4Zm-7.5 7-4 4h3.3A10 10 0 0 0 19.9 17l-2.1-2.1A8 8 0 0 1 4.5 15H7l-2.5-4Z" fill="currentColor" transform="translate(1 1) scale(.92)" /></svg>
              <span>Reset</span>
            </button>
          </div>
          <p class="form-message" id="form-message" role="status" aria-live="polite"></p>
        </form>
      </section>

      <section class="panel table-panel" id="students" aria-labelledby="students-heading">
        <div class="table-heading">
          <h2 id="students-heading">Data Mahasiswa</h2>
          <label class="search-box" for="student-search">
            <span class="visually-hidden">Cari mahasiswa</span>
            <input id="student-search" type="search" placeholder="Cari mahasiswa..." />
            <span class="search-icon" aria-hidden="true">
              <svg viewBox="0 0 24 24"><circle cx="10.8" cy="10.8" r="6.8" fill="none" stroke="currentColor" stroke-width="2.5" /><path d="m16 16 5 5" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>
            </span>
          </label>
        </div>

        <div class="table-wrap">
          <table>
            <thead>
              <tr>
                <th scope="col">No</th>
                <th scope="col">NIM</th>
                <th scope="col">Nama</th>
                <th scope="col">Jurusan</th>
                <th scope="col">Email</th>
                <th scope="col">Aksi</th>
              </tr>
            </thead>
            <tbody id="student-rows"></tbody>
          </table>
          <p class="empty-state" id="empty-state" hidden>Mahasiswa tidak ditemukan.</p>
        </div>
        <nav class="pagination" id="pagination" aria-label="Halaman data mahasiswa"></nav>
      </section>
    </main>
    <footer id="about">Student Management <span aria-hidden="true">·</span> Kelola data mahasiswa dengan mudah</footer>
  </body>
</html>

STYLES .css
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap");

:root {
  font-family: Inter, "Segoe UI", Arial, sans-serif;
  color: #17243a;
  background: #eaf1f8;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-optical-sizing: auto;
  font-weight: 400;
  --blue: #1769e8;
  --navy: #123b66;
  --border: #dce4ed;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 320px;
  min-height: 100vh;
  margin: 0;
  background:
    radial-gradient(ellipse at 15% 0%, rgba(206, 226, 249, 0.75), transparent 38rem),
    #edf2f8;
}

button,
input,
select {
  font: inherit;
}

.topbar {
  min-height: 82px;
  display: flex;
  align-items: center;
  gap: 32px;
  padding: 0 clamp(20px, 4vw, 54px);
  color: #fff;
  background: linear-gradient(110deg, #123b66, #174675);
  box-shadow: 0 5px 18px rgba(22, 51, 82, 0.14);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 15px;
  color: inherit;
  font-size: clamp(1.1rem, 2vw, 1.45rem);
  font-weight: 750;
  letter-spacing: -0.045em;
  text-decoration: none;
  white-space: nowrap;
}

.brand-icon {
  display: grid;
  width: 44px;
  height: 44px;
  place-items: center;
}

.brand-icon svg {
  width: 100%;
  height: 100%;
}

.main-nav {
  display: flex;
  align-items: center;
  gap: clamp(22px, 4vw, 52px);
  margin-left: auto;
}

.main-nav a {
  color: rgba(255, 255, 255, 0.91);
  font-size: 0.96rem;
  font-weight: 600;
  text-decoration: none;
  transition: color 160ms ease;
}

.main-nav a:hover,
.main-nav a:focus-visible {
  color: #b9d7ff;
}

.profile-button {
  display: grid;
  width: 42px;
  height: 42px;
  padding: 8px;
  place-items: center;
  color: #174675;
  background: #f3f7fc;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.profile-button svg {
  width: 100%;
  height: 100%;
}

.dashboard {
  display: grid;
  grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.65fr);
  align-items: stretch;
  gap: 20px;
  width: min(1540px, 100%);
  margin: 0 auto;
  padding: 24px clamp(18px, 2.4vw, 32px) 26px;
}

.panel {
  min-width: 0;
  padding: clamp(22px, 2.5vw, 34px);
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid rgba(216, 226, 237, 0.8);
  border-radius: 20px;
  box-shadow: 0 12px 35px rgba(35, 62, 91, 0.07);
}

.form-panel h1,
.table-heading h2 {
  margin: 0;
  color: #17243a;
  font-size: clamp(1.24rem, 2vw, 1.55rem);
  font-weight: 750;
  letter-spacing: -0.045em;
}

#student-form {
  display: flex;
  flex-direction: column;
  margin-top: 24px;
}

#student-form label {
  margin: 0 0 8px;
  color: #273449;
  font-size: 0.96rem;
  font-weight: 600;
}

#student-form input,
#student-form select {
  width: 100%;
  min-height: 47px;
  margin: 0 0 18px;
  padding: 0 14px;
  color: #25334a;
  background-color: #fff;
  border: 1px solid #cfd9e4;
  border-radius: 9px;
  outline: none;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}

#student-form input::placeholder,
.search-box input::placeholder {
  color: #97a3b3;
}

#student-form select {
  cursor: pointer;
}

#student-form input:focus,
#student-form select:focus,
.search-box:focus-within {
  border-color: #4a91f5;
  box-shadow: 0 0 0 3px rgba(23, 105, 232, 0.12);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
  margin-top: 12px;
}

.button {
  display: inline-flex;
  min-height: 46px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 14px;
  color: #fff;
  border: 0;
  border-radius: 9px;
  cursor: pointer;
  font-weight: 650;
  transition: transform 150ms ease, filter 150ms ease, box-shadow 150ms ease;
}

.button:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow: 0 5px 13px rgba(30, 60, 95, 0.16);
}

.button:active {
  transform: translateY(0);
}

.button svg {
  width: 18px;
  height: 18px;
}

.button-primary {
  background: linear-gradient(135deg, #287cf2, #1262dc);
}

.button-muted {
  background: #71839a;
}

.button-danger {
  background: #ed414b;
}

.form-message {
  min-height: 20px;
  margin: 12px 0 0;
  color: #167448;
  font-size: 0.84rem;
}

.form-message.is-error {
  color: #c02c38;
}

.table-panel {
  display: flex;
  flex-direction: column;
  padding: clamp(22px, 2.5vw, 34px);
}

.table-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 23px;
}

.search-box {
  display: flex;
  width: min(100%, 270px);
  min-height: 44px;
  align-items: center;
  overflow: hidden;
  border: 1px solid #d5dee8;
  border-radius: 9px;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}

.search-box input {
  width: 100%;
  min-width: 0;
  height: 42px;
  padding: 0 13px;
  color: #25334a;
  background: transparent;
  border: 0;
  outline: 0;
}

.search-box input:focus {
  box-shadow: none;
}

.search-icon {
  display: grid;
  width: 48px;
  min-width: 48px;
  height: 44px;
  place-items: center;
  color: #53657b;
  border-left: 1px solid #e1e7ef;
}

.search-icon svg {
  width: 19px;
  height: 19px;
}

.table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid #dce4ed;
  border-radius: 9px;
}

table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}

th,
td {
  padding: 11px 13px;
  border-right: 1px solid #e0e6ed;
  border-bottom: 1px solid #e0e6ed;
  white-space: nowrap;
}

th:last-child,
td:last-child {
  border-right: 0;
}

tbody tr:last-child td {
  border-bottom: 0;
}

th {
  height: 52px;
  color: #fff;
  background: linear-gradient(120deg, #2879ed, #1260dc);
  font-size: 0.9rem;
  font-weight: 650;
}

td {
  height: 52px;
  color: #28364b;
  background: #fff;
  font-size: 0.88rem;
}

tbody tr:nth-child(even) td {
  background: #f8fafc;
}

tbody tr:hover td {
  background: #eff6ff;
}

th:first-child,
td:first-child {
  width: 54px;
  text-align: center;
}

th:last-child,
td:last-child {
  width: 110px;
  text-align: center;
}

.row-actions {
  display: flex;
  justify-content: center;
  gap: 7px;
}

.icon-button {
  display: inline-grid;
  width: 35px;
  height: 35px;
  padding: 8px;
  place-items: center;
  color: #fff;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
  transition: filter 150ms ease, transform 150ms ease;
}

.icon-button:hover {
  filter: brightness(1.1);
  transform: translateY(-1px);
}

.icon-button svg {
  width: 100%;
  height: 100%;
}

.edit-button {
  background: #2377eb;
}

.delete-button {
  background: #ee424d;
}

.empty-state {
  margin: 0;
  padding: 28px 16px;
  color: #68788e;
  text-align: center;
}

.pagination {
  display: flex;
  justify-content: flex-end;
  gap: 0;
  margin-top: 24px;
}

.page-button {
  display: grid;
  min-width: 42px;
  height: 42px;
  padding: 0 11px;
  place-items: center;
  color: #34445a;
  background: #fff;
  border: 1px solid #dce4ed;
  border-right: 0;
  cursor: pointer;
  font-weight: 550;
  transition: background 150ms ease, color 150ms ease;
}

.page-button:first-child {
  border-radius: 8px 0 0 8px;
}

.page-button:last-child {
  border-right: 1px solid #dce4ed;
  border-radius: 0 8px 8px 0;
}

.page-button:hover:not(:disabled) {
  color: var(--blue);
  background: #eef5ff;
}

.page-button.is-active {
  color: #fff;
  background: linear-gradient(135deg, #2879ed, #1260dc);
  border-color: #1769e8;
}

.page-button:disabled {
  color: #9ba7b5;
  cursor: not-allowed;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

footer {
  padding: 0 20px 22px;
  color: #8390a0;
  font-size: 0.8rem;
  text-align: center;
}

footer span {
  padding: 0 4px;
}

@media (min-width: 1200px) {
  .dashboard {
    min-height: calc(100vh - 112px);
  }
}

@media (max-width: 900px) {
  .dashboard {
    grid-template-columns: 1fr;
  }

  .form-panel {
    order: 1;
  }

  .table-panel {
    order: 2;
  }
}

@media (max-width: 560px) {
  .topbar {
    min-height: 72px;
    gap: 12px;
    padding: 0 16px;
  }

  .brand {
    gap: 8px;
    font-size: 1rem;
  }

  .brand-icon {
    width: 34px;
    height: 34px;
  }

  .main-nav {
    gap: 12px;
  }

  .main-nav a {
    font-size: 0.76rem;
  }

  .profile-button {
    display: none;
  }

  .dashboard {
    gap: 14px;
    padding: 14px 12px 18px;
  }

  .panel {
    padding: 20px 16px;
    border-radius: 15px;
  }

  .table-heading {
    align-items: stretch;
    flex-direction: column;
    margin-bottom: 16px;
  }

  .search-box {
    width: 100%;
  }

  .pagination {
    justify-content: center;
    margin-top: 18px;
  }

  .page-button {
    min-width: 36px;
    height: 38px;
    padding: 0 8px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

app.js

const initialStudents = [
  { nim: "231001", name: "Andi Pratama", major: "Informatika", email: "andi@mail.com" },
  { nim: "231002", name: "Siti Aisyah", major: "Sistem Informasi", email: "siti@mail.com" },
  { nim: "231003", name: "Budi Santoso", major: "Teknik Komputer", email: "budi@mail.com" },
  { nim: "231004", name: "Nina Marlina", major: "Manajemen", email: "nina@mail.com" },
  { nim: "231005", name: "Rizky Pratama", major: "Informatika", email: "rizky@mail.com" },
];

const pageSize = 5;
let students = [...initialStudents];
let currentPage = 1;
let editingNim = null;

const form = document.querySelector("#student-form");
const formHeading = document.querySelector("#form-heading");
const submitLabel = document.querySelector("#submit-label");
const formMessage = document.querySelector("#form-message");
const searchInput = document.querySelector("#student-search");
const rows = document.querySelector("#student-rows");
const emptyState = document.querySelector("#empty-state");
const pagination = document.querySelector("#pagination");

function getFilteredStudents() {
  const query = searchInput.value.trim().toLocaleLowerCase("id");
  if (!query) return students;

  return students.filter((student) =>
    [student.nim, student.name, student.major, student.email]
      .some((value) => value.toLocaleLowerCase("id").includes(query)),
  );
}

function makeIcon(path) {
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  svg.setAttribute("viewBox", "0 0 24 24");
  svg.setAttribute("aria-hidden", "true");
  const shape = document.createElementNS("http://www.w3.org/2000/svg", "path");
  shape.setAttribute("d", path);
  shape.setAttribute("fill", "currentColor");
  svg.append(shape);
  return svg;
}

function renderPagination(totalPages) {
  pagination.replaceChildren();
  if (totalPages <= 1) return;

  const addPageButton = (label, page, { active = false, disabled = false, ariaLabel } = {}) => {
    const button = document.createElement("button");
    button.type = "button";
    button.className = `page-button${active ? " is-active" : ""}`;
    button.textContent = label;
    button.disabled = disabled;
    if (ariaLabel) button.setAttribute("aria-label", ariaLabel);
    if (active) button.setAttribute("aria-current", "page");
    button.addEventListener("click", () => {
      currentPage = page;
      renderStudents();
    });
    pagination.append(button);
  };

  addPageButton("«", 1, { disabled: currentPage === 1, ariaLabel: "Halaman pertama" });
  addPageButton("‹", currentPage - 1, { disabled: currentPage === 1, ariaLabel: "Halaman sebelumnya" });

  const visiblePages = Math.min(totalPages, 5);
  let firstPage = Math.max(1, currentPage - 2);
  firstPage = Math.min(firstPage, totalPages - visiblePages + 1);
  for (let page = firstPage; page < firstPage + visiblePages; page += 1) {
    addPageButton(String(page), page, { active: page === currentPage });
  }

  addPageButton("›", currentPage + 1, { disabled: currentPage === totalPages, ariaLabel: "Halaman berikutnya" });
  addPageButton("»", totalPages, { disabled: currentPage === totalPages, ariaLabel: "Halaman terakhir" });
}

function renderStudents() {
  const filteredStudents = getFilteredStudents();
  const totalPages = Math.ceil(filteredStudents.length / pageSize);
  currentPage = Math.min(Math.max(currentPage, 1), Math.max(totalPages, 1));
  const firstIndex = (currentPage - 1) * pageSize;
  const pageStudents = filteredStudents.slice(firstIndex, firstIndex + pageSize);

  rows.replaceChildren();
  pageStudents.forEach((student, index) => {
    const row = document.createElement("tr");
    [String(firstIndex + index + 1), student.nim, student.name, student.major, student.email].forEach((value) => {
      const cell = document.createElement("td");
      cell.textContent = value;
      row.append(cell);
    });

    const actionCell = document.createElement("td");
    const actions = document.createElement("div");
    actions.className = "row-actions";

    const editButton = document.createElement("button");
    editButton.type = "button";
    editButton.className = "icon-button edit-button";
    editButton.setAttribute("aria-label", `Edit ${student.name}`);
    editButton.append(makeIcon("M3 17.25V21h3.75L17.8 9.95l-3.75-3.75L4 16.25l-1 1ZM20.7 7.05a1 1 0 0 0 0-1.4l-2.35-2.35a1 1 0 0 0-1.4 0l-1.83 1.83 3.75 3.75 1.83-1.83Z"));
    editButton.addEventListener("click", () => beginEdit(student));

    const deleteButton = document.createElement("button");
    deleteButton.type = "button";
    deleteButton.className = "icon-button delete-button";
    deleteButton.setAttribute("aria-label", `Hapus ${student.name}`);
    deleteButton.append(makeIcon("M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12ZM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4Z"));
    deleteButton.addEventListener("click", () => removeStudent(student));

    actions.append(editButton, deleteButton);
    actionCell.append(actions);
    row.append(actionCell);
    rows.append(row);
  });

  emptyState.hidden = filteredStudents.length > 0;
  renderPagination(totalPages);
}

function showMessage(message, isError = false) {
  formMessage.textContent = message;
  formMessage.classList.toggle("is-error", isError);
}

function clearForm() {
  form.reset();
  editingNim = null;
  formHeading.textContent = "Form Student";
  submitLabel.textContent = "Simpan";
  showMessage("");
}

function beginEdit(student) {
  editingNim = student.nim;
  form.elements.nim.value = student.nim;
  form.elements.name.value = student.name;
  form.elements.major.value = student.major;
  form.elements.email.value = student.email;
  formHeading.textContent = "Edit Mahasiswa";
  submitLabel.textContent = "Simpan Perubahan";
  showMessage(`Sedang mengedit data ${student.name}.`);
  form.elements.name.focus();
}

function removeStudent(student) {
  students = students.filter((item) => item.nim !== student.nim);
  if (editingNim === student.nim) clearForm();
  showMessage(`Data ${student.name} berhasil dihapus.`);
  renderStudents();
}

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const formData = new FormData(form);
  const student = {
    nim: String(formData.get("nim")).trim(),
    name: String(formData.get("name")).trim(),
    major: String(formData.get("major")).trim(),
    email: String(formData.get("email")).trim(),
  };

  const duplicate = students.some((item) => item.nim === student.nim && item.nim !== editingNim);
  if (duplicate) {
    showMessage("NIM tersebut sudah terdaftar. Gunakan NIM yang berbeda.", true);
    form.elements.nim.focus();
    return;
  }

  if (editingNim) {
    students = students.map((item) => item.nim === editingNim ? student : item);
    showMessage(`Data ${student.name} berhasil diperbarui.`);
  } else {
    students.unshift(student);
    showMessage(`Data ${student.name} berhasil ditambahkan.`);
  }

  clearForm();
  currentPage = 1;
  renderStudents();
});

document.querySelector("#cancel-button").addEventListener("click", clearForm);
document.querySelector("#reset-button").addEventListener("click", clearForm);
searchInput.addEventListener("input", () => {
  currentPage = 1;
  renderStudents();
});

renderStudents();

CS

Komentar

Postingan populer dari blog ini

5025251096_Gabriel Mesly Managam Siahaan_Website Portofolio