Minggu, 05 April 2026

Prompt Spesifikasi

 


https://script.google.com/macros/s/AKfycbyDU8pg2I6D50qZ6crEr9xEeql4ZxD-Tzqkha_AAT3TnyILj7oafiF_GyI-Vz4Ns2m-kQ/exec

https://docs.google.com/spreadsheets/d/1KZMNZZe9cG6QszuS9z7u7LkRHle2pX9hYzGSrUNnWrA/edit?gid=1189522197#gid=1189522197

https://script.google.com/u/0/home/projects/1cPdSU-ZLH5LQr8gLXiUtDI4Xz3nW0Xe5ce11j54i77CfoN6FEi7mQSjF/edit

https://gemini.google.com/app/fc065652c9c1f59a?hl=id

https://chatgpt.com/c/69d300c5-e378-839e-be05-df9602fc4cd8


Saya seorang progammer google apps script. Buatkan spesifikasi aplikasi untuk mengelola Perpustakaan. user aplikasi , admin, pegawai, anggota, guest. Business process, meliputi pencetakan kartu anggota, peminjaman, pengembalian, dan penghitungan denda. Laporan berupa buku keluar masuk. Tambahkan dashboard

SPESIFIKASI APLIKASI PERPUSTAKAAN (Google Apps Script)

1. 🎯 Tujuan Aplikasi

Aplikasi ini digunakan untuk:

  • Mengelola data buku dan anggota
  • Mengatur proses peminjaman & pengembalian
  • Menghitung denda otomatis
  • Mencetak kartu anggota
  • Menyajikan laporan & dashboard

2. 👥 Role Pengguna (User Management)

1. Admin

  • Full akses sistem
  • Kelola user (pegawai, anggota)
  • Kelola data buku
  • Akses semua laporan & dashboard

2. Pegawai (Petugas Perpustakaan)

  • Input data anggota
  • Proses peminjaman & pengembalian
  • Cetak kartu anggota
  • Lihat laporan operasional

3. Anggota

  • Melihat katalog buku
  • Melihat riwayat peminjaman
  • Melihat status denda

4. Guest

  • Hanya melihat katalog buku (read-only)

3. ⚙️ Arsitektur Sistem (GAS)

🔹 Teknologi

  • Google Apps Script (Backend + Logic)
  • Google Sheets (Database)
  • HTML Service (Frontend UI)
  • Google Drive (Penyimpanan kartu anggota)

🔹 Struktur Spreadsheet

  1. Users
  2. Buku
  3. Anggota
  4. Transaksi
  5. Denda
  6. Log Aktivitas

4. 📊 Struktur Data (Database Design)

📁 Sheet: Users

FieldTipe
user_idString
namaString
emailString
roleadmin/pegawai/anggota

📁 Sheet: Buku

FieldTipe
buku_idString
judulString
penulisString
kategoriString
stokNumber
lokasi_rakString

📁 Sheet: Anggota

FieldTipe
anggota_idString
namaString
alamatString
no_hpString
tanggal_daftarDate
statusaktif/nonaktif

📁 Sheet: Transaksi

FieldTipe
transaksi_idString
anggota_idString
buku_idString
tanggal_pinjamDate
tanggal_jatuh_tempoDate
tanggal_kembaliDate
statuspinjam/kembali

📁 Sheet: Denda

FieldTipe
denda_idString
transaksi_idString
jumlah_dendaNumber
statusbelum bayar/sudah bayar


5. 🔄 Business Process

A. 🎫 Pencetakan Kartu Anggota

Flow:

  1. Pegawai input data anggota
  2. Sistem generate:
    • ID anggota
    • QR Code / Barcode
  3. Template kartu dibuat (HTML → PDF)
  4. Disimpan di Google Drive
  5. Bisa di-print

👉 Output:

  • File PDF kartu anggota

B. 📥 Peminjaman Buku

Flow:

  1. Pegawai pilih anggota
  2. Scan/input buku
  3. Sistem:
    • Cek stok
    • Kurangi stok
    • Set tanggal pinjam
    • Set jatuh tempo (misal +7 hari)
  4. Simpan transaksi

👉 Status: Pinjam


C. 📤 Pengembalian Buku

Flow:

  1. Pegawai input transaksi
  2. Sistem:
    • Update tanggal kembali
    • Tambah stok buku
    • Hitung keterlambatan

👉 Jika terlambat:

  • Generate denda otomatis

D. 💰 Perhitungan Denda

Formula:

denda = jumlah_hari_terlambat × tarif_harian

Contoh:

  • Terlambat 5 hari
  • Tarif: Rp1000/hari
    👉 Denda = Rp5000

E. 📊 Laporan Buku Keluar Masuk

Jenis laporan:

  1. Buku dipinjam (keluar)
  2. Buku dikembalikan (masuk)
  3. Rekap harian / bulanan

6. 📈 Dashboard (Looker Studio / GAS Web UI)

🔹 KPI Utama:

  • Total Buku
  • Total Anggota
  • Buku Dipinjam
  • Buku Tersedia
  • Total Denda

🔹 Grafik:

  • 📊 Peminjaman per bulan
  • 📈 Pengembalian per bulan
  • 🔥 Buku paling populer
  • 👥 Aktivitas anggota

7. 🧩 Fitur Utama

🔍 Katalog Buku

  • Search
  • Filter kategori
  • Status stok

📄 Riwayat Transaksi

  • Per anggota
  • Per periode

🔐 Authentication

  • Login berbasis email
  • Role-based access

8. 🧱 Struktur Project GAS

