371 lines
15 KiB
JavaScript
371 lines
15 KiB
JavaScript
const calendarEl = document.getElementById('calendar');
|
|
let nextAvailableDateSelector = $('.djangoAppt_next-available-date')
|
|
const body = $('body');
|
|
let nonWorkingDays = [];
|
|
let selectedDate = rescheduledDate || null;
|
|
let staffId = $('#staff_id').val() || null;
|
|
let previouslySelectedCell = null;
|
|
let isRequestInProgress = false;
|
|
|
|
const calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: 'dayGridMonth',
|
|
initialDate: selectedDate,
|
|
timeZone: timezone,
|
|
headerToolbar: {
|
|
left: 'title',
|
|
right: 'prev,today,next',
|
|
},
|
|
|
|
themeSystem: 'bootstrap',
|
|
nowIndicator: true,
|
|
bootstrapFontAwesome: {
|
|
close: 'fa-times',
|
|
prev: 'fa-chevron-left',
|
|
next: 'fa-chevron-right',
|
|
prevYear: 'fa-angle-double-left',
|
|
nextYear: 'fa-angle-double-right'
|
|
},
|
|
selectable: true,
|
|
dateClick: function (info) {
|
|
const day = info.date.getDay(); // Get the day of the week (0 for Sunday, 6 for Saturday)
|
|
if (nonWorkingDays.includes(day)) {
|
|
return;
|
|
}
|
|
|
|
// If there's a previously selected cell, remove the class
|
|
if (previouslySelectedCell) {
|
|
previouslySelectedCell.classList.remove('selected-cell');
|
|
}
|
|
|
|
// Add the class to the currently clicked cell
|
|
info.dayEl.classList.add('selected-cell');
|
|
|
|
// Store the currently clicked cell
|
|
previouslySelectedCell = info.dayEl;
|
|
|
|
selectedDate = info.dateStr;
|
|
getAvailableSlots(info.dateStr, staffId);
|
|
},
|
|
datesSet: function (info) {
|
|
highlightSelectedDate();
|
|
},
|
|
selectAllow: function (info) {
|
|
const day = info.start.getDay(); // Get the day of the week (0 for Sunday, 6 for Saturday)
|
|
if (nonWorkingDays.includes(day)) {
|
|
return false; // Disallow selection for non-working days
|
|
}
|
|
return (info.start >= getDateWithoutTime(new Date()));
|
|
},
|
|
dayCellClassNames: function (info) {
|
|
const day = info.date.getDay();
|
|
if (nonWorkingDays.includes(day)) {
|
|
return ['disabled-day'];
|
|
}
|
|
return [];
|
|
},
|
|
});
|
|
|
|
calendar.setOption('locale', locale);
|
|
|
|
$(document).ready(function () {
|
|
staffId = $('#staff_id').val() || null;
|
|
calendar.render();
|
|
const currentDate = rescheduledDate || moment.tz(timezone).format('YYYY-MM-DD');
|
|
getAvailableSlots(currentDate, staffId);
|
|
});
|
|
|
|
function highlightSelectedDate() {
|
|
setTimeout(function () {
|
|
const dateCell = document.querySelector(`.fc-daygrid-day[data-date='${selectedDate}']`);
|
|
if (dateCell) {
|
|
dateCell.classList.add('selected-cell');
|
|
previouslySelectedCell = dateCell;
|
|
}
|
|
}, 10);
|
|
}
|
|
|
|
body.on('click', '.djangoAppt_btn-request-next-slot', function () {
|
|
const serviceId = $(this).data('service-id');
|
|
requestNextAvailableSlot(serviceId);
|
|
})
|
|
|
|
body.on('click', '.btn-submit-appointment', function () {
|
|
const selectedSlot = $('.djangoAppt_appointment-slot.selected').text();
|
|
const selectedDate = $('.djangoAppt_date_chosen').text();
|
|
if (!selectedSlot || !selectedDate) {
|
|
alert(selectDateAndTimeAlertTxt);
|
|
return;
|
|
}
|
|
if (selectedSlot && selectedDate) {
|
|
const startTime = convertTo24Hour(selectedSlot);
|
|
const APPOINTMENT_BASE_TEMPLATE = localStorage.getItem('APPOINTMENT_BASE_TEMPLATE');
|
|
// Convert the selectedDate string to a valid format
|
|
const dateParts = selectedDate.split(', ');
|
|
const monthDayYear = dateParts[1] + "," + dateParts[2];
|
|
const formattedDate = new Date(monthDayYear + " " + startTime);
|
|
|
|
const date = formattedDate.toISOString().slice(0, 10);
|
|
const endTimeDate = new Date(formattedDate.getTime() + serviceDuration * 60000);
|
|
const endTime = formatTime(endTimeDate);
|
|
const reasonForRescheduling = $('#reason_for_rescheduling').val();
|
|
const form = $('.appointment-form');
|
|
let formAction = rescheduledDate ? appointmentRescheduleURL : appointmentRequestSubmitURL;
|
|
form.attr('action', formAction);
|
|
if (!form.find('input[name="appointment_request_id"]').length) {
|
|
form.append($('<input>', {
|
|
type: 'hidden',
|
|
name: 'appointment_request_id',
|
|
value: appointmentRequestId
|
|
}));
|
|
}
|
|
form.append($('<input>', {type: 'hidden', name: 'date', value: date}));
|
|
form.append($('<input>', {type: 'hidden', name: 'start_time', value: startTime}));
|
|
form.append($('<input>', {type: 'hidden', name: 'end_time', value: endTime}));
|
|
form.append($('<input>', {type: 'hidden', name: 'service', value: serviceId}));
|
|
form.append($('<input>', {type: 'hidden', name: 'reason_for_rescheduling', value: reasonForRescheduling}));
|
|
form.submit();
|
|
} else {
|
|
const warningContainer = $('.warning-message');
|
|
if (warningContainer.find('submit-warning') === 0) {
|
|
warningContainer.append('<p class="submit-warning">' + selectTimeSlotWarningTxt + '</p>');
|
|
}
|
|
}
|
|
});
|
|
|
|
$('#staff_id').on('change', function () {
|
|
staffId = $(this).val() || null; // If staffId is an empty string, set it to null
|
|
let currentDate = null
|
|
if (selectedDate == null) {
|
|
currentDate = moment.tz(timezone).format('YYYY-MM-DD');
|
|
} else {
|
|
currentDate = selectedDate;
|
|
}
|
|
fetchNonWorkingDays(staffId, function (newNonWorkingDays) {
|
|
nonWorkingDays = newNonWorkingDays; // Update the nonWorkingDays array
|
|
calendar.render(); // Re-render the calendar to apply changes
|
|
|
|
// Fetch available slots for the current date
|
|
getAvailableSlots(currentDate, staffId);
|
|
});
|
|
});
|
|
|
|
|
|
function fetchNonWorkingDays(staffId, callback) {
|
|
if (!staffId || staffId === 'none') {
|
|
nonWorkingDays = []; // Reset nonWorkingDays
|
|
calendar.render(); // Re-render the calendar
|
|
callback([]);
|
|
return; // Exit the function early
|
|
}
|
|
let ajaxData = {
|
|
'staff_member': staffId,
|
|
};
|
|
|
|
$.ajax({
|
|
url: getNonWorkingDaysURL,
|
|
data: ajaxData,
|
|
dataType: 'json',
|
|
success: function (data) {
|
|
if (data.error) {
|
|
console.error('Error fetching non-working days:', data.message);
|
|
callback([]);
|
|
} else {
|
|
nonWorkingDays = data.non_working_days;
|
|
calendar.render();
|
|
callback(data.non_working_days);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function getDateWithoutTime(dt) {
|
|
dt.setHours(0, 0, 0, 0);
|
|
return dt;
|
|
}
|
|
|
|
function convertTo24Hour(time12h) {
|
|
const [time, modifier] = time12h.split(' ');
|
|
let [hours, minutes] = time.split(':');
|
|
|
|
if (hours === '12') {
|
|
hours = '00';
|
|
}
|
|
|
|
if (modifier.toUpperCase() === 'PM') {
|
|
hours = parseInt(hours, 10) + 12;
|
|
}
|
|
|
|
return `${hours}:${minutes}`;
|
|
}
|
|
|
|
function formatTime(date) {
|
|
const hours = date.getHours();
|
|
const minutes = date.getMinutes();
|
|
return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes);
|
|
}
|
|
|
|
function getAvailableSlots(selectedDate, staffId = null) {
|
|
// Update the slot list with the available slots for the selected date
|
|
const slotList = $('#slot-list');
|
|
const slotContainer = $('.slot-container');
|
|
const errorMessageContainer = $('.error-message');
|
|
|
|
// Clear previous error messages and slots
|
|
slotList.empty();
|
|
errorMessageContainer.find('.djangoAppt_no-availability-text').remove();
|
|
|
|
// Remove the "Next available date" message
|
|
nextAvailableDateSelector = $('.djangoAppt_next-available-date'); // Update the selector
|
|
nextAvailableDateSelector.remove();
|
|
|
|
// Correctly check if staffId is 'none', null, or undefined and exit the function if true
|
|
// Check if 'staffId' is 'none', null, or undefined and display an error message
|
|
if (staffId === 'none' || staffId === null || staffId === undefined) {
|
|
console.log('No staff ID provided, displaying error message.');
|
|
const errorMessage = $('<p class="djangoAppt_no-availability-text">' + noStaffMemberSelectedTxt + '</p>');
|
|
errorMessageContainer.append(errorMessage);
|
|
// Optionally disable the "submit" button here
|
|
$('.btn-submit-appointment').attr('disabled', 'disabled');
|
|
return; // Exit the function early
|
|
}
|
|
|
|
let ajaxData = {
|
|
'selected_date': selectedDate,
|
|
'staff_member': staffId,
|
|
};
|
|
fetchNonWorkingDays(staffId, function (nonWorkingDays) {
|
|
// Check if nonWorkingDays is an array
|
|
if (Array.isArray(nonWorkingDays)) {
|
|
// Update the FullCalendar configuration
|
|
// calendar.setOption('hiddenDays', nonWorkingDays);
|
|
} else {
|
|
// Handle the case where there's an error or no data
|
|
// For now, we'll just log it, but you can handle it more gracefully if needed
|
|
console.error('Failed to get non-working days:', nonWorkingDays);
|
|
}
|
|
});
|
|
|
|
// Send an AJAX request to get the available slots for the selected date
|
|
if (isRequestInProgress) {
|
|
return; // Exit the function if a request is already in progress
|
|
}
|
|
isRequestInProgress = true;
|
|
$.ajax({
|
|
url: availableSlotsAjaxURL,
|
|
data: ajaxData,
|
|
dataType: 'json',
|
|
success: function (data) {
|
|
if (data.available_slots.length === 0) {
|
|
const selectedDateObj = moment.tz(selectedDate, timezone);
|
|
const selectedD = selectedDateObj.toDate();
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
if (selectedD < today) {
|
|
// Show an error message
|
|
errorMessageContainer.append('<p class="djangoAppt_no-availability-text">' + dateInPastErrorTxt + '</p>');
|
|
if (slotContainer.find('.djangoAppt_btn-request-next-slot').length === 0) {
|
|
slotContainer.append(`<button class="btn btn-danger djangoAppt_btn-request-next-slot" data-service-id="${serviceId}">` + requestNonAvailableSlotBtnTxt + `</button>`);
|
|
}
|
|
// Disable the 'submit' button
|
|
$('.btn-submit-appointment').attr('disabled', 'disabled');
|
|
} else {
|
|
errorMessageContainer.find('.djangoAppt_no-availability-text').remove();
|
|
if (errorMessageContainer.find('.djangoAppt_no-availability-text').length === 0) {
|
|
errorMessageContainer.append(`<p class="djangoAppt_no-availability-text">${data.message}</p>`);
|
|
}
|
|
// Check if the returned message is 'No availability'
|
|
if (data.message.toLowerCase() === 'no availability') {
|
|
if (slotContainer.find('.djangoAppt_btn-request-next-slot').length === 0) {
|
|
slotContainer.append(`<button class="btn btn-danger djangoAppt_btn-request-next-slot" data-service-id="${serviceId}">` + requestNonAvailableSlotBtnTxt + `</button>`);
|
|
}
|
|
} else {
|
|
$('.djangoAppt_btn-request-next-slot').remove();
|
|
}
|
|
}
|
|
} else {
|
|
// remove the button to request for next available slot
|
|
$('.djangoAppt_no-availability-text').remove();
|
|
$('.djangoAppt_btn-request-next-slot').remove();
|
|
const uniqueSlots = [...new Set(data.available_slots)]; // remove duplicates
|
|
for (let i = 0; i < uniqueSlots.length; i++) {
|
|
slotList.append('<li class="djangoAppt_appointment-slot">' + uniqueSlots[i] + '</li>');
|
|
}
|
|
|
|
// Attach click event to the slots
|
|
$('.djangoAppt_appointment-slot').on('click', function () {
|
|
// Remove the 'selected' class from all other appointment slots
|
|
$('.djangoAppt_appointment-slot').removeClass('selected');
|
|
|
|
// Add the 'selected' class to the clicked appointment slot
|
|
$(this).addClass('selected');
|
|
|
|
// Enable the submit button
|
|
$('.btn-submit-appointment').removeAttr('disabled');
|
|
|
|
// Continue with the existing logic
|
|
const selectedSlot = $(this).text();
|
|
$('#service-datetime-chosen').text(data.date_chosen + ' ' + selectedSlot);
|
|
});
|
|
}
|
|
// Update the date chosen
|
|
$('.djangoAppt_date_chosen').text(data.date_chosen);
|
|
$('#service-datetime-chosen').text(data.date_chosen);
|
|
isRequestInProgress = false;
|
|
},
|
|
error: function() {
|
|
isRequestInProgress = false; // Ensure the flag is reset even if the request fails
|
|
}
|
|
});
|
|
}
|
|
|
|
function requestNextAvailableSlot(serviceId) {
|
|
const requestNextAvailableSlotURL = requestNextAvailableSlotURLTemplate.replace('0', serviceId);
|
|
if (staffId === null) {
|
|
return;
|
|
}
|
|
let ajaxData = {
|
|
'staff_member': staffId,
|
|
};
|
|
$.ajax({
|
|
url: requestNextAvailableSlotURL,
|
|
data: ajaxData,
|
|
dataType: 'json',
|
|
success: function (data) {
|
|
// If there's an error, just log it and return
|
|
let nextAvailableDateResponse = null;
|
|
let formattedDate = null;
|
|
if (data.error) {
|
|
nextAvailableDateResponse = data.message;
|
|
} else {
|
|
// Set the date in the calendar to the next available date
|
|
nextAvailableDateResponse = data.next_available_date;
|
|
const selectedDateObj = moment.tz(nextAvailableDateResponse, timezone);
|
|
const nextAvailableDate = selectedDateObj.toDate();
|
|
formattedDate = new Intl.DateTimeFormat('en-US', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
}).format(nextAvailableDate);
|
|
}
|
|
|
|
// Check if the .next-available-date element already exists
|
|
nextAvailableDateSelector = $('.djangoAppt_next-available-date'); // Update the selector
|
|
let nextAvailableDateText = null;
|
|
if (data.error) {
|
|
nextAvailableDateText = nextAvailableDateResponse;
|
|
} else {
|
|
nextAvailableDateText = `Next available date: ${formattedDate}`;
|
|
}
|
|
if (nextAvailableDateSelector.length > 0) {
|
|
// Update the content of the existing .next-available-date element
|
|
nextAvailableDateSelector.text(nextAvailableDateText);
|
|
} else {
|
|
// If the .next-available-date element doesn't exist, create and append it
|
|
const nextDateText = `<p class="djangoAppt_next-available-date">${nextAvailableDateText}</p>`;
|
|
$('.djangoAppt_btn-request-next-slot').after(nextDateText);
|
|
}
|
|
}
|
|
});
|
|
}
|