Jumat, 09 Januari 2026

Login Apps Script

 













Bustkan prompt untuk Build a Advance Google Sheets CRUD Web App with Real-Time Data

PROMPT UTAMA (SIAP COPY–PASTE)

Build an Advanced Google Sheets CRUD Web App with Real-Time Data

Act as a senior full-stack developer specialized in Google Apps Script, SPA architecture, and Google Sheets as a database.

🎯 Objective

Build a production-ready Advanced CRUD Web App using:

  • Google Apps Script (Web App)

  • Google Sheets as a real-time database

  • Single Page Application (SPA)


🧩 Backend (Google Apps Script – code.gs)

  1. Use Google Sheets as the primary database.

  2. Implement a REST-like API Router using doGet(e) and doPost(e):

    • list → Read all data

    • get → Read single record by ID

    • create → Insert new record

    • update → Update record

    • delete → Delete record

  3. Automatically initialize sheet structure if the sheet does not exist:

    • Columns: id, timestamp, nama, kategori, deskripsi, status

  4. Generate UUID for primary key.

  5. Return all responses in JSON format only.

  6. Enable CORS-safe access for frontend fetch.

  7. Optimize performance with:

    • Batch read/write

    • Minimal Spreadsheet calls


🧩 Frontend (index.html)

  1. Build a Single Page Application (SPA) using:

    • Vanilla JavaScript (no framework)

    • Fetch API

  2. UI Requirements:

    • Responsive layout

    • Modern dashboard UI (Tailwind CSS or Bootstrap 5)

    • Hero section

    • Data table with:

      • Live reload (real-time refresh after CRUD)

      • Search & filter

      • Pagination

    • Modal form for:

      • Add data

      • Edit data

      • Delete confirmation

  3. Form validation (client-side).

  4. Show loading state & error handling.


🔄 Real-Time Behavior

  • After Create / Update / Delete:

    • Automatically refresh UI without page reload

  • Use:

    • google.script.run OR

    • fetch() to Web App endpoint


🧱 Architecture

  • Clean separation:

    • SheetService → Data access layer

    • ApiController → Routing layer

    • UIController → Frontend logic

  • Commented code and scalable structure.


📦 Deliverables

  1. code.gs (complete backend)

  2. index.html (complete frontend SPA)

  3. Step-by-step deployment instructions:

    • Deploy as Web App

    • Access permission setup

  4. Clear explanation of:

    • Data flow

    • CRUD lifecycle

    • Real-time update mechanism


⭐ Bonus (Optional but Preferred)

  • Toast notifications

  • Soft delete support

  • Role-based access (admin/viewer)

  • Export data to CSV

Output the final solution with clean, professional, and well-structured code.


 

Kamis, 08 Januari 2026

Vibe Coding

 







PROMPT


I want to create a HTML dashboard from data ini Google Sheets using Apps Script.

Google Sheet structure
Retrieve data from a single tab called "SALES"
Column A: DATE
Column B: NAME (ignore for display)
Column C: SALES REP
Column D: SECTOR
Column E: AMOUNT

General UI:
1. I want dark mode by default (dark background, white text)
2. Display all amounts in USD currency (no cents)
3. Responsive Bootstrap 5 layout
4. Keep layout clean and compact
5. Make sure charts, tables, and scorecards update immediately when filters change
6. Don't include any extra text

Filters:
1. Quick range dropdown: YTD, MTD, Last 7 Days, Last 14 Das, Last 30 Days, and individual years
2. Start Date/ End Date pickers
3. Sales Rep dropdown (multi-select) with "All" option at top that clears other selections.
4. Sector dropdown (multi-select) with "All" option at top that clears other selections
5. Add a reset button on the far right to clear all filters.
6. All filters should apply automatically

Scorecards
Three scorecards with title and number both centers showing
1. Total Sales
2. # of Sales
3. Avg Sale Amount

Chart Section :
1. Title "Sales" with "By Sales Rep/ By Sector" dropdown next to it to toggle the display for the chart and breakdown.
2. Stacked bar chart in dark mode
3. Show total amount over each bar
4. Modern darker pastel palette distinct colors per rep/ sector
5. Mouse over shows exact amount per segment
6. Chart automatically switchers: 
A. Monthly mode is the date range>30 days
B. Daily mode is under 30 days
7. Below the chart, show data table for chart with one column for each day or month as displayed in the chart
8. Table should be dark mode with white text and subtle striping










Create an App from Google Sheets using AppSheet

 












Rabu, 07 Januari 2026

Desain UI Template

 





















Untuk tahun 2026, terdapat berbagai sumber terpercaya untuk mendapatkan template UI HTML/CSS yang dioptimalkan khusus untuk perangkat mobile atau bersifat responsive (mobile-first). 
1. Sumber Template Mobile-Friendly Terbaik (Gratis & Modern)
  • HTML5 UP!: Menyediakan koleksi template yang sepenuhnya responsif, dibangun dengan HTML5 dan CSS3, serta sangat mudah dikustomisasi.
  • Colorlib: Memiliki daftar lebih dari 30 template mobile-friendly terbaru untuk 2026, termasuk template spesifik seperti "Uza" untuk bisnis.
  • TemplateMo: Menawarkan lebih dari 600 template HTML/CSS gratis yang diperbarui secara rutin hingga akhir 2025/awal 2026.
  • UIdeck: Fokus pada landing page aplikasi mobile dan UI Kit berbasis Bootstrap atau Tailwind CSS yang modern.
  • W3Schools Templates: Menyediakan layout dasar yang sangat ringan dan mudah dipelajari untuk struktur mobile murni. 
2. Pilihan UI Kit Khusus Mobile (Komponen Siap Pakai)
Jika Anda ingin membangun antarmuka aplikasi dari nol, gunakan UI Kit berikut:
  • Material Kit 2 (Bootstrap 5): Kit UI gratis berbasis Material Design yang sangat responsif untuk tampilan aplikasi Android di web.
  • Materia eCommerce Kit: Khusus untuk desain toko online mobile yang mengutamakan visual produk.
  • HTMLrev: Repositori yang mengkurasi template HTML berbasis Tailwind dan Bootstrap untuk SaaS dan aplikasi web. 
3. Framework Rekomendasi 2026
Untuk hasil yang lebih profesional dan ringan di mobile, pertimbangkan menggunakan framework ini sebagai basis:
  • Tailwind CSS: Sangat populer untuk kontrol desain mobile-first yang presisi.
  • Bootstrap 5: Pilihan paling stabil untuk komponen UI mobile yang sudah matang (seperti menu hamburger, modal, dan cards). 


QR - Barcode Explore

 








Selasa, 06 Januari 2026

ePerpustakaan

 






1.58 59
7.26 35
11.31 27
13.37 85

59352785

Code.gs

// ==================== KONFIGURASI ====================
const SPREADSHEET_ID = '1FTLbiPwBR9y4x63RhsdlR5jfKimejhPt3AMz1kyFabE';// <<< GANTI DENGAN ID SPREADSHEET ANDA
const DRIVE_FOLDER_ID = '1E3YGMUwzqf8yFkle7EsFrAv9_DekXdAo'; // <<< GANTI DENGAN ID FOLDER GOOGLE DRIVE ANDA



function getSpreadsheet() {
  return SpreadsheetApp.openById(SPREADSHEET_ID);
}

// ==================== INITIALIZE SHEETS ====================
/**
 * Fungsi untuk membuat sheet dan mengisi data sampel
 * Jalankan fungsi ini HANYA SEKALI saat pertama kali setup
 */
function initializeSheets() {
  const ss = getSpreadsheet();
 
  // ========== SHEET USERS ==========
  let usersSheet = ss.getSheetByName('Users');
  if (usersSheet) {
    ss.deleteSheet(usersSheet);
  }
  usersSheet = ss.insertSheet('Users');
 
  // Header
  usersSheet.getRange('A1:D1').setValues([['email', 'password', 'role', 'nama']]);
  usersSheet.getRange('A1:D1').setFontWeight('bold').setBackground('#1e40af').setFontColor('#ffffff');
 
  // Data sampel
  const usersData = [
    ['admin@perpus.com', 'admin123', 'Admin', 'Administrator Perpustakaan'],
    ['guru@sekolah.com', 'guru123', 'Admin', 'Ibu Siti Rahayu'],
    ['siswa1@sekolah.com', 'siswa123', 'Siswa', 'Ahmad Rizki Maulana'],
    ['siswa2@sekolah.com', 'siswa123', 'Siswa', 'Siti Nurhaliza'],
    ['siswa3@sekolah.com', 'siswa123', 'Siswa', 'Budi Santoso'],
    ['siswa4@sekolah.com', 'siswa123', 'Siswa', 'Dewi Lestari'],
    ['siswa5@sekolah.com', 'siswa123', 'Siswa', 'Rian Pratama']
  ];
  usersSheet.getRange(2, 1, usersData.length, 4).setValues(usersData);
  usersSheet.setFrozenRows(1);
  usersSheet.autoResizeColumns(1, 4);
 
  // ========== SHEET BOOKS ==========
  let booksSheet = ss.getSheetByName('Books');
  if (booksSheet) {
    ss.deleteSheet(booksSheet);
  }
  booksSheet = ss.insertSheet('Books');
 
  // Header
  booksSheet.getRange('A1:E1').setValues([['id', 'judul', 'penulis', 'stok', 'coverURL']]);
  booksSheet.getRange('A1:E1').setFontWeight('bold').setBackground('#1e40af').setFontColor('#ffffff');
 
  // Data sampel
  const booksData = [
    ['BK001', 'Laskar Pelangi', 'Andrea Hirata', 8, 'https://images.tokopedia.net/img/cache/500-square/VqbcmM/2021/5/28/d7e6c0e5-c6e7-4e7e-8e0d-0e5c7f4d6e7d.jpg'],
    ['BK002', 'Bumi Manusia', 'Pramoedya Ananta Toer', 5, 'https://cdn.gramedia.com/uploads/items/9789799731234.jpg'],
    ['BK003', 'Sang Pemimpi', 'Andrea Hirata', 6, 'https://cdn.gramedia.com/uploads/items/Sang_Pemimpi.jpg'],
    ['BK004', 'Negeri 5 Menara', 'Ahmad Fuadi', 7, 'https://cdn.gramedia.com/uploads/items/9786020331188_Negeri-5-Menara.jpg'],
    ['BK005', 'Perahu Kertas', 'Dee Lestari', 4, 'https://cdn.gramedia.com/uploads/items/9786024246945_Perahu-Kertas.jpg'],
    ['BK006', 'Ronggeng Dukuh Paruk', 'Ahmad Tohari', 5, 'https://cdn.gramedia.com/uploads/items/9789799101488.jpg'],
    ['BK007', 'Ayat-Ayat Cinta', 'Habiburrahman El Shirazy', 10, 'https://cdn.gramedia.com/uploads/items/9789793062792.jpg'],
    ['BK008', 'Saman', 'Ayu Utami', 3, 'https://cdn.gramedia.com/uploads/items/9786024803520.jpg'],
    ['BK009', 'Pulang', 'Leila S. Chudori', 6, 'https://cdn.gramedia.com/uploads/items/9786024246037.jpg'],
    ['BK010', 'Cantik Itu Luka', 'Eka Kurniawan', 4, 'https://cdn.gramedia.com/uploads/items/9786024246105.jpg'],
    ['BK011', 'The Da Vinci Code', 'Dan Brown', 5, 'https://cdn.gramedia.com/uploads/items/9780307474278.jpg'],
    ['BK012', 'Harry Potter and the Philosopher Stone', 'J.K. Rowling', 8, 'https://cdn.gramedia.com/uploads/items/9781408855652.jpg'],
    ['BK013', 'The Alchemist', 'Paulo Coelho', 7, 'https://cdn.gramedia.com/uploads/items/9780062315007.jpg'],
    ['BK014', '1984', 'George Orwell', 6, 'https://cdn.gramedia.com/uploads/items/9780451524935.jpg'],
    ['BK015', 'To Kill a Mockingbird', 'Harper Lee', 5, 'https://cdn.gramedia.com/uploads/items/9780061120084.jpg']
  ];
  booksSheet.getRange(2, 1, booksData.length, 5).setValues(booksData);
  booksSheet.setFrozenRows(1);
  booksSheet.autoResizeColumns(1, 5);
 
  // ========== SHEET BORROW ==========
  let borrowSheet = ss.getSheetByName('Borrow');
  if (borrowSheet) {
    ss.deleteSheet(borrowSheet);
  }
  borrowSheet = ss.insertSheet('Borrow');
 
  // Header
  borrowSheet.getRange('A1:G1').setValues([['id', 'siswa', 'bukuId', 'judulBuku', 'tanggalPinjam', 'tanggalKembali', 'status']]);
  borrowSheet.getRange('A1:G1').setFontWeight('bold').setBackground('#1e40af').setFontColor('#ffffff');
 
  // Data sampel peminjaman
  const today = new Date();
  const yesterday = new Date(today);
  yesterday.setDate(yesterday.getDate() - 1);
  const twoDaysAgo = new Date(today);
  twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
  const threeDaysAgo = new Date(today);
  threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
  const weekAgo = new Date(today);
  weekAgo.setDate(weekAgo.getDate() - 7);
 
  const borrowData = [
    ['BR001', 'siswa1@sekolah.com', 'BK001', 'Laskar Pelangi', Utilities.formatDate(threeDaysAgo, Session.getScriptTimeZone(), 'dd/MM/yyyy'), '', 'Pending'],
    ['BR002', 'siswa2@sekolah.com', 'BK002', 'Bumi Manusia', Utilities.formatDate(twoDaysAgo, Session.getScriptTimeZone(), 'dd/MM/yyyy'), '', 'Approved'],
    ['BR003', 'siswa3@sekolah.com', 'BK003', 'Sang Pemimpi', Utilities.formatDate(yesterday, Session.getScriptTimeZone(), 'dd/MM/yyyy'), '', 'Pending'],
    ['BR004', 'siswa1@sekolah.com', 'BK004', 'Negeri 5 Menara', Utilities.formatDate(weekAgo, Session.getScriptTimeZone(), 'dd/MM/yyyy'), Utilities.formatDate(yesterday, Session.getScriptTimeZone(), 'dd/MM/yyyy'), 'Returned'],
    ['BR005', 'siswa4@sekolah.com', 'BK005', 'Perahu Kertas', Utilities.formatDate(twoDaysAgo, Session.getScriptTimeZone(), 'dd/MM/yyyy'), '', 'Approved'],
    ['BR006', 'siswa5@sekolah.com', 'BK007', 'Ayat-Ayat Cinta', Utilities.formatDate(today, Session.getScriptTimeZone(), 'dd/MM/yyyy'), '', 'Pending']
  ];
  borrowSheet.getRange(2, 1, borrowData.length, 7).setValues(borrowData);
  borrowSheet.setFrozenRows(1);
  borrowSheet.autoResizeColumns(1, 7);
 
  // Pindahkan sheet ke urutan yang benar
  ss.setActiveSheet(usersSheet);
  ss.moveActiveSheet(1);
  ss.setActiveSheet(booksSheet);
  ss.moveActiveSheet(2);
  ss.setActiveSheet(borrowSheet);
  ss.moveActiveSheet(3);
 
  // Hapus sheet default jika ada
  const defaultSheet = ss.getSheetByName('Sheet1');
  if (defaultSheet && ss.getSheets().length > 3) {
    ss.deleteSheet(defaultSheet);
  }
 
  Logger.log('✅ INISIALISASI BERHASIL!');
  Logger.log('Sheet Users, Books, dan Borrow telah dibuat dengan data sampel.');
  Logger.log('');
  Logger.log('Login Admin:');
  Logger.log('• Email: admin@perpus.com');
  Logger.log('• Password: admin123');
  Logger.log('');
  Logger.log('Login Siswa:');
  Logger.log('• Email: siswa1@sekolah.com');
  Logger.log('• Password: siswa123');
  Logger.log('');
  Logger.log('Silakan deploy aplikasi sebagai Web App!');
 
  return 'Inisialisasi berhasil! Lihat log untuk detail.';
}

