Skip to main content

Search

Membuat Aplikasi Nomor Antrian dengan Google Sheet dan Apps Script

Nomor antrian biasa dipakai oleh  rumah sakit, bank, atau perusahaan lainnya dalam melakukan pelayanan kepada pelanggan. Untuk memilikinya sebuah perusahaan tentu tidak sedikit biaya yang dikeluarkan.


Nah kali ini saya akan berbagi tentang cara Membuat Aplikasi Nomor Antrian dengan Google Sheet dan Apps Script yang mudah dan murah, akan tetapi perlu selalu terkonek oleh internet.

Untuk melakukannya berikut adalah langkah-langkah yang bisa kalian lakukan

1. Langkah pertama yang harus dilakukan adalah membuat spreadsheet baru di google sheets sebagai data pengujung yang mengambil nomor antrian. nantinya data ini hanya ditampung sementara selama satu hari setelah hari berganti maka data akan kembali ke nomor awal atau nomor 1. Buatlah sheet seperti dibawah ini


2. Langkah berikutnya buat script baru di apps script 


3. Langkah berikutnya copy kan script di bawah ini sesuai dengan nama file, disini kita buat Code.gs digunakan untuk menampung function, index.html digunakan untuk menampilkan halaman utama, antrian.html digunakan untuk halaman pengambilan nomor antrian, display.html digunakan untuk menampilkan halaman nomor antrian saat dipanggil admin, admin.html digunakan untuk pemanggilan nomor antrian berdasarkan nomor antrian yang telah diambil



Code.gs

var sheetName = 'AntrianData';
var previousNumberKey = 'previousNumber';
var lastCalledNumberKey = 'lastCalledNumber';
var updateRequiredKey = 'updateRequired';
var lastDateKey = 'lastDate';

function doGet(e) {
return HtmlService.createTemplateFromFile('index').evaluate();
}

function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function antrian() {
return HtmlService.createHtmlOutputFromFile('antrian').getContent();
}

function display() {
return HtmlService.createHtmlOutputFromFile('display').getContent();
}

function admin() {
return HtmlService.createHtmlOutputFromFile('admin').getContent();
}

function ambilNomorAntrian() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var lastRow = sheet.getLastRow();
var today = new Date();
var currentDateString = Utilities.formatDate(today, Session.getScriptTimeZone(), 'yyyy-MM-dd');
// Periksa tanggal terakhir yang disimpan
var lastDate = PropertiesService.getScriptProperties().getProperty(lastDateKey);
// Jika tanggal saat ini berbeda dengan tanggal terakhir, reset nomor ke 1
if (lastDate !== currentDateString) {
sheet.clear(); // Opsional: Hapus data jika ingin mulai dari awal setiap hari
sheet.appendRow(['Nomor', 'Tanggal', 'Status']); // Header
PropertiesService.getScriptProperties().setProperty(lastDateKey, currentDateString);
var newNumber = 1;
} else {
// Jika tanggal sama, gunakan nomor terakhir + 1
var newNumber = lastRow > 1 ? sheet.getRange(lastRow, 1).getValue() + 1 : 1;
}

var waktuAntrian = new Date();
sheet.appendRow([newNumber, waktuAntrian, 'waiting']);
return newNumber;
}

function getNomorAntrianSaatIni() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var range = sheet.getRange('A:C');
var values = range.getValues();

for (var i = values.length - 1; i >= 1; i--) {
if (values[i][2] === 'called') {
return values[i][0];
}
}
return 'Belum ada nomor antrian yang dipanggil';
}

function panggilNomorBerikut() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var range = sheet.getRange('A:C');
var values = range.getValues();
var previousNumber = PropertiesService.getScriptProperties().getProperty(previousNumberKey);
previousNumber = previousNumber ? parseInt(previousNumber, 10) : null;

if (semuaNomorDipanggil()) {
return 'Semua nomor sudah dipanggil';
}

