Skip to content

Instantly share code, notes, and snippets.

@arpeggio068
Last active February 20, 2024 09:03
Show Gist options
  • Save arpeggio068/d39d583f158a79f4ccc63a1e76e1367c to your computer and use it in GitHub Desktop.
Save arpeggio068/d39d583f158a79f4ccc63a1e76e1367c to your computer and use it in GitHub Desktop.
User Booking
//Code.gs
const spreadsheetId = 'Your Google Sheet ID';
const ss = SpreadsheetApp.openById(spreadsheetId);
function doGet(e){
return HtmlService.createTemplateFromFile("main").evaluate()
.setTitle("User Booking")
.addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function loadPartialHTML_(partial) {
const htmlServ =HtmlService.createTemplateFromFile(partial);
return htmlServ.evaluate().getContent();
}
function loadAddView(){
return loadPartialHTML_("index");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////// Add Record Func /////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
function getCalendar(){ //get disable days and full booked days
const ws = ss.getSheetByName("data");
const data = ws.getRange(2,1,ws.getLastRow()-1,7).getValues();
let disableDays = []
data.forEach(function(a){
if(a[1] == "holiday"){
let moddate = a[4].split("/")[2]+"/"+a[4].split("/")[1]+"/"+a[4].split("/")[0]
disableDays.push(
new Date(moddate).setHours(0,0,0,0)
)
}
})
let countQ = [];
data.forEach(function(b){
if(b[1] != "holiday"){
countQ.push([
b[4], //date
countIF(data,4,b[4])
])
}
})
countQ.forEach(function(c){
if(c[1] >= 4){ //max 4 person for 1 day, 9:00 = 2 person, 10:00 = 2 person
let fullqdate = c[0].split("/")[2]+"/"+c[0].split("/")[1]+"/"+c[0].split("/")[0]
disableDays.push(
new Date(fullqdate).setHours(0,0,0,0)
)
}
})
let uniqueDays = [];
disableDays.forEach(function(d){
if(uniqueDays.indexOf(d) === -1){
uniqueDays.push(d);
}
});
let curUniqueDays = [];
let currentDate = new Date()
let currentDate2 = new Date(currentDate).setHours(0,0,0,0)
uniqueDays.forEach(function(e){
if(new Date(e) >= currentDate2){
curUniqueDays.push(e)
}
})
//Logger.log(uniqueDays);
return curUniqueDays;
}
function getFullQ(userdate){ //get full Q time
//userdate = "22/4/2022"
const ws = ss.getSheetByName("data");
const data = ws.getRange(2,1,ws.getLastRow()-1,7).getValues();
let countQ = [];
let fullQ = [];
data.forEach(function(a){
if(a[4] == userdate && a[1] != "holiday"){
countQ.push([
a[5], //time
countIFS2(data,4,a[4],5,a[5])
])
}
})
countQ.forEach(function(b){
if(b[1] >= 2){ //max 2 person per time, 9:00 = 2 person, 10:00 = 2 person
fullQ.push(b[0])
}
})
let uniqueFullQ = [];
fullQ.forEach(function(d){
if(uniqueFullQ.indexOf(d) === -1){
uniqueFullQ.push(d);
}
});
//Logger.log(uniqueFullQ);
return uniqueFullQ;
}
function addNewRow(rowData) {
const ws = ss.getSheetByName("data");
const newID = uniqueId();
let fullQ = getFullQ(rowData.date)
if(!fullQ.includes(rowData.time)){
ws.appendRow([newID(),
rowData.custID,
rowData.name,
rowData.tel,
"'"+rowData.date,
"'"+rowData.time,
rowData.admin,
new Date()
]);
return "success";
}
else{
return "fail";
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////// End Add Record Func /////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Code.gs
//utility.gs
//////// count function//////////////////
function countIF(array,index1,condition1){ //count for 1 condition
var countVal = 0;
array.forEach(function(e){
if(e[index1] == condition1){
countVal ++
}
});
return countVal
}
function countIFS2(array,index1,condition1,index2,condition2){ //count for 2 condition
var countVal = 0;
array.forEach(function(e){
if(e[index1] == condition1 && e[index2] == condition2){
countVal ++
}
});
return countVal
}
////////end count function//////////////////
function uniqueId(){
const firstItem = {
value: "0"
};
/*length can be increased for lists with more items.*/
let counter = "123456789".split('')
.reduce((acc, curValue, curIndex, arr) => {
const curObj = {};
curObj.value = curValue;
curObj.prev = acc;
return curObj;
}, firstItem);
firstItem.prev = counter;
return function () {
let now = Date.now();
if (typeof performance === "object" && typeof performance.now === "function") {
now = performance.now().toString().replace('.', '');
}
counter = counter.prev;
return `${now}${Math.random().toString(16).substr(2)}${counter.value}`;
}
}
//utility.gs
<!-- main.html -->
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<style>
h3, h4, h5, h6 {text-align: center;}
.nav-link{
cursor:pointer;
.form-elegant .font-small {
font-size: 0.8rem; }
.form-elegant .z-depth-1a {
-webkit-box-shadow: 0 2px 5px 0 rgba(55, 161, 255, 0.26), 0 4px 12px 0 rgba(121, 155, 254, 0.25);
box-shadow: 0 2px 5px 0 rgba(55, 161, 255, 0.26), 0 4px 12px 0 rgba(121, 155, 254, 0.25); }
.form-elegant .z-depth-1-half,
.form-elegant .btn:hover {
-webkit-box-shadow: 0 5px 11px 0 rgba(85, 182, 255, 0.28), 0 4px 15px 0 rgba(36, 133, 255, 0.15);
box-shadow: 0 5px 11px 0 rgba(85, 182, 255, 0.28), 0 4px 15px 0 rgba(36, 133, 255, 0.15); }
}
/*#loading {
position: fixed;
top: 0;
left: 0;
z-index: 10000;
width: 100vw;
height: 100vh;
background-color: rgba(255,255,255,0.9);
}*/
.nav {
background-color: #ffffff !important;
}
*{
box-sizing: border-box;
}
body {
font: 16px Arial;
}
.own-set{
padding: 10px;
width: 500px;
}
div.ui-datepicker, .ui-datepicker input{font-size:110%;}
label.required::before {
content: '*';
margin-right: 4px;
color: red;
}
#divMenuUraianKerja{
border: 1px solid rgba(0,0,0,0.2);
padding: 20px;
margin-left:10px;
margin-right:10px;
margin-top: 15px;
background-color: #F2F2E5;
font-family: 'Prompt';
}
</style>
</head>
<body>
<div style="max-width:1000px;margin:20px auto;">
<div id = "maincontainer" class="container">
<h5 style="color:#0552A6">User Booking</h5>
<div id="loading" class="d-flex justify-content-center align-items-center invisible" style="color:#F0530A;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Please wait...
</div>
<div id="app">
</div>
</div><!-- Close container -->
</div> <!-- Close userform -->
<!-- <div id = "loading" class="d-flex justify-content-center align-items-center invisible">
<div class="spinner-grow text-info" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
<div>กำลังโหลดหน้าเวป โปรดรอสักครู่ ...</div>
</div> -->
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
<!--Autocomplete & Calendar JQuery-->
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.0/jquery-ui.js"></script>
<!--Autocomplete & Calendar JQuery-->
<?!=include('utility-js')?>
<script>
var disabledDaysArray = [];
var gName = "", gTel = "";
//-----------------------------------------------------------------------------------//
//--------------- Date Input Configurating Function ----------------------------//
//-----------------------------------------------------------------------------------//
function populateDates(disabledDays){
let currentDate = new Date();
let results = [];
for(let i=0; i<disabledDays.length; i++ ){
results += new Date(disabledDays[i]).toDateString();
}
$(function(){
$("#datepicker" ).datepicker({
minDate: +1,
maxDate: +45,
dateFormat: "d/m/yy",
// dayNamesMin: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส"],
// monthNames: ["มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน",
// "ธันวาคม"],
beforeShowDay: function(date){ if(results.indexOf(date.toDateString())>-1
// || date.getDay() === 0
// || date.getDay() === 6
)
{return [false];}else{return [true];}
}
});
});
disabledDays.forEach(function(r){
let date = new Date(r).getDate();
let month = new Date(r).getMonth() +1;
let year = new Date(r).getFullYear();
let strDate = date+"/"+month+"/"+year
disabledDaysArray.push(strDate)
})
loadingEnd();
}
//-----------------------------------------------------------------------------------//
//--------------- End Date Input Configurating Function ---------------------------//
//-----------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------//
//--------------- Check Validate Function ----------------------------//
//-----------------------------------------------------------------------------------//
function checkPersonal(){
let name = document.getElementById("name").value.trim();
let tel = document.getElementById("tel").value.trim();
if(name.length == 0 || tel.length == 0 ){
return false;
}
else{
return true;
}
}
function validate(){
let fieldsToValidate = document.querySelectorAll("#userform input,#userform textarea,#userform select");
Array.prototype.forEach.call(fieldsToValidate,function(el){
if(el.checkValidity()){
el.classList.remove("is-invalid");
}else{
el.classList.add("is-invalid");
}
});
return Array.prototype.every.call(fieldsToValidate,function(el){
return el.checkValidity();
});
}
//-----------------------------------------------------------------------------------//
//--------------- End Check Validate Function ---------------------------//
//-----------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------//
//--------------- Date Picker Change Function ----------------------------//
//-----------------------------------------------------------------------------------//
function resetTimeOption(){
var optionHTML = '<option disabled selected value="">Select Time</option>';
optionHTML += '<option value="9:00">9:00</option>';
optionHTML += '<option value="10:00">10:00</option>';
// optionHTML += '<option value="11:00">11:00</option>';
// optionHTML += '<option value="13:00">13:00</option>';
// optionHTML += '<option value="14:00">14:00</option>';
// optionHTML += '<option value="15:00">15:00</option>';
// optionHTML += '<option value="16:00">16:00</option>';
// optionHTML += '<option value="17:00">17:00</option>';
// optionHTML += '<option value="18:00">18:00</option>';
// optionHTML += '<option value="19:00">19:00</option>';
document.getElementById('time').innerHTML = optionHTML;
}
function setTimeSelector(timeArray){
if(timeArray && timeArray !== undefined && timeArray.length != 0){
for(let i=0; i<timeArray.length; i++){
const foundOption = [...document.querySelectorAll(".select-time option")]
.find(o => timeArray.includes(o.value));
if(foundOption){
foundOption.remove();
}
}
}
else{
resetTimeOption();
}
document.getElementById("display_error").innerHTML = "Please select a time!";
document.getElementById("display_error").style = "color:green";
setTimeout(function(){
document.getElementById("display_error").innerHTML = "";
},3000);
document.getElementById("save-btn").disabled = false;
document.getElementById("time").disabled = false;
}
function afterDatepickerChange() { //for time select
resetTimeOption();
document.getElementById("save-btn").disabled = true;
document.getElementById("time").disabled = true;
let telpattern = document.getElementById('tel').checkValidity();
let userdate = document.getElementById('datepicker').value;
if(checkPersonal() == false){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Please completed personal information before select a date!'
})
$("#datepicker").val("");
}
else if(telpattern == false){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Invalid phone number format!'
//showConfirmButton: false
//timer: 20000
})
$("#datepicker").val("");
}
else{
document.getElementById("display_error").innerHTML = "Searching time...";
document.getElementById("display_error").style = "color:green";
google.script.run.withSuccessHandler(setTimeSelector).getFullQ(userdate);
}
}
//-----------------------------------------------------------------------------------//
//--------------- End Date Picker Change Function ----------------------------//
//-----------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------//
//--------------- Add Booking Function ----------------------------//
//-----------------------------------------------------------------------------------//
function afterButtonClick(){
if(checkDateFormat()){
if(validate()){
document.getElementById("save-btn").disabled = true;
Swal.fire({
position: 'center',
icon: 'warning',
title: 'Add record',
text: "Please wait...",
showConfirmButton: false
//timer: 20000
})
document.getElementById("display_error").innerHTML = "Add record...";
document.getElementById("display_error").style = "color:green";
loadingStart();
let custID = "ex_user";
let name = document.getElementById("name");
let tel = document.getElementById("tel");
let date = document.getElementById("datepicker");
let time = document.getElementById("time");
let admin = "customer";
gName = name.value;
gTel = tel.value;
let rowData = {
custID:custID,
name:name.value,
tel:tel.value,
date:date.value,
time:time.value,
admin:admin
};
google.script.run.withSuccessHandler(function(return_string){
if(return_string == "success"){
Swal.fire({
position: 'center',
icon: 'success',
html: '<div style = "font-size:22px;color:green;">Success, please capture the screen</div>'+
'<h5>User Booking</h5>'+
'<div style="font-size:17px;color:black;">'+"Name "+rowData.name+" Date "+rowData.date
+" Time "+rowData.time+'</div>',
//showConfirmButton: true,
timer: 100000
})
loadAddView();
document.getElementById("display_error").innerHTML = "Success!";
document.getElementById("display_error").style = "color:green";
setTimeout(function(){
document.getElementById("display_error").innerHTML = "";
},10000);
}
else{
Swal.fire({
position: 'center',
icon: 'error',
html: '<div style = "font-size:20px;color:red;">Fully booked, please select new time!</div>'
//timer: 10000
})
loadAddViewOnFail();
document.getElementById("display_error").innerHTML = "Fully booked, please select new time!";
document.getElementById("display_error").style = "color:red";
setTimeout(function(){
document.getElementById("display_error").innerHTML = "";
},10000);
}
document.getElementById("save-btn").disabled = false;
}).addNewRow(rowData);
}else{
document.getElementById("display_error").innerHTML = "Please completed all required information!";
document.getElementById("display_error").style = "color:red";
setTimeout(function(){
document.getElementById("display_error").innerHTML = "";
//document.getElementById("display_error").style = "color:green";
},5000);
$('#errorNotification').toast('show');
}
}
}
//-----------------------------------------------------------------------------------//
//--------------- End Add Booking Function ----------------------------//
//-----------------------------------------------------------------------------------//
document.addEventListener("DOMContentLoaded",loadAddView);
</script>
</body>
</html>
<!------------------------------------------------------------------------------------------->
<!-- main.html -->
<!-- index.html -->
<div id = "userform" class = "container">
<div class="z-depth-4" style="max-width: 800px;margin: 20px auto;"id="divMenuUraianKerja">
<h6>Information part</h6>
<div class="ui-widget">
<input type="text" id="name" name="name" class="form-control" placeholder="Name" required>
<div class = "invalid-feedback">required</div>
</div><br>
<div class="ui-widget">
<input type="text" id="tel" name="tel" class="form-control" placeholder="Tel" pattern="[0][0-9]{2}-[0-9]{7}"required>
<div id = "telpattern" class = "invalid-feedback">Incorrect tel format</div>
<p style="font-size:13px;color:blue;">Tel e.g. 088-9995555</p>
</div>
</div><!--divMenuUraianKerja -->
<div class="z-depth-4" style="max-width: 800px;margin: 20px auto;"id="divMenuUraianKerja">
<h6>Booking part</h6>
<div >
<input type="text" id="datepicker" class="form-control" placeholder="Date" onchange="afterDatepickerChange()"required>
<div class = "invalid-feedback">required</div>
</div>
<br><div class="ui-widget" id="create">
<span class="value">
<select id ="time" name="time-choose" class="form-control select-time" required>
</select>
<div class = "invalid-feedback">required</div>
</span>
</div>
<div id = "display_error"></div>
<br><button id = "save-btn" type="button" class="btn btn-primary mb-2" onclick="afterButtonClick()">Booking</button>
</div><!--divMenuUraianKerja -->
<center><p style="font-size:14px;color:brown;">Contact us, Tel 089-77700777</p></center>
</div><!-- Container -->
<!-- index.html -->
<!-- utility-js.html -->
<script>
//------------------------------------------------------------------------------------------//
//---------------- Load Page Function ------------------------------------//
//------------------------------------------------------------------------------------------//
function loadingStart(){
document.getElementById("loading").classList.remove("invisible");
document.getElementById('app').style.display='none';
}
function loadingEnd(){
document.getElementById("loading").classList.add("invisible");
document.getElementById('app').style.display='block';
}
function loadView(options){
let id = typeof options.id === "undefined" ? "app": options.id;
let cb = typeof options.callback === "undefined" ? function(){}: options.callback;
loadingStart();
google.script.run.withSuccessHandler(function(html){
document.getElementById(id).innerHTML = html;
loadingEnd();
typeof options.params === "undefined" ? cb():cb(options.params);
})[options.func]();
}
function loadAddView(){
loadView({func:"loadAddView",callback:setAddView});
}
function loadAddViewOnFail(){
loadView({func:"loadAddView",callback:setAddViewOnFail});
}
function setAddView(){
loadingStart();
letGoTrim();
google.script.run.withSuccessHandler(populateDates).getCalendar();
}
function setAddViewOnFail(){
loadingStart();
document.getElementById("name").value = gName;
document.getElementById("tel").value = gTel;
letGoTrim();
google.script.run.withSuccessHandler(populateDates).getCalendar();
}
//------------------------------------------------------------------------------------------//
//---------------- End Load Page Function ------------------------------------//
//------------------------------------------------------------------------------------------//
function trim_text(el){
el.value = el.value.
replace(/(^\s*)|(\s*$)/gi, ""). // removes leading and trailing spaces
replace(/[ ]{2,}/gi, " "). // replaces multiple spaces with one space
replace(/\n +/, "\n"); // Removes spaces after newlines
return;
}
function letGoTrim(){
$(function(){
$("textarea").change(function(){
trim_text(this);
});
$("input").change(function(){
trim_text(this);
});
});
}
function checkLeapYears(input){
year = parseInt(input);
if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)){
return true;
}else{
return false;
}
}
function isInt(n){
return n != "" && !isNaN(n) && Math.round(n) == n;
}
function isFloat(n){
return n != "" && !isNaN(n) && Math.round(n) != n;
}
function addDayToCurrentDate(days){
let currentDate = new Date();
return new Date(currentDate.setDate(currentDate.getDate() + days)).setHours(0,0,0,0);
}
function checkDateFormat(){//Protect user type the date by themselves
let chooseDate = document.getElementById('datepicker').value;
let dayFormat = chooseDate.split('/');
let date = dayFormat[0];
let month = dayFormat[1];
let year = dayFormat[2];
let thisYear = new Date().getFullYear();
let oneDaysBefore = new Date(addDayToCurrentDate(1));
let fourtyfiveDaysBefore = new Date(addDayToCurrentDate(45));
let userDate = new Date(dayFormat[2], dayFormat[1] - 1, dayFormat[0]);
let time = document.getElementById('time').value.trim();
if(dayFormat.length != 3 || isFloat(date) || isFloat(month) || isFloat(year) ){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Incorrected date format, please select from the calendar only!'
})
$("#datepicker").val("");
return false;
}
else if((Number(date) < 10 && date.length > 1) || (Number(month) < 10 && month.length > 1) || (year.length != 4)){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Incorrected date format, please select from the calendar only!'
})
$("#datepicker").val("");
return false;
}
else if(disabledDaysArray.includes(chooseDate)){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Incorrected date format, please select from the calendar only!'
})
$("#datepicker").val("");
return false;
}
else if(date <1 || date > 31 || month < 1 || month > 12 || year > thisYear+1){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Incorrected date format, please select from the calendar only!'
})
$("#datepicker").val("");
return false;
}
else if(checkLeapYears(year) === true && month == 2 && date > 29 || month == 4 && date == 31 ||
checkLeapYears(year) === false && month == 2 && date > 28 || month == 6 && date == 31 ||
month == 9 && date == 31 || month == 11 && date == 31){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Incorrected date format, please select from the calendar only!'
})
$("#datepicker").val("");
return false;
}
else if(userDate < oneDaysBefore || userDate > fourtyfiveDaysBefore){
Swal.fire({
position: 'center',
icon: 'error',
title: 'Must be booked at least 1 day in advance!'
})
$("#datepicker").val("");
return false;
}
/*else if(userDate.getDay() == 0 || userDate.getDay() == 6){
Swal.fire({
position: 'center',
icon: 'error',
title: 'งดนัดวันเสาร์ อาทิตย์!'
})
$("#datepicker").val("");
return false;
}*/
else if(time.length == 0){
document.getElementById("display_error").innerHTML = "Please select a time!";
document.getElementById("display_error").style = "color:red";
setTimeout(function(){
document.getElementById("display_error").innerHTML = "";
},5000);
return false;
}
else{
return true;
}
}//End Func checkDateFormat
</script>
<!-- utility-js.html -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment