Rabu, 17 Desember 2025

Apps Script - News Portal 1

 




Buatkan aplikasi Studi Kasus (Final UI) Aplikasi News Portal ✔ SPA ✔ CRUD Lengkap ✔ Bootstrap 5 ✔ Hero Section ✔ Responsive




Code.gs

/*************************
 * KONFIGURASI
 *************************/
const SPREADSHEET_ID = '1f4qRC-_vELEhSLSVn3NvRrd-PsZp1vhwIWDra7nA7c8';
const SHEET_NAME = 'News';

/*************************
 * ENTRY POINT
 *************************/
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('News Portal')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

/*************************
 * HELPER
 *************************/
function getSheet() {
  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  const sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) throw new Error('Sheet "News" tidak ditemukan');
  return sheet;
}

/*************************
 * READ
 *************************/
function getNews() {
  const sheet = getSheet();
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];

  const range = sheet.getRange(2, 1, lastRow - 1, 5);
  const values = range.getValues();

  // 🔑 NORMALISASI DATA (PENTING)
  return values.map(r => ({
    id: String(r[0]),                      // paksa string
    judul: String(r[1] || ''),
    kategori: String(r[2] || ''),
    konten: String(r[3] || ''),
    tanggal: r[4]
      ? Utilities.formatDate(
          new Date(r[4]),
          Session.getScriptTimeZone(),
          'yyyy-MM-dd'
        )
      : ''
  }));
}

/*************************
 * CREATE
 *************************/
function addNews(n) {
  const sheet = getSheet();
  sheet.appendRow([
    Date.now().toString(), // simpan ID sebagai STRING
    n.judul,
    n.kategori,
    n.konten,
    new Date()
  ]);
  return true;
}

/*************************
 * UPDATE
 *************************/
function updateNews(n) {
  const sheet = getSheet();
  const ids = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();

  for (let i = 0; i < ids.length; i++) {
    if (String(ids[i][0]) === String(n.id)) {
      sheet.getRange(i + 2, 2, 1, 3).setValues([[
        n.judul,
        n.kategori,
        n.konten
      ]]);
      return true;
    }
  }
  return false;
}

/*************************
 * DELETE
 *************************/
function deleteNews(id) {
  const sheet = getSheet();
  const ids = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();

  for (let i = 0; i < ids.length; i++) {
    if (String(ids[i][0]) === String(id)) {
      sheet.deleteRow(i + 2);
      return true;
    }
  }
  return false;
}


Index.html

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">

  <!-- Bootstrap -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">

  <style>
    body { background: #f8f9fa; }
    .hero {
      background: linear-gradient(120deg, #212529, #0d6efd);
      color: white;
      padding: 90px 20px;
      border-radius: 0 0 40px 40px;
    }
    .card { border-radius: 16px; }
  </style>
</head>

<body>

<!-- HERO -->
<section class="hero text-center">
  <div class="container">
    <h1 class="display-5 fw-bold">News Portal</h1>
    <p class="lead">SPA berbasis Google Apps Script</p>
  </div>
</section>

<!-- CONTENT -->
<div class="container mt-5">
  <div class="row">
    <!-- FORM -->
    <div class="col-md-4 mb-4">
      <div class="card shadow-sm">
        <div class="card-body">
          <h5 class="card-title mb-3">Form Berita</h5>
          <input type="hidden" id="id">

          <input id="judul" class="form-control mb-2" placeholder="Judul">
          <input id="kategori" class="form-control mb-2" placeholder="Kategori">
          <textarea id="konten" class="form-control mb-3" rows="4" placeholder="Konten"></textarea>

          <div class="d-grid gap-2">
            <button class="btn btn-primary" onclick="simpan()">Simpan</button>
            <button class="btn btn-outline-secondary" onclick="resetForm()">Batal</button>
          </div>
        </div>
      </div>
    </div>

    <!-- NEWS LIST -->
    <div class="col-md-8">
      <div class="row" id="newsList"></div>
    </div>
  </div>
</div>

<script>
  let news = [];
  let modeEdit = false;

  document.addEventListener('DOMContentLoaded', loadNews);

  function loadNews() {
    google.script.run
      .withSuccessHandler(data => {
        news = Array.isArray(data) ? data : [];
        render();
      })
      .withFailureHandler(err => {
        alert('Gagal load data: ' + err.message);
      })
      .getNews();
  }

  function render() {
    const el = document.getElementById('newsList');
    el.innerHTML = '';

    if (news.length === 0) {
      el.innerHTML = `<p class="text-muted">Belum ada berita</p>`;
      return;
    }

    news.forEach(n => {
      el.innerHTML += `
        <div class="col-md-6 mb-4">
          <div class="card h-100 shadow-sm">
            <div class="card-body">
              <span class="badge bg-primary">${n.kategori}</span>
              <h5 class="mt-2">${n.judul}</h5>
              <p class="text-muted small">${n.tanggal}</p>
              <p>${n.konten.slice(0, 120)}...</p>
            </div>
            <div class="card-footer text-end bg-white">
              <button class="btn btn-sm btn-warning" onclick="editNews('${n.id}')">Edit</button>
              <button class="btn btn-sm btn-danger" onclick="hapusNews('${n.id}')">Hapus</button>
            </div>
          </div>
        </div>`;
    });
  }

  function simpan() {
    const data = {
      id: document.getElementById('id').value,
      judul: judul.value,
      kategori: kategori.value,
      konten: konten.value
    };

    const cb = () => {
      resetForm();
      loadNews();
    };

    modeEdit
      ? google.script.run.withSuccessHandler(cb).updateNews(data)
      : google.script.run.withSuccessHandler(cb).addNews(data);
  }

  function editNews(id) {
    const n = news.find(x => x.id === id);
    if (!n) return;

    idInput.value = n.id;
    judul.value = n.judul;
    kategori.value = n.kategori;
    konten.value = n.konten;
    modeEdit = true;
  }

  function hapusNews(id) {
    if (!confirm('Hapus berita ini?')) return;
    google.script.run.withSuccessHandler(loadNews).deleteNews(id);
  }

  function resetForm() {
    idInput.value = '';
    judul.value = '';
    kategori.value = '';
    konten.value = '';
    modeEdit = false;
  }

  const idInput = document.getElementById('id');
  const judul = document.getElementById('judul');
  const kategori = document.getElementById('kategori');
  const konten = document.getElementById('konten');
</script>

</body>
</html>


Tidak ada komentar:

Posting Komentar

Laporan KUS - Sertifikat

  https://www.youtube.com/watch?v=1xegpb4fLk4 =arrayformula(if(row(A:A)=1;"Image";substitute(F:F;"open?";"uc?export...