📁 LibraryApp
├── Code.gs
├── Auth.gs
├── Buku.gs
├── Anggota.gs
├── Transaksi.gs
├── Denda.gs
├── Dashboard.gs
├── utils.gs
├── index.html
├── dashboard.html
├── anggota.html
├── buku.html

9. 🔌 API Function (GAS)

Contoh fungsi:

function tambahAnggota(data) {}
function cetakKartu(anggotaId) {}
function pinjamBuku(data) {}
function kembaliBuku(transaksiId) {}
function hitungDenda(transaksiId) {}
function getDashboardData() {}

10. 🖥️ UI Halaman

📌 Halaman:

  • Login
  • Dashboard
  • Data Buku
  • Data Anggota
  • Transaksi
  • Laporan

11. 🚀 Pengembangan Lanjutan

  • Integrasi QR Scanner (HTML5)
  • Notifikasi email jatuh tempo
  • Integrasi Looker Studio untuk dashboard lanjutan
  • Mobile-friendly UI
  • Export laporan ke PDF/Excel

12. 📌 Contoh Alur Lengkap

  1. Admin tambah pegawai
  2. Pegawai input anggota
  3. Cetak kartu
  4. Anggota pinjam buku
  5. Sistem catat transaksi
  6. Pengembalian → hitung denda
  7. Data masuk laporan & dashboard

13. 🎯 Nilai Tambah Sistem

  • Paperless (semua digital)
  • Real-time data
  • Mudah dikembangkan (berbasis GAS)
  • Cocok untuk sekolah / SMK / kampus

👉 Jika Anda ingin next step:

Saya bisa bantu:

  • ✔️ Generate template Google Sheet siap pakai
  • ✔️ Buatkan UI HTML + CSS + GAS
  • ✔️ Buatkan kode lengkap (CRUD + transaksi + dashboard)

Tinggal bilang: “lanjut buatkan kodenya” 🚀


PROMPT GENERATOR APLIKASI PERPUSTAKAAN (GAS)

Menulis

Bertindaklah sebagai seorang expert Google Apps Script developer dan system analyst.

Saya ingin membuat aplikasi manajemen perpustakaan berbasis Google Apps Script (GAS) dengan Google Sheets sebagai database dan HTML Service sebagai frontend.

Buatkan aplikasi lengkap (backend + frontend) dengan spesifikasi berikut:

  1. ROLE USER:
  • Admin: full akses
  • Pegawai: kelola anggota, transaksi peminjaman & pengembalian
  • Anggota: lihat katalog, riwayat, denda
  • Guest: hanya lihat katalog
  1. STRUKTUR DATABASE (Google Sheets):
    Buatkan skema sheet berikut beserta fieldnya:
  • Users (user_id, nama, email, role)
  • Buku (buku_id, judul, penulis, kategori, stok, lokasi_rak)
  • Anggota (anggota_id, nama, alamat, no_hp, tanggal_daftar, status)
  • Transaksi (transaksi_id, anggota_id, buku_id, tanggal_pinjam, tanggal_jatuh_tempo, tanggal_kembali, status)
  • Denda (denda_id, transaksi_id, jumlah_denda, status)
  • Log Aktivitas
  1. FITUR UTAMA:
  • CRUD Data Buku
  • CRUD Data Anggota
  • Sistem Login (Session sederhana)
  • Role-based access control
  • Pencetakan kartu anggota (generate PDF + QR Code)
  • Peminjaman buku:
    • cek stok
    • kurangi stok
    • generate tanggal jatuh tempo (7 hari)
  • Pengembalian buku:
    • update stok
    • hitung keterlambatan
  • Perhitungan denda otomatis:
    denda = hari keterlambatan × 1000
  • Laporan:
    • buku keluar (dipinjam)
    • buku masuk (dikembalikan)
    • rekap harian dan bulanan
  1. DASHBOARD:
    Buatkan dashboard dengan:
  • total buku
  • total anggota
  • buku dipinjam
  • buku tersedia
  • total denda
  • grafik peminjaman per bulan
  1. STRUKTUR PROJECT GAS:
    Pisahkan file:
  • Code.gs (entry point)
  • Auth.gs
  • Buku.gs
  • Anggota.gs
  • Transaksi.gs
  • Denda.gs
  • Dashboard.gs
  • Utils.gs
  1. FRONTEND (HTML + CSS + JS):
    Buatkan halaman:
  • login.html
  • dashboard.html
  • buku.html
  • anggota.html
  • transaksi.html

Gunakan:

  • Bootstrap atau Tailwind (pilih salah satu)
  • AJAX (google.script.run)
  1. OUTPUT YANG DIHARAPKAN:
  • Kode lengkap siap deploy di Google Apps Script
  • Struktur Google Sheet otomatis dibuat
  • UI modern dan responsive
  • Fungsi-fungsi utama sudah berjalan (CRUD + transaksi)
  1. TAMBAHAN:
  • Sertakan komentar pada setiap fungsi
  • Sertakan contoh data dummy
  • Sertakan langkah deployment (publish web app)
  • Gunakan clean code dan modular structure

Tampilkan hasil dalam format:

  1. Struktur folder
  2. Kode backend per file
  3. Kode frontend per halaman
  4. Cara setup dan deploy

Jika terlalu panjang, bagi menjadi beberapa bagian dan mulai dari backend terlebih dahulu.


Buatkan aplikasi perpustakaan berbasis Google Apps Script dengan Google Sheets sebagai database dan HTML frontend.

Fitur:

  • Login multi role (admin, pegawai, anggota, guest)
  • CRUD buku & anggota
  • Peminjaman & pengembalian buku
  • Hitung denda otomatis
  • Cetak kartu anggota PDF + QR Code
  • Dashboard statistik
  • Laporan buku keluar masuk

Gunakan:

  • Struktur modular GAS
  • HTML + Bootstrap
  • google.script.run

Output:

  • Kode lengkap backend & frontend
  • Struktur spreadsheet
  • Cara deploy

Code.gs

/**
 * SISTEM MANAJEMEN PERPUSTAKAAN (GAS)
 * Backend Script - Mengelola database Google Sheets dan Logika Bisnis
 */

const SPREADSHEET_ID = SpreadsheetApp.getActiveSpreadsheet().getId();
const APP_ID = typeof __app_id !== 'undefined' ? __app_id : 'lib-manager-001';

// --- INITIALIZATION ---

function doGet() {
  return HtmlService.createTemplateFromFile('index')
    .evaluate()
    .setTitle('E-Perpus: Sistem Manajemen Perpustakaan')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * Fungsi untuk setup awal database (Sheet dan Header)
 */
function setupDatabase() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheets = {
    'Users': ['user_id', 'nama', 'email', 'role'],
    'Buku': ['buku_id', 'judul', 'penulis', 'kategori', 'stok', 'lokasi_rak'],
    'Anggota': ['anggota_id', 'nama', 'alamat', 'no_hp', 'tanggal_daftar', 'status'],
    'Transaksi': ['transaksi_id', 'anggota_id', 'buku_id', 'tanggal_pinjam', 'tanggal_jatuh_tempo', 'tanggal_kembali', 'status'],
    'Denda': ['denda_id', 'transaksi_id', 'jumlah_denda', 'status'],
    'Log': ['timestamp', 'user', 'aktivitas']
  };

  for (let name in sheets) {
    let sheet = ss.getSheetByName(name);
    if (!sheet) {
      sheet = ss.insertSheet(name);
      sheet.appendRow(sheets[name]);
      sheet.getRange(1, 1, 1, sheets[name].length).setFontWeight('bold').setBackground('#f3f4f6');
    }
  }

  // Tambahkan dummy Admin jika Users kosong
  const userSheet = ss.getSheetByName('Users');
  if (userSheet.getLastRow() === 1) {
    userSheet.appendRow(['U001', 'Admin Utama', Session.getActiveUser().getEmail(), 'Admin']);
    // Tambahkan data dummy lainnya untuk pengujian
    ss.getSheetByName('Buku').appendRow(['B001', 'Clean Code', 'Robert C. Martin', 'Teknologi', '5', 'Rak A1']);
    ss.getSheetByName('Anggota').appendRow(['A001', 'Budi Santoso', 'Jakarta', '0812345678', new Date(), 'Aktif']);
  }
 
  return "Database berhasil disiapkan!";
}

// --- AUTHENTICATION ---

function authenticateUser(email) {
  const data = getSheetData('Users');
  const user = data.find(u => u.email.toLowerCase() === email.toLowerCase());
  if (user) {
    logAktivitas(user.nama, 'Login ke sistem');
    return user;
  }
  return null;
}

// --- UTILS & DATA FETCHING ---

function getSheetData(sheetName) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  const values = sheet.getDataRange().getValues();
  const headers = values.shift();
  return values.map(row => {
    let obj = {};
    headers.forEach((h, i) => obj[h] = row[i]);
    return obj;
  });
}

function logAktivitas(user, aktivitas) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log');
  sheet.appendRow([new Date(), user, aktivitas]);
}

// --- DASHBOARD LOGIC ---

function getDashboardStats() {
  const buku = getSheetData('Buku');
  const anggota = getSheetData('Anggota');
  const transaksi = getSheetData('Transaksi');
  const denda = getSheetData('Denda');

  const totalStok = buku.reduce((acc, b) => acc + parseInt(b.stok), 0);
  const dipinjam = transaksi.filter(t => t.status === 'Dipinjam').length;
  const totalDenda = denda.filter(d => d.status === 'Belum Lunas')
                          .reduce((acc, d) => acc + parseFloat(d.jumlah_denda), 0);

  return {
    totalBuku: buku.length,
    totalStok: totalStok,
    totalAnggota: anggota.length,
    bukuDipinjam: dipinjam,
    bukuTersedia: totalStok - dipinjam,
    totalDenda: totalDenda,
    recentLog: getSheetData('Log').slice(-5).reverse()
  };
}

// --- BUKU CRUD ---

function saveBuku(data) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Buku');
  if (data.isEdit) {
    const rows = sheet.getDataRange().getValues();
    for (let i = 1; i < rows.length; i++) {
      if (rows[i][0] === data.buku_id) {
        sheet.getRange(i + 1, 1, 1, 6).setValues([[data.buku_id, data.judul, data.penulis, data.kategori, data.stok, data.lokasi_rak]]);
        break;
      }
    }
  } else {
    const id = 'B' + Utilities.formatDate(new Date(), "GMT+7", "mss");
    sheet.appendRow([id, data.judul, data.penulis, data.kategori, data.stok, data.lokasi_rak]);
  }
  return { success: true };
}

function deleteBuku(id) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Buku');
  const rows = sheet.getDataRange().getValues();
  for (let i = 1; i < rows.length; i++) {
    if (rows[i][0] === id) {
      sheet.deleteRow(i + 1);
      return { success: true };
    }
  }
}

// --- ANGGOTA CRUD ---

