Cara Membuat Booking Padel dengan Google Apps Script + Google Sheets Gratis

aplikasi booking padel

Cara Membuat Booking Padel dengan Google Script Apps Gratis

Cara membuat booking padel online gratis tanpa aplikasi berbayar kini menjadi solusi favorit bagi pengelola lapangan dan komunitas olahraga. Dengan Google Script Apps, sistem booking padel dapat berjalan otomatis, real-time, dan terintegrasi langsung dengan Google Sheets.

Menariknya, booking padel dengan Google Script Apps gratis tidak membutuhkan kemampuan coding tingkat lanjut. Cukup bermodal akun Google, Anda sudah bisa membuat sistem reservasi yang rapi, profesional, dan mudah digunakan oleh pelanggan. Anda juga bisa membuat Absensi menggunakan GPS agar lebih aman dan flexible.

Kenapa Booking Padel Menggunakan Google Script Apps?

  • Gratis tanpa biaya langganan bulanan
  • Mudah dikustomisasi sesuai jam operasional lapangan
  • Data booking tersimpan otomatis di Google Sheets
  • Aman karena berbasis cloud Google

Alur Sistem Booking Padel

Sistem booking padel berbasis Google Script Apps bekerja dengan alur sederhana: user mengisi form, data masuk ke spreadsheet, script memvalidasi jadwal, lalu sistem mengirimkan konfirmasi booking secara otomatis.

Langkah-Langkah Membuat Booking Padel Gratis

1. Membuat Google Form Booking

Google Form digunakan sebagai tampilan utama untuk pelanggan. Field penting meliputi nama, nomor WhatsApp, tanggal main, jam bermain, dan pilihan lapangan.

2. Menghubungkan Form ke Google Sheets

Setiap data yang masuk dari Google Form akan otomatis tersimpan ke Google Sheets dan berfungsi sebagai database booking.

$ads={1}

3. Menambahkan Google Apps Script

Dari Google Sheets, buka menu Extensions lalu Apps Script untuk menambahkan logika pengecekan jadwal dan validasi booking.

4. Mengatur Notifikasi Otomatis

Sistem dapat mengirim email konfirmasi atau notifikasi WhatsApp agar pelanggan mengetahui status booking mereka.

Code Booking Padel

Code.gs

/* ===================== CONFIG ===================== */
const SPREADSHEET_ID = '1iT0d6a1Mf-tGquAkUefV7Akr4cLu5ZkPiDNoHRFBidQ';

const SHEET_MASTER   = 'MasterLapangan';
const SHEET_MENU     = 'Menu';
const SHEET_BOOKINGS = 'Bookings';

const OPEN_TIME = 8 * 60;
const CLOSE_TIME = 23 * 60;
const SLOT_MINUTES = 60;

const PENDING_EXPIRE_MINUTES = 30;
const TEMP_LOCK_MINUTES = 5;

/* ===================== CORE ===================== */
function ss(){ return SpreadsheetApp.openById(SPREADSHEET_ID); }
function sh(n){ return ss().getSheetByName(n); }

function toMin(t){
  const [h,m]=t.split(':').map(Number);
  return h*60+m;
}
function toTime(m){
  return String(Math.floor(m/60)).padStart(2,'0')+':'+String(m%60).padStart(2,'0');
}

/* ===================== MASTER ===================== */
function getMasterLapangan(){
  return sh(SHEET_MASTER).getDataRange().getValues().slice(1)
    .map(r=>({id:r[0],nama:r[1],harga:r[2]}));
}
function getMenuList(){
  return sh(SHEET_MENU).getDataRange().getValues().slice(1)
    .map(r=>({id:r[0],nama:r[1],harga:r[2]}));
}

/* ===================== BOOKING DATA ===================== */
function getAllBookings(){
  return sh(SHEET_BOOKINGS).getDataRange().getValues().slice(1).map(r=>({
    booking_id:r[0],
    created:r[1],
    nama:r[2],
    email:r[3],
    telepon:r[4],
    tanggal:r[5],
    jam_mulai:r[6],
    durasi:r[7],
    lapangan_id:r[8],
    total_menu:r[9],
    total_lap:r[10],
    total:r[11],
    status:r[12],
    payment_status:r[15],
    checkin_status:r[17]
  }));
}

