Converted to an SPA

This commit is contained in:
pranav nachnekar 2019-09-03 12:04:52 +05:30
parent eb4fa966b0
commit 2791054327
12 changed files with 364 additions and 297 deletions

View File

@ -1,38 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Book Appointment") }}{% endblock %}
{% block page_content %}
<div class="container">
<!-- title: Book an appointment -->
<div class="text-center mb-5">
<h3>Book an appointment</h3>
<h4>Select the date and your timezone</h4>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 align-self-center ">
<form name="myform">
<input
type="date"
onchange="ondatechange()"
name="appointment-date"
id="appointment-date"
class="form-control mt-3"
min="{{ from_date }}"
max="{{ to_date }}">
<select name="appointment-timezone" id="appointment-timezone" class="form-control mt-3">
{% if timezones %}
{% for timezone in timezones%}
<option value="{{timezone.offset}}">{{timezone.timezone_name}}</option>
{% endfor %}
{% endif %}
</select>
</form>
<button class="form-control mt-3 btn btn-dark" id="next-button" onclick="next()">
Next
</button>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,25 +0,0 @@
{% include 'erpnext/public/js/date_polyfill.js' %}
let holidays = [];
{% if holidays %}
holidays = {{holidays}}
{% endif %}
function next() {
let date = document.getElementsByName('appointment-date')[0].value;
if(holidays.includes(date)){
frappe.throw("That day is a holiday")
}
if(date === ""){
frappe.throw("Please select a date")
}
let tz = document.getElementsByName('appointment-timezone')[0].value;
window.location = `/book-appointment/2?date=${date}&tz=${tz}`;
}
function ondatechange(){
let date = document.getElementById('appointment-date')
if(holidays.includes(date.value)){
frappe.throw("That day is a holiday")
}
}

View File

@ -1,17 +0,0 @@
import frappe
def get_context(context):
settings = frappe.get_doc('Appointment Booking Settings')
holiday_list = frappe.get_doc('Holiday List',settings.holiday_list)
holidays = []
for holiday in holiday_list.holidays:
print(str(holiday.holiday_date))
holidays.append(str(holiday.holiday_date))
context.holidays = holidays
context.from_date = holiday_list.from_date
context.to_date = holiday_list.to_date
timezones = frappe.get_all('Timezone',fields=["timezone_name","offset"])
context.timezones = timezones
return context

View File

@ -1,60 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Book Appointment") }}{% endblock %}
{% block page_content %}
<style>
.time-slot {
margin: 0 0;
border: 0.5px solid #cccccc;
min-height: 100px;
}
.time-slot:hover {
background: #ddd;
}
.time-slot.unavailable {
background: #bbb;
color: #777777
}
input[type="radio"] {
visibility: hidden;
display: none;
}
.time-slot.selected {
color: white;
background: #5e64ff;
}
</style>
<div class="container">
<div class="text-center mb-5">
{% if is_holiday %}
<h3> This day is a holiday</h3>
{% else %}
<h3>Pick A Time Slot</h3>
<h4>Selected date is {{ date }}</h4>
</div>
<!-- Start of main content-->
<div class="mt-3 justify-content-center">
<div class="row">
{% for timeslot in timeslots %}
<div class="col-md time-slot {% if timeslot.unavailable %}unavailable{% endif %}" id="{{ timeslot.time.time() }}">{{ timeslot.time.time().strftime('%H : %M') }}</div>
{% endfor %}
</div>
<div class="row justify-content-center">
<div class="col-md-4 align-self-center">
<button class="form-control mt-5 btn btn-dark" onclick="next()">
Next
</button>
</div>
</div>
{% endif %}
</div>
</div>
<!-- End of main content -->
{% endblock %}

View File

@ -1,31 +0,0 @@
let time_slot_divs = document.getElementsByClassName('time-slot');
function get_available_slots() {
frappe.db
}
function select_time() {
if (this.classList.contains("unavailable")) {
return
}
console.log(this.id)
try{
selected_element = document.getElementsByClassName('selected')[0]
}catch(e){
this.classList.add('selected')
}
selected_element.classList.remove('selected');
this.classList.add('selected');
}
for (var i = 0; i < time_slot_divs.length; i++) {
time_slot_divs[i].addEventListener('click', select_time);
}
function next() {
let urlParams = new URLSearchParams(window.location.search);
let date = urlParams.get("date");
let tz = urlParams.get("tz");
let time_slot = document.querySelector(".selected").id;
window.location.href = `/book-appointment/3?date=${date}&tz=${tz}&time=${time_slot}`;
}

View File

@ -1,93 +0,0 @@
import frappe
import datetime
def get_context(context):
# Get query parameters
date = frappe.form_dict['date']
tz = frappe.form_dict['tz']
tz = int(tz)
# Database queries
settings = frappe.get_doc('Appointment Booking Settings')
holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
# Format datetimes
format_string = '%Y-%m-%d %H:%M:%S'
start_time = datetime.datetime.strptime(date+' 00:00:00', format_string)
end_time = datetime.datetime.strptime(date+' 23:59:59', format_string)
# Convert to ist
start_time = _convert_to_ist(start_time, tz)
end_time = _convert_to_ist(end_time, tz)
timeslots = get_available_slots_between(start_time, end_time, settings)
converted_timeslots = []
print('Appointments')
print(frappe.get_list('Appointment',fields=['from_time']))
for timeslot in timeslots:
if timeslot > end_time or timeslot < start_time:
pass
else:
if frappe.db.count('Appointment',{'from_time':start_time.time()}) < settings.number_of_agents:
converted_timeslots.append(dict(time=_convert_to_tz(timeslot, tz), unavailable=False))
else:
converted_timeslots.append(dict(time=_convert_to_tz(timeslot, tz),unavailable=True))
context.timeslots = converted_timeslots
context.date = date
return context
def _is_holiday(date, holiday_list):
for holiday in holiday_list.holidays:
if holiday.holiday_date.isoformat() == date:
return True
return False
def _convert_to_ist(datetime_object, timezone):
offset = datetime.timedelta(minutes=timezone)
datetime_object = datetime_object + offset
offset = datetime.timedelta(minutes=-330)
datetime_object = datetime_object - offset
return datetime_object
def _convert_to_tz(datetime_object, timezone):
offset = datetime.timedelta(minutes=timezone)
datetime_object = datetime_object - offset
offset = datetime.timedelta(minutes=-330)
datetime_object = datetime_object + offset
return datetime_object
def get_available_slots_between(start_time_parameter, end_time_parameter, settings):
records = get_records(start_time_parameter, end_time_parameter, settings)
timeslots = []
appointment_duration = datetime.timedelta(
minutes=settings.appointment_duration)
for record in records:
if record.day_of_week == weekdays[start_time_parameter.weekday()]:
current_time = _deltatime_to_datetime(
start_time_parameter, record.from_time)
end_time = _deltatime_to_datetime(
start_time_parameter, record.to_time)
elif record.day_of_week == weekdays[end_time_parameter.weekday()]:
current_time = _deltatime_to_datetime(
end_time_parameter, record.from_time)
end_time = _deltatime_to_datetime(
end_time_parameter, record.to_time)
while current_time + appointment_duration <= end_time:
timeslots.append(current_time)
current_time += appointment_duration
return timeslots
def get_records(start_time, end_time, settings):
records = []
for record in settings.availability_of_slots:
if record.day_of_week == weekdays[start_time.weekday()] or record.day_of_week == weekdays[end_time.weekday()]:
records.append(record)
return records
def _deltatime_to_datetime(date, deltatime):
time = (datetime.datetime.min + deltatime).time()
return datetime.datetime.combine(date.date(), time)
weekdays = ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"]

View File