function saveAnggota(data) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Anggota');
  if (data.isEdit) {
    const rows = sheet.getDataRange().getValues();
    for (let i = 1; i < rows.length; i++) {
      if (rows[i][0] === data.anggota_id) {
        sheet.getRange(i + 1, 1, 1, 6).setValues([[data.anggota_id, data.nama, data.alamat, data.no_hp, rows[i][4], data.status]]);
        break;
      }
    }
  } else {
    const id = 'A' + Utilities.formatDate(new Date(), "GMT+7", "mss");
    sheet.appendRow([id, data.nama, data.alamat, data.no_hp, new Date(), 'Aktif']);
  }
  return { success: true };
}

// --- TRANSAKSI LOGIC ---

function pinjamBuku(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const bukuSheet = ss.getSheetByName('Buku');
  const transSheet = ss.getSheetByName('Transaksi');
 
  // 1. Cek Stok
  const bukuData = getSheetData('Buku');
  const buku = bukuData.find(b => b.buku_id === data.buku_id);
 
  if (!buku || parseInt(buku.stok) <= 0) {
    throw new Error("Stok buku tidak mencukupi!");
  }

  // 2. Kurangi Stok
  const rows = bukuSheet.getDataRange().getValues();
  for (let i = 1; i < rows.length; i++) {
    if (rows[i][0] === data.buku_id) {
      bukuSheet.getRange(i + 1, 5).setValue(parseInt(rows[i][4]) - 1);
      break;
    }
  }

  // 3. Catat Transaksi
  const tId = 'T' + Utilities.formatDate(new Date(), "GMT+7", "mss");
  const tglPinjam = new Date();
  const tglTempo = new Date();
  tglTempo.setDate(tglPinjam.getDate() + 7); // Default 7 hari

  transSheet.appendRow([tId, data.anggota_id, data.buku_id, tglPinjam, tglTempo, '', 'Dipinjam']);
 
  return { success: true, id: tId };
}

function kembalikanBuku(transaksiId) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const transSheet = ss.getSheetByName('Transaksi');
  const bukuSheet = ss.getSheetByName('Buku');
  const dendaSheet = ss.getSheetByName('Denda');
 
  const transData = getSheetData('Transaksi');
  const transaksi = transData.find(t => t.transaksi_id === transaksiId);
 
  if (!transaksi || transaksi.status === 'Kembali') return;

  const tglKembali = new Date();
  const tglTempo = new Date(transaksi.tanggal_jatuh_tempo);
 
  // Hitung Denda (1000 per hari keterlambatan)
  let dendaAmount = 0;
  if (tglKembali > tglTempo) {
    const diffTime = Math.abs(tglKembali - tglTempo);
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    dendaAmount = diffDays * 1000;
  }

  // Update Transaksi
  const rows = transSheet.getDataRange().getValues();
  for (let i = 1; i < rows.length; i++) {
    if (rows[i][0] === transaksiId) {
      transSheet.getRange(i + 1, 6).setValue(tglKembali);
      transSheet.getRange(i + 1, 7).setValue('Kembali');
      break;
    }
  }

  // Tambah Stok Kembali
  const bukuRows = bukuSheet.getDataRange().getValues();
  for (let i = 1; i < bukuRows.length; i++) {
    if (bukuRows[i][0] === transaksi.buku_id) {
      bukuSheet.getRange(i + 1, 5).setValue(parseInt(bukuRows[i][4]) + 1);
      break;
    }
  }

  // Catat Denda jika ada
  if (dendaAmount > 0) {
    const dId = 'D' + Utilities.formatDate(new Date(), "GMT+7", "mss");
    dendaSheet.appendRow([dId, transaksiId, dendaAmount, 'Belum Lunas']);
  }

  return { success: true, denda: dendaAmount };
}

// --- PDF GENERATION ---

function generateKartuAnggota(anggotaId) {
  const anggota = getSheetData('Anggota').find(a => a.anggota_id === anggotaId);
  if (!anggota) return null;

  const qrUrl = `https://quickchart.io/qr?text=${anggotaId}&size=150`;
 
  const html = `
    <div style="width: 350px; border: 2px solid #3b82f6; border-radius: 10px; padding: 20px; font-family: sans-serif;">
      <h2 style="color: #1e40af; margin-top: 0;">KARTU ANGGOTA PERPUS</h2>
      <hr>
      <div style="display: flex; margin-top: 15px;">
        <div style="flex: 1;">
          <p><strong>ID:</strong> ${anggota.anggota_id}</p>
          <p><strong>Nama:</strong> ${anggota.nama}</p>
          <p><strong>Daftar:</strong> ${new Date(anggota.tanggal_daftar).toLocaleDateString()}</p>
        </div>
        <div>
          <img src="${qrUrl}" width="80" />
        </div>
      </div>
      <p style="font-size: 10px; color: gray; margin-top: 20px;">*Kartu ini berlaku selama menjadi anggota aktif.</p>
    </div>
  `;
 
  const blob = HtmlService.createHtmlOutput(html).getAs('application/pdf');
  return Utilities.base64Encode(blob.getBytes());
}

index.html

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sistem Perpustakaan Digital</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
    <style>
        .page-transition { transition: opacity 0.3s ease; }
        .hidden { display: none; }
        .sidebar-active { background-color: #3b82f6; color: white; }
    </style>
</head>
<body class="bg-gray-50 font-sans text-gray-900">

    <!-- LOGIN SECTION -->
    <div id="login-page" class="min-h-screen flex items-center justify-center bg-blue-600 px-4">
        <div class="bg-white p-8 rounded-xl shadow-2xl w-full max-w-md">
            <div class="text-center mb-8">
                <div class="inline-block p-4 bg-blue-100 rounded-full mb-4">
                    <i class="fas fa-book-reader text-3xl text-blue-600"></i>
                </div>
                <h1 class="text-2xl font-bold text-gray-800">E-Perpus Login</h1>
                <p class="text-gray-500">Gunakan email anda untuk masuk</p>
            </div>
            <div class="space-y-4">
                <div>
                    <label class="block text-sm font-medium text-gray-700">Email Address</label>
                    <input type="email" id="email-input" class="w-full px-4 py-3 mt-1 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="admin@example.com">
                </div>
                <button onclick="handleLogin()" id="btn-login" class="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition">
                    Masuk ke Sistem
                </button>
                <div id="login-error" class="text-red-500 text-sm text-center hidden mt-2"></div>
            </div>
        </div>
    </div>

    <!-- MAIN APP SECTION (Hidden initially) -->
    <div id="app-container" class="min-h-screen flex hidden">
        <!-- Sidebar -->
        <aside class="w-64 bg-slate-900 text-white hidden md:flex flex-col">
            <div class="p-6 text-xl font-bold flex items-center gap-2 border-b border-slate-800">
                <i class="fas fa-university text-blue-400"></i>
                <span>LibManager</span>
            </div>
            <nav class="flex-1 p-4 space-y-2 mt-4" id="main-nav">
                <button onclick="showPage('dashboard')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition sidebar-active" data-page="dashboard">
                    <i class="fas fa-chart-line w-5"></i> Dashboard
                </button>
                <button onclick="showPage('buku')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="buku">
                    <i class="fas fa-book w-5"></i> Katalog Buku
                </button>
                <button onclick="showPage('anggota')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="anggota">
                    <i class="fas fa-users w-5"></i> Anggota
                </button>
                <button onclick="showPage('transaksi')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="transaksi">
                    <i class="fas fa-exchange-alt w-5"></i> Transaksi
                </button>
            </nav>
            <div class="p-4 border-t border-slate-800">
                <div class="flex items-center gap-3 p-2">
                    <div id="user-avatar" class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center font-bold">A</div>
                    <div class="overflow-hidden">
                        <p id="user-name" class="font-medium truncate">User Name</p>
                        <p id="user-role" class="text-xs text-gray-400">Role</p>
                    </div>
                </div>
                <button onclick="logout()" class="w-full mt-4 text-left px-4 py-2 text-red-400 hover:bg-red-500/10 rounded-lg transition">
                    <i class="fas fa-sign-out-alt mr-2"></i> Logout
                </button>
            </div>
        </aside>

        <!-- Main Content -->
        <main class="flex-1 overflow-y-auto">
            <header class="bg-white border-b h-16 flex items-center justify-between px-8">
                <h2 id="page-title" class="text-xl font-bold text-gray-800">Dashboard</h2>
                <div class="flex items-center gap-4">
                    <button onclick="setupDB()" class="text-xs bg-gray-100 px-3 py-1 rounded hover:bg-gray-200">System Setup</button>
                    <span id="current-date" class="text-sm text-gray-500"></span>
                </div>
            </header>

            <div id="content-area" class="p-8">
                <!-- Dashboard Page -->
                <div id="page-dashboard" class="page-content">
                    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
                            <p class="text-gray-500 text-sm font-medium">Total Judul Buku</p>
                            <h3 id="stat-buku" class="text-3xl font-bold mt-1">0</h3>
                        </div>
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
                            <p class="text-gray-500 text-sm font-medium">Anggota Terdaftar</p>
                            <h3 id="stat-anggota" class="text-3xl font-bold mt-1">0</h3>
                        </div>
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
                            <p class="text-gray-500 text-sm font-medium">Sedang Dipinjam</p>
                            <h3 id="stat-pinjam" class="text-3xl font-bold mt-1 text-orange-600">0</h3>
                        </div>
                        <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
                            <p class="text-gray-500 text-sm font-medium">Total Denda Berjalan</p>
                            <h3 id="stat-denda" class="text-3xl font-bold mt-1 text-red-600">Rp 0</h3>
                        </div>
                    </div>
                   
                    <div class="bg-white rounded-xl shadow-sm border p-6">
                        <h4 class="font-bold mb-4 text-gray-700">Log Aktivitas Terakhir</h4>
                        <div class="overflow-x-auto">
                            <table class="w-full text-left">
                                <thead class="text-gray-400 text-xs uppercase border-b">
                                    <tr>
                                        <th class="py-3 px-4">Waktu</th>
                                        <th class="py-3 px-4">User</th>
                                        <th class="py-3 px-4">Aktivitas</th>
                                    </tr>
                                </thead>
                                <tbody id="log-table-body" class="text-sm divide-y">
                                    <!-- Rows added by JS -->
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>

                <!-- Buku Page -->
                <div id="page-buku" class="page-content hidden">
                    <div class="flex justify-between items-center mb-6">
                        <h3 class="text-lg font-bold">Katalog Buku</h3>
                        <button onclick="openBukuModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center gap-2">
                            <i class="fas fa-plus"></i> Tambah Buku
                        </button>
                    </div>
                    <div class="bg-white rounded-xl shadow-sm border overflow-hidden">
                        <table class="w-full text-left border-collapse">
                            <thead class="bg-gray-50 text-gray-600 text-sm">
                                <tr>
                                    <th class="p-4 border-b">ID</th>
                                    <th class="p-4 border-b">Judul</th>
                                    <th class="p-4 border-b">Penulis</th>
                                    <th class="p-4 border-b">Stok</th>
                                    <th class="p-4 border-b">Lokasi</th>
                                    <th class="p-4 border-b text-center">Aksi</th>
                                </tr>
                            </thead>
                            <tbody id="buku-table-body"></tbody>
                        </table>
                    </div>
                </div>

                <!-- Anggota Page -->
                <div id="page-anggota" class="page-content hidden">
                    <div class="flex justify-between items-center mb-6">
                        <h3 class="text-lg font-bold">Manajemen Anggota</h3>
                        <button onclick="openAnggotaModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
                             Tambah Anggota
                        </button>
                    </div>
                    <div class="bg-white rounded-xl shadow-sm border overflow-hidden">
                        <table class="w-full text-left border-collapse">
                            <thead class="bg-gray-50 text-gray-600 text-sm">
                                <tr>
                                    <th class="p-4 border-b">ID</th>
                                    <th class="p-4 border-b">Nama</th>
                                    <th class="p-4 border-b">No HP</th>
                                    <th class="p-4 border-b">Status</th>
                                    <th class="p-4 border-b text-center">Aksi</th>
                                </tr>
                            </thead>
                            <tbody id="anggota-table-body"></tbody>
                        </table>
                    </div>
                </div>

                <!-- Transaksi Page -->
                <div id="page-transaksi" class="page-content hidden">
                    <div class="flex justify-between items-center mb-6">
                        <h3 class="text-lg font-bold">Transaksi Peminjaman</h3>
                        <button onclick="openTransaksiModal()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700">
                             Pinjam Buku Baru
                        </button>
                    </div>
                    <div class="bg-white rounded-xl shadow-sm border overflow-hidden">
                        <table class="w-full text-left border-collapse">
                            <thead class="bg-gray-50 text-gray-600 text-sm">
                                <tr>
                                    <th class="p-4 border-b">ID</th>
                                    <th class="p-4 border-b">Anggota</th>
                                    <th class="p-4 border-b">Buku</th>
                                    <th class="p-4 border-b">Jatuh Tempo</th>
                                    <th class="p-4 border-b">Status</th>
                                    <th class="p-4 border-b text-center">Aksi</th>
                                </tr>
                            </thead>
                            <tbody id="trans-table-body"></tbody>
                        </table>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <!-- MODALS -->
    <div id="modal-overlay" class="fixed inset-0 bg-black/50 hidden flex items-center justify-center p-4 z-50">
        <!-- Buku Modal -->
        <div id="modal-buku" class="bg-white rounded-xl max-w-lg w-full p-6 hidden">
            <h3 class="text-xl font-bold mb-4" id="buku-modal-title">Data Buku</h3>
            <form id="form-buku" onsubmit="handleBukuSubmit(event)" class="space-y-4">
                <input type="hidden" id="buku-id-input">
                <div class="grid grid-cols-2 gap-4">
                    <div class="col-span-2">
                        <label class="text-sm font-medium">Judul Buku</label>
                        <input type="text" id="buku-judul" required class="w-full border p-2 rounded mt-1">
                    </div>
                    <div>
                        <label class="text-sm font-medium">Penulis</label>
                        <input type="text" id="buku-penulis" required class="w-full border p-2 rounded mt-1">
                    </div>
                    <div>
                        <label class="text-sm font-medium">Kategori</label>
                        <select id="buku-kategori" class="w-full border p-2 rounded mt-1">
                            <option>Teknologi</option><option>Sains</option><option>Sastra</option><option>Sejarah</option>
                        </select>
                    </div>
                    <div>
                        <label class="text-sm font-medium">Stok</label>
                        <input type="number" id="buku-stok" required class="w-full border p-2 rounded mt-1">
                    </div>
                    <div>
                        <label class="text-sm font-medium">Lokasi Rak</label>
                        <input type="text" id="buku-rak" required class="w-full border p-2 rounded mt-1">
                    </div>
                </div>
                <div class="flex justify-end gap-2 pt-4">
                    <button type="button" onclick="closeModal()" class="px-4 py-2 text-gray-500">Batal</button>
                    <button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-lg">Simpan</button>
                </div>
            </form>
        </div>

        <!-- Transaksi Modal -->
        <div id="modal-trans" class="bg-white rounded-xl max-w-lg w-full p-6 hidden">
            <h3 class="text-xl font-bold mb-4">Peminjaman Buku Baru</h3>
            <form id="form-trans" onsubmit="handlePinjamSubmit(event)" class="space-y-4">
                <div>
                    <label class="text-sm font-medium">Pilih Anggota</label>
                    <select id="trans-anggota-select" required class="w-full border p-2 rounded mt-1"></select>
                </div>
                <div>
                    <label class="text-sm font-medium">Pilih Buku</label>
                    <select id="trans-buku-select" required class="w-full border p-2 rounded mt-1"></select>
                </div>
                <p class="text-xs text-gray-500 italic">*Lama pinjaman standar adalah 7 hari.</p>
                <div class="flex justify-end gap-2 pt-4">
                    <button type="button" onclick="closeModal()" class="px-4 py-2 text-gray-500">Batal</button>
                    <button type="submit" class="px-6 py-2 bg-green-600 text-white rounded-lg">Konfirmasi Pinjam</button>
                </div>
            </form>
        </div>
    </div>

    <!-- NOTIFICATION SYSTEM -->
    <div id="toast" class="fixed bottom-8 right-8 bg-slate-800 text-white px-6 py-3 rounded-xl shadow-2xl transition-all duration-300 opacity-0 translate-y-10 pointer-events-none z-[100]">
        <span id="toast-msg"></span>
    </div>

    <script>
        // --- GLOBAL STATE ---
        let currentUser = null;
        let dbData = { buku: [], anggota: [], transaksi: [] };

        // --- APP INITIALIZATION ---
        document.addEventListener('DOMContentLoaded', () => {
            document.getElementById('current-date').innerText = new Date().toLocaleDateString('id-ID', { dateStyle: 'full' });
        });

        function setupDB() {
            notify("Menyiapkan database...");
            google.script.run.withSuccessHandler(res => {
                notify(res);
            }).setupDatabase();
        }

        // --- AUTH LOGIC ---
        function handleLogin() {
            const email = document.getElementById('email-input').value;
            const btn = document.getElementById('btn-login');
            const error = document.getElementById('login-error');
           
            if (!email) return;
           
            btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Autentikasi...';
            btn.disabled = true;
            error.classList.add('hidden');

            google.script.run.withSuccessHandler(user => {
                if (user) {
                    currentUser = user;
                    initApp();
                } else {
                    error.innerText = "Email tidak ditemukan!";
                    error.classList.remove('hidden');
                    btn.innerHTML = 'Masuk ke Sistem';
                    btn.disabled = false;
                }
            }).authenticateUser(email);
        }

        function initApp() {
            document.getElementById('login-page').classList.add('hidden');
            document.getElementById('app-container').classList.remove('hidden');
            document.getElementById('user-name').innerText = currentUser.nama;
            document.getElementById('user-role').innerText = currentUser.role;
            document.getElementById('user-avatar').innerText = currentUser.nama.charAt(0);
           
            refreshDashboard();
            loadBuku();
            loadAnggota();
            loadTransaksi();
        }

        function logout() {
            window.location.reload();
        }

        // --- NAVIGATION & UI ---
        function showPage(pageId) {
            document.querySelectorAll('.page-content').forEach(p => p.classList.add('hidden'));
            document.getElementById('page-' + pageId).classList.remove('hidden');
            document.getElementById('page-title').innerText = pageId.charAt(0).toUpperCase() + pageId.slice(1);
           
            document.querySelectorAll('.nav-link').forEach(l => {
                if (l.dataset.page === pageId) l.classList.add('sidebar-active');
                else l.classList.remove('sidebar-active');
            });
        }

        function notify(msg) {
            const toast = document.getElementById('toast');
            document.getElementById('toast-msg').innerText = msg;
            toast.classList.remove('opacity-0', 'translate-y-10');
            toast.classList.add('opacity-100', 'translate-y-0');
            setTimeout(() => {
                toast.classList.add('opacity-0', 'translate-y-10');
                toast.classList.remove('opacity-100', 'translate-y-0');
            }, 3000);
        }

        // --- DATA LOADERS ---
        function refreshDashboard() {
            google.script.run.withSuccessHandler(stats => {
                document.getElementById('stat-buku').innerText = stats.totalBuku;
                document.getElementById('stat-anggota').innerText = stats.totalAnggota;
                document.getElementById('stat-pinjam').innerText = stats.bukuDipinjam;
                document.getElementById('stat-denda').innerText = 'Rp ' + stats.totalDenda.toLocaleString('id-ID');
               
                const logBody = document.getElementById('log-table-body');
                logBody.innerHTML = stats.recentLog.map(log => `
                    <tr>
                        <td class="py-2 px-4 text-xs text-gray-500">${new Date(log.timestamp).toLocaleTimeString()}</td>
                        <td class="py-2 px-4 font-medium">${log.user}</td>
                        <td class="py-2 px-4 text-gray-600">${log.aktivitas}</td>
                    </tr>
                `).join('');
            }).getDashboardStats();
        }

        function loadBuku() {
            google.script.run.withSuccessHandler(data => {
                dbData.buku = data;
                const body = document.getElementById('buku-table-body');
                body.innerHTML = data.map(b => `
                    <tr>
                        <td class="p-4 text-sm">${b.buku_id}</td>
                        <td class="p-4 font-medium">${b.judul}</td>
                        <td class="p-4 text-sm text-gray-600">${b.penulis}</td>
                        <td class="p-4 text-sm">${b.stok}</td>
                        <td class="p-4 text-sm font-mono text-blue-600">${b.lokasi_rak}</td>
                        <td class="p-4 text-center">
                            <button onclick="editBuku('${b.buku_id}')" class="text-blue-500 p-1"><i class="fas fa-edit"></i></button>
                            <button onclick="deleteBuku('${b.buku_id}')" class="text-red-400 p-1 ml-2"><i class="fas fa-trash"></i></button>
                        </td>
                    </tr>
                `).join('');
            }).getSheetData('Buku');
        }

        function loadAnggota() {
            google.script.run.withSuccessHandler(data => {
                dbData.anggota = data;
                const body = document.getElementById('anggota-table-body');
                body.innerHTML = data.map(a => `
                    <tr>
                        <td class="p-4 text-sm">${a.anggota_id}</td>
                        <td class="p-4 font-medium">${a.nama}</td>
                        <td class="p-4 text-sm">${a.no_hp}</td>
                        <td class="p-4"><span class="bg-green-100 text-green-700 px-2 py-1 rounded text-xs">${a.status}</span></td>
                        <td class="p-4 text-center">
                            <button onclick="printCard('${a.anggota_id}')" title="Print Kartu" class="text-purple-600 p-1"><i class="fas fa-id-card"></i></button>
                        </td>
                    </tr>
                `).join('');
            }).getSheetData('Anggota');
        }

        function loadTransaksi() {
            google.script.run.withSuccessHandler(data => {
                dbData.transaksi = data;
                const body = document.getElementById('trans-table-body');
                body.innerHTML = data.map(t => {
                    const statusClass = t.status === 'Dipinjam' ? 'bg-orange-100 text-orange-700' : 'bg-blue-100 text-blue-700';
                    return `
                    <tr>
                        <td class="p-4 text-xs font-mono">${t.transaksi_id}</td>
                        <td class="p-4 text-sm">${t.anggota_id}</td>
                        <td class="p-4 text-sm">${t.buku_id}</td>
                        <td class="p-4 text-sm text-red-600">${new Date(t.tanggal_jatuh_tempo).toLocaleDateString()}</td>
                        <td class="p-4"><span class="${statusClass} px-2 py-1 rounded text-xs">${t.status}</span></td>
                        <td class="p-4 text-center">
                            ${t.status === 'Dipinjam' ? `<button onclick="handleKembali('${t.transaksi_id}')" class="text-xs bg-slate-800 text-white px-3 py-1 rounded">Kembalikan</button>` : '-'}
                        </td>
                    </tr>
                `}).join('');
            }).getSheetData('Transaksi');
        }

        // --- FORM HANDLERS ---
        function openBukuModal(id = null) {
            document.getElementById('modal-overlay').classList.remove('hidden');
            document.getElementById('modal-buku').classList.remove('hidden');
            document.getElementById('form-buku').reset();
            document.getElementById('buku-id-input').value = id || '';
            document.getElementById('buku-modal-title').innerText = id ? 'Edit Buku' : 'Tambah Buku Baru';
           
            if (id) {
                const b = dbData.buku.find(x => x.buku_id === id);
                document.getElementById('buku-judul').value = b.judul;
                document.getElementById('buku-penulis').value = b.penulis;
                document.getElementById('buku-kategori').value = b.kategori;
                document.getElementById('buku-stok').value = b.stok;
                document.getElementById('buku-rak').value = b.lokasi_rak;
            }
        }

        function handleBukuSubmit(e) {
            e.preventDefault();
            const id = document.getElementById('buku-id-input').value;
            const data = {
                isEdit: !!id,
                buku_id: id,
                judul: document.getElementById('buku-judul').value,
                penulis: document.getElementById('buku-penulis').value,
                kategori: document.getElementById('buku-kategori').value,
                stok: document.getElementById('buku-stok').value,
                lokasi_rak: document.getElementById('buku-rak').value
            };
           
            google.script.run.withSuccessHandler(() => {
                notify("Buku berhasil disimpan!");
                closeModal();
                loadBuku();
                refreshDashboard();
            }).saveBuku(data);
        }

        function openTransaksiModal() {
            document.getElementById('modal-overlay').classList.remove('hidden');
            document.getElementById('modal-trans').classList.remove('hidden');
           
            const bSelect = document.getElementById('trans-buku-select');
            const aSelect = document.getElementById('trans-anggota-select');
           
            bSelect.innerHTML = dbData.buku.map(b => `<option value="${b.buku_id}">${b.judul} (${b.stok})</option>`).join('');
            aSelect.innerHTML = dbData.anggota.map(a => `<option value="${a.anggota_id}">${a.nama}</option>`).join('');
        }

        function handlePinjamSubmit(e) {
            e.preventDefault();
            const data = {
                anggota_id: document.getElementById('trans-anggota-select').value,
                buku_id: document.getElementById('trans-buku-select').value
            };
           
            google.script.run.withSuccessHandler(res => {
                notify("Buku berhasil dipinjam!");
                closeModal();
                loadTransaksi();
                loadBuku();
                refreshDashboard();
            }).withFailureHandler(err => {
                notify("Error: " + err.message);
            }).pinjamBuku(data);
        }

        function handleKembali(id) {
            if (!confirm("Konfirmasi pengembalian buku?")) return;
            google.script.run.withSuccessHandler(res => {
                notify(res.denda > 0 ? `Buku kembali! Denda keterlambatan: Rp ${res.denda}` : "Buku kembali tepat waktu!");
                loadTransaksi();
                loadBuku();
                refreshDashboard();
            }).kembalikanBuku(id);
        }

        function printCard(id) {
            notify("Menyiapkan kartu PDF...");
            google.script.run.withSuccessHandler(base64 => {
                const blob = b64toBlob(base64, 'application/pdf');
                const url = URL.createObjectURL(blob);
                window.open(url);
            }).generateKartuAnggota(id);
        }

        function closeModal() {
            document.getElementById('modal-overlay').classList.add('hidden');
            document.querySelectorAll('#modal-overlay > div').forEach(m => m.classList.add('hidden'));
        }

        // Helper to convert base64 to Blob for PDF preview
        function b64toBlob(b64Data, contentType = '', sliceSize = 512) {
            const byteCharacters = atob(b64Data);
            const byteArrays = [];
            for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                const slice = byteCharacters.slice(offset, offset + sliceSize);
                const byteNumbers = new Array(slice.length);
                for (let i = 0; i < slice.length; i++) byteNumbers[i] = slice.charCodeAt(i);
                byteArrays.push(new Uint8Array(byteNumbers));
            }
            return new Blob(byteArrays, {type: contentType});
        }
    </script>
</body>
</html>

Tidak ada komentar:

Posting Komentar

PROMPT - Social Media