for (var i = 1; i < values.length; i++) {
if (values[i][2] === 'waiting') {
if (previousNumber !== null) {
sheet.getRange(previousNumber + 1, 3).setValue('called');
}
values[i][2] = 'called';
sheet.getRange(i + 1, 3).setValue('called');
PropertiesService.getScriptProperties().setProperty(previousNumberKey, i);
PropertiesService.getScriptProperties().setProperty(lastCalledNumberKey, values[i][0]);
PropertiesService.getScriptProperties().setProperty(updateRequiredKey, 'true');
return values[i][0];
}
}
return 'Tidak ada nomor antrian yang menunggu';
}

function semuaNomorDipanggil() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var values = sheet.getRange('A:C').getValues();
for (var i = 1; i < values.length; i++) {
if (values[i][2] === 'waiting') {
return false; // Masih ada nomor yang menunggu
}
}
return true; // Semua nomor sudah dipanggil
}

function getNomorBelumDipanggil() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var values = sheet.getRange('A:C').getValues();
var waitingNumbers = [];

for (var i = 1; i < values.length; i++) {
if (values[i][2] === 'waiting') {
waitingNumbers.push(values[i][0]);
}
}

return waitingNumbers.length > 0 ? waitingNumbers.join(', ') : 'Semua nomor sudah dipanggil';
}

function ulangiNomor() {
var previousNumber = PropertiesService.getScriptProperties().getProperty(previousNumberKey);
if (previousNumber !== null) {
previousNumber = parseInt(previousNumber, 10);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
var values = sheet.getRange('A:C').getValues();
var currentStatus = sheet.getRange(previousNumber + 1, 3).getValue();
if (currentStatus === 'called') {
sheet.getRange(previousNumber + 1, 3).setValue('waiting');
PropertiesService.getScriptProperties().setProperty(lastCalledNumberKey, values[previousNumber][0]);
PropertiesService.getScriptProperties().setProperty(updateRequiredKey, 'true');
return values[previousNumber][0];
} else {
return 'Tidak ada nomor yang dipanggil sebelumnya';
}
}
return 'Tidak ada nomor yang dipanggil sebelumnya';
}

function getLastCalledNumber() {
return PropertiesService.getScriptProperties().getProperty(lastCalledNumberKey) || 'Belum ada nomor antrian yang dipanggil';
}

function isUpdateRequired() {
return PropertiesService.getScriptProperties().getProperty(updateRequiredKey) === 'true';
}

function setUpdateComplete() {
PropertiesService.getScriptProperties().setProperty(updateRequiredKey, 'false');
}


halaman utama aplikas nomor antrian



index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Antrian</title>
<!-- Font Awesome CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: linear-gradient(to right, #000000, #C8102E);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
}

header {
background-color: rgba(255, 255, 255, 0.2);
padding: 20px;
width: 100%;
position: fixed;
top: 0;
left: 0;
border-bottom: 1px solid #ddd;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
}

h1 {
margin: 0;
font-size: 2.5em;
color: #fff;
}

main {
margin-top: 80px; /* Space for fixed header */
width: 100%;
max-width: 1200px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}

.card-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
}

.card {
background: rgba(255, 255, 255, 0.2);
border-radius: 15px;
padding: 20px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
transition: transform 0.3s ease, box-shadow 0.3s ease;
width: 300px;
height: 150px;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
text-align: left;
}

.card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4);
}

.card .icon {
font-size: 2.5em;
margin-right: 15px;
color: #fff;
}

.card .content {
display: flex;
flex-direction: column;
justify-content: center;
}

.card .title {
font-size: 1.5em;
margin-bottom: 10px;
}

.card .description {
font-size: 1em;
color: #ccc;
}

#card-ambil-antrian {
background: linear-gradient(to right, #4CAF50, #66BB6A);
}

#card-display-antrian {
background: linear-gradient(to right, #2196F3, #0B80E2);
}