@ -1,22 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Book Appointment") }}{% endblock %}
{% block page_content %}
<div class="container">
<div class="text-center mb-5">
<h3>Add details</h3>
<h4>Selected date is {{ date }} at {{ time }}</h4>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 align-items-center">
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name" required>
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="Contact Number" required>
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype" required>
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10" placeholder="Notes"></textarea>
<button class="btn btn-primary form-control mt-3" onclick="submit()">Submit</button>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,11 +0,0 @@
function submit(){
let params = new URLSearchParams(window.location.search);
const date = params.get('date');
const time = params.get('time');
const tz = params.get('tz');
const customer_name = document.getElementById('customer_name').value;
const customer_number = document.getElementById('customer_number').value;
const customer_skype = document.getElementById('customer_skype').value;
const customer_notes = document.getElementById('customer_notes').value;
console.log({date,time,tz,customer_name,customer_number,customer_skype,customer_notes});
}

View File

@ -0,0 +1,25 @@
.time-slot {
margin: 0 0;
border: 0.5px solid #cccccc;
min-height: 100px;
}
.time-slot:hover {
background: #ddd;
}
.time-slot.unavailable {
background: #bbb;
color: #777777
}
input[type="radio"] {
visibility: hidden;
display: none;
}
.time-slot.selected {
color: white;
background: #5e64ff;
}

View File

