<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buku Tamu Digital</title>
<!-- Tailwind CSS CDN untuk Styling Modern -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts: Inter untuk Typografi Clean -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Kustomisasi scrollbar untuk bagian daftar pengunjung */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-slate-50 min-h-screen text-slate-800 antialiased flex flex-col justify-between">
<!-- Container Notifikasi (Toast) Kustom -->
<div id="toast-container" class="fixed top-5 right-5 z-50 flex flex-col gap-3 max-w-sm w-full"></div>
<!-- Header & Navigasi -->
<header class="bg-white border-b border-slate-200 sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="bg-indigo-600 p-2 rounded-lg text-white">
<i data-lucide="book-open" class="w-6 h-6"></i>
</div>
<div>
<h1 class="text-lg font-bold text-slate-900 leading-tight">Buku Tamu Digital</h1>
<p class="text-xs text-slate-500">Sistem Pencatatan Pengunjung Instansi</p>
</div>
</div>
<!-- Tombol Generate Database Quick-Access -->
<div>
<button
onclick="triggerSetupDatabase()"
id="btn-quick-setup"
class="flex items-center gap-2 px-3 py-1.5 bg-emerald-50 text-emerald-700 border border-emerald-200 hover:bg-emerald-100 rounded-md text-xs font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
>
<i data-lucide="database-backup" class="w-4 h-4"></i>
<span>Setup Database</span>
</button>
</div>
</div>
</header>
<!-- Konten Utama -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 flex-grow w-full">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Kolom Kiri: Form Input Data -->
<section class="lg:col-span-5 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm flex flex-col justify-between h-fit">
<div>
<div class="flex items-center gap-2 mb-6">
<i data-lucide="user-plus" class="w-5 h-5 text-indigo-600"></i>
<h2 class="text-lg font-semibold text-slate-900">Formulir Kunjungan</h2>
</div>
<form id="form-guest" onsubmit="handleFormSubmit(event)" class="space-y-4">
<!-- Nama Lengkap -->
<div>
<label for="input-nama" class="block text-sm font-medium text-slate-700 mb-1">Nama Lengkap <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400">
<i data-lucide="user" class="w-4 h-4"></i>
</span>
<input
type="text"
id="input-nama"
required
placeholder="Contoh: John Doe"
class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm placeholder:text-slate-400 focus:outline-none transition-all"
>
</div>
</div>
<!-- Email -->
<div>
<label for="input-email" class="block text-sm font-medium text-slate-700 mb-1">Alamat Email <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400">
<i data-lucide="mail" class="w-4 h-4"></i>
</span>
<input
type="email"
id="input-email"
required
placeholder="Contoh: johndoe@email.com"
class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm placeholder:text-slate-400 focus:outline-none transition-all"
>
</div>
</div>
<!-- Instansi -->
<div>
<label for="input-instansi" class="block text-sm font-medium text-slate-700 mb-1">Instansi / Organisasi <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400">
<i data-lucide="building" class="w-4 h-4"></i>
</span>
<input
type="text"
id="input-instansi"
required
placeholder="Nama instansi atau 'Pribadi'"
class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm placeholder:text-slate-400 focus:outline-none transition-all"
>
</div>
</div>
<!-- Keperluan -->
<div>
<label for="input-keperluan" class="block text-sm font-medium text-slate-700 mb-1">Keperluan Kunjungan <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400">
<i data-lucide="clipboard-list" class="w-4 h-4"></i>
</span>
<select
id="input-keperluan"
required
class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm focus:outline-none transition-all bg-white"
>
<option value="" disabled selected>Pilih Keperluan...</option>
<option value="Pertemuan Bisnis / Rapat">Pertemuan Bisnis / Rapat</option>
<option value="Kunjungan Dinas / Koordinasi">Kunjungan Dinas / Koordinasi</option>
<option value="Pengiriman / Kurir">Pengiriman / Kurir</option>
<option value="Wawancara / Lamaran Kerja">Wawancara / Lamaran Kerja</option>
<option value="Lainnya">Lainnya (Tulis di pesan)</option>
</select>
</div>
</div>
<!-- Pesan / Kesan -->
<div>
<label for="input-pesan" class="block text-sm font-medium text-slate-700 mb-1">Pesan / Kesan / Keterangan Tambahan</label>
<textarea
id="input-pesan"
rows="3"
placeholder="Tulis pesan atau keterangan tambahan Anda di sini..."
class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm placeholder:text-slate-400 focus:outline-none transition-all"
></textarea>
</div>
<!-- Tombol Submit -->
<button
type="submit"
id="btn-submit"
class="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-lg text-sm shadow-sm hover:shadow transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<i data-lucide="send" class="w-4 h-4"></i>
<span id="btn-text-submit">Simpan Kehadiran</span>
</button>
</form>
</div>
</section>
<!-- Kolom Kanan: Live Log / Riwayat Kunjungan -->
<section class="lg:col-span-7 flex flex-col gap-6">
<!-- List Daftar Pengunjung Terbaru -->
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm flex flex-col flex-grow">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-2">
<i data-lucide="history" class="w-5 h-5 text-indigo-600"></i>
<h2 class="text-lg font-semibold text-slate-900">Daftar Pengunjung Terbaru</h2>
</div>
<button
onclick="loadGuestList()"
id="btn-refresh"
class="p-2 text-slate-500 hover:text-indigo-600 hover:bg-indigo-50 rounded-lg transition-colors duration-200"
title="Perbarui Data"
>
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
</button>
</div>
<!-- Bagian Tabel Responsif -->
<div class="overflow-x-auto border border-slate-100 rounded-xl max-h-[450px] overflow-y-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50 sticky top-0 z-10">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">No</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Waktu</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Nama & Email</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Instansi & Keperluan</th>
</tr>
</thead>
<tbody id="guest-table-body" class="bg-white divide-y divide-slate-100">
<!-- Data akan dimasukkan oleh Javascript -->
<tr>
<td colspan="4" class="px-6 py-10 text-center text-sm text-slate-400">
<div class="flex flex-col items-center justify-center gap-2">
<i data-lucide="loader-2" class="w-8 h-8 animate-spin text-indigo-500"></i>
<span>Memuat data pengunjung...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Info / Tips Section -->
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 flex items-start gap-3">
<i data-lucide="info" class="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5"></i>
<div>
<h3 class="text-sm font-semibold text-blue-900">Petunjuk Penggunaan Database</h3>
<p class="text-xs text-blue-700 mt-1">
Data yang Anda input di sini akan secara real-time tersimpan pada Google Spreadsheet Anda. Jika ini pertama kali Anda menggunakan aplikasi ini, silakan tekan tombol <strong>Setup Database</strong> di bagian kanan atas halaman.
</p>
</div>
</div>
</section>
</div>
</main>
<!-- Footer -->
<footer class="bg-white border-t border-slate-200 mt-12 py-4">
<div class="max-w-7xl mx-auto px-4 text-center text-xs text-slate-500">
© 2026 Aplikasi Buku Tamu Digital • Terintegrasi Google Apps Script & Google Sheets
</div>
</footer>
<!-- Javascript Logic -->
<script>
// Inisialisasi ikon Lucide
lucide.createIcons();
// Jalankan pemuatan data saat halaman pertama kali dibuka
window.onload = function() {
loadGuestList();
};
/**
* Memunculkan notifikasi kustom (Toast)
*/
function showToast(message, type = 'success') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
const bgColor = type === 'success' ? 'bg-emerald-500' : 'bg-rose-500';
const icon = type === 'success' ? 'check-circle' : 'alert-circle';
toast.className = `flex items-center gap-3 px-4 py-3 text-white rounded-xl shadow-lg transform translate-y-2 opacity-0 transition-all duration-300 ${bgColor}`;
toast.innerHTML = `
<i data-lucide="${icon}" class="w-5 h-5 flex-shrink-0"></i>
<div class="text-sm font-medium">${message}</div>
`;
container.appendChild(toast);
lucide.createIcons();
// Animasi masuk
setTimeout(() => {
toast.classList.remove('translate-y-2', 'opacity-0');
}, 50);
// Penghapusan otomatis setelah 4 detik
setTimeout(() => {
toast.classList.add('translate-y-2', 'opacity-0');
setTimeout(() => toast.remove(), 300);
}, 4000);
}
/**
* Menjalankan Inisialisasi Database Spreadsheet via Web
*/
function triggerSetupDatabase() {
const btn = document.getElementById('btn-quick-setup');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Sedang Memproses...</span>`;
lucide.createIcons();
google.script.run
.withSuccessHandler(function(response) {
btn.disabled = false;
btn.innerHTML = originalText;
lucide.createIcons();
if (response.success) {
showToast(response.message, 'success');
loadGuestList(); // Muat ulang list setelah setup berhasil
} else {
showToast(response.message, 'error');
}
})
.withFailureHandler(function(error) {
btn.disabled = false;
btn.innerHTML = originalText;
lucide.createIcons();
showToast("Koneksi gagal: " + error.toString(), 'error');
})
.setupDatabase();
}
/**
* Memuat dan memperbarui daftar pengunjung dari Google Sheets
*/
function loadGuestList() {
const tableBody = document.getElementById('guest-table-body');
const btnRefresh = document.getElementById('btn-refresh');
if(btnRefresh) {
btnRefresh.classList.add('animate-spin');
}
google.script.run
.withSuccessHandler(function(data) {
if (btnRefresh) btnRefresh.classList.remove('animate-spin');
if (!data || data.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-10 text-center text-sm text-slate-400">
<div class="flex flex-col items-center justify-center gap-1">
<i data-lucide="inbox" class="w-8 h-8 text-slate-300"></i>
<span>Belum ada data pengunjung yang terdaftar.</span>
<span class="text-xs text-slate-400">Cobalah untuk mengisi Formulir Kunjungan di sebelah kiri.</span>
</div>
</td>
</tr>
`;
lucide.createIcons();
return;
}
tableBody.innerHTML = '';
data.forEach(guest => {
const row = document.createElement('tr');
row.className = 'hover:bg-slate-50 transition-colors';
// Format tampilan tanggal
const dateStr = guest.tanggal ? guest.tanggal.toString() : '-';
row.innerHTML = `
<td class="px-4 py-3 whitespace-nowrap text-sm font-semibold text-slate-500">${guest.no}</td>
<td class="px-4 py-3 whitespace-nowrap text-xs text-slate-500">${dateStr}</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="text-sm font-semibold text-slate-900">${guest.nama}</div>
<div class="text-xs text-slate-400">${guest.email}</div>
</td>
<td class="px-4 py-3">
<div class="text-sm text-slate-800 font-medium">${guest.instansi}</div>
<div class="inline-flex mt-1 items-center px-2 py-0.5 rounded text-xs font-medium bg-indigo-50 text-indigo-700">
${guest.keperluan}
</div>
</td>
`;
tableBody.appendChild(row);
});
lucide.createIcons();
})
.withFailureHandler(function(error) {
if (btnRefresh) btnRefresh.classList.remove('animate-spin');
let errMsg = error.toString();
if (errMsg.includes("DATABASE_NOT_FOUND")) {
tableBody.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-8 text-center text-sm text-amber-600 bg-amber-50 rounded-xl">
<div class="flex flex-col items-center justify-center gap-2">
<i data-lucide="database" class="w-8 h-8 text-amber-500"></i>
<span class="font-semibold">Database Belum Siap</span>
<span class="text-xs text-amber-700 max-w-sm mx-auto leading-relaxed">
Sheet "Buku Tamu" tidak ditemukan. Klik tombol <strong>Setup Database</strong> di kanan atas untuk menginisialisasi spreadsheet Anda.
</span>
</div>
</td>
</tr>
`;
} else {
tableBody.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-8 text-center text-sm text-rose-500 bg-rose-50 rounded-xl">
<div class="flex flex-col items-center justify-center gap-2">
<i data-lucide="alert-triangle" class="w-8 h-8 text-rose-500"></i>
<span class="font-semibold">Gagal Memuat Data</span>
<span class="text-xs text-rose-700">${errMsg}</span>
</div>
</td>
</tr>
`;
}
lucide.createIcons();
})
.getGuests();
}
/**
* Mengatur pengiriman formulir pengunjung
*/
function handleFormSubmit(event) {
event.preventDefault();
const btnSubmit = document.getElementById('btn-submit');
const btnText = document.getElementById('btn-text-submit');
const form = document.getElementById('form-guest');
const guestData = {
nama: document.getElementById('input-nama').value,
email: document.getElementById('input-email').value,
instansi: document.getElementById('input-instansi').value,
keperluan: document.getElementById('input-keperluan').value,
pesan: document.getElementById('input-pesan').value
};
// Efek Loading pada tombol submit
btnSubmit.disabled = true;
const originalText = btnText.innerText;
btnText.innerText = "Mengirim...";
google.script.run
.withSuccessHandler(function(response) {
btnSubmit.disabled = false;
btnText.innerText = originalText;
if (response.success) {
showToast(response.message, 'success');
form.reset(); // Kosongkan form kembali setelah berhasil
loadGuestList(); // Update daftar list terbaru
} else {
showToast(response.message, 'error');
}
})
.withFailureHandler(function(error) {
btnSubmit.disabled = false;
btnText.innerText = originalText;
showToast("Pengiriman gagal: " + error.toString(), 'error');
})
.submitGuestData(guestData);
}
</script>
</body>
</html>