/* ===================== AUTO RELEASE ===================== */
function autoReleaseExpiredBookings(){
  const sheet=sh(SHEET_BOOKINGS);
  const data=sheet.getDataRange().getValues();
  const now=new Date();

  for(let i=1;i<data.length;i++){
    if(data[i][12]=='pending'){
      const diff=(now-new Date(data[i][1]))/60000;
      if(diff>PENDING_EXPIRE_MINUTES){
        sheet.getRange(i+1,13).setValue('expired');
        sheet.getRange(i+1,16).setValue('expired');
      }
    }
  }
}

/* ===================== AVAILABILITY ===================== */
function getHourlyAvailability(lapanganId,tanggal){
  autoReleaseExpiredBookings();

  const bookings=getAllBookings().filter(b=>
    b.lapangan_id==lapanganId &&
    b.tanggal==tanggal &&
    ['pending','approved'].includes(b.status)
  );

  let result=[];
  for(let t=OPEN_TIME;t<CLOSE_TIME;t+=SLOT_MINUTES){
    let available=true;
    bookings.forEach(b=>{
      const s=toMin(b.jam_mulai);
      const e=s+(b.durasi*60);
      if(t<e && (t+SLOT_MINUTES)>s) available=false;
    });
    result.push({time:toTime(t),available});
  }
  return result;
}

/* ===================== BOOKING STEP 1 ===================== */
function createBookingStep1(d){
  autoReleaseExpiredBookings();

  const slot=getHourlyAvailability(d.lapangan_id,d.tanggal)
    .find(s=>s.time==d.jam_mulai);

  if(!slot||!slot.available){
    return {success:false,message:'Lapangan tidak tersedia'};
  }

  const lap=getMasterLapangan().find(l=>l.id==d.lapangan_id);
  const totalLap=lap.harga*d.durasi_jam;
  const id='B'+Date.now();

  sh(SHEET_BOOKINGS).appendRow([
    id,new Date(),d.nama,d.email,d.telepon,
    d.tanggal,d.jam_mulai,d.durasi_jam,d.lapangan_id,
    0,totalLap,totalLap,'pending','LOCK',
    '', 'unpaid','', 'not_checked',''
  ]);

  return {success:true,bookingId:id};
}

function setPaymentMethod(bookingId, method) {
  const sheet = sh(SHEET_BOOKINGS);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i < data.length; i++) {
    if (data[i][0] == bookingId) {
      sheet.getRange(i + 1, 15).setValue(method);        // payment_method
      sheet.getRange(i + 1, 16).setValue('waiting');     // payment_status
      sheet.getRange(i + 1, 14).setValue('Menunggu konfirmasi admin'); // note
      return true;
    }
  }
  return false;
}

/* ===================== MENU ORDER ===================== */
function addMenuOrder(bookingId,items){
  let total=0;
  items.forEach(i=>total+=i.qty*i.harga);

  const sheet=sh(SHEET_BOOKINGS);
  const data=sheet.getDataRange().getValues();

  for(let i=1;i<data.length;i++){
    if(data[i][0]==bookingId){
      sheet.getRange(i+1,10).setValue(total);
      sheet.getRange(i+1,12).setValue(data[i][10]+total);
    }
  }
}

/* ===================== PAYMENT ===================== */
function adminConfirmPayment(bookingId,method='QRIS'){
  const sheet=sh(SHEET_BOOKINGS);
  const data=sheet.getDataRange().getValues();

  for(let i=1;i<data.length;i++){
    if(data[i][0]==bookingId){
      sheet.getRange(i+1,13).setValue('approved');
      sheet.getRange(i+1,15).setValue(method);
      sheet.getRange(i+1,16).setValue('paid');
      sendBookingEmail(bookingId);
    }
  }
}

function adminApproveBooking(bookingId) {
  const sheet = sh(SHEET_BOOKINGS);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i < data.length; i++) {
    if (data[i][0] == bookingId) {

      sheet.getRange(i + 1, 13).setValue('approved'); // status
      sheet.getRange(i + 1, 16).setValue('paid');     // payment_status
      sheet.getRange(i + 1, 14).setValue('Pembayaran dikonfirmasi admin');

      // kirim email + QR
      sendBookingEmail(bookingId);
      return true;
    }
  }
  return false;
}

/* ===================== EMAIL + QR ===================== */
function getBookingQRCode(id){
  const url=ScriptApp.getService().getUrl();
  return 'https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl='+
    encodeURIComponent(url+'?action=checkin&id='+id);
}