@ -0,0 +1,70 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Book Appointment") }}{% endblock %}
{% block page_content %}
<div class="container">
<!-- title: Book an appointment -->
<div id="select-date">
<div class="text-center mb-5">
<h3>Book an appointment</h3>
<p class="lead">Select the date and your timezone</p>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 align-self-center ">
<form name="myform">
<input type="date" onchange="validate_date()" name="appointment-date" id="appointment-date"
class="form-control mt-3">
<select name="appointment-timezone" id="appointment-timezone" class="form-control mt-3">
</select>
</form>
<button class="form-control mt-3 btn btn-dark" id="next-button" onclick="navigate_to_time_select()">
Next
</button>
</div>
</div>
</div>
<!--Select Time Slot-->
<div id="select-time">
<div class="text-center mb-5">
<h3>Pick A Time Slot</h3>
<p class="lead">Selected date is <span class="date-span">Date Span</span></p>
</div>
<div class="mt-3 justify-content-center">
<div class="row" id="timeslot-container">
</div>
<div class="row justify-content-center">
<div class="col-md-4 align-self-center">
<button class="form-control mt-5 btn btn-dark" onclick="initialise_enter_details()">
Next
</button>
</div>
</div>
</div>
</div>
<!--Enter Details-->
<div id="enter-details">
<div class="text-center mb-5">
<h3>Add details</h3>
<p class="lead">Selected date is <span class="date-span">Date Span</span> at <span class="time-span"> time </span></p>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 align-items-center">
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name"
required>
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number"
placeholder="Contact Number" required>
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype"
required>
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
placeholder="Notes"></textarea>
<button class="btn btn-primary form-control mt-3" onclick="submit()">Submit</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,146 @@
frappe.ready(() => {
initialise_select_date()
})
var holiday_list = [];
function navigator(page_no) {
let select_date_div = document.getElementById('select-date');
select_date_div.style.display = 'none';
let select_time_div = document.getElementById('select-time');
select_time_div.style.display = 'none';
let contact_details_div = document.getElementById('enter-details');
contact_details_div.style.display = 'none';
let page;
switch (page_no) {
case 1: page = select_date_div; break;
case 2: page = select_time_div; break;
case 3: page = contact_details_div; break;
}
page.style.display = 'block'
}
// Page 1
async function initialise_select_date() {
navigator(1);
let timezones, settings;
settings = (await frappe.call({
method: 'erpnext.www.book-appointment.index.get_appointment_settings'
})).message
timezones = (await frappe.call({
method: 'erpnext.www.book-appointment.index.get_timezones'
})).message;
holiday_list = (await frappe.call({
method: 'erpnext.www.book-appointment.index.get_holiday_list',
args: {
'holiday_list_name': settings.holiday_list
}
})).message;
let date_picker = document.getElementById('appointment-date');
date_picker.max = holiday_list.to_date;
date_picker.min = holiday_list.from_date;
date_picker.value = (new Date()).toISOString().substr(0, 10);
let timezones_element = document.getElementById('appointment-timezone');
var offset = new Date().getTimezoneOffset();
timezones.forEach(timezone => {
var opt = document.createElement('option');
opt.value = timezone.offset;
opt.innerHTML = timezone.timezone_name;
opt.defaultSelected = (offset == timezone.offset)
timezones_element.appendChild(opt)
});
}
function validate_date() {
let date_picker = document.getElementById('appointment-date');
if (date_picker.value === '') {
frappe.throw('Please select a date')
}
}
// Page 2
async function navigate_to_time_select() {
navigator(2);
timezone = document.getElementById('appointment-timezone').value
date = document.getElementById('appointment-date').value;
var date_spans = document.getElementsByClassName('date-span');
for (var i = 0; i < date_spans.length; i++) date_spans[i].innerHTML = date;
// date_span.addEventListener('click',initialise_select_date)
// date_span.style.color = '#5e64ff';
// date_span.style.textDecoration = 'underline';
// date_span.style.cursor = 'pointer';
var slots = (await frappe.call({
method: 'erpnext.www.book-appointment.index.get_appointment_slots',
args: {
date: date,
timezone: timezone
}
})).message;
let timeslot_container = document.getElementById('timeslot-container');
console.log(slots)
if (slots.length <= 0) {
let message_div = document.createElement('p');
message_div.innerHTML = "There are no slots available on this date";
timeslot_container.appendChild(message_div);
}
for (let i = 0; i < slots.length; i++) {
const slot = slots[i];
var timeslot_div = document.createElement('div');
timeslot_div.classList.add('time-slot');
timeslot_div.classList.add('col-md');
if (!slot.availability) {
timeslot_div.classList.add('unavailable')
}
timeslot_div.innerHTML = slot.time.substr(11, 20);
timeslot_div.id = slot.time.substr(11, 20);
timeslot_container.appendChild(timeslot_div);
}
set_default_timeslot()
let time_slot_divs = document.getElementsByClassName('time-slot');
for (var i = 0; i < time_slot_divs.length; i++) {
time_slot_divs[i].addEventListener('click', select_time);
}
}
function select_time() {
if (this.classList.contains("unavailable")) {
return
}
try {
selected_element = document.getElementsByClassName('selected')[0]
} catch (e) {
this.classList.add("selected")
}
selected_element.classList.remove("selected");
this.classList.add("selected");
}
function set_default_timeslot() {
let timeslots = document.getElementsByClassName('time-slot')
for (let i = 0; i < timeslots.length; i++) {
const timeslot = timeslots[i];
if (!timeslot.classList.contains('unavailable')) {
timeslot.classList.add("selected");
break;
}
}
}
function initialise_enter_details() {
navigator(3);
let time_div = document.getElementsByClassName('selected')[0];
let time_span = document.getElementsByClassName('time-span')[0];
time_span.innerHTML = time_div.id
}
function submit() {
var date = document.getElementById('appointment-date').value;
var time = document.getElementsByClassName('selected')[0].id;
contact = {};
contact.name = document.getElementById('customer_name').value;
contact.number = document.getElementById('customer_number').value;
contact.skype = document.getElementById('customer_skype').value;
contact.notes = document.getElementById('customer_notes').value;
console.log({ date, time, contact });
}

View File