#card-admin {
background: linear-gradient(to right, #F44336, #E53935);
}

footer {
background-color: rgba(255, 255, 255, 0.2);
padding: 10px;
width: 100%;
text-align: center;
position: fixed;
bottom: 0;
left: 0;
border-top: 1px solid #ddd;
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.5);
}

@media (max-width: 768px) {
.card {
width: 90%;
max-width: 400px;
}
}
</style>
</head>
<body>
<header>
<h1>Dashboard Antrian</h1>
</header>
<main>
<div class="card-container">
<div class="card" id="card-ambil-antrian" onclick="loadPage('antrian')">
<div class="icon"><i class="fas fa-ticket-alt"></i></div>
<div class="content">
<div class="title">Ambil Nomor Antrian</div>
<div class="description">Klik untuk mengambil nomor antrian Anda</div>
</div>
</div>
<div class="card" id="card-display-antrian" onclick="loadPage('display')">
<div class="icon"><i class="fas fa-list"></i></div>
<div class="content">
<div class="title">Display Antrian</div>
<div class="description">Klik untuk melihat nomor antrian yang sedang dipanggil</div>
</div>
</div>
<div class="card" id="card-admin" onclick="loadPage('admin')">
<div class="icon"><i class="fas fa-cogs"></i></div>
<div class="content">
<div class="title">Admin</div>
<div class="description">Klik untuk mengelola sistem antrian</div>
</div>
</div>
</div>
</main>
<footer>
&copy; 2024 Dashboard Antrian
</footer>
<script>
function loadPage(pageName) {
google.script.run.withSuccessHandler(function(htmlContent) {
document.open();
document.write(htmlContent);
document.close();
})[pageName]();
}
</script>
</body>
</html>


Halaman ambil nomor antrian



antrian.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ambil Nomor Antrian</title>
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(to right, #C8102E, #000000);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
text-align: center;
}

h1 {
font-size: 3em;
margin-bottom: 20px;
}

/* Styling untuk tombol lingkaran 3D */
.circle-button {
background: linear-gradient(to bottom, #004d00, #00cc00); /* Gradasi warna hijau tua dan hijau muda */
border: none;
color: #fff;
border-radius: 50%;
width: 200px;
height: 200px;
font-size: 1.2em;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3), inset 0 2px 4px rgba(0, 0, 0, 0.3); /* Efek bayangan 3D */
transition: all 0.3s ease;
}

.circle-button:hover {
background: linear-gradient(to bottom, #003300, #00e600); /* Gradasi lebih gelap saat hover */
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.5), inset 0 4px 8px rgba(0, 0, 0, 0.4); /* Bayangan lebih dalam saat hover */
}

#nomorAntrian {
margin-top: 20px;
font-size: 20px;
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
}

.line {
margin: 10px 0;
border-top: 2px dotted #fff;
}

#nomor {
font-size: 50px; /* Ukuran font untuk nomor antrian */
}

@media print {
.circle-button {
display: none !important;
}
.judul_nomor {
display:none;
}
.line {
border-top: 2px dotted black;
}
}
</style>
</head>
<body>
<h1 class="judul_nomor">Ambil Nomor Antrian</h1>
<button class="circle-button" onclick="ambilNomorAntrian()"><h2>Ambil</h2></button>
<div id="nomorAntrian">
<div id="judul"></div>
<div class="line"></div>
<div id="nomor"></div>
<div class="line"></div>
<div id="tanggal"></div>
</div>
<script>
function ambilNomorAntrian() {
google.script.run.withSuccessHandler(function(result) {
var nomorAntrianDiv = document.getElementById('nomorAntrian');
document.getElementById('judul').innerHTML = 'Nomor Antrian';
document.getElementById('nomor').innerHTML = result;
document.getElementById('tanggal').innerHTML = 'Tanggal: ' + formatTanggal(new Date());

// Tampilkan dialog pencetakan
setTimeout(function() {
window.print();
}, 100); // Delay singkat untuk memastikan konten siap dicetak
}).ambilNomorAntrian();
}

