Rabu, 08 April 2026

Gemini - Aplikasi Travel 2

 



PROMPT

Membangun Aplikasi Landing Page Travel (SPA) dengan Google Apps Script

Peran: Bertindaklah sebagai Senior Full-stack Web Developer.

Tujuan: Buatlah sebuah aplikasi web satu halaman (Single Page Application - SPA) untuk layanan "Info Travel" khusus destinasi Labuan Bajo. Aplikasi ini harus mengintegrasikan formulir Lead Generation dengan Google Sheets sebagai database melalui Google Apps Script.

1. Struktur Backend (Code.gs)

Buatlah logika server-side yang mencakup:

  • doGet(): Fungsi untuk menyajikan file index.html.

  • setupDatabase(): Fungsi inisialisasi untuk membuat header kolom (Nama, Email, WhatsApp, Timestamp) di spreadsheet jika masih kosong.

  • saveLead(formData): Fungsi untuk menyimpan isian formulir ke baris baru.

  • getLeads(): Fungsi untuk mengambil data dari spreadsheet. Penting: Konversi semua objek Date menjadi string ISO (toISOString) agar data bisa dikirim ke frontend tanpa error serialisasi.

2. Struktur Frontend (index.html)

Gunakan Tailwind CSS untuk gaya dan bangun antarmuka dengan struktur SPA (dua bagian utama: Home dan Dashboard Admin) dengan strategi Mobile-First:

A. Bagian Home (Landing Page):

  • Tujuan Utama (CTA): Lead Generation. Fokuskan formulir untuk mendapatkan brosur harga dan jadwal perjalanan.

  • Hero Section:

    • Headline: "Jelajahi Surga Tersembunyi Labuan Bajo Tanpa Ribet Logistik."

    • Sub-headline: "Nikmati pengalaman Live on Board premium dengan rute terbaik, fotografer profesional, dan layanan bintang lima dalam satu paket perjalanan."

    • Visual: Gunakan latar belakang foto cinematic kapal Phinisi yang sedang berlayar di perairan jernih Pulau Padar saat matahari terbit.

    • Lead Form: Pada tampilan mobile, pastikan formulir berada tepat setelah headline. Gunakan input: Nama, Email, WhatsApp.

  • Social Proof: Tambahkan elemen statistik "Telah dipercaya oleh 2.500+ Traveler sejak 2021" disertai 3 testimoni singkat yang dilengkapi foto wajah pelanggan.

  • Features & Benefits:

    1. All-in Service: Makan, penginapan, dan alat snorkeling sudah siap. (Benefit: Anda cukup bawa koper).

    2. Professional Documentation: Drone dan kamera mirrorless di setiap trip. (Benefit: Feed Instagram Anda akan luar biasa).

  • CTA Button: Gunakan tombol besar berwarna Oranye Terang (#fb8500) dengan teks "Cek Ketersediaan Slot".

B. Bagian Dashboard Admin:

  • Tabel data responsif untuk menampilkan: Waktu Masuk, Nama, Email, dan WhatsApp (dengan link otomatis ke wa.me).

  • Tombol "Refresh Data" yang memicu pengambilan data terbaru.

3. Strategi Desain & Responsif

  • Warna: Gunakan palet Ocean Blue (#0077b6) dan Sunset Orange (#fb8500) untuk menciptakan kontras yang menarik.

  • Typography: Gunakan font minimal 16px untuk memastikan keterbacaan pada perangkat mobile.

  • Whitespace: Terapkan jarak antar seksi (margin) sebesar 80px agar elemen penting lebih menonjol dan nyaman dipandang.

4. Logika Komunikasi Data

  • Gunakan google.script.run untuk mengirim data dari formulir ke fungsi saveLead.

  • Gunakan google.script.run dengan withSuccessHandler untuk mengambil data melalui getLeads dan menampilkannya di tabel dashboard.

  • Implementasikan navigasi SPA sederhana berbasis JavaScript (fungsi MapsTo) untuk berpindah antar seksi tanpa reload halaman.

5. Fitur Tambahan & UX

  • Tambahkan Loading Spinner saat data sedang dikirim atau diambil.

  • Implementasikan Modal Success setelah formulir berhasil dikirim.

6. Output yang Diharapkan

Berikan dua file terpisah:

  1. Code.gs: Kode backend Google Apps Script.

  2. index.html: Kode frontend lengkap (HTML, Tailwind CSS, JS).


PERBAIKAN

Backend (Code.gs):

    • Filtering: Menambahkan filter pada getLeads() untuk mengabaikan baris kosong yang sering muncul jika Spreadsheet pernah dihapus isinya secara manual.

    • Robust Date Handling: Menambahkan blok try-catch saat mengonversi timestamp ke ISOString. Google Apps Script sering mengalami error saat mencoba mengirim objek Date murni melalui google.script.run, jadi konversi ke string ISO adalah cara paling aman.

    • Error Prevention: Menggunakan String() pada setiap kolom untuk memastikan data yang dikirim ke frontend selalu bertipe string, menghindari error serialisasi.

  • Frontend (index.html):

    • Data Validation: Menambahkan pengecekan lead.timestamp sebelum merendernya untuk menghindari tampilan "Invalid Date".

    • UI Feedback: Memperbaiki pesan "Sedang memuat data" agar pengguna tahu aplikasi sedang bekerja saat berpindah ke Dashboard Admin di Canvas.

    • Clean WA Link: Logika pembersihan nomor WhatsApp diperkuat agar tetap valid meskipun pengguna memasukkan karakter non-angka.


  • Code.gs

    /**
     * Fungsi utama untuk menyajikan file index.html ke browser.
     */
    function doGet() {
      return HtmlService.createTemplateFromFile('index')
        .evaluate()
        .setTitle('Travel Labuan Bajo - Explore the Paradise')
        .addMetaTag('viewport', 'width=device-width, initial-scale=1.0')
        .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
    }

    /**
     * Inisialisasi Database: Membuat header jika sheet masih kosong.
     */
    function setupDatabase() {
      const ss = SpreadsheetApp.getActive();
      const sheet = ss.getSheets()[0];
     
      if (sheet.getLastRow() === 0) {
        sheet.appendRow(['Timestamp', 'Nama', 'Email', 'WhatsApp']);
        sheet.getRange(1, 1, 1, 4)
          .setFontWeight('bold')
          .setBackground('#0077b6')
          .setFontColor('white');
      }
    }

    /**
     * Menyimpan data formulir ke Google Sheets.
     */
    function saveLead(formData) {
      try {
        setupDatabase();
        const ss = SpreadsheetApp.getActive();
        const sheet = ss.getSheets()[0];
       
        sheet.appendRow([
          new Date(),
          formData.nama,
          formData.email,
          formData.whatsapp
        ]);
       
        return { success: true, message: 'Data berhasil disimpan!' };
      } catch (error) {
        return { success: false, message: error.toString() };
      }
    }

    /**
     * Perbaikan: Mengambil data leads dengan pembersihan baris kosong
     * dan penanganan serialisasi objek Date yang lebih aman.
     */
    function getLeads() {
      try {
        const ss = SpreadsheetApp.getActive();
        const sheet = ss.getSheets()[0];
        const lastRow = sheet.getLastRow();
       
        // Jika hanya ada header atau kosong, kembalikan array kosong
        if (lastRow <= 1) return [];
       
        // Ambil data mulai dari baris ke-2 (menghindari header)
        const values = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
       
        const leads = values
          .filter(row => row[1] || row[2] || row[3]) // Filter hanya baris yang berisi data
          .map(row => {
            let ts = row[0];
            let isoDate = "";

            // Pastikan konversi ke ISO string aman
            try {
              if (ts instanceof Date) {
                isoDate = ts.toISOString();
              } else if (ts) {
                isoDate = new Date(ts).toISOString();
              } else {
                isoDate = new Date().toISOString();
              }
            } catch (e) {
              isoDate = new Date().toISOString();
            }
           
            return {
              timestamp: isoDate,
              nama: String(row[1] || ""),
              email: String(row[2] || ""),
              whatsapp: String(row[3] || "")
            };
          });
       
        // Balik urutan agar data terbaru ada di atas
        return leads.reverse();
       
      } catch (error) {
        console.error("Gagal mengambil data: " + error.toString());
        // Kembalikan array kosong agar frontend tidak error saat parsing
        return [];
      }
    }

    index.html

    <!DOCTYPE html>
    <html lang="id">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Info Travel Labuan Bajo</title>
      <script src="https://cdn.tailwindcss.com"></script>
      <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
      <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; scroll-behavior: smooth; }
        .bg-ocean { background-color: #0077b6; }
        .bg-sunset { background-color: #fb8500; }
        .text-ocean { color: #0077b6; }
        .text-sunset { color: #fb8500; }
        .section-gap { margin-top: 80px; margin-bottom: 80px; }
       
        /* Hero Image Overlay */
        .hero-overlay {
          background: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.7)),
                      url('https://images.unsplash.com/photo-1516690561799-46d8f74f9abf?ixlib=rb-4.0.3&auto=format&fit=crop&w=1920&q=80');
          background-size: cover;
          background-position: center;
        }

        /* Loading Spinner */
        .loader {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #fb8500;
          border-radius: 50%;
          width: 30px;
          height: 30px;
          animation: spin 1s linear infinite;
        }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
       
        .hidden { display: none; }
      </style>
    </head>
    <body class="bg-gray-50 text-gray-900 overflow-x-hidden">

      <!-- Loading Overlay -->
      <div id="loadingOverlay" class="fixed inset-0 bg-black bg-opacity-50 z-[100] flex items-center justify-center hidden">
        <div class="bg-white p-6 rounded-xl flex flex-col items-center">
          <div class="loader mb-4"></div>
          <p class="font-semibold">Memproses...</p>
        </div>
      </div>

      <!-- Modal Success -->
      <div id="successModal" class="fixed inset-0 bg-black bg-opacity-50 z-[110] flex items-center justify-center hidden">
        <div class="bg-white p-8 rounded-2xl max-w-sm w-full mx-4 text-center shadow-2xl">
          <div class="w-20 h-20 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
            </svg>
          </div>
          <h3 class="text-2xl font-bold mb-2">Berhasil!</h3>
          <p class="text-gray-600 mb-6">Brosur dan jadwal perjalanan telah dikirim ke WhatsApp/Email Anda.</p>
          <button onclick="closeModal()" class="w-full py-3 bg-ocean text-white rounded-lg font-bold">Tutup</button>
        </div>
      </div>

      <!-- Navigation -->
      <nav class="sticky top-0 z-50 bg-white shadow-sm py-4 px-6 flex justify-between items-center">
        <div class="flex items-center gap-2 cursor-pointer" onclick="navigateTo('home')">
          <div class="w-10 h-10 bg-ocean rounded-lg flex items-center justify-center text-white">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>
          </div>
          <span class="text-xl font-extrabold tracking-tight text-ocean">LABUAN BAJO</span>
        </div>
        <button onclick="navigateTo('admin')" class="text-sm font-semibold text-gray-500 hover:text-ocean flex items-center gap-1">
          Admin <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4" /></svg>
        </button>
      </nav>

      <!-- HOME VIEW -->
      <main id="home-view" class="block">
        <!-- Hero Section -->
        <section class="relative min-h-[90vh] flex flex-col justify-center hero-overlay text-white px-6">
          <div class="max-w-6xl mx-auto grid lg:grid-cols-2 gap-12 items-center">
            <div>
              <h1 class="text-4xl md:text-6xl font-extrabold leading-tight mb-6">
                Jelajahi Surga Tersembunyi <span class="text-sunset">Labuan Bajo</span> Tanpa Ribet Logistik.
              </h1>
              <p class="text-lg md:text-xl text-gray-200 mb-8 max-w-lg">
                Nikmati pengalaman Live on Board premium dengan rute terbaik, fotografer profesional, dan layanan bintang lima dalam satu paket perjalanan.
              </p>
              <div class="hidden lg:flex items-center gap-4 text-sm font-medium">
                <div class="flex -space-x-3">
                  <img class="w-10 h-10 rounded-full border-2 border-white" src="https://i.pravatar.cc/100?u=1" alt="User">
                  <img class="w-10 h-10 rounded-full border-2 border-white" src="https://i.pravatar.cc/100?u=2" alt="User">
                  <img class="w-10 h-10 rounded-full border-2 border-white" src="https://i.pravatar.cc/100?u=3" alt="User">
                </div>
                <span>Telah dipercaya oleh 2.500+ Traveler</span>
              </div>
            </div>

            <!-- Lead Form -->
            <div id="leadFormContainer" class="bg-white rounded-3xl p-8 shadow-2xl text-gray-900 lg:ml-auto max-w-md w-full">
              <h2 class="text-2xl font-bold mb-2">Dapatkan Brosur Gratis</h2>
              <p class="text-gray-500 mb-6 text-sm">Isi data untuk cek ketersediaan slot dan promo bulan ini.</p>
             
              <form id="leadForm" onsubmit="handleFormSubmit(event)" class="space-y-4">
                <div>
                  <label class="block text-sm font-bold mb-1 ml-1">Nama Lengkap</label>
                  <input type="text" name="nama" required class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-sunset focus:ring-2 focus:ring-sunset outline-none transition-all" placeholder="Masukkan nama Anda">
                </div>
                <div>
                  <label class="block text-sm font-bold mb-1 ml-1">Email Aktif</label>
                  <input type="email" name="email" required class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-sunset focus:ring-2 focus:ring-sunset outline-none transition-all" placeholder="nama@email.com">
                </div>
                <div>
                  <label class="block text-sm font-bold mb-1 ml-1">Nomor WhatsApp</label>
                  <input type="tel" name="whatsapp" required class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-sunset focus:ring-2 focus:ring-sunset outline-none transition-all" placeholder="0812xxxxxxx">
                </div>
                <button type="submit" class="w-full bg-sunset hover:bg-orange-600 text-white font-extrabold py-4 rounded-xl shadow-lg shadow-orange-200 transition-all transform hover:-translate-y-1 active:scale-95 text-lg">
                  Cek Ketersediaan Slot
                </button>
                <p class="text-[10px] text-center text-gray-400 mt-4">Data Anda aman bersama kami. Kami tidak menyukai spam.</p>
              </form>
            </div>
          </div>
        </section>

        <!-- Stats & Social Proof (Mobile) -->
        <section class="lg:hidden bg-ocean text-white py-12 px-6 text-center">
           <h3 class="text-3xl font-bold mb-2">2.500+</h3>
           <p class="text-blue-100">Traveler puas sejak 2021</p>
        </section>

        <!-- Features Section -->
        <section class="max-w-6xl mx-auto px-6 section-gap">
          <div class="text-center mb-16">
            <span class="text-sunset font-bold tracking-widest uppercase text-sm">Fasilitas Premium</span>
            <h2 class="text-3xl md:text-4xl font-extrabold mt-2">Mengapa Memilih Kami?</h2>
          </div>
         
          <div class="grid md:grid-cols-2 gap-12">
            <div class="flex gap-6">
              <div class="flex-shrink-0 w-16 h-16 bg-blue-100 rounded-2xl flex items-center justify-center text-ocean">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>
              </div>
              <div>
                <h4 class="text-xl font-bold mb-2">All-in Service</h4>
                <p class="text-gray-600">Makan 3x sehari, penginapan kapal VIP, dan alat snorkeling sudah siap. <strong>Benefit:</strong> Anda cukup bawa koper, kami urus sisanya.</p>
              </div>
            </div>
            <div class="flex gap-6">
              <div class="flex-shrink-0 w-16 h-16 bg-blue-100 rounded-2xl flex items-center justify-center text-ocean">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
              </div>
              <div>
                <h4 class="text-xl font-bold mb-2">Professional Documentation</h4>
                <p class="text-gray-600">Tim kreatif kami membawa Drone dan kamera mirrorless di setiap trip. <strong>Benefit:</strong> Feed Instagram Anda akan luar biasa tanpa repot selfie.</p>
              </div>
            </div>
          </div>
        </section>

        <!-- Testimonials -->
        <section class="bg-blue-50 py-20 px-6">
          <div class="max-w-6xl mx-auto">
            <div class="grid md:grid-cols-3 gap-8">
              <!-- Testi 1 -->
              <div class="bg-white p-8 rounded-2xl shadow-sm italic">
                <p class="mb-6">"Gak nyesel pilih paket Live on Board ini. Makanannya enak banget, kru kapalnya ramah, dan fotonya juara!"</p>
                <div class="flex items-center gap-3">
                  <img src="https://i.pravatar.cc/100?u=9" class="w-10 h-10 rounded-full" alt="">
                  <div><h5 class="font-bold not-italic">Andini Putri</h5><span class="text-xs text-gray-400 not-italic">Solo Traveler</span></div>
                </div>
              </div>
              <!-- Testi 2 -->
              <div class="bg-white p-8 rounded-2xl shadow-sm italic">
                <p class="mb-6">"Rute perjalanannya efisien banget, gak kerasa capek karena fasilitas kapalnya nyaman banget kayak hotel."</p>
                <div class="flex items-center gap-3">
                  <img src="https://i.pravatar.cc/100?u=12" class="w-10 h-10 rounded-full" alt="">
                  <div><h5 class="font-bold not-italic">Budi Santoso</h5><span class="text-xs text-gray-400 not-italic">Family Trip</span></div>
                </div>
              </div>
              <!-- Testi 3 -->
              <div class="bg-white p-8 rounded-2xl shadow-sm italic">
                <p class="mb-6">"Service bintang lima harga terjangkau. Dokumentasi drone-nya keren parah buat konten sosmed!"</p>
                <div class="flex items-center gap-3">
                  <img src="https://i.pravatar.cc/100?u=15" class="w-10 h-10 rounded-full" alt="">
                  <div><h5 class="font-bold not-italic">Siska Amelia</h5><span class="text-xs text-gray-400 not-italic">Backpacker</span></div>
                </div>
              </div>
            </div>
          </div>
        </section>

        <!-- Footer -->
        <footer class="bg-gray-900 text-white py-12 px-6">
          <div class="max-w-6xl mx-auto text-center">
            <p class="text-gray-400">&copy; 2024 Labuan Bajo Travel. All rights reserved.</p>
          </div>
        </footer>
      </main>

      <!-- ADMIN VIEW -->
      <main id="admin-view" class="hidden max-w-6xl mx-auto px-6 py-12">
        <div class="flex justify-between items-center mb-8">
          <div>
            <h2 class="text-3xl font-extrabold text-ocean">Dashboard Lead</h2>
            <p class="text-gray-500">Daftar calon traveler yang tertarik.</p>
          </div>
          <button onclick="refreshData()" class="flex items-center gap-2 bg-ocean text-white px-5 py-2 rounded-lg font-bold hover:bg-blue-700 transition-colors">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
            Refresh Data
          </button>
        </div>

        <div class="bg-white rounded-2xl shadow-sm overflow-hidden border border-gray-100">
          <div class="overflow-x-auto">
            <table class="w-full text-left border-collapse">
              <thead>
                <tr class="bg-gray-50 text-gray-500 text-sm uppercase">
                  <th class="px-6 py-4 font-bold">Waktu Masuk</th>
                  <th class="px-6 py-4 font-bold">Nama</th>
                  <th class="px-6 py-4 font-bold">Email</th>
                  <th class="px-6 py-4 font-bold">WhatsApp</th>
                </tr>
              </thead>
              <tbody id="leadsTableBody" class="divide-y divide-gray-100">
                <!-- Data akan dimuat di sini -->
              </tbody>
            </table>
          </div>
          <div id="noDataMessage" class="p-12 text-center text-gray-400 hidden">
            Belum ada data masuk.
          </div>
        </div>
      </main>

      <script>
        // SPA Navigation Logic
        function navigateTo(page) {
          const home = document.getElementById('home-view');
          const admin = document.getElementById('admin-view');
         
          if (page === 'home') {
            home.classList.remove('hidden');
            admin.classList.add('hidden');
            window.scrollTo(0, 0);
          } else {
            home.classList.add('hidden');
            admin.classList.remove('hidden');
            refreshData();
          }
        }

        // Modal & Overlay Helpers
        function showLoading(show) {
          document.getElementById('loadingOverlay').classList.toggle('hidden', !show);
        }
       
        function showModal() {
          document.getElementById('successModal').classList.remove('hidden');
        }

        function closeModal() {
          document.getElementById('successModal').classList.add('hidden');
        }

        // Form Submission
        function handleFormSubmit(event) {
          event.preventDefault();
          const form = event.target;
          const formData = {
            nama: form.nama.value,
            email: form.email.value,
            whatsapp: form.whatsapp.value
          };

          showLoading(true);

          google.script.run
            .withSuccessHandler((response) => {
              showLoading(false);
              if (response.success) {
                form.reset();
                showModal();
              } else {
                alert('Error: ' + response.message);
              }
            })
            .withFailureHandler((error) => {
              showLoading(false);
              alert('System Error: ' + error);
            })
            .saveLead(formData);
        }

        // Fetch Data for Admin
        function refreshData() {
          const tbody = document.getElementById('leadsTableBody');
          const emptyMsg = document.getElementById('noDataMessage');
          tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-12 text-center text-gray-400 italic">Sedang mengambil data...</td></tr>';
         
          google.script.run
            .withSuccessHandler((leads) => {
              tbody.innerHTML = '';
              if (!leads || leads.length === 0) {
                emptyMsg.classList.remove('hidden');
                return;
              }
             
              emptyMsg.classList.add('hidden');
              leads.forEach(lead => {
                const date = new Date(lead.timestamp).toLocaleString('id-ID', {
                  day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit'
                });
               
                const waClean = lead.whatsapp.replace(/\D/g, '');
                const waLink = waClean.startsWith('0') ? '62' + waClean.substring(1) : waClean;

                const tr = document.createElement('tr');
                tr.className = 'hover:bg-blue-50 transition-colors';
                tr.innerHTML = `
                  <td class="px-6 py-4 text-sm text-gray-500">${date}</td>
                  <td class="px-6 py-4 font-bold text-gray-800">${lead.nama}</td>
                  <td class="px-6 py-4 text-gray-600">${lead.email}</td>
                  <td class="px-6 py-4">
                    <a href="https://wa.me/${waLink}" target="_blank" class="flex items-center gap-2 text-green-600 font-semibold hover:underline">
                      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24"><path d="M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.588-5.946 0-6.556 5.332-11.888 11.888-11.888 3.176 0 6.161 1.237 8.404 3.484 2.247 2.247 3.484 5.232 3.484 8.404 0 6.556-5.332 11.888-11.888 11.888-2.015 0-3.986-.511-5.741-1.483l-6.246 1.704zm6.103-4.494l.439.261c1.554.921 3.346 1.408 5.185 1.408 5.404 0 9.801-4.397 9.801-9.801 0-2.618-1.02-5.079-2.872-6.932-1.851-1.852-4.312-2.872-6.93-2.872-5.404 0-9.801 4.398-9.801 9.801 0 1.957.584 3.869 1.687 5.503l.287.427-1.114 4.07 4.162-1.135zm11.536-5.372c-.287-.144-1.702-.841-1.965-.937-.262-.096-.454-.144-.646.144-.192.288-.743.937-.91 1.129-.167.192-.335.216-.622.072-.287-.144-1.21-.447-2.305-1.424-.852-.759-1.426-1.702-1.594-1.99-.168-.287-.018-.443.126-.586.13-.13.287-.335.431-.504.144-.168.192-.288.287-.48.096-.192.048-.36-.024-.504-.072-.144-.646-1.56-.884-2.136-.231-.558-.467-.48-.646-.489-.167-.008-.359-.01-.55-.01s-.503.072-.767.359c-.263.287-1.006.984-1.006 2.399 0 1.416 1.03 2.783 1.174 2.975.144.192 2.027 3.096 4.91 4.341.686.296 1.222.473 1.64.606.689.219 1.317.189 1.812.115.553-.083 1.702-.696 1.942-1.368.239-.672.239-1.248.167-1.368-.071-.12-.263-.192-.55-.336z"/></svg>
                      Hubungi
                    </a>
                  </td>
                `;
                tbody.appendChild(tr);
              });
            })
            .withFailureHandler((error) => {
              tbody.innerHTML = `<tr><td colspan="4" class="px-6 py-12 text-center text-red-500">Error: ${error}</td></tr>`;
            })
            .getLeads();
        }
      </script>
    </body>
    </html>

    Tidak ada komentar:

    Posting Komentar

    Web dengan Firebase

      https://www.youtube.com/watch?v=_KgCFwWTORI&list=PLJTyZKho7eUhpYMoKmYq9aeU20dRiWVes https://www.youtube.com/watch?v=2CPE5yKzMqE