// ==================== RENDER HTML ====================
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('Perpustakaan Digital')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
    .addMetaTag("viewport", "width=device-width, initial-scale=1.0")
    .setFaviconUrl('https://w7.pngwing.com/pngs/184/833/png-transparent-exam-test-checklist-online-learning-education-online-document-online-learning-icon.png');
}

// ==================== AUTENTIKASI ====================
function login(email, password) {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
  const data = usersSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === email && data[i][1] === password) {
      return {
        success: true,
        user: {
          email: data[i][0],
          role: data[i][2],
          nama: data[i][3]
        }
      };
    }
  }
 
  return { success: false, message: 'Email atau password salah' };
}

// ==================== FUNGSI BUKU ====================
function getBooks() {
  const ss = getSpreadsheet();
  const booksSheet = ss.getSheetByName('Books');
  const data = booksSheet.getDataRange().getValues();
  const books = [];
 
  for (let i = 1; i < data.length; i++) {
    books.push({
      id: data[i][0],
      judul: data[i][1],
      penulis: data[i][2],
      stok: data[i][3],
      coverURL: data[i][4]
    });
  }
 
  return books;
}

// --- FUNGSI BANTUAN UNTUK UPLOAD KE DRIVE ---
function processFileUpload(fileData) {
  try {
    let folder;
    if (DRIVE_FOLDER_ID) {
      try {
        folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
      } catch (e) {
        folder = DriveApp.getRootFolder();
      }
    } else {
      folder = DriveApp.getRootFolder();
    }

    const contentType = fileData.mimeType;
    const check = contentType.split('/');
    if (check[0] !== 'image') {
      throw new Error('File harus berupa gambar');
    }

    // Decode base64 string ke blob
    const decoded = Utilities.base64Decode(fileData.data);
    const blob = Utilities.newBlob(decoded, contentType, fileData.fileName);
   
    // Simpan file ke Drive
    const file = folder.createFile(blob);
   
    // PENTING: Set permission agar gambar bisa dilihat di tag <img> HTML
    file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
   
    // Mengembalikan URL yang bisa di-render oleh tag <img>
    return "https://lh3.googleusercontent.com/d/" + file.getId();
   
  } catch (error) {
    throw new Error('Gagal upload gambar: ' + error.toString());
  }
}

// --- MODIFIKASI FUNGSI ADD BOOK ---
function addBook(judul, penulis, stok, coverURL, fileData) {
  const ss = getSpreadsheet();
  const booksSheet = ss.getSheetByName('Books');
 
  // Jika ada file yang diupload, proses upload dulu
  let finalCoverURL = coverURL;
  if (fileData && fileData.data) {
    finalCoverURL = processFileUpload(fileData);
  } else if (!finalCoverURL) {
    // Default image jika tidak ada URL dan tidak ada upload
    finalCoverURL = 'https://via.placeholder.com/200x300/667eea/ffffff?text=No+Cover';
  }

  const lastRow = booksSheet.getLastRow();
  // Generate ID unik yang lebih aman
  const newId = 'BK' + (lastRow + Math.floor(Math.random() * 1000)).toString().padStart(3, '0');
 
  booksSheet.appendRow([newId, judul, penulis, stok, finalCoverURL]);
  return { success: true, message: 'Buku berhasil ditambahkan' };
}

// --- MODIFIKASI FUNGSI UPDATE BOOK ---
function updateBook(id, judul, penulis, stok, coverURL, fileData) {
  const ss = getSpreadsheet();
  const booksSheet = ss.getSheetByName('Books');
  const data = booksSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === id) {
     
      // Logika URL Gambar:
      // 1. Jika ada upload baru (fileData), pakai itu.
      // 2. Jika tidak ada upload, tapi ada coverURL (dari input hidden/text), pakai itu (url lama).
      let finalCoverURL = coverURL;
     
      if (fileData && fileData.data) {
        finalCoverURL = processFileUpload(fileData);
      }
     
      booksSheet.getRange(i + 1, 2, 1, 4).setValues([[judul, penulis, stok, finalCoverURL]]);
      return { success: true, message: 'Buku berhasil diupdate' };
    }
  }
 
  return { success: false, message: 'Buku tidak ditemukan' };
}
function deleteBook(id) {
  const ss = getSpreadsheet();
  const booksSheet = ss.getSheetByName('Books');
  const data = booksSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === id) {
      booksSheet.deleteRow(i + 1);
      return { success: true, message: 'Buku berhasil dihapus' };
    }
  }
 
  return { success: false, message: 'Buku tidak ditemukan' };
}

// ==================== FUNGSI SISWA ====================
function getStudents() {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
  const data = usersSheet.getDataRange().getValues();
  const students = [];
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][2] === 'Siswa') {
      students.push({
        email: data[i][0],
        nama: data[i][3]
      });
    }
  }
 
  return students;
}

function addStudent(email, password, nama) {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
 
  usersSheet.appendRow([email, "'" + password, 'Siswa', nama]);
  return { success: true, message: 'Siswa berhasil ditambahkan' };
}

function updateStudent(oldEmail, newEmail, password, nama) {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
  const data = usersSheet.getDataRange().getValues();
  let studentFound = false;

  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === oldEmail && data[i][2] === 'Siswa') {
      // Update nama dan password jika ada
      if (password) {
        usersSheet.getRange(i + 1, 2).setValue("'" + password);
      }
      usersSheet.getRange(i + 1, 4).setValue(nama);
     
      // Jika email juga diubah, panggil fungsi terpisah
      if (oldEmail !== newEmail) {
        updateStudentEmail(oldEmail, newEmail);
      }
     
      studentFound = true;
      return { success: true, message: 'Data siswa berhasil diupdate' };
    }
  }
 
  if (!studentFound) {
    return { success: false, message: 'Siswa tidak ditemukan' };
  }
}

function updateStudentEmail(oldEmail, newEmail) {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
  const borrowSheet = ss.getSheetByName('Borrow');

  // 1. Update di Sheet Users
  const usersData = usersSheet.getDataRange().getValues();
  for (let i = 1; i < usersData.length; i++) {
    if (usersData[i][0] === oldEmail && usersData[i][2] === 'Siswa') {
      usersSheet.getRange(i + 1, 1).setValue(newEmail);
      break;
    }
  }

  // 2. Update di Sheet Borrow
  const borrowData = borrowSheet.getDataRange().getValues();
  for (let i = 1; i < borrowData.length; i++) {
    if (borrowData[i][1] === oldEmail) {
      borrowSheet.getRange(i + 1, 2).setValue(newEmail);
    }
  }
}

function deleteStudent(email) {
  const ss = getSpreadsheet();
  const usersSheet = ss.getSheetByName('Users');
  const data = usersSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === email && data[i][2] === 'Siswa') {
      usersSheet.deleteRow(i + 1);
      return { success: true, message: 'Siswa berhasil dihapus' };
    }
  }
 
  return { success: false, message: 'Siswa tidak ditemukan' };
}

// ==================== FUNGSI PEMINJAMAN ====================
function borrowBook(siswa, bukuId) {
  const ss = getSpreadsheet();
  const borrowSheet = ss.getSheetByName('Borrow');
  const booksSheet = ss.getSheetByName('Books');
 
  // Cek stok buku
  const booksData = booksSheet.getDataRange().getValues();
  let bookFound = false;
  let bookTitle = '';
 
  for (let i = 1; i < booksData.length; i++) {
    if (booksData[i][0] === bukuId) {
      bookFound = true;
      bookTitle = booksData[i][1];
      if (booksData[i][3] <= 0) {
        return { success: false, message: 'Stok buku tidak tersedia' };
      }
      break;
    }
  }
 
  if (!bookFound) {
    return { success: false, message: 'Buku tidak ditemukan' };
  }
 
  const lastRow = borrowSheet.getLastRow();
  const newId = 'BR' + (lastRow).toString().padStart(3, '0');
  const today = new Date();
 
  borrowSheet.appendRow([
    newId,
    siswa,
    bukuId,
    bookTitle,
    Utilities.formatDate(today, Session.getScriptTimeZone(), 'dd/MM/yyyy'),
    '',
    'Pending'
  ]);
 
  return { success: true, message: 'Pengajuan peminjaman berhasil diajukan' };
}

function getBorrowHistory(email, role) {
  const ss = getSpreadsheet();
  const borrowSheet = ss.getSheetByName('Borrow');
  const data = borrowSheet.getDataRange().getDisplayValues();
  const history = [];
 
  for (let i = 1; i < data.length; i++) {
    if (role === 'Siswa' && data[i][1] !== email) continue;
   
    history.push({
      id: data[i][0],
      siswa: data[i][1],
      bukuId: data[i][2],
      judulBuku: data[i][3],
      tanggalPinjam: data[i][4],
      tanggalKembali: data[i][5],
      status: data[i][6]
    });
  }
 
  return history;
}

function approveBorrow(borrowId) {
  const ss = getSpreadsheet();
  const borrowSheet = ss.getSheetByName('Borrow');
  const booksSheet = ss.getSheetByName('Books');
  const data = borrowSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === borrowId) {
      const bukuId = data[i][2];
     
      // Kurangi stok buku
      const booksData = booksSheet.getDataRange().getValues();
      for (let j = 1; j < booksData.length; j++) {
        if (booksData[j][0] === bukuId) {
          const currentStok = booksData[j][3];
          booksSheet.getRange(j + 1, 4).setValue(currentStok - 1);
          break;
        }
      }
     
      borrowSheet.getRange(i + 1, 7).setValue('Approved');
      return { success: true, message: 'Peminjaman disetujui' };
    }
  }
 
  return { success: false, message: 'Data peminjaman tidak ditemukan' };
}

function rejectBorrow(borrowId) {
  const ss = getSpreadsheet();
  const borrowSheet = ss.getSheetByName('Borrow');
  const data = borrowSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === borrowId) {
      borrowSheet.getRange(i + 1, 7).setValue('Rejected');
      return { success: true, message: 'Peminjaman ditolak' };
    }
  }
 
  return { success: false, message: 'Data peminjaman tidak ditemukan' };
}

function returnBook(borrowId) {
  const ss = getSpreadsheet();
  const borrowSheet = ss.getSheetByName('Borrow');
  const booksSheet = ss.getSheetByName('Books');
  const data = borrowSheet.getDataRange().getValues();
 
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === borrowId) {
      const bukuId = data[i][2];
      const today = new Date();
     
      // Tambah stok buku
      const booksData = booksSheet.getDataRange().getValues();
      for (let j = 1; j < booksData.length; j++) {
        if (booksData[j][0] === bukuId) {
          const currentStok = booksData[j][3];
          booksSheet.getRange(j + 1, 4).setValue(currentStok + 1);
          break;
        }
      }
     
      borrowSheet.getRange(i + 1, 6).setValue(Utilities.formatDate(today, Session.getScriptTimeZone(), 'dd/MM/yyyy'));
      borrowSheet.getRange(i + 1, 7).setValue('Returned');
      return { success: true, message: 'Buku berhasil dikembalikan' };
    }
  }
 
  return { success: false, message: 'Data peminjaman tidak ditemukan' };
}

// ==================== STATISTIK ====================
function getStatistics() {
  const ss = getSpreadsheet();
  const booksSheet = ss.getSheetByName('Books');
  const borrowSheet = ss.getSheetByName('Borrow');
  const usersSheet = ss.getSheetByName('Users');
 
  const booksData = booksSheet.getDataRange().getValues();
  const borrowData = borrowSheet.getDataRange().getDisplayValues();
  const usersData = usersSheet.getDataRange().getValues();

  let totalBooks = booksData.length - 1;
  let totalStok = 0;
  for (let i = 1; i < booksData.length; i++) {
    totalStok += booksData[i][3];
  }

  let totalStudents = 0;
  for (let i = 1; i < usersData.length; i++) {
    if (usersData[i][2] === 'Siswa') {
      totalStudents++;
    }
  }
 
  let pending = 0;
  let waitingReturn = 0;
  let returned = 0;
  let rejected = 0;
 
  for (let i = 1; i < borrowData.length; i++) {
    const status = borrowData[i][6];
    if (status === 'Approved') {
      waitingReturn++;
    } else if (status === 'Pending') {
      pending++;
    } else if (status === 'Returned') {
      returned++;
    } else if (status === 'Rejected') {
      rejected++;
    }
  }
 
  return {
    totalBooks: totalBooks,
    totalStok: totalStok,
    totalStudents: totalStudents,
    pending: pending,
    waitingReturn: waitingReturn,
    returned: returned,
    rejected: rejected
  };
}

// ==================== FUNGSI OTORISASI ====================
/**
 * Jalankan fungsi ini sekali dari editor Apps Script untuk memberikan izin akses ke Google Drive.
 */
function authorizeDrive() {
  try {
    DriveApp.getRootFolder();
    Logger.log('✅ Otorisasi Google Drive berhasil!');
  } catch (e) {
    Logger.log('🛑 Gagal memberikan otorisasi: ' + e.toString());
    Logger.log('Silakan jalankan fungsi ini lagi dan pastikan Anda menyetujui semua izin yang diminta.');
  }
}


Index.html