function formatTanggal(tanggal) {
const bulan = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
const hari = tanggal.getDate();
const bulanNama = bulan[tanggal.getMonth()];
const tahun = tanggal.getFullYear();
const jam = String(tanggal.getHours()).padStart(2, '0');
const menit = String(tanggal.getMinutes()).padStart(2, '0');
const detik = String(tanggal.getSeconds()).padStart(2, '0');
return `${hari} ${bulanNama} ${tahun} ${jam}:${menit}:${detik}`;
}
</script>
</body>
</html>


Halaman admin untuk memanggil nomor antrian




admin.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Antrian</title>
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(to right, #000000, #C8102E); /* Gradasi hitam ke merah */
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
text-align: center;
}

h1 {
font-size: 2.5em;
margin-bottom: 20px;
}

button {
color: #fff;
border: none;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 10px;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
margin: 10px;
}

.panggil-button {
background: linear-gradient(to right, #004d00, #009900); /* Gradasi hijau untuk tombol */
}

.ulangi-button {
background: linear-gradient(to right, #003366, #3399ff); /* Gradasi biru untuk tombol */
}

button:hover {
transform: scale(1.05); /* Zoom in effect saat hover */
}

.panggil-button:hover {
background: linear-gradient(to right, #006600, #00b300); /* Gradasi hijau lebih terang saat hover */
}

.ulangi-button:hover {
background: linear-gradient(to right, #004080, #66ccff); /* Gradasi biru lebih terang saat hover */
}

#nomorDipanggil, #panggilanTerakhir, #status, #nomorBelumDipanggil {
margin-top: 20px;
font-size: 1.5em;
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
width: 80%;
max-width: 500px; /* Maksimal lebar konten */
}
</style>
</head>
<body>
<h1>Admin Antrian</h1>
<div>
<button class="panggil-button" onclick="panggilNomorBerikut()">Panggil Nomor Berikut</button>
<button class="ulangi-button" onclick="ulangiNomor()">Ulangi Panggilan Nomor</button>
</div>
<div id="nomorDipanggil"></div>
<div id="nomorBelumDipanggil"></div> <!-- Menampilkan nomor yang belum dipanggil -->
<script>
function panggilNomorBerikut() {
google.script.run.withSuccessHandler(function(result) {
if (result === 'Semua nomor sudah dipanggil') {
document.getElementById('status').innerHTML = result;
} else if (!isNaN(result)) {
document.getElementById('nomorDipanggil').innerHTML = "Nomor Dipanggil: " + result;
google.script.run.setUpdateComplete(); // Tandai update selesai
}
}).panggilNomorBerikut();
}

function ulangiNomor() {
google.script.run.withSuccessHandler(function(result) {
document.getElementById('nomorDipanggil').innerHTML = "Nomor Diulang: " + result;
google.script.run.setUpdateComplete(); // Tandai update selesai
}).ulangiNomor();
}

function updateNomorBelumDipanggil() {
google.script.run.withSuccessHandler(function(result) {
document.getElementById('nomorBelumDipanggil').innerHTML = "Nomor Belum Dipanggil: " + result;
}).getNomorBelumDipanggil();
}

// Panggil fungsi update secara berkala untuk memperbarui status nomor belum dipanggil
setInterval(updateNomorBelumDipanggil, 5000);

// Inisialisasi nomor belum dipanggil saat halaman pertama kali dimuat
updateNomorBelumDipanggil();
</script>
</body>
</html>


Halalaman untuk menampilkan nomor antrian



display.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Display Antrian</title>
<style>
/* CSS */
body {
font-family: Arial, sans-serif;
color: #fff;
margin: 0;
padding: 0;
background-color: #f4f4f4;
display: flex;
flex-direction: column;
min-height: 100vh;
overflow-x: hidden;
}

.header {
background: linear-gradient(to right, #C8102E, #000000);
color: #fff;
padding: 20px;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.header h1 {
margin-top: 0;
}

.content {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: #C8102E;
box-sizing: border-box;
}

.antrian-info {
flex: 1;
height: 480px;
background: #000000;
color: #fff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
text-align: center;
box-sizing: border-box;
}

.antrian-info h2 {
margin: 0;
font-size: 30px;
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
border-radius: 5px;
z-index: 1;
}

.antrian-info .number {
font-size: 250px;
font-weight: bold;
margin-top: 60px;
}

#video {
flex: 1;
margin-left: 20px;
border-radius: 15px;
}

#video iframe {
width: 100%;
height: 480px;
border: none;
border-radius: 15px;
}

.footer {
background: linear-gradient(to right, #C8102E, #000000);
color: #fff;
padding: 10px;
text-align: center;
position: relative;
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
}

.digital-clock {
font-size: 20px;
font-weight: bold;
}

.runtext-container {
background: linear-gradient(to right, #C8102E, #000000);
color: #fff;
padding: 10px;
border-radius: 15px;
flex: 1;
margin-left: 10px;
}

.runtext-container h3 {
margin: 0;
font-size: 18px;
}
</style>
</head>
<body>
<div class="header">
<h1>Display Antrian</h1>
</div>
<div class="content">
<div class="antrian-info" id="nomorDipanggil">
<h2>Nomor Antrian</h2>
<div class="number">
<!-- Nomor antrian akan ditampilkan di sini -->
</div>
</div>
<div id="video">
<iframe src="https://www.youtube.com/embed/6stlCkUDG_s?autoplay=1&mute=1" allow="autoplay; encrypted-media"></iframe>
</div>
</div>
<div class="footer">
<div class="digital-clock" id="clock">
<!-- Jam digital akan ditampilkan di sini -->
</div>
<div class="runtext-container">
<marquee direction="left" onmouseover="this.stop();" onmouseout="this.start();">
<h3>Teks berjalan dengan gradasi warna merah tua dan hitam | Sistem antrian otomatis | Video ini mengajarkan cara membuat sistem antrian yang efektif dengan Google Sheets dan Google Apps Script</h3>
</marquee>
</div>
</div>
<script>
function speakNumber(number) {
if ('speechSynthesis' in window) {
var utterance = new SpeechSynthesisUtterance("Nomor antrian " + number);
utterance.lang = 'id-ID';
utterance.voice = speechSynthesis.getVoices().find(voice => voice.lang === 'id-ID' && voice.name.includes('Google'));
utterance.pitch = 1;
utterance.rate = 0.9;
speechSynthesis.speak(utterance);
} else {
alert('Browser tidak mendukung speech synthesis.');
}
}

function updateDisplay() {
google.script.run.withSuccessHandler(function(result) {
if (result) {
google.script.run.withSuccessHandler(function(number) {
if (!isNaN(number)) {
var formattedNumber = Math.floor(number);
document.querySelector('.antrian-info .number').innerHTML = formattedNumber;
speakNumber(formattedNumber);
playAudio(); // Panggil fungsi playAudio setelah nomor diperbarui
google.script.run.setUpdateComplete();
}
}).getLastCalledNumber();
}
}).isUpdateRequired();
}

function playAudio() {
var audio = document.getElementById('bel');
audio.play().catch(function(error) {
console.error('Error playing audio:', error);
});
}

setInterval(updateDisplay, 1000);
updateDisplay();

function updateClock() {
var now = new Date();
var hours = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
var timeString = hours + ':' + minutes + ':' + seconds;
document.getElementById('clock').textContent = timeString;
}

setInterval(updateClock, 1000);
updateClock();
</script>
</body>
</html>

4. Setelah selesai sekarang jalan dengan melakukan deploy webapp

Cek video  di bawah ini