<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistem Perpustakaan Digital</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.page-transition { transition: opacity 0.3s ease; }
.hidden { display: none; }
.sidebar-active { background-color: #3b82f6; color: white; }
</style>
</head>
<body class="bg-gray-50 font-sans text-gray-900">
<!-- LOGIN SECTION -->
<div id="login-page" class="min-h-screen flex items-center justify-center bg-blue-600 px-4">
<div class="bg-white p-8 rounded-xl shadow-2xl w-full max-w-md">
<div class="text-center mb-8">
<div class="inline-block p-4 bg-blue-100 rounded-full mb-4">
<i class="fas fa-book-reader text-3xl text-blue-600"></i>
</div>
<h1 class="text-2xl font-bold text-gray-800">E-Perpus Login</h1>
<p class="text-gray-500">Gunakan email anda untuk masuk</p>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">Email Address</label>
<input type="email" id="email-input" class="w-full px-4 py-3 mt-1 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="admin@example.com">
</div>
<button onclick="handleLogin()" id="btn-login" class="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition">
Masuk ke Sistem
</button>
<div id="login-error" class="text-red-500 text-sm text-center hidden mt-2"></div>
</div>
</div>
</div>
<!-- MAIN APP SECTION (Hidden initially) -->
<div id="app-container" class="min-h-screen flex hidden">
<!-- Sidebar -->
<aside class="w-64 bg-slate-900 text-white hidden md:flex flex-col">
<div class="p-6 text-xl font-bold flex items-center gap-2 border-b border-slate-800">
<i class="fas fa-university text-blue-400"></i>
<span>LibManager</span>
</div>
<nav class="flex-1 p-4 space-y-2 mt-4" id="main-nav">
<button onclick="showPage('dashboard')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition sidebar-active" data-page="dashboard">
<i class="fas fa-chart-line w-5"></i> Dashboard
</button>
<button onclick="showPage('buku')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="buku">
<i class="fas fa-book w-5"></i> Katalog Buku
</button>
<button onclick="showPage('anggota')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="anggota">
<i class="fas fa-users w-5"></i> Anggota
</button>
<button onclick="showPage('transaksi')" class="nav-link w-full flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 transition" data-page="transaksi">
<i class="fas fa-exchange-alt w-5"></i> Transaksi
</button>
</nav>
<div class="p-4 border-t border-slate-800">
<div class="flex items-center gap-3 p-2">
<div id="user-avatar" class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center font-bold">A</div>
<div class="overflow-hidden">
<p id="user-name" class="font-medium truncate">User Name</p>
<p id="user-role" class="text-xs text-gray-400">Role</p>
</div>
</div>
<button onclick="logout()" class="w-full mt-4 text-left px-4 py-2 text-red-400 hover:bg-red-500/10 rounded-lg transition">
<i class="fas fa-sign-out-alt mr-2"></i> Logout
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-y-auto">
<header class="bg-white border-b h-16 flex items-center justify-between px-8">
<h2 id="page-title" class="text-xl font-bold text-gray-800">Dashboard</h2>
<div class="flex items-center gap-4">
<button onclick="setupDB()" class="text-xs bg-gray-100 px-3 py-1 rounded hover:bg-gray-200">System Setup</button>
<span id="current-date" class="text-sm text-gray-500"></span>
</div>
</header>
<div id="content-area" class="p-8">
<!-- Dashboard Page -->
<div id="page-dashboard" class="page-content">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-gray-500 text-sm font-medium">Total Judul Buku</p>
<h3 id="stat-buku" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-gray-500 text-sm font-medium">Anggota Terdaftar</p>
<h3 id="stat-anggota" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-gray-500 text-sm font-medium">Sedang Dipinjam</p>
<h3 id="stat-pinjam" class="text-3xl font-bold mt-1 text-orange-600">0</h3>
</div>
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<p class="text-gray-500 text-sm font-medium">Total Denda Berjalan</p>
<h3 id="stat-denda" class="text-3xl font-bold mt-1 text-red-600">Rp 0</h3>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border p-6">
<h4 class="font-bold mb-4 text-gray-700">Log Aktivitas Terakhir</h4>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="text-gray-400 text-xs uppercase border-b">
<tr>
<th class="py-3 px-4">Waktu</th>
<th class="py-3 px-4">User</th>
<th class="py-3 px-4">Aktivitas</th>
</tr>
</thead>
<tbody id="log-table-body" class="text-sm divide-y">
<!-- Rows added by JS -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Buku Page -->
<div id="page-buku" class="page-content hidden">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-bold">Katalog Buku</h3>
<button onclick="openBukuModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center gap-2">
<i class="fas fa-plus"></i> Tambah Buku
</button>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<table class="w-full text-left border-collapse">
<thead class="bg-gray-50 text-gray-600 text-sm">
<tr>
<th class="p-4 border-b">ID</th>
<th class="p-4 border-b">Judul</th>
<th class="p-4 border-b">Penulis</th>
<th class="p-4 border-b">Stok</th>
<th class="p-4 border-b">Lokasi</th>
<th class="p-4 border-b text-center">Aksi</th>
</tr>
</thead>
<tbody id="buku-table-body"></tbody>
</table>
</div>
</div>
<!-- Anggota Page -->
<div id="page-anggota" class="page-content hidden">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-bold">Manajemen Anggota</h3>
<button onclick="openAnggotaModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
Tambah Anggota
</button>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<table class="w-full text-left border-collapse">
<thead class="bg-gray-50 text-gray-600 text-sm">
<tr>
<th class="p-4 border-b">ID</th>
<th class="p-4 border-b">Nama</th>
<th class="p-4 border-b">No HP</th>
<th class="p-4 border-b">Status</th>
<th class="p-4 border-b text-center">Aksi</th>
</tr>
</thead>
<tbody id="anggota-table-body"></tbody>
</table>
</div>
</div>
<!-- Transaksi Page -->
<div id="page-transaksi" class="page-content hidden">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-bold">Transaksi Peminjaman</h3>
<button onclick="openTransaksiModal()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700">
Pinjam Buku Baru
</button>
</div>
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<table class="w-full text-left border-collapse">
<thead class="bg-gray-50 text-gray-600 text-sm">
<tr>
<th class="p-4 border-b">ID</th>
<th class="p-4 border-b">Anggota</th>
<th class="p-4 border-b">Buku</th>
<th class="p-4 border-b">Jatuh Tempo</th>
<th class="p-4 border-b">Status</th>
<th class="p-4 border-b text-center">Aksi</th>
</tr>
</thead>
<tbody id="trans-table-body"></tbody>
</table>
</div>
</div>
</div>
</main>
</div>
<!-- MODALS -->
<div id="modal-overlay" class="fixed inset-0 bg-black/50 hidden flex items-center justify-center p-4 z-50">
<!-- Buku Modal -->
<div id="modal-buku" class="bg-white rounded-xl max-w-lg w-full p-6 hidden">
<h3 class="text-xl font-bold mb-4" id="buku-modal-title">Data Buku</h3>
<form id="form-buku" onsubmit="handleBukuSubmit(event)" class="space-y-4">
<input type="hidden" id="buku-id-input">
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2">
<label class="text-sm font-medium">Judul Buku</label>
<input type="text" id="buku-judul" required class="w-full border p-2 rounded mt-1">
</div>
<div>
<label class="text-sm font-medium">Penulis</label>
<input type="text" id="buku-penulis" required class="w-full border p-2 rounded mt-1">
</div>
<div>
<label class="text-sm font-medium">Kategori</label>
<select id="buku-kategori" class="w-full border p-2 rounded mt-1">
<option>Teknologi</option><option>Sains</option><option>Sastra</option><option>Sejarah</option>
</select>
</div>
<div>
<label class="text-sm font-medium">Stok</label>
<input type="number" id="buku-stok" required class="w-full border p-2 rounded mt-1">
</div>
<div>
<label class="text-sm font-medium">Lokasi Rak</label>
<input type="text" id="buku-rak" required class="w-full border p-2 rounded mt-1">
</div>
</div>
<div class="flex justify-end gap-2 pt-4">
<button type="button" onclick="closeModal()" class="px-4 py-2 text-gray-500">Batal</button>
<button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-lg">Simpan</button>
</div>
</form>
</div>
<!-- Transaksi Modal -->
<div id="modal-trans" class="bg-white rounded-xl max-w-lg w-full p-6 hidden">
<h3 class="text-xl font-bold mb-4">Peminjaman Buku Baru</h3>
<form id="form-trans" onsubmit="handlePinjamSubmit(event)" class="space-y-4">
<div>
<label class="text-sm font-medium">Pilih Anggota</label>
<select id="trans-anggota-select" required class="w-full border p-2 rounded mt-1"></select>
</div>
<div>
<label class="text-sm font-medium">Pilih Buku</label>
<select id="trans-buku-select" required class="w-full border p-2 rounded mt-1"></select>
</div>
<p class="text-xs text-gray-500 italic">*Lama pinjaman standar adalah 7 hari.</p>
<div class="flex justify-end gap-2 pt-4">
<button type="button" onclick="closeModal()" class="px-4 py-2 text-gray-500">Batal</button>
<button type="submit" class="px-6 py-2 bg-green-600 text-white rounded-lg">Konfirmasi Pinjam</button>
</div>
</form>
</div>
</div>
<!-- NOTIFICATION SYSTEM -->
<div id="toast" class="fixed bottom-8 right-8 bg-slate-800 text-white px-6 py-3 rounded-xl shadow-2xl transition-all duration-300 opacity-0 translate-y-10 pointer-events-none z-[100]">
<span id="toast-msg"></span>
</div>
<script>
// --- GLOBAL STATE ---
let currentUser = null;
let dbData = { buku: [], anggota: [], transaksi: [] };
// --- APP INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('current-date').innerText = new Date().toLocaleDateString('id-ID', { dateStyle: 'full' });
});
function setupDB() {
notify("Menyiapkan database...");
google.script.run.withSuccessHandler(res => {
notify(res);
}).setupDatabase();
}
// --- AUTH LOGIC ---
function handleLogin() {
const email = document.getElementById('email-input').value;
const btn = document.getElementById('btn-login');
const error = document.getElementById('login-error');
if (!email) return;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Autentikasi...';
btn.disabled = true;
error.classList.add('hidden');
google.script.run.withSuccessHandler(user => {
if (user) {
currentUser = user;
initApp();
} else {
error.innerText = "Email tidak ditemukan!";
error.classList.remove('hidden');
btn.innerHTML = 'Masuk ke Sistem';
btn.disabled = false;
}
}).authenticateUser(email);
}
function initApp() {
document.getElementById('login-page').classList.add('hidden');
document.getElementById('app-container').classList.remove('hidden');
document.getElementById('user-name').innerText = currentUser.nama;
document.getElementById('user-role').innerText = currentUser.role;
document.getElementById('user-avatar').innerText = currentUser.nama.charAt(0);
refreshDashboard();
loadBuku();
loadAnggota();
loadTransaksi();
}
function logout() {
window.location.reload();
}
// --- NAVIGATION & UI ---
function showPage(pageId) {
document.querySelectorAll('.page-content').forEach(p => p.classList.add('hidden'));
document.getElementById('page-' + pageId).classList.remove('hidden');
document.getElementById('page-title').innerText = pageId.charAt(0).toUpperCase() + pageId.slice(1);
document.querySelectorAll('.nav-link').forEach(l => {
if (l.dataset.page === pageId) l.classList.add('sidebar-active');
else l.classList.remove('sidebar-active');
});
}
function notify(msg) {
const toast = document.getElementById('toast');
document.getElementById('toast-msg').innerText = msg;
toast.classList.remove('opacity-0', 'translate-y-10');
toast.classList.add('opacity-100', 'translate-y-0');
setTimeout(() => {
toast.classList.add('opacity-0', 'translate-y-10');
toast.classList.remove('opacity-100', 'translate-y-0');
}, 3000);
}
// --- DATA LOADERS ---
function refreshDashboard() {
google.script.run.withSuccessHandler(stats => {
document.getElementById('stat-buku').innerText = stats.totalBuku;
document.getElementById('stat-anggota').innerText = stats.totalAnggota;
document.getElementById('stat-pinjam').innerText = stats.bukuDipinjam;
document.getElementById('stat-denda').innerText = 'Rp ' + stats.totalDenda.toLocaleString('id-ID');
const logBody = document.getElementById('log-table-body');
logBody.innerHTML = stats.recentLog.map(log => `
<tr>
<td class="py-2 px-4 text-xs text-gray-500">${new Date(log.timestamp).toLocaleTimeString()}</td>
<td class="py-2 px-4 font-medium">${log.user}</td>
<td class="py-2 px-4 text-gray-600">${log.aktivitas}</td>
</tr>
`).join('');
}).getDashboardStats();
}
function loadBuku() {
google.script.run.withSuccessHandler(data => {
dbData.buku = data;
const body = document.getElementById('buku-table-body');
body.innerHTML = data.map(b => `
<tr>
<td class="p-4 text-sm">${b.buku_id}</td>
<td class="p-4 font-medium">${b.judul}</td>
<td class="p-4 text-sm text-gray-600">${b.penulis}</td>
<td class="p-4 text-sm">${b.stok}</td>
<td class="p-4 text-sm font-mono text-blue-600">${b.lokasi_rak}</td>
<td class="p-4 text-center">
<button onclick="editBuku('${b.buku_id}')" class="text-blue-500 p-1"><i class="fas fa-edit"></i></button>
<button onclick="deleteBuku('${b.buku_id}')" class="text-red-400 p-1 ml-2"><i class="fas fa-trash"></i></button>
</td>
</tr>
`).join('');
}).getSheetData('Buku');
}
function loadAnggota() {
google.script.run.withSuccessHandler(data => {
dbData.anggota = data;
const body = document.getElementById('anggota-table-body');
body.innerHTML = data.map(a => `
<tr>
<td class="p-4 text-sm">${a.anggota_id}</td>
<td class="p-4 font-medium">${a.nama}</td>
<td class="p-4 text-sm">${a.no_hp}</td>
<td class="p-4"><span class="bg-green-100 text-green-700 px-2 py-1 rounded text-xs">${a.status}</span></td>
<td class="p-4 text-center">
<button onclick="printCard('${a.anggota_id}')" title="Print Kartu" class="text-purple-600 p-1"><i class="fas fa-id-card"></i></button>
</td>
</tr>
`).join('');
}).getSheetData('Anggota');
}
function loadTransaksi() {
google.script.run.withSuccessHandler(data => {
dbData.transaksi = data;
const body = document.getElementById('trans-table-body');
body.innerHTML = data.map(t => {
const statusClass = t.status === 'Dipinjam' ? 'bg-orange-100 text-orange-700' : 'bg-blue-100 text-blue-700';
return `
<tr>
<td class="p-4 text-xs font-mono">${t.transaksi_id}</td>
<td class="p-4 text-sm">${t.anggota_id}</td>
<td class="p-4 text-sm">${t.buku_id}</td>
<td class="p-4 text-sm text-red-600">${new Date(t.tanggal_jatuh_tempo).toLocaleDateString()}</td>
<td class="p-4"><span class="${statusClass} px-2 py-1 rounded text-xs">${t.status}</span></td>
<td class="p-4 text-center">
${t.status === 'Dipinjam' ? `<button onclick="handleKembali('${t.transaksi_id}')" class="text-xs bg-slate-800 text-white px-3 py-1 rounded">Kembalikan</button>` : '-'}
</td>
</tr>
`}).join('');
}).getSheetData('Transaksi');
}
// --- FORM HANDLERS ---
function openBukuModal(id = null) {
document.getElementById('modal-overlay').classList.remove('hidden');
document.getElementById('modal-buku').classList.remove('hidden');
document.getElementById('form-buku').reset();
document.getElementById('buku-id-input').value = id || '';
document.getElementById('buku-modal-title').innerText = id ? 'Edit Buku' : 'Tambah Buku Baru';
if (id) {
const b = dbData.buku.find(x => x.buku_id === id);
document.getElementById('buku-judul').value = b.judul;
document.getElementById('buku-penulis').value = b.penulis;
document.getElementById('buku-kategori').value = b.kategori;
document.getElementById('buku-stok').value = b.stok;
document.getElementById('buku-rak').value = b.lokasi_rak;
}
}
function handleBukuSubmit(e) {
e.preventDefault();
const id = document.getElementById('buku-id-input').value;
const data = {
isEdit: !!id,
buku_id: id,
judul: document.getElementById('buku-judul').value,
penulis: document.getElementById('buku-penulis').value,
kategori: document.getElementById('buku-kategori').value,
stok: document.getElementById('buku-stok').value,
lokasi_rak: document.getElementById('buku-rak').value
};
google.script.run.withSuccessHandler(() => {
notify("Buku berhasil disimpan!");
closeModal();
loadBuku();
refreshDashboard();
}).saveBuku(data);
}
function openTransaksiModal() {
document.getElementById('modal-overlay').classList.remove('hidden');
document.getElementById('modal-trans').classList.remove('hidden');
const bSelect = document.getElementById('trans-buku-select');
const aSelect = document.getElementById('trans-anggota-select');
bSelect.innerHTML = dbData.buku.map(b => `<option value="${b.buku_id}">${b.judul} (${b.stok})</option>`).join('');
aSelect.innerHTML = dbData.anggota.map(a => `<option value="${a.anggota_id}">${a.nama}</option>`).join('');
}
function handlePinjamSubmit(e) {
e.preventDefault();
const data = {
anggota_id: document.getElementById('trans-anggota-select').value,
buku_id: document.getElementById('trans-buku-select').value
};
google.script.run.withSuccessHandler(res => {
notify("Buku berhasil dipinjam!");
closeModal();
loadTransaksi();
loadBuku();
refreshDashboard();
}).withFailureHandler(err => {
notify("Error: " + err.message);
}).pinjamBuku(data);
}
function handleKembali(id) {
if (!confirm("Konfirmasi pengembalian buku?")) return;
google.script.run.withSuccessHandler(res => {
notify(res.denda > 0 ? `Buku kembali! Denda keterlambatan: Rp ${res.denda}` : "Buku kembali tepat waktu!");
loadTransaksi();
loadBuku();
refreshDashboard();
}).kembalikanBuku(id);
}
function printCard(id) {
notify("Menyiapkan kartu PDF...");
google.script.run.withSuccessHandler(base64 => {
const blob = b64toBlob(base64, 'application/pdf');
const url = URL.createObjectURL(blob);
window.open(url);
}).generateKartuAnggota(id);
}
function closeModal() {
document.getElementById('modal-overlay').classList.add('hidden');
document.querySelectorAll('#modal-overlay > div').forEach(m => m.classList.add('hidden'));
}
// Helper to convert base64 to Blob for PDF preview
function b64toBlob(b64Data, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) byteNumbers[i] = slice.charCodeAt(i);
byteArrays.push(new Uint8Array(byteNumbers));
}
return new Blob(byteArrays, {type: contentType});
}
</script>
</body>
</html>
Tidak ada komentar:
Posting Komentar