<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Perpustakaan Digital</title>
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Inter', sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
    }

    /* ==================== LOGIN PAGE ==================== */
    .login-container {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      padding: 20px;
      position: relative;
      overflow: hidden;
    }

    .login-container::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: url('https://images.unsplash.com/photo-1521587760476-6c12a4b040da?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80') center/cover no-repeat;
      filter: brightness(0.4);
      z-index: 0;
    }

    .login-container::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 1;
    }

    .login-box {
      position: relative;
      z-index: 2;
      background: rgba(255, 255, 255, 0.15);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      border: 1px solid rgba(255, 255, 255, 0.3);
      padding: 50px 40px;
      border-radius: 24px;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 
                  inset 0 1px 0 rgba(255, 255, 255, 0.3);
      width: 100%;
      max-width: 440px;
      animation: fadeInUp 0.6s ease-out;
    }

    @keyframes fadeInUp {
      from {
        opacity: 0;
        transform: translateY(30px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }

    .login-header {
      text-align: center;
      margin-bottom: 40px;
    }

    .login-header::before {
      content: '📚';
      font-size: 64px;
      display: block;
      margin-bottom: 16px;
      animation: float 3s ease-in-out infinite;
    }

    @keyframes float {
      0%, 100% {
        transform: translateY(0px);
      }
      50% {
        transform: translateY(-10px);
      }
    }

    .login-header h1 {
      color: white;
      font-size: 32px;
      margin-bottom: 8px;
      font-weight: 700;
      text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
      letter-spacing: -0.5px;
    }

    .login-header p {
      color: rgba(255, 255, 255, 0.9);
      font-size: 15px;
      text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
    }

    .form-group {
      margin-bottom: 24px;
    }

    .form-group label {
      display: block;
      margin-bottom: 10px;
      color: white;
      font-weight: 600;
      font-size: 14px;
      text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
      letter-spacing: 0.3px;
    }

    .form-group input {
      width: 100%;
      padding: 14px 20px;
      border: 2px solid rgba(255, 255, 255, 0.3);
      background: rgba(255, 255, 255, 0.2);
      backdrop-filter: blur(10px);
      -webkit-backdrop-filter: blur(10px);
      border-radius: 12px;
      font-size: 15px;
      transition: all 0.3s;
      color: white;
      font-weight: 500;
    }

    .form-group input::placeholder {
      color: rgba(255, 255, 255, 0.6);
    }

    .form-group input:focus {
      outline: none;
      border-color: rgba(255, 255, 255, 0.6);
      background: rgba(255, 255, 255, 0.25);
      box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.1),
                  0 8px 20px rgba(0, 0, 0, 0.2);
      transform: translateY(-2px);
    }
    
    /* MODAL FORM OVERRIDES */
    .modal .form-group label {
        color: #475569;
        text-shadow: none;
    }
    .modal .form-group input {
        color: #1e293b;
        background: #f8fafc;
        border: 1px solid #e2e8f0;
        backdrop-filter: none;
    }
    .modal .form-group input::placeholder {
      color: #94a3b8;
    }
    .modal .form-group input:focus {
        border-color: #667eea;
        background: white;
        box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
        transform: none;
    }
    .modal .btn-primary {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
    }
    .modal .btn-primary:hover {
        background: linear-gradient(135deg, #5a6fd8 0%, #683a92 100%);
    }

    .btn {
      width: 100%;
      padding: 14px;
      border: none;
      border-radius: 12px;
      font-size: 16px;
      font-weight: 700;
      cursor: pointer;
      transition: all 0.3s;
      letter-spacing: 0.5px;
      text-transform: uppercase;
    }

    .btn-primary {
      background: rgba(255, 255, 255, 0.95);
      color: #667eea;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
      position: relative;
      overflow: hidden;
    }

    .btn-primary::before {
      content: '';
      position: absolute;
      top: 0;
      left: -100%;
      width: 100%;
      height: 100%;
      background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.3), transparent);
      transition: left 0.5s;
    }

    .btn-primary:hover {
      transform: translateY(-3px);
      box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
      background: white;
    }

    .btn-primary:hover::before {
      left: 100%;
    }

    .btn-primary:active {
      transform: translateY(-1px);
    }

    .btn-loading {
        cursor: not-allowed !important;
        background: #ccc !important;
        box-shadow: none !important;
        transform: none !important;
    }

    .btn-loading .btn-text {
        visibility: hidden;
        opacity: 0;
    }

    .btn-loading .spinner {
        display: inline-block;
        opacity: 1;
    }

    .spinner {
        display: none;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 20px;
        height: 20px;
        border: 2px solid rgba(102, 126, 234, 0.5);
        border-top-color: #667eea;
        border-radius: 50%;
        animation: spin 1s linear infinite;
        opacity: 0;
        transition: opacity 0.3s;
    }

    @keyframes spin {
        to {
            transform: translate(-50%, -50%) rotate(360deg);
        }
    }

    /* ==================== DASHBOARD LAYOUT ==================== */
    .dashboard {
      display: none;
      min-height: 100vh;
      background: #f1f5f9;
    }

    body.dashboard-open {
      overflow: hidden; /* Prevent scrolling when dashboard is open */
    }
    
    .wrapper {
        display: flex;
        flex-direction: column;
        min-height: 100vh;
        background: #f1f5f9;
    }

    .sidebar {
      position: fixed;
      left: 0;
      top: 0;
      bottom: 0;
      width: 260px;
      background: linear-gradient(180deg, #2c3e95 0%, #1e2b6f 100%);
      padding: 0;
      overflow-y: auto;
      z-index: 1000;
      box-shadow: 4px 0 12px rgba(0, 0, 0, 0.15);
      transition: width 0.3s ease;
    }

    .main-header {
      position: fixed;
      top: 0;
      left: 260px;
      right: 0;
      background: white;
      padding: 20px;
      z-index: 999;
      display: flex;
      justify-content: space-between;
      align-items: center;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      transition: left 0.3s ease;
    }

    .content-wrapper {
      margin-left: 260px;
      margin-top: 100px; /* Height of main-header + some padding */
      padding: 20px;
      transition: margin-left 0.3s ease;
    }
    
    /* Collapsed state */
    body.sidebar-collapsed .sidebar {
      width: 80px;
    }
    
    body.sidebar-collapsed .sidebar .sidebar-header h2,
    body.sidebar-collapsed .sidebar .sidebar-header p,
    body.sidebar-collapsed .sidebar .menu-item span:not(.material-icons),
    body.sidebar-collapsed .sidebar .menu-label,
    body.sidebar-collapsed .sidebar .menu-badge {
      display: none;
    }

    body.sidebar-collapsed .sidebar .menu-item {
      justify-content: center;
    }
    
    body.sidebar-collapsed .sidebar .menu-item .material-icons {
      margin-right: 0;
    }

    body.sidebar-collapsed .main-header {
      left: 80px;
    }
    
    body.sidebar-collapsed .content-wrapper {
      margin-left: 80px;
    }

    .sidebar-toggle {
        background: none;
        border: none;
        color: #334155;
        cursor: pointer;
        font-size: 24px;
        margin-right: 16px;
    }

    .header-left {
        display: flex;
        align-items: center;
    }

    .top-bar-left { /* Keep for backward compatibility */
        display: flex;
        align-items: center;
    }

    .sidebar-header {
      background: linear-gradient(135deg, #4154b3 0%, #2c3e95 100%);
      color: white;
      padding: 24px 20px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.1);
      margin-bottom: 8px;
      text-align: center;
    }

    .sidebar-logo {
      width: 50px;
      height: 50px;
      background: white;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      margin: 0 auto 12px;
      font-size: 28px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
    }

    .sidebar-header h2 {
      font-size: 18px;
      margin-bottom: 4px;
      font-weight: 700;
      letter-spacing: 0.3px;
    }

    .sidebar-header p {
      font-size: 12px;
      opacity: 0.85;
      font-weight: 400;
    }

    .menu-section {
      padding: 0 12px;
      margin-bottom: 8px;
    }

    .menu-label {
      color: rgba(255, 255, 255, 0.5);
      font-size: 11px;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 1px;
      padding: 16px 16px 8px;
    }

    .menu-item {
      display: flex;
      align-items: center;
      padding: 13px 16px;
      color: rgba(255, 255, 255, 0.85);
      text-decoration: none;
      border-radius: 10px;
      margin-bottom: 4px;
      transition: all 0.3s;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      position: relative;
    }

    .menu-item:hover {
      background: rgba(255, 255, 255, 0.1);
      color: white;
      transform: translateX(4px);
    }

    .menu-item.active {
      background: linear-gradient(135deg, #5b6fd8 0%, #4154b3 100%);
      color: white;
      box-shadow: 0 4px 12px rgba(91, 111, 216, 0.4);
    }

    .menu-item.active::before {
      content: '';
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 4px;
      height: 70%;
      background: white;
      border-radius: 0 4px 4px 0;
    }

    .menu-item .material-icons {
      margin-right: 14px;
      font-size: 22px;
    }

    .menu-badge {
      margin-left: auto;
      background: #ef4444;
      color: white;
      font-size: 11px;
      font-weight: 700;
      padding: 2px 8px;
      border-radius: 10px;
      min-width: 20px;
      text-align: center;
    }



    .top-bar-left h3 {
      color: #1e293b;
      font-size: 26px;
      font-weight: 700;
      margin-bottom: 4px;
    }

    .top-bar-subtitle {
      color: #64748b;
      font-size: 14px;
      display: flex;
      align-items: center;
      gap: 6px;
    }

    .top-bar-subtitle .material-icons {
      font-size: 16px;
    }

    .user-info {
      display: flex;
      align-items: center;
      gap: 16px;
      background: #f8fafc;
      padding: 10px 16px;
      border-radius: 12px;
      border: 1px solid #e2e8f0;
      cursor: pointer;
      transition: background-color 0.2s;
    }
    
    .user-info:hover {
        background-color: #f1f5f9;
    }

    .user-menu-container {
      position: relative;
    }

    .dropdown-menu {
      display: none;
      position: absolute;
      top: 110%;
      right: 0;
      background: white;
      border-radius: 8px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.15);
      padding: 8px;
      z-index: 1200;
      width: 100%;
    }
    
    .user-menu-container.open .dropdown-menu {
      display: block;
    }

    .dropdown-menu .logout-btn {
        width: 100%;
        justify-content: center;
    }


    .user-avatar {
      width: 42px;
      height: 42px;
      border-radius: 50%;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: 700;
      font-size: 16px;
      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
    }

    .user-details {
      display: flex;
      flex-direction: column;
    }

    .user-name {
      font-weight: 600;
      color: #1e293b;
      font-size: 14px;
    }

    .user-role {
      font-size: 12px;
      color: #64748b;
      display: flex;
      align-items: center;
      gap: 4px;
    }

    .user-role .material-icons {
      font-size: 14px;
      color: #10b981;
    }

    .logout-btn {
      padding: 10px 20px;
      background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
      color: white;
      border: none;
      border-radius: 10px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 600;
      transition: all 0.3s;
      display: flex;
      align-items: center;
      gap: 6px;
      box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
    }

    .logout-btn:hover {
      background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
      transform: translateY(-2px);
      box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
    }

    .logout-btn .material-icons {
      font-size: 18px;
    }

    /* ==================== STATS CARDS ==================== */
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
      gap: 20px;
      margin-bottom: 24px;
    }

    .stat-card {
      background: white;
      padding: 24px;
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
      display: flex;
      align-items: center;
      gap: 16px;
      transition: all 0.3s;
    }

    .stat-card:hover {
      transform: translateY(-4px);
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    }

    .stat-icon {
      width: 60px;
      height: 60px;
      border-radius: 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 28px;
    }

    .stat-icon.blue {
      background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
      color: white;
    }

    .stat-icon.green {
      background: linear-gradient(135deg, #10b981 0%, #059669 100%);
      color: white;
    }

    .stat-icon.red {
      background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
      color: white;
    }

    .stat-icon.purple {
      background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
      color: white;
    }

    .stat-icon.orange {
      background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
      color: white;
    }

    .stat-icon.teal {
      background: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%);
      color: white;
    }

    .stat-icon.cyan {
      background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%);
      color: white;
    }

    .stat-icon.pink {
      background: linear-gradient(135deg, #ec4899 0%, #d946ef 100%);
      color: white;
    }

    .stat-info h4 {
      color: #64748b;
      font-size: 13px;
      font-weight: 500;
      margin-bottom: 4px;
    }

    .stat-info .stat-value {
      color: #1e293b;
      font-size: 28px;
      font-weight: 700;
    }

    /* ==================== CONTENT CARD ==================== */
    .content-card {
      background: white;
      padding: 24px;
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
      margin-bottom: 24px;
    }

    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
    }

    .card-header h4 {
      color: #1e293b;
      font-size: 18px;
      font-weight: 600;
    }

    .btn-add {
      padding: 10px 20px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      display: flex;
      align-items: center;
      gap: 6px;
      transition: all 0.3s;
    }

    .btn-add:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
    }

    .table-controls {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
      flex-wrap: wrap;
      gap: 12px;
    }

    .search-container {
      position: relative;
    }

    .search-container input {
      padding: 8px 12px 8px 36px;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
      font-size: 14px;
    }

    .search-container .material-icons {
      position: absolute;
      left: 10px;
      top: 50%;
      transform: translateY(-50%);
      color: #94a3b8;
      font-size: 20px;
    }

    .page-size-container {
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 14px;
      color: #475569;
    }

    .page-size-container select {
      padding: 8px 12px;
      border: 1px solid #e2e8f0;
      border-radius: 8px;
      font-size: 14px;
    }

    .table-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 16px;
      padding-top: 16px;
      border-top: 1px solid #e2e8f0;
      flex-wrap: wrap;
      gap: 12px;
    }

    .pagination-info {
      font-size: 14px;
      color: #475569;
    }

    .pagination-controls button {
      padding: 6px 12px;
      border: 1px solid #e2e8f0;
      background: white;
      border-radius: 6px;
      cursor: pointer;
      margin-left: 4px;
      transition: background-color 0.2s;
    }

    .pagination-controls button:hover:not(:disabled) {
      background-color: #f8fafc;
    }

    .pagination-controls button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    /* ==================== TABLE ==================== */
    .table-container {
      overflow-x: auto;
    }

    table {
      width: 100%;
      border-collapse: collapse;
    }

    thead {
      background: #f8fafc;
    }

    th {
      padding: 12px;
      text-align: left;
      color: #475569;
      font-weight: 600;
      font-size: 13px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    td {
      padding: 12px;
      border-top: 1px solid #e2e8f0;
      color: #334155;
      font-size: 14px;
    }

    tr:hover {
      background: #f8fafc;
    }

    /* ==================== BADGES ==================== */
    .badge {
      display: inline-block;
      padding: 4px 12px;
      border-radius: 12px;
      font-size: 12px;
      font-weight: 600;
    }

    .badge.pending {
      background: #fef3c7;
      color: #92400e;
    }

    .badge.approved {
      background: #d1fae5;
      color: #065f46;
    }

    .badge.rejected {
      background: #fee2e2;
      color: #991b1b;
    }

    .badge.returned {
      background: #dbeafe;
      color: #1e40af;
    }

    /* ==================== ACTION BUTTONS ==================== */
    .action-btn {
      padding: 6px 12px;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 12px;
      font-weight: 500;
      margin-right: 6px;
      transition: all 0.3s;
    }

    .btn-approve {
      background: #10b981;
      color: white;
    }

    .btn-approve:hover {
      background: #059669;
    }

    .btn-reject {
      background: #ef4444;
      color: white;
    }

    .btn-reject:hover {
      background: #dc2626;
    }

    .btn-edit {
      background: #3b82f6;
      color: white;
    }

    .btn-edit:hover {
      background: #2563eb;
    }

    .btn-delete {
      background: #ef4444;
      color: white;
    }

    .btn-delete:hover {
      background: #dc2626;
    }

    .btn-return {
      background: #8b5cf6;
      color: white;
    }

    .btn-return:hover {
      background: #7c3aed;
    }

    .btn-borrow {
      background: #10b981;
      color: white;
    }

    .btn-borrow:hover {
      background: #059669;
    }

    /* ==================== MODAL ==================== */
    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.5);
      z-index: 2000;
      align-items: center;
      justify-content: center;
    }

    .modal-content {
      background: white;
      padding: 30px;
      border-radius: 16px;
      width: 90%;
      max-width: 500px;
      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
      max-height: 80vh; /* Add this line */
      overflow-y: auto; /* Add this line */
    }

    .modal-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
    }

    .modal-header h3 {
      color: #1e293b;
      font-size: 20px;
    }

    .close-modal {
      background: none;
      border: none;
      font-size: 24px;
      cursor: pointer;
      color: #64748b;
    }

    /* ==================== BOOKS GRID ==================== */
    .books-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 20px;
    }

    .book-card {
      background: white;
      border-radius: 12px;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
      transition: all 0.3s;
    }

    .book-card:hover {
      transform: translateY(-4px);
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    }

    .book-cover {
      width: 100%;
      height: 250px;
      object-fit: cover;
      background: #e2e8f0;
    }

    .book-info {
      padding: 16px;
    }

    .book-info h5 {
      color: #1e293b;
      font-size: 16px;
      margin-bottom: 6px;
      font-weight: 600;
    }

    .book-info p {
      color: #64748b;
      font-size: 13px;
      margin-bottom: 4px;
    }

    .book-stok {
      display: flex;
      align-items: center;
      gap: 6px;
      color: #10b981;
      font-size: 13px;
      font-weight: 600;
      margin-top: 8px;
    }

    .hidden {
      display: none !important;
    }

    /* ==================== LOADING ==================== */
    .loading {
      text-align: center;
      padding: 40px;
      color: #64748b;
    }

    /* ==================== RESPONSIVE ==================== */
    .sidebar-overlay {
      display: none;
    }

    @media (max-width: 768px) {
      body.sidebar-mobile-active .sidebar-overlay {
        display: block;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0,0,0,0.5);
        z-index: 1099; /* Below sidebar but above content */
      }
    
      .main-header {
        position: static;
        left: 0;
        width: 100%;
      }
      
      .content-wrapper {
        margin-left: 0;
        margin-top: 20px;
      }
      
      .sidebar {
        transform: translateX(-100%);
        z-index: 1100; /* Ensure sidebar is on top */
      }
      
      body.sidebar-mobile-active .sidebar {
        transform: translateX(0);
      }

      body.sidebar-collapsed .sidebar {
          transform: translateX(-100%); /* Ensure it's hidden when toggling on desktop then resizing */
      }
      
      body.sidebar-collapsed .main-header, body.sidebar-collapsed .content-wrapper {
        left: 0;
        margin-left: 0;
      }

      .stats-grid {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <!-- ==================== LOGIN PAGE ==================== -->
  <div id="loginPage" class="login-container">
    <div class="login-box">
      <div class="login-header">
        <h1>Perpustakaan Digital</h1>
        <p>Silakan masuk untuk melanjutkan</p>
      </div>
      <form id="loginForm">
        <div class="form-group">
          <label>Email</label>
          <input type="email" id="emailInput" required placeholder="nama@email.com">
        </div>
        <div class="form-group">
          <label>Password</label>
          <input type="password" id="passwordInput" required placeholder="Masukkan password">
        </div>
        <button type="submit" class="btn btn-primary">
          <span class="btn-text">Masuk</span>
          <span class="spinner"></span>
        </button>
      </form>
    </div>
  </div>

  <!-- ==================== ADMIN DASHBOARD ==================== -->
  <div id="adminDashboard" class="dashboard">
    <div class="wrapper">
      <div class="sidebar-overlay" onclick="toggleSidebar()"></div>
      <div class="sidebar">
        <div class="sidebar-header">
          <div class="sidebar-logo">📚</div>
          <h2>Perpustakaan Digital</h2>
          <p>Admin Panel</p>
        </div>
        <div class="menu-section">
          <div class="menu-label">Menu Utama</div>
          <div class="menu-item active" onclick="showSection('dashboard')">
            <span class="material-icons">dashboard</span>
            <span>Dashboard</span>
          </div>
          <div class="menu-item" onclick="showSection('borrowRequests')">
            <span class="material-icons">pending_actions</span>
            <span>Peminjaman Pending</span>
            <span class="menu-badge" id="pendingBadge">0</span>
          </div>
          <div class="menu-item" onclick="showSection('allBorrows')">
            <span class="material-icons">history</span>
            <span>Riwayat Peminjaman</span>
          </div>
        </div>
        <div class="menu-section">
          <div class="menu-label">Kelola Data</div>
          <div class="menu-item" onclick="showSection('masterBooks')">
            <span class="material-icons">menu_book</span>
            <span>Master Buku</span>
          </div>
          <div class="menu-item" onclick="showSection('masterStudents')">
            <span class="material-icons">people</span>
            <span>Master Siswa</span>
          </div>
        </div>
      </div>

      <header class="main-header">
        <div class="header-left">
          <button class="sidebar-toggle" onclick="toggleSidebar()">
            <span class="material-icons">menu</span>
          </button>
          <div class="top-bar-left">
            <h3 id="pageTitle">Dashboard</h3>
          </div>
        </div>
        <div class="user-menu-container">
          <div class="user-info" onclick="toggleUserMenu()">
            <div class="user-avatar" id="adminAvatar">A</div>
            <div class="user-details">
              <span class="user-name" id="adminName">Administrator</span>
              <span class="user-role">
                <span class="material-icons">verified</span>
                Admin
              </span>
            </div>
          </div>
          <div class="dropdown-menu" id="adminUserMenu">
            <button class="logout-btn" onclick="logout()">
              <span class="material-icons">logout</span>
              <span>Keluar</span>
            </button>
          </div>
        </div>
      </header>

      <main class="content-wrapper">
        <!-- Dashboard Stats -->
        <div id="dashboardSection">
          <div class="stats-grid">
            <div class="stat-card">
              <div class="stat-icon blue">
                <span class="material-icons">menu_book</span>
              </div>
              <div class="stat-info">
                <h4>Total Judul Buku</h4>
                <div class="stat-value" id="totalBooks">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon cyan">
                <span class="material-icons">inventory_2</span>
              </div>
              <div class="stat-info">
                <h4>Total Stok Buku</h4>
                <div class="stat-value" id="totalStock">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon orange">
                <span class="material-icons">groups</span>
              </div>
              <div class="stat-info">
                <h4>Total Siswa</h4>
                <div class="stat-value" id="totalStudents">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon red">
                <span class="material-icons">pending</span>
              </div>
              <div class="stat-info">
                <h4>Peminjaman Pending</h4>
                <div class="stat-value" id="pendingBorrows">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon purple">
                <span class="material-icons">assignment_turned_in</span>
              </div>
              <div class="stat-info">
                <h4>Menunggu Pengembalian</h4>
                <div class="stat-value" id="waitingReturn">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon green">
                <span class="material-icons">assignment_return</span>
              </div>
              <div class="stat-info">
                <h4>Buku Dikembalikan</h4>
                <div class="stat-value" id="returnedBooks">0</div>
              </div>
            </div>
            <div class="stat-card">
              <div class="stat-icon pink">
                <span class="material-icons">cancel</span>
              </div>
              <div class="stat-info">
                <h4>Peminjaman Ditolak</h4>
                <div class="stat-value" id="rejectedBorrows">0</div>
              </div>
            </div>
          </div>
        </div>

        <!-- Borrow Requests -->
        <div id="borrowRequestsSection" class="hidden">
          <div class="content-card">
            <div class="card-header">
              <h4>Pengajuan Peminjaman</h4>
              <div class="table-controls">
                <div class="search-container">
                  <span class="material-icons">search</span>
                  <input type="text" id="borrowRequestsSearch" placeholder="Cari...">
                </div>
                <div class="page-size-container">
                  <span>Tampilkan:</span>
                  <select id="borrowRequestsPageSize">
                    <option value="10">10</option>
                    <option value="25">25</option>
                    <option value="50">50</option>
                    <option value="100">100</option>
                    <option value="all">Semua</option>
                  </select>
                </div>
              </div>
            </div>
            <div class="table-container">
              <table id="borrowRequestsTable">
                <thead>
                  <tr>
                    <th>ID</th>
                    <th>Siswa</th>
                    <th>Buku</th>
                    <th>Tanggal Pinjam</th>
                    <th>Status</th>
                    <th>Aksi</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
            <div class="table-footer">
              <div id="borrowRequestsPaginationInfo" class="pagination-info"></div>
              <div class="pagination-controls">
                <button id="borrowRequestsPrevPage">Sebelumnya</button>
                <button id="borrowRequestsNextPage">Berikutnya</button>
              </div>
            </div>
          </div>
        </div>

        <!-- All Borrows -->
        <div id="allBorrowsSection" class="hidden">
          <div class="content-card">
            <div class="card-header">
              <h4>Riwayat Peminjaman</h4>
              <div class="table-controls">
                <div class="search-container">
                  <span class="material-icons">search</span>
                  <input type="text" id="allBorrowsSearch" placeholder="Cari...">
                </div>
                <div class="page-size-container">
                  <span>Tampilkan:</span>
                  <select id="allBorrowsPageSize">
                    <option value="10">10</option>
                    <option value="25">25</option>
                    <option value="50">50</option>
                    <option value="100">100</option>
                    <option value="all">Semua</option>
                  </select>
                </div>
              </div>
            </div>
            <div class="table-container">
              <table id="allBorrowsTable">
                <thead>
                  <tr>
                    <th>ID</th>
                    <th>Siswa</th>
                    <th>Buku</th>
                    <th>Tanggal Pinjam</th>
                    <th>Tanggal Kembali</th>
                    <th>Status</th>
                    <th>Aksi</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
            <div class="table-footer">
              <div id="allBorrowsPaginationInfo" class="pagination-info"></div>
              <div class="pagination-controls">
                <button id="allBorrowsPrevPage">Sebelumnya</button>
                <button id="allBorrowsNextPage">Berikutnya</button>
              </div>
            </div>
          </div>
        </div>

        <!-- Master Books -->
        <div id="masterBooksSection" class="hidden">
          <div class="content-card">
            <div class="card-header">
              <h4>Master Buku</h4>
               <div class="table-controls">
                <div class="search-container">
                  <span class="material-icons">search</span>
                  <input type="text" id="masterBooksSearch" placeholder="Cari...">
                </div>
                <div class="page-size-container">
                  <span>Tampilkan:</span>
                  <select id="masterBooksPageSize">
                    <option value="10">10</option>
                    <option value="25">25</option>
                    <option value="50">50</option>
                    <option value="100">100</option>
                    <option value="all">Semua</option>
                  </select>
                </div>
                 <button class="btn-add" onclick="openAddBookModal()">
                    <span class="material-icons">add</span>
                    Tambah Buku
                </button>
              </div>
            </div>
            <div class="table-container">
              <table id="masterBooksTable">
                <thead>
                  <tr>
                    <th>ID</th>
                    <th>Judul</th>
                    <th>Penulis</th>
                    <th>Stok</th>
                    <th>Aksi</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
            <div class="table-footer">
                <div id="masterBooksPaginationInfo" class="pagination-info"></div>
                <div class="pagination-controls">
                    <button id="masterBooksPrevPage">Sebelumnya</button>
                    <button id="masterBooksNextPage">Berikutnya</button>
                </div>
            </div>
          </div>
        </div>

        <!-- Master Students -->
        <div id="masterStudentsSection" class="hidden">
          <div class="content-card">
            <div class="card-header">
              <h4>Master Siswa</h4>
              <div class="table-controls">
                <div class="search-container">
                  <span class="material-icons">search</span>
                  <input type="text" id="masterStudentsSearch" placeholder="Cari...">
                </div>
                <div class="page-size-container">
                  <span>Tampilkan:</span>
                  <select id="masterStudentsPageSize">
                    <option value="10">10</option>
                    <option value="25">25</option>
                    <option value="50">50</option>
                    <option value="100">100</option>
                    <option value="all">Semua</option>
                  </select>
                </div>
                <button class="btn-add" onclick="openAddStudentModal()">
                    <span class="material-icons">add</span>
                    Tambah Siswa
                </button>
              </div>
            </div>
            <div class="table-container">
              <table id="masterStudentsTable">
                <thead>
                  <tr>
                    <th>Email</th>
                    <th>Nama</th>
                    <th>Aksi</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
             <div class="table-footer">
                <div id="masterStudentsPaginationInfo" class="pagination-info"></div>
                <div class="pagination-controls">
                    <button id="masterStudentsPrevPage">Sebelumnya</button>
                    <button id="masterStudentsNextPage">Berikutnya</button>
                </div>
            </div>
          </div>
        </div>
      </main>
    </div>
  </div>

  <!-- ==================== STUDENT DASHBOARD ==================== -->
  <div id="studentDashboard" class="dashboard">
    <div class="wrapper">
      <div class="sidebar-overlay" onclick="toggleSidebar()"></div>
      <div class="sidebar">
        <div class="sidebar-header">
          <div class="sidebar-logo">📚</div>
          <h2>Perpustakaan Digital</h2>
          <p>Siswa Panel</p>
        </div>
        <div class="menu-section">
          <div class="menu-label">Menu Utama</div>
          <div class="menu-item active" onclick="showStudentSection('studentBooks')">
            <span class="material-icons">menu_book</span>
            <span>Daftar Buku</span>
          </div>
          <div class="menu-item" onclick="showStudentSection('studentBorrows')">
            <span class="material-icons">history</span>
            <span>Riwayat Peminjaman</span>
          </div>
        </div>
      </div>

      <header class="main-header">
        <div class="header-left">
          <button class="sidebar-toggle" onclick="toggleSidebar()">
            <span class="material-icons">menu</span>
          </button>
          <div class="top-bar-left">
            <h3 id="studentPageTitle">Daftar Buku</h3>
          </div>
        </div>
        <div class="user-menu-container">
          <div class="user-info" onclick="toggleUserMenu()">
            <div class="user-avatar" id="studentAvatar">S</div>
            <div class="user-details">
              <span class="user-name" id="studentName">Siswa</span>
              <span class="user-role">
                <span class="material-icons">school</span>
                Siswa
              </span>
            </div>
          </div>
          <div class="dropdown-menu" id="studentUserMenu">
            <button class="logout-btn" onclick="logout()">
              <span class="material-icons">logout</span>
              <span>Keluar</span>
            </button>
          </div>
        </div>
      </header>

      <main class="content-wrapper">
        <!-- Student Books -->
        <div id="studentBooksSection">
          <div class="content-card">
            <div class="card-header">
              <h4>Daftar Buku Tersedia</h4>
            </div>
            <div class="books-grid" id="studentBooksGrid"></div>
          </div>
        </div>

        <!-- Student Borrows -->
        <div id="studentBorrowsSection" class="hidden">
          <div class="content-card">
            <div class="card-header">
              <h4>Riwayat Peminjaman Saya</h4>
            </div>
            <div class="table-container">
              <table id="studentBorrowsTable">
                <thead>
                  <tr>
                    <th>ID</th>
                    <th>Buku</th>
                    <th>Tanggal Pinjam</th>
                    <th>Tanggal Kembali</th>
                    <th>Status</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
          </div>
        </div>
      </main>
    </div>
  </div>

  <!-- ==================== MODALS ==================== -->
  <!-- Add/Edit Book Modal -->
  <div id="bookModal" class="modal">
    <div class="modal-content">
      <div class="modal-header">
        <h3 id="bookModalTitle">Tambah Buku</h3>
        <button class="close-modal" onclick="closeModal('bookModal')">&times;</button>
      </div>
<form id="bookForm">
  <input type="hidden" id="bookId">
  <input type="hidden" id="currentBookUrl"> 
  
  <div class="form-group">
    <label>Judul Buku</label>
    <input type="text" id="bookJudul" required>
  </div>
  <div class="form-group">
    <label>Penulis</label>
    <input type="text" id="bookPenulis" required>
  </div>
  <div class="form-group">
    <label>Stok</label>
    <input type="number" id="bookStok" required min="0">
  </div>
  
  <div class="form-group">
    <label>Cover Buku (Upload Gambar)</label>
    <input type="file" id="bookCoverFile" accept="image/*">
    <div style="margin-top: 5px; font-size: 12px; color: #64748b;">
       Atau masukkan URL manual (opsional):
    </div>
    <input type="text" id="bookCoverUrlInput" placeholder="https://...">
    
    <div id="imagePreviewContainer" style="margin-top: 10px; display: none;">
        <p style="font-size: 12px; margin-bottom: 5px;">Preview:</p>
        <img id="imagePreview" src="" style="max-width: 100px; max-height: 150px; border-radius: 8px; border: 1px solid #ddd;">
    </div>
  </div>

  <button type="submit" class="btn btn-primary">Simpan</button>
</form>
    </div>
  </div>

  <!-- Add/Edit Student Modal -->
  <div id="studentModal" class="modal">
    <div class="modal-content">
      <div class="modal-header">
        <h3 id="studentModalTitle">Tambah Siswa</h3>
        <button class="close-modal" onclick="closeModal('studentModal')">&times;</button>
      </div>
      <form id="studentForm">
        <input type="hidden" id="studentEmailOld">
        <div class="form-group">
          <label>Email</label>
          <input type="email" id="studentEmail" required>
        </div>
        <div class="form-group">
          <label>Password</label>
          <input type="password" id="studentPassword" required>
        </div>
        <div class="form-group">
          <label>Nama Lengkap</label>
          <input type="text" id="studentNama" required>
        </div>
<button type="submit" class="btn btn-primary">
          <span class="btn-text">Simpan</span>
          <span class="spinner"></span>
        </button>
      </form>
    </div>
  </div>

<script>
    // ==================== GLOBAL VARIABLES ====================
    let currentUser = null;

    // ==================== UTILITY FUNCTIONS ====================
    function toggleUserMenu() {
      const menuContainer = event.currentTarget.closest('.user-menu-container');
      if (menuContainer) {
        menuContainer.classList.toggle('open');
      }
    }

    window.addEventListener('click', function(e) {
      document.querySelectorAll('.user-menu-container.open').forEach(function(menu) {
        if (!menu.contains(e.target)) {
          menu.classList.remove('open');
        }
      });
    });

    function updateCurrentDate() {
      const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
      const date = new Date().toLocaleDateString('id-ID', options);
      const dateElement = document.getElementById('currentDate'); // Pastikan ID ini ada di HTML header jika ingin tampil
      // Opsional: Tampilkan tanggal di elemen lain jika ada
    }

    function toggleSidebar() {
      if (window.innerWidth <= 768) {
        document.body.classList.toggle('sidebar-mobile-active');
      } else {
        document.body.classList.toggle('sidebar-collapsed');
      }
    }

    function showResultAlert(result) {
        Swal.fire({
            title: result.success ? 'Berhasil' : 'Gagal',
            text: result.message,
            icon: result.success ? 'success' : 'error',
            timer: result.success ? 1500 : 3000,
            showConfirmButton: false,
        });
    }

    function closeModal(modalId) {
      document.getElementById(modalId).style.display = 'none';
    }

    // Close modal when clicking outside
    window.onclick = function(event) {
      if (event.target.classList.contains('modal')) {
        event.target.style.display = 'none';
      }
    }

    // ==================== IMAGE UPLOAD UTILITIES ====================
    // Fungsi mengubah File menjadi Base64 String
    function getBase64(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
          // Ambil string setelah tanda koma (hapus prefix data:image/...)
          resolve(reader.result.split(',')[1]);
        };
        reader.onerror = error => reject(error);
      });
    }

    // Event listener untuk preview gambar saat file dipilih
    const fileInput = document.getElementById('bookCoverFile');
    if(fileInput){
        fileInput.addEventListener('change', function(e) {
            const file = e.target.files[0];
            const previewContainer = document.getElementById('imagePreviewContainer');
            const previewImage = document.getElementById('imagePreview');
            
            if (file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    previewImage.src = e.target.result;
                    previewContainer.style.display = 'block';
                }
                reader.readAsDataURL(file);
            }
        });
    }

    // ==================== SESSION MANAGEMENT ====================
    function checkSession() {
      const storedUser = sessionStorage.getItem('currentUser');
      if (storedUser) {
        currentUser = JSON.parse(storedUser);
        document.getElementById('loginPage').style.display = 'none';
        
        if (currentUser.role === 'Admin') {
          document.getElementById('adminDashboard').style.display = 'block';
          document.getElementById('adminName').textContent = currentUser.nama;
          document.getElementById('adminAvatar').textContent = currentUser.nama.charAt(0).toUpperCase();
          updateCurrentDate();
          loadAdminDashboard();
        } else {
          document.getElementById('studentDashboard').style.display = 'block';
          document.getElementById('studentName').textContent = currentUser.nama;
          document.getElementById('studentAvatar').textContent = currentUser.nama.charAt(0).toUpperCase();
          updateCurrentDate();
          loadStudentDashboard();
        }
      }
    }

    document.addEventListener('DOMContentLoaded', checkSession);

    // ==================== LOGIN & LOGOUT ====================
    document.getElementById('loginForm').addEventListener('submit', function(e) {
      e.preventDefault();
      const email = document.getElementById('emailInput').value;
      const password = document.getElementById('passwordInput').value;
      const loginButton = this.querySelector('button[type="submit"]');

      loginButton.disabled = true;
      loginButton.classList.add('btn-loading');

      const handleLoginResponse = (result) => {
        loginButton.disabled = false;
        loginButton.classList.remove('btn-loading');

        if (result && result.success) {
          sessionStorage.setItem('currentUser', JSON.stringify(result.user));
          currentUser = result.user;
          document.getElementById('loginPage').style.display = 'none';
          
          if (result.user.role === 'Admin') {
            document.getElementById('adminDashboard').style.display = 'block';
            document.getElementById('adminName').textContent = result.user.nama;
            document.getElementById('adminAvatar').textContent = result.user.nama.charAt(0).toUpperCase();
            loadAdminDashboard();
          } else {
            document.getElementById('studentDashboard').style.display = 'block';
            document.getElementById('studentName').textContent = result.user.nama;
            document.getElementById('studentAvatar').textContent = result.user.nama.charAt(0).toUpperCase();
            loadStudentDashboard();
          }
        } else {
          Swal.fire({
            title: 'Gagal Masuk',
            text: result.message || 'Terjadi kesalahan.',
            icon: 'error'
          });
        }
      };
      
      const handleLoginError = (err) => {
        loginButton.disabled = false;
        loginButton.classList.remove('btn-loading');
        Swal.fire({
            title: 'Error',
            text: 'Koneksi bermasalah: ' + err,
            icon: 'error'
        });
      };

      google.script.run
        .withSuccessHandler(handleLoginResponse)
        .withFailureHandler(handleLoginError)
        .login(email, password);
    });

    function logout() {
      sessionStorage.removeItem('currentUser');
      currentUser = null;
      document.getElementById('loginPage').style.display = 'flex';
      document.getElementById('adminDashboard').style.display = 'none';
      document.getElementById('studentDashboard').style.display = 'none';
      document.getElementById('emailInput').value = '';
      document.getElementById('passwordInput').value = '';
    }

    // ==================== ADMIN DASHBOARD LOGIC ====================
    function loadAdminDashboard() {
      loadStatistics();
      loadBorrowRequests();
      loadAllBorrows();
      loadMasterBooks();
      loadMasterStudents();
    }

    function showSection(section) {
      // Hide all sections
      document.querySelectorAll('.content-wrapper > div').forEach(div => div.classList.add('hidden'));
      
      // Remove active class from menu
      document.querySelectorAll('#adminDashboard .menu-item').forEach(item => item.classList.remove('active'));
      
      // Show selected section & Activate menu
      // Note: Mapping manual based on order or ID logic
      const menuItems = document.querySelectorAll('#adminDashboard .menu-item');
      
      if (section === 'dashboard') {
        document.getElementById('dashboardSection').classList.remove('hidden');
        document.getElementById('pageTitle').textContent = 'Dashboard';
        menuItems[0].classList.add('active');
        loadStatistics();
      } else if (section === 'borrowRequests') {
        document.getElementById('borrowRequestsSection').classList.remove('hidden');
        document.getElementById('pageTitle').textContent = 'Peminjaman Pending';
        menuItems[1].classList.add('active');
        loadBorrowRequests();
      } else if (section === 'allBorrows') {
        document.getElementById('allBorrowsSection').classList.remove('hidden');
        document.getElementById('pageTitle').textContent = 'Riwayat Peminjaman';
        menuItems[2].classList.add('active');
        loadAllBorrows();
      } else if (section === 'masterBooks') {
        document.getElementById('masterBooksSection').classList.remove('hidden');
        document.getElementById('pageTitle').textContent = 'Master Buku';
        menuItems[3].classList.add('active');
        loadMasterBooks();
      } else if (section === 'masterStudents') {
        document.getElementById('masterStudentsSection').classList.remove('hidden');
        document.getElementById('pageTitle').textContent = 'Master Siswa';
        menuItems[4].classList.add('active');
        loadMasterStudents();
      }
    }

    function loadStatistics() {
      google.script.run.withSuccessHandler(function(stats) {
          document.getElementById('totalBooks').textContent = stats.totalBooks;
          document.getElementById('totalStock').textContent = stats.totalStok;
          document.getElementById('totalStudents').textContent = stats.totalStudents;
          document.getElementById('pendingBorrows').textContent = stats.pending;
          document.getElementById('waitingReturn').textContent = stats.waitingReturn;
          document.getElementById('returnedBooks').textContent = stats.returned;
          document.getElementById('rejectedBorrows').textContent = stats.rejected;
          
          const badge = document.getElementById('pendingBadge');
          if (badge) {
            badge.textContent = stats.pending;
            badge.style.display = stats.pending > 0 ? 'block' : 'none';
          }
      }).getStatistics();
    }

    // ==================== BORROW REQUESTS TABLE ====================
    let pendingBorrowsData = [];
    let borrowRequestsCurrentPage = 1;
    let borrowRequestsPageSize = 10;
    let borrowRequestsSearchQuery = '';

    function loadBorrowRequests() {
      google.script.run.withSuccessHandler(function(history) {
          pendingBorrowsData = history.filter(h => h.status === 'Pending');
          borrowRequestsCurrentPage = 1;
          renderBorrowRequestsTable();
      }).getBorrowHistory(currentUser.email, currentUser.role);
    }

    function renderBorrowRequestsTable() {
      const query = borrowRequestsSearchQuery.toLowerCase();
      const filteredData = pendingBorrowsData.filter(item => {
        return item.id.toLowerCase().includes(query) ||
               item.siswa.toLowerCase().includes(query) ||
               item.judulBuku.toLowerCase().includes(query);
      });

      const totalItems = filteredData.length;
      const pageSize = borrowRequestsPageSize === 'all' ? totalItems : parseInt(borrowRequestsPageSize);
      const totalPages = Math.ceil(totalItems / pageSize);
      const start = (borrowRequestsCurrentPage - 1) * pageSize;
      const end = start + pageSize;
      const paginatedData = filteredData.slice(start, end);

      const tbody = document.querySelector('#borrowRequestsTable tbody');
      tbody.innerHTML = '';

      if (paginatedData.length === 0) {
        tbody.innerHTML = '<tr><td colspan="6" style="text-align: center; padding: 40px;">Tidak ada data</td></tr>';
      } else {
        paginatedData.forEach(item => {
          const row = tbody.insertRow();
          row.innerHTML = `
            <td>${item.id}</td>
            <td>${item.siswa}</td>
            <td>${item.judulBuku}</td>
            <td>${item.tanggalPinjam}</td>
            <td><span class="badge pending">Pending</span></td>
            <td>
              <button class="action-btn btn-approve" onclick="approveBorrow('${item.id}')"><span class="material-icons" style="font-size:14px">check</span></button>
              <button class="action-btn btn-reject" onclick="rejectBorrow('${item.id}')"><span class="material-icons" style="font-size:14px">close</span></button>
            </td>
          `;
        });
      }
      
      // Pagination Controls
      document.getElementById('borrowRequestsPaginationInfo').textContent = `Menampilkan ${totalItems > 0 ? start + 1 : 0}-${Math.min(end, totalItems)} dari ${totalItems}`;
      document.getElementById('borrowRequestsPrevPage').disabled = borrowRequestsCurrentPage === 1;
      document.getElementById('borrowRequestsNextPage').disabled = borrowRequestsCurrentPage === totalPages || totalItems === 0;
    }

    // Event Listeners for Borrow Request Table
    document.getElementById('borrowRequestsSearch').addEventListener('input', (e) => { borrowRequestsSearchQuery = e.target.value; borrowRequestsCurrentPage = 1; renderBorrowRequestsTable(); });
    document.getElementById('borrowRequestsPageSize').addEventListener('change', (e) => { borrowRequestsPageSize = e.target.value; borrowRequestsCurrentPage = 1; renderBorrowRequestsTable(); });
    document.getElementById('borrowRequestsPrevPage').addEventListener('click', () => { if(borrowRequestsCurrentPage > 1) { borrowRequestsCurrentPage--; renderBorrowRequestsTable(); }});
    document.getElementById('borrowRequestsNextPage').addEventListener('click', () => { borrowRequestsCurrentPage++; renderBorrowRequestsTable(); });

    // ==================== ALL BORROWS TABLE ====================
    let allBorrowsData = [];
    let allBorrowsCurrentPage = 1;
    let allBorrowsPageSize = 10;
    let allBorrowsSearchQuery = '';

    function loadAllBorrows() {
        google.script.run.withSuccessHandler(function(history) {
            allBorrowsData = history;
            allBorrowsCurrentPage = 1;
            renderAllBorrowsTable();
        }).getBorrowHistory(currentUser.email, currentUser.role);
    }

    function renderAllBorrowsTable() {
        const query = allBorrowsSearchQuery.toLowerCase();
        const filteredData = allBorrowsData.filter(item => {
            return item.id.toLowerCase().includes(query) || item.siswa.toLowerCase().includes(query) || item.judulBuku.toLowerCase().includes(query) || item.status.toLowerCase().includes(query);
        });
        
        const totalItems = filteredData.length;
        const pageSize = allBorrowsPageSize === 'all' ? totalItems : parseInt(allBorrowsPageSize);
        const totalPages = Math.ceil(totalItems / pageSize);
        const start = (allBorrowsCurrentPage - 1) * pageSize;
        const end = start + pageSize;
        const paginatedData = filteredData.slice(start, end);

        const tbody = document.querySelector('#allBorrowsTable tbody');
        tbody.innerHTML = '';

        if (paginatedData.length === 0) {
             tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; padding: 40px;">Tidak ada data</td></tr>';
        } else {
            paginatedData.forEach(item => {
                const row = tbody.insertRow();
                let statusBadge = `<span class="badge ${item.status.toLowerCase()}">${item.status}</span>`;
                let actionBtn = '';
                if (item.status === 'Approved') {
                    actionBtn = `<button class="action-btn btn-return" onclick="returnBook('${item.id}')">Kembalikan</button>`;
                }
                row.innerHTML = `<td>${item.id}</td><td>${item.siswa}</td><td>${item.judulBuku}</td><td>${item.tanggalPinjam}</td><td>${item.tanggalKembali || '-'}</td><td>${statusBadge}</td><td>${actionBtn}</td>`;
            });
        }
        
        document.getElementById('allBorrowsPaginationInfo').textContent = `Menampilkan ${totalItems > 0 ? start + 1 : 0}-${Math.min(end, totalItems)} dari ${totalItems}`;
        document.getElementById('allBorrowsPrevPage').disabled = allBorrowsCurrentPage === 1;
        document.getElementById('allBorrowsNextPage').disabled = allBorrowsCurrentPage === totalPages || totalItems === 0;
    }
    
    // Event Listeners for All Borrows
    document.getElementById('allBorrowsSearch').addEventListener('input', (e) => { allBorrowsSearchQuery = e.target.value; allBorrowsCurrentPage = 1; renderAllBorrowsTable(); });
    document.getElementById('allBorrowsPageSize').addEventListener('change', (e) => { allBorrowsPageSize = e.target.value; allBorrowsCurrentPage = 1; renderAllBorrowsTable(); });
    document.getElementById('allBorrowsPrevPage').addEventListener('click', () => { if(allBorrowsCurrentPage > 1) { allBorrowsCurrentPage--; renderAllBorrowsTable(); }});
    document.getElementById('allBorrowsNextPage').addEventListener('click', () => { allBorrowsCurrentPage++; renderAllBorrowsTable(); });


    // ==================== MASTER BOOKS LOGIC (WITH UPLOAD) ====================
    let masterBooksData = [];
    let masterBooksCurrentPage = 1;
    let masterBooksPageSize = 10;
    let masterBooksSearchQuery = '';

    function loadMasterBooks() {
      google.script.run.withSuccessHandler(function(books) {
          masterBooksData = books;
          masterBooksCurrentPage = 1;
          renderMasterBooksTable();
      }).getBooks();
    }

    function renderMasterBooksTable() {
        const query = masterBooksSearchQuery.toLowerCase();
        const filteredData = masterBooksData.filter(item => {
            return item.id.toLowerCase().includes(query) || item.judul.toLowerCase().includes(query) || item.penulis.toLowerCase().includes(query);
        });

        const totalItems = filteredData.length;
        const pageSize = masterBooksPageSize === 'all' ? totalItems : parseInt(masterBooksPageSize);
        const totalPages = Math.ceil(totalItems / pageSize);
        const start = (masterBooksCurrentPage - 1) * pageSize;
        const end = start + pageSize;
        const paginatedData = filteredData.slice(start, end);

        const tbody = document.querySelector('#masterBooksTable tbody');
        tbody.innerHTML = '';

        if (paginatedData.length === 0) {
            tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;">Tidak ada data</td></tr>';
        } else {
            paginatedData.forEach(book => {
                const row = tbody.insertRow();
                row.innerHTML = `
                    <td>${book.id}</td>
                    <td>${book.judul}</td>
                    <td>${book.penulis}</td>
                    <td>${book.stok}</td>
                    <td>
                        <button class="action-btn btn-edit" onclick="openEditBookModal('${book.id}')">Edit</button>
                        <button class="action-btn btn-delete" onclick="deleteBook('${book.id}')">Hapus</button>
                    </td>
                `;
            });
        }
        
        document.getElementById('masterBooksPaginationInfo').textContent = `Menampilkan ${totalItems > 0 ? start + 1 : 0}-${Math.min(end, totalItems)} dari ${totalItems}`;
        document.getElementById('masterBooksPrevPage').disabled = masterBooksCurrentPage === 1;
        document.getElementById('masterBooksNextPage').disabled = masterBooksCurrentPage === totalPages || totalItems === 0;
    }

    // Event Listeners for Master Books
    document.getElementById('masterBooksSearch').addEventListener('input', (e) => { masterBooksSearchQuery = e.target.value; masterBooksCurrentPage = 1; renderMasterBooksTable(); });
    document.getElementById('masterBooksPageSize').addEventListener('change', (e) => { masterBooksPageSize = e.target.value; masterBooksCurrentPage = 1; renderMasterBooksTable(); });
    document.getElementById('masterBooksPrevPage').addEventListener('click', () => { if(masterBooksCurrentPage > 1) { masterBooksCurrentPage--; renderMasterBooksTable(); }});
    document.getElementById('masterBooksNextPage').addEventListener('click', () => { masterBooksCurrentPage++; renderMasterBooksTable(); });


    function openAddBookModal() {
        document.getElementById('bookForm').reset();
        document.getElementById('bookId').value = '';
        document.getElementById('bookModalTitle').textContent = 'Tambah Buku';
        document.getElementById('imagePreviewContainer').style.display = 'none';
        document.getElementById('imagePreview').src = '';
        document.getElementById('bookModal').style.display = 'flex';
    }

    function openEditBookModal(id) {
        const book = masterBooksData.find(b => b.id === id);
        if (book) {
            document.getElementById('bookId').value = book.id;
            document.getElementById('bookJudul').value = book.judul;
            document.getElementById('bookPenulis').value = book.penulis;
            document.getElementById('bookStok').value = book.stok;
            document.getElementById('currentBookUrl').value = book.coverURL;
            document.getElementById('bookCoverUrlInput').value = book.coverURL;
            
            const previewContainer = document.getElementById('imagePreviewContainer');
            const previewImage = document.getElementById('imagePreview');
            if (book.coverURL) {
              previewImage.src = book.coverURL;
              previewContainer.style.display = 'block';
            } else {
              previewContainer.style.display = 'none';
            }

            document.getElementById('bookModalTitle').textContent = 'Edit Buku';
            document.getElementById('bookModal').style.display = 'flex';
        }
    }

    function deleteBook(id) {
        Swal.fire({
            title: 'Anda yakin?',
            text: "Data buku akan dihapus permanen!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            cancelButtonColor: '#3085d6',
            confirmButtonText: 'Ya, hapus!',
            cancelButtonText: 'Batal'
        }).then((result) => {
            if (result.isConfirmed) {
                google.script.run.withSuccessHandler(function(res) {
                    showResultAlert(res);
                    if(res.success) {
                        loadMasterBooks();
                    }
                }).deleteBook(id);
            }
        });
    }

    document.getElementById('bookForm').addEventListener('submit', async function(e) {
        e.preventDefault();
        const id = document.getElementById('bookId').value;
        const judul = document.getElementById('bookJudul').value;
        const penulis = document.getElementById('bookPenulis').value;
        const stok = document.getElementById('bookStok').value;
        const coverUrlInput = document.getElementById('bookCoverUrlInput').value;
        const coverFile = document.getElementById('bookCoverFile').files[0];

        let fileData = null;
        if (coverFile) {
          const base64Data = await getBase64(coverFile);
          fileData = {
            fileName: coverFile.name,
            mimeType: coverFile.type,
            data: base64Data
          };
        }
        
        let coverURL = document.getElementById('currentBookUrl').value;
        if(coverUrlInput !== coverURL) {
            coverURL = coverUrlInput;
        }

        const handler = function(res) {
            showResultAlert(res);
            if(res.success) {
                closeModal('bookModal');
                loadMasterBooks();
                loadStatistics();
            }
        };

        if (id) {
            google.script.run.withSuccessHandler(handler).updateBook(id, judul, penulis, stok, coverURL, fileData);
        } else {
            google.script.run.withSuccessHandler(handler).addBook(judul, penulis, stok, coverURL, fileData);
        }
    });


    function loadMasterBooks() {
      google.script.run.withSuccessHandler(function(books) {
          masterBooksData = books;
          masterBooksCurrentPage = 1;
          renderMasterBooksTable();
      }).getBooks();
    }

    function renderMasterBooksTable() {
        const query = masterBooksSearchQuery.toLowerCase();
        const filteredData = masterBooksData.filter(item => item.judul.toLowerCase().includes(query) || item.penulis.toLowerCase().includes(query));
        
        const totalItems = filteredData.length;
        const pageSize = masterBooksPageSize === 'all' ? totalItems : parseInt(masterBooksPageSize);
        const totalPages = Math.ceil(totalItems / pageSize);
        const start = (masterBooksCurrentPage - 1) * pageSize;
        const end = start + pageSize;
        const paginatedData = filteredData.slice(start, end);
        
        const tbody = document.querySelector('#masterBooksTable tbody');
        tbody.innerHTML = '';
        
        if(paginatedData.length === 0){
             tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;">Tidak ada data</td></tr>';
        } else {
            paginatedData.forEach(book => {
                const row = tbody.insertRow();
                row.innerHTML = `
                    <td>${book.id}</td>
                    <td>${book.judul}</td>
                    <td>${book.penulis}</td>
                    <td>${book.stok}</td>
                    <td>
                        <button class="action-btn btn-edit" onclick='editBook(${JSON.stringify(book)})'>Edit</button>
                        <button class="action-btn btn-delete" onclick="deleteBook('${book.id}')">Hapus</button>
                    </td>
                `;
            });
        }
        document.getElementById('masterBooksPaginationInfo').textContent = `Menampilkan ${totalItems > 0 ? start + 1 : 0}-${Math.min(end, totalItems)} dari ${totalItems}`;
        document.getElementById('masterBooksPrevPage').disabled = masterBooksCurrentPage === 1;
        document.getElementById('masterBooksNextPage').disabled = masterBooksCurrentPage === totalPages || totalItems === 0;
    }

    // Event Listeners Master Books
    document.getElementById('masterBooksSearch').addEventListener('input', (e) => { masterBooksSearchQuery = e.target.value; masterBooksCurrentPage = 1; renderMasterBooksTable(); });
    document.getElementById('masterBooksPageSize').addEventListener('change', (e) => { masterBooksPageSize = e.target.value; masterBooksCurrentPage = 1; renderMasterBooksTable(); });
    document.getElementById('masterBooksPrevPage').addEventListener('click', () => { if(masterBooksCurrentPage > 1) { masterBooksCurrentPage--; renderMasterBooksTable(); }});
    document.getElementById('masterBooksNextPage').addEventListener('click', () => { if(masterBooksCurrentPage * parseInt(masterBooksPageSize) < masterBooksData.length) { masterBooksCurrentPage++; renderMasterBooksTable(); }});

    // --- BOOK MODAL & FORM SUBMIT ---
    function openAddBookModal() {
      document.getElementById('bookForm').reset();
      document.getElementById('bookModalTitle').textContent = 'Tambah Buku';
      document.getElementById('bookId').value = '';
      document.getElementById('currentBookUrl').value = '';
      
      // Reset Preview
      document.getElementById('imagePreviewContainer').style.display = 'none';
      document.getElementById('imagePreview').src = '';
      
      document.getElementById('bookModal').style.display = 'flex';
    }

    function editBook(book) {
      document.getElementById('bookModalTitle').textContent = 'Edit Buku';
      document.getElementById('bookId').value = book.id;
      document.getElementById('bookJudul').value = book.judul;
      document.getElementById('bookPenulis').value = book.penulis;
      document.getElementById('bookStok').value = book.stok;
      
      // Reset file input
      document.getElementById('bookCoverFile').value = '';

      // Handle URL & Preview
      const urlInput = document.getElementById('bookCoverUrlInput');
      const currentUrlHidden = document.getElementById('currentBookUrl');
      const previewContainer = document.getElementById('imagePreviewContainer');
      const previewImage = document.getElementById('imagePreview');

      if (book.coverURL && book.coverURL !== 'null' && book.coverURL !== '') {
        urlInput.value = book.coverURL;
        currentUrlHidden.value = book.coverURL;
        previewImage.src = book.coverURL;
        previewContainer.style.display = 'block';
      } else {
        urlInput.value = '';
        currentUrlHidden.value = '';
        previewContainer.style.display = 'none';
      }

      document.getElementById('bookModal').style.display = 'flex';
    }

    // SUBMIT BUKU (ASYNC UPLOAD)
    document.getElementById('bookForm').addEventListener('submit', async function(e) {
      e.preventDefault();
      
      const bookId = document.getElementById('bookId').value;
      const judul = document.getElementById('bookJudul').value;
      const penulis = document.getElementById('bookPenulis').value;
      const stok = parseInt(document.getElementById('bookStok').value);
      
      const manualUrl = document.getElementById('bookCoverUrlInput').value;
      const oldUrl = document.getElementById('currentBookUrl').value;
      // Prioritaskan URL manual jika diisi, jika tidak gunakan URL lama
      const coverURL = manualUrl || oldUrl;

      const fileInput = document.getElementById('bookCoverFile');
      let fileData = null;

      const submitButton = this.querySelector('button[type="submit"]');
      submitButton.disabled = true;
      submitButton.classList.add('btn-loading');

      try {
          // Proses File Upload jika ada
          if (fileInput.files.length > 0) {
              const file = fileInput.files[0];
              // Batasi ukuran file (misal 2MB) agar tidak timeout
              if (file.size > 2 * 1024 * 1024) {
                  throw new Error("Ukuran gambar terlalu besar (Maks 2MB)");
              }
              
              const base64 = await getBase64(file);
              fileData = {
                  data: base64,
                  mimeType: file.type,
                  fileName: file.name
              };
          }

          const handleResponse = (result) => {
            submitButton.disabled = false;
            submitButton.classList.remove('btn-loading');
            if (result.success) {
              Swal.fire('Berhasil!', result.message, 'success');
              closeModal('bookModal');
              loadMasterBooks();
            } else {
              Swal.fire('Gagal!', result.message, 'error');
            }
          };

          const handleError = (err) => {
            submitButton.disabled = false;
            submitButton.classList.remove('btn-loading');
            Swal.fire('Error!', 'Terjadi kesalahan: ' + err.message, 'error');
          };

          // Panggil Fungsi Google Apps Script
          if (bookId) {
              // Update: Kirim data termasuk fileData
              google.script.run
                .withSuccessHandler(handleResponse)
                .withFailureHandler(handleError)
                .updateBook(bookId, judul, penulis, stok, coverURL, fileData);
          } else {
              // Add: Kirim data termasuk fileData
              google.script.run
                .withSuccessHandler(handleResponse)
                .withFailureHandler(handleError)
                .addBook(judul, penulis, stok, coverURL, fileData);
          }

      } catch (error) {
          submitButton.disabled = false;
          submitButton.classList.remove('btn-loading');
          Swal.fire('Error Upload!', error.message, 'error');
      }
    });

    function deleteBook(id) {
        Swal.fire({
            title: 'Hapus Buku?',
            text: "Data tidak bisa dikembalikan!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            confirmButtonText: 'Hapus'
        }).then((result) => {
            if (result.isConfirmed) {
                google.script.run.withSuccessHandler(function(res){
                    showResultAlert(res);
                    if(res.success) loadMasterBooks();
                }).deleteBook(id);
            }
        });
    }

    // ==================== MASTER STUDENTS LOGIC ====================
    let masterStudentsData = [];
    let masterStudentsCurrentPage = 1;
    let masterStudentsPageSize = 10;
    let masterStudentsSearchQuery = '';

    function loadMasterStudents() {
      google.script.run.withSuccessHandler(function(students) {
          masterStudentsData = students;
          masterStudentsCurrentPage = 1;
          renderMasterStudentsTable();
      }).getStudents();
    }

    function renderMasterStudentsTable() {
        const query = masterStudentsSearchQuery.toLowerCase();
        const filteredData = masterStudentsData.filter(item => item.email.toLowerCase().includes(query) || item.nama.toLowerCase().includes(query));
        
        const totalItems = filteredData.length;
        const pageSize = masterStudentsPageSize === 'all' ? totalItems : parseInt(masterStudentsPageSize);
        const totalPages = Math.ceil(totalItems / pageSize);
        const start = (masterStudentsCurrentPage - 1) * pageSize;
        const end = start + pageSize;
        const paginatedData = filteredData.slice(start, end);
        
        const tbody = document.querySelector('#masterStudentsTable tbody');
        tbody.innerHTML = '';
        
        if(paginatedData.length === 0){
             tbody.innerHTML = '<tr><td colspan="3" style="text-align: center; padding: 40px;">Tidak ada data</td></tr>';
        } else {
            paginatedData.forEach(student => {
                const row = tbody.insertRow();
                row.innerHTML = `
                    <td>${student.email}</td>
                    <td>${student.nama}</td>
                    <td>
                        <button class="action-btn btn-edit" onclick='editStudent(${JSON.stringify(student)})'>Edit</button>
                        <button class="action-btn btn-delete" onclick="deleteStudent('${student.email}')">Hapus</button>
                    </td>
                `;
            });
        }
        document.getElementById('masterStudentsPaginationInfo').textContent = `Menampilkan ${totalItems > 0 ? start + 1 : 0}-${Math.min(end, totalItems)} dari ${totalItems}`;
        document.getElementById('masterStudentsPrevPage').disabled = masterStudentsCurrentPage === 1;
        document.getElementById('masterStudentsNextPage').disabled = masterStudentsCurrentPage === totalPages || totalItems === 0;
    }
    
    // Event Listeners Master Students
    document.getElementById('masterStudentsSearch').addEventListener('input', (e) => { masterStudentsSearchQuery = e.target.value; masterStudentsCurrentPage = 1; renderMasterStudentsTable(); });
    document.getElementById('masterStudentsPageSize').addEventListener('change', (e) => { masterStudentsPageSize = e.target.value; masterStudentsCurrentPage = 1; renderMasterStudentsTable(); });
    document.getElementById('masterStudentsPrevPage').addEventListener('click', () => { if(masterStudentsCurrentPage > 1) { masterStudentsCurrentPage--; renderMasterStudentsTable(); }});
    document.getElementById('masterStudentsNextPage').addEventListener('click', () => { masterStudentsCurrentPage++; renderMasterStudentsTable(); });

    function openAddStudentModal() {
      document.getElementById('studentModalTitle').textContent = 'Tambah Siswa';
      document.getElementById('studentEmailOld').value = '';
      document.getElementById('studentEmail').value = '';
      document.getElementById('studentPassword').value = '';
      document.getElementById('studentNama').value = '';
      document.getElementById('studentModal').style.display = 'flex';
    }

    function editStudent(student) {
      document.getElementById('studentModalTitle').textContent = 'Edit Siswa';
      document.getElementById('studentEmailOld').value = student.email;
      document.getElementById('studentEmail').value = student.email;
      document.getElementById('studentPassword').value = '';
      document.getElementById('studentPassword').required = false;
      document.getElementById('studentNama').value = student.nama;
      document.getElementById('studentModal').style.display = 'flex';
    }

    document.getElementById('studentForm').addEventListener('submit', function(e) {
      e.preventDefault();
      const oldEmail = document.getElementById('studentEmailOld').value;
      const email = document.getElementById('studentEmail').value;
      const password = document.getElementById('studentPassword').value;
      const nama = document.getElementById('studentNama').value;
      const submitButton = this.querySelector('button[type="submit"]');

      submitButton.disabled = true;
      submitButton.classList.add('btn-loading');
      
      const handler = (result) => {
          submitButton.disabled = false;
          submitButton.classList.remove('btn-loading');
          showResultAlert(result);
          if(result.success) { closeModal('studentModal'); loadMasterStudents(); }
      };

      if (oldEmail) {
        google.script.run.withSuccessHandler(handler).updateStudent(oldEmail, email, password, nama);
      } else {
        google.script.run.withSuccessHandler(handler).addStudent(email, password, nama);
      }
    });

    function deleteStudent(email) {
        Swal.fire({
            title: 'Hapus Siswa?',
            text: "Data tidak bisa dikembalikan!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#d33',
            confirmButtonText: 'Hapus'
        }).then((result) => {
            if (result.isConfirmed) {
                google.script.run.withSuccessHandler(function(res){
                    showResultAlert(res);
                    if(res.success) loadMasterStudents();
                }).deleteStudent(email);
            }
        });
    }

    // ==================== APPROVE / REJECT / RETURN LOGIC ====================
    function approveBorrow(borrowId) {
      Swal.fire({ title: 'Setujui?', text: "Stok buku akan berkurang.", icon: 'question', showCancelButton: true, confirmButtonText: 'Ya' }).then((result) => {
        if (result.isConfirmed) {
          google.script.run.withSuccessHandler(function(res) {
              showResultAlert(res);
              if (res.success) { loadBorrowRequests(); loadAllBorrows(); loadStatistics(); }
            }).approveBorrow(borrowId);
        }
      });
    }

    function rejectBorrow(borrowId) {
      Swal.fire({ title: 'Tolak?', icon: 'warning', showCancelButton: true, confirmButtonText: 'Ya', confirmButtonColor: '#d33' }).then((result) => {
        if (result.isConfirmed) {
          google.script.run.withSuccessHandler(function(res) {
              showResultAlert(res);
              if (res.success) { loadBorrowRequests(); loadAllBorrows(); loadStatistics(); }
            }).rejectBorrow(borrowId);
        }
      });
    }

    function returnBook(borrowId) {
      Swal.fire({ title: 'Buku Kembali?', text: "Stok akan bertambah.", icon: 'question', showCancelButton: true, confirmButtonText: 'Ya' }).then((result) => {
        if (result.isConfirmed) {
          google.script.run.withSuccessHandler(function(res) {
              showResultAlert(res);
              if (res.success) { loadAllBorrows(); loadStatistics(); }
            }).returnBook(borrowId);
        }
      });
    }

    // ==================== STUDENT DASHBOARD LOGIC ====================
    function loadStudentDashboard() {
      loadStudentBooks();
      loadStudentBorrows();
    }

    function showStudentSection(section) {
      document.getElementById('studentBooksSection').classList.add('hidden');
      document.getElementById('studentBorrowsSection').classList.add('hidden');
      
      document.querySelectorAll('#studentDashboard .menu-item').forEach(item => item.classList.remove('active'));
      
      const menuItems = document.querySelectorAll('#studentDashboard .menu-item');

      if (section === 'studentBooks') {
        document.getElementById('studentBooksSection').classList.remove('hidden');
        document.getElementById('studentPageTitle').textContent = 'Daftar Buku';
        menuItems[0].classList.add('active');
        loadStudentBooks();
      } else if (section === 'studentBorrows') {
        document.getElementById('studentBorrowsSection').classList.remove('hidden');
        document.getElementById('studentPageTitle').textContent = 'Riwayat Peminjaman';
        menuItems[1].classList.add('active');
        loadStudentBorrows();
      }
    }

    function loadStudentBooks() {
      google.script.run.withSuccessHandler(function(books) {
          const grid = document.getElementById('studentBooksGrid');
          grid.innerHTML = '';
          
          if (books.length === 0) {
            grid.innerHTML = '<p style="text-align: center; padding: 40px; grid-column: 1/-1;">Belum ada buku tersedia</p>';
            return;
          }
          
          books.forEach(book => {
            const card = document.createElement('div');
            card.className = 'book-card';
            // Gunakan placeholder jika coverURL kosong/null
            const imgUrl = (book.coverURL && book.coverURL !== 'null') ? book.coverURL : 'https://via.placeholder.com/200x300/667eea/ffffff?text=Buku';
            
            card.innerHTML = `
              <img src="${imgUrl}" alt="${book.judul}" class="book-cover">
              <div class="book-info">
                <h5>${book.judul}</h5>
                <p>Penulis: ${book.penulis}</p>
                <div class="book-stok">
                  <span class="material-icons" style="font-size: 16px;">inventory_2</span>
                  Stok: ${book.stok}
                </div>
                <button class="action-btn btn-borrow" onclick="borrowBookStudent('${book.id}')" ${book.stok <= 0 ? 'disabled' : ''} style="margin-top: 12px; width: 100%;">
                  ${book.stok > 0 ? 'Pinjam Buku' : 'Stok Habis'}
                </button>
              </div>
            `;
            grid.appendChild(card);
          });
      }).getBooks();
    }

    function loadStudentBorrows() {
      google.script.run.withSuccessHandler(function(history) {
          const tbody = document.querySelector('#studentBorrowsTable tbody');
          tbody.innerHTML = '';
          
          if (history.length === 0) {
            tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;">Belum ada riwayat</td></tr>';
            return;
          }
          
          history.forEach(item => {
            const row = tbody.insertRow();
            let statusBadge = `<span class="badge ${item.status.toLowerCase()}">${item.status}</span>`;
            
            row.innerHTML = `
              <td>${item.id}</td>
              <td>${item.judulBuku}</td>
              <td>${item.tanggalPinjam}</td>
              <td>${item.tanggalKembali || '-'}</td>
              <td>${statusBadge}</td>
            `;
          });
      }).getBorrowHistory(currentUser.email, currentUser.role);
    }

    function borrowBookStudent(bukuId) {
      Swal.fire({ title: 'Pinjam Buku?', icon: 'question', showCancelButton: true, confirmButtonText: 'Ya' }).then((result) => {
        if (result.isConfirmed) {
          google.script.run.withSuccessHandler(function(res) {
              showResultAlert(res);
              if (res.success) { loadStudentBooks(); loadStudentBorrows(); }
            }).borrowBook(currentUser.email, bukuId);
        }
      });
    }
  </script>
</body>
</html>

Login Apps Script

  https://www.youtube.com/watch?v=Uxal8eSVc7s https://www.youtube.com/watch?v=8gmzU3LoTpY&list=PLzONZ57BD6XnKeZmJ33eAwDLdafNVrvaV&in...