@ -0,0 +1,123 @@
import frappe
import datetime
@frappe.whitelist(allow_guest=True)
def get_appointment_settings():
settings = frappe.get_doc('Appointment Booking Settings')
return settings
@frappe.whitelist(allow_guest=True)
def get_holiday_list(holiday_list_name):
holiday_list = frappe.get_doc('Holiday List',holiday_list_name)
return holiday_list
@frappe.whitelist(allow_guest=True)
def get_timezones():
timezones = frappe.get_list('Timezone',fields='*')
return timezones
@frappe.whitelist(allow_guest=True)
def get_appointment_slots(date,timezone):
timezone = int(timezone)
format_string = '%Y-%m-%d %H:%M:%S'
query_start_time = datetime.datetime.strptime(date + ' 00:00:00',format_string)
query_end_time = datetime.datetime.strptime(date + ' 23:59:59',format_string)
query_start_time = _convert_to_ist(query_start_time,timezone)
query_end_time = _convert_to_ist(query_end_time,timezone)
# Database queries
settings = frappe.get_doc('Appointment Booking Settings')
holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
timeslots = get_available_slots_between(query_start_time, query_end_time, settings)
# Filter timeslots based on date
converted_timeslots = []
for timeslot in timeslots:
# Check if holiday
if _is_holiday(timeslot.date(),holiday_list):
converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=False))
continue
# Check availability
if check_availabilty(timeslot,settings):
converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=True))
else:
converted_timeslots.append(dict(time=_convert_to_tz(timeslot,timezone),availability=False))
date_required = datetime.datetime.strptime(date + ' 00:00:00',format_string).date()
converted_timeslots = filter_timeslots(date_required,converted_timeslots)
return converted_timeslots
def get_available_slots_between(query_start_time, query_end_time, settings):
records = _get_records(query_start_time, query_end_time, settings)
timeslots = []
appointment_duration = datetime.timedelta(
minutes=settings.appointment_duration)
for record in records:
if record.day_of_week == WEEKDAYS[query_start_time.weekday()]:
current_time = _deltatime_to_datetime(
query_start_time, record.from_time)
end_time = _deltatime_to_datetime(
query_start_time, record.to_time)
else :
current_time = _deltatime_to_datetime(
query_end_time, record.from_time)
end_time = _deltatime_to_datetime(
query_end_time, record.to_time)
while current_time + appointment_duration <= end_time:
timeslots.append(current_time)
current_time += appointment_duration
return timeslots
@frappe.whitelist(allow_guest=True)
def create_appointment(date,time,contact):
appointment = frappe.frappe.get_doc('Appointment')
appointment.scheduled_time = date
def filter_timeslots(date,timeslots):
filtered_timeslots = []
for timeslot in timeslots:
if(timeslot['time'].date() == date):
filtered_timeslots.append(timeslot)
return filtered_timeslots
def check_availabilty(timeslot,settings):
return frappe.db.count('Appointment',{'scheduled_time':timeslot})<settings.number_of_agents
def _is_holiday(date, holiday_list):
for holiday in holiday_list.holidays:
if holiday.holiday_date == date:
return True
return False
def _get_records(start_time, end_time, settings):
records = []
for record in settings.availability_of_slots:
if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]:
records.append(record)
return records
def _deltatime_to_datetime(date, deltatime):
time = (datetime.datetime.min + deltatime).time()
return datetime.datetime.combine(date.date(), time)
def _datetime_to_deltatime(date_time):
midnight = datetime.datetime.combine(date_time.date(),datetime.time.min)
return (date_time-midnight)
def _convert_to_ist(datetime_object, timezone):
offset = datetime.timedelta(minutes=timezone)
datetime_object = datetime_object + offset
offset = datetime.timedelta(minutes=-330)
datetime_object = datetime_object - offset
return datetime_object
def _convert_to_tz(datetime_object, timezone):
offset = datetime.timedelta(minutes=timezone)
datetime_object = datetime_object - offset
offset = datetime.timedelta(minutes=-330)
datetime_object = datetime_object + offset
return datetime_object
WEEKDAYS = ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"]