function sendBookingEmail(id){
  const b=getAllBookings().find(x=>x.booking_id==id);
  const qr=getBookingQRCode(id);

  MailApp.sendEmail({
    to:b.email,
    subject:'Booking Padel Approved',
    htmlBody:`
      <h3>Booking Padel</h3>
      <p>Kode: <b>${id}</b></p>
      <p>${b.tanggal} ${b.jam_mulai}</p>
      <img src="${qr}">
    `
  });
}

/* ===================== CHECK-IN ===================== */
function doGet(e) {

  e = e || {};
  e.parameter = e.parameter || {};

  // ===== QR CHECK-IN =====
  if (e.parameter.action === 'checkin') {
    return checkin(e.parameter.id);
  }

  // ===== PAGE ROUTING =====
  const page = e.parameter.page || 'Index';

  const tpl = HtmlService.createTemplateFromFile(page);
  tpl.baseUrl = ScriptApp.getService().getUrl();

  return tpl.evaluate()
    .setTitle('Booking Padel')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function checkin(id){
  const sheet=sh(SHEET_BOOKINGS);
  const data=sheet.getDataRange().getValues();

  for(let i=1;i<data.length;i++){
    if(data[i][0]==id){
      if(data[i][16]!='paid')
        return HtmlService.createHtmlOutput('❌ Belum bayar');
      if(data[i][17]=='checked')
        return HtmlService.createHtmlOutput('⚠️ Sudah check-in');

      sheet.getRange(i+1,18).setValue('checked');
      sheet.getRange(i+1,19).setValue(new Date());
      return HtmlService.createHtmlOutput('✅ Check-in berhasil');
    }
  }
  return HtmlService.createHtmlOutput('❌ Booking tidak ditemukan');
}

Index.html

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <title>Booking Padel</title>
</head>
<body>
  <h2>🏓 Sistem Booking Padel</h2>
  <ul>
    <li><a href="<?= baseUrl ?>?page=Pelanggan">Booking Lapangan</a></li>
    <li><a href="<?= baseUrl ?>?page=Checkin">Check-in</a></li>
    <li><a href="<?= baseUrl ?>?page=Admin">Admin</a></li>
    <li><a href="<?= baseUrl ?>?page=Pemilik">Pemilik</a></li>
  </ul>
</body>
</html>

Admin.html

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
</head>
<body>

<h3>🛠 Admin – Konfirmasi Pembayaran</h3>

<input id="bookingId" placeholder="Masukkan Booking ID">
<br><br>
<button onclick="approve()">Approve Pembayaran</button>

<script>
function approve(){
  if(!bookingId.value){
    alert('Booking ID wajib diisi');
    return;
  }

  google.script.run.withSuccessHandler(()=>{
    alert('Pembayaran dikonfirmasi.\nEmail & QR Code telah dikirim ke pelanggan.');
  }).adminApproveBooking(bookingId.value);
}
</script>

</body>
</html>

$ads={1}

Pelanggan.html

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    body { font-family: Arial; padding: 15px; }
    h3 { margin-top: 25px; }
    input, select, button { margin: 5px 0; padding: 6px; }
    table { border-collapse: collapse; }
    td { padding: 5px; }
  </style>
</head>

<body>

<h2>🎾 Booking Lapangan Padel</h2>

<table>
<tr><td>Nama</td><td><input id="nama"></td></tr>
<tr><td>Email</td><td><input id="email"></td></tr>
<tr><td>No HP</td><td><input id="hp"></td></tr>
<tr><td>Tanggal</td><td><input type="date" id="tanggal"></td></tr>

<tr>
  <td>Lapangan</td>
  <td>
    <select id="lapangan"></select>
  </td>
</tr>

<tr>
  <td>Jam Mulai</td>
  <td>
    <select id="jam"></select>
    <button onclick="cekJam()">Cek Jam</button>
  </td>
</tr>

<tr>
  <td>Durasi</td>
  <td>
    <select id="durasi">
      <option value="1">1 Jam</option>
      <option value="2">2 Jam</option>
      <option value="3">3 Jam</option>
    </select>
  </td>
</tr>
</table>

<button onclick="booking()">Booking</button>

<hr>

<h3>🍜 Order Menu</h3>

<div id="menu"></div>
<button onclick="simpanMenu()">Simpan Menu</button>

<hr>

<h3>💳 Pembayaran</h3>

<select id="payment">
  <option value="">-- Pilih Metode Pembayaran --</option>
  <option value="QRIS">QRIS</option>
  <option value="Transfer BCA">Transfer BCA</option>
  <option value="Transfer Mandiri">Transfer Mandiri</option>
</select>

<br><br>
<button onclick="bayar()">Bayar & Konfirmasi via WhatsApp</button>

<script>
let bookingId = '';

/* ========= INIT ========= */
google.script.run.withSuccessHandler(loadInit).getInitData();

function loadInit(res){
  lapangan.innerHTML = res.lapangan.map(l=>`<option>${l}</option>`).join('');
  jam.innerHTML = res.jam.map(j=>`<option>${j}</option>`).join('');

  let html = '';
  res.menu.forEach(m=>{
    html += `
      <div>
        <input type="number" min="0" id="menu_${m.id}" value="0">
        ${m.nama} (${m.harga})
      </div>`;
  });
  menu.innerHTML = html;
}

/* ========= CEK JAM ========= */
function cekJam(){
  google.script.run.withSuccessHandler(res=>{
    alert(res ? 'Jam tersedia ✅' : 'Jam sudah dibooking ❌');
  }).checkAvailability(
    tanggal.value,
    lapangan.value,
    jam.value,
    durasi.value
  );
}

/* ========= BOOKING ========= */
function booking(){
  if(!nama.value || !email.value || !tanggal.value){
    alert('Lengkapi data booking');
    return;
  }

  google.script.run.withSuccessHandler(id=>{
    bookingId = id;
    alert('Booking berhasil.\nID: ' + id);
  }).createBooking({
    nama: nama.value,
    email: email.value,
    hp: hp.value,
    tanggal: tanggal.value,
    lapangan: lapangan.value,
    jam: jam.value,
    durasi: durasi.value
  });
}

/* ========= SIMPAN MENU ========= */
function simpanMenu(){
  if(!bookingId){
    alert('Lakukan booking terlebih dahulu');
    return;
  }

  let items = [];
  document.querySelectorAll('[id^=menu_]').forEach(i=>{
    if(i.value > 0){
      items.push({id:i.id.replace('menu_',''), qty:i.value});
    }
  });

  google.script.run.withSuccessHandler(()=>{
    alert('Menu tersimpan');
  }).saveMenu(bookingId, items);
}

/* ========= BAYAR ========= */
function bayar(){
  if(!bookingId){
    alert('Booking belum ada');
    return;
  }
  if(!payment.value){
    alert('Pilih metode pembayaran');
    return;
  }

  google.script.run.withSuccessHandler(()=>{
    const pesan = `
Halo Admin,
Saya sudah melakukan pembayaran booking padel.

Booking ID : ${bookingId}
Metode     : ${payment.value}

Mohon dicek ya 🙏
    `;

    const waAdmin = '6281234567890'; // 🔴 GANTI NOMOR ADMIN
    window.open(
      'https://wa.me/' + waAdmin + '?text=' + encodeURIComponent(pesan),
      '_blank'
    );

    alert('Silakan kirim bukti pembayaran via WhatsApp');
  }).setPaymentMethod(bookingId, payment.value);
}
</script>

</body>
</html>

Pemilik.html

<!DOCTYPE html>
<html>
<head><base target="_top"></head>
<body>
<h3>📈 Dashboard Pemilik</h3>

<button onclick="load()">Refresh</button>
<pre id="out"></pre>

<script>
function load(){
  google.script.run.withSuccessHandler(d=>{
    out.textContent=JSON.stringify(d,null,2);
  }).getAllBookings();
}
</script>
</body>
</html>


Untuk mendowload source code klik link berikut {getButton} $text={Download File} $icon={download}

Kelebihan Sistem Booking Padel Ini

  • Tidak memerlukan server hosting
  • Cocok untuk UMKM dan komunitas padel
  • Mudah dikembangkan ke sistem yang lebih besar

Dengan mengikuti panduan cara membuat booking padel dengan Google Script Apps gratis, Anda bisa membangun sistem reservasi yang efisien, hemat biaya, dan siap digunakan kapan saja. Solusi ini sangat ideal untuk pengelola lapangan padel yang ingin beralih ke sistem digital.

Gatot

Penulis merupakan seorang IT profesional baik network engineer, software development(Delphi, Java, Android, iOS, PHP, NextJs, Golang, Flutter), System Analysis, SEO, database administrator, troubleshooting, dan juga content creator(facebook, youtube, atau tiktok). Tulisan merupakan bagian yang pernah dikerjakan dan dilakukan setiap hari.

Lebih baru Lebih lama

Contact