// Constants
const Constants = {
MOBILE_WIDTH_SMALL: 350,
MOBILE_WIDTH: 450,
SMALL_TABLET_WIDTH: 650,
TABLET_WIDTH: 767,
MEDIUM_WIDTH: 991,
DEFAULT_START_TIME: '09:00'
};
// Application State
const AppState = {
eventIdSelected: null, calendar: null, isEditingAppointment: false, isCreating: false, isUserStaffAdmin: true,
};
document.addEventListener("DOMContentLoaded", initializeCalendar);
window.addEventListener('resize', updateCalendarConfig);
document.getElementById('eventDetailsModal').addEventListener('keypress', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
document.getElementById('eventSubmitBtn').click();
}
});
// Throttle resize events
let resizeTimeout;
window.addEventListener('resize', function () {
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
resizeTimeout = setTimeout(function () {
initializeCalendar()
}, 500); // Only rerender at most, every 100ms
});
document.addEventListener("DOMContentLoaded", function () {
// Wait for a 50ms after the DOM is ready before initializing the calendar
setUserStaffAdminFlag().then(() => {
setTimeout(initializeCalendar, 50);
});
});
const AppStateProxy = new Proxy(AppState, {
set(target, property, value) {
console.log(`Setting ${property} to ${value}`)
// Check if the property being changed is 'isCreating'
if (value === true) {
attachEventListeners(); // Attach event listeners if isCreating becomes true
// (didn't check if property is isCreating, since AppStateProxy is only set with it)
}
target[property] = value; // Set the property value
return true; // Indicate successful setting
}
});
function attachEventListeners() {
// Checks if the DOM is already loaded
if (document.readyState === "complete" || document.readyState === "interactive") {
// DOM is already ready, attach event listeners directly
attachEventListenersToDropdown();
} else {
// If the DOM is not yet ready, then wait for the DOMContentLoaded event
document.addEventListener('DOMContentLoaded', function () {
attachEventListenersToDropdown();
});
}
}
function attachEventListenersToDropdown() {
const staffDropdown = document.getElementById('staffSelect');
if (staffDropdown && !staffDropdown.getAttribute('listener-attached')) {
staffDropdown.setAttribute('listener-attached', 'true');
staffDropdown.addEventListener('change', async function () {
const selectedStaffId = this.value;
const servicesDropdown = document.getElementById('serviceSelect');
const services = await fetchServicesForStaffMember(selectedStaffId);
updateServicesDropdown(servicesDropdown, services);
});
}
}
function initializeCalendar() {
const formattedAppointments = formatAppointmentsForCalendar(appointments);
const calendarEl = document.getElementById('calendar');
AppState.calendar = new FullCalendar.Calendar(calendarEl, getCalendarConfig(formattedAppointments));
AppState.calendar.setOption('locale', locale);
AppState.calendar.render();
}
function formatAppointmentsForCalendar(appointments) {
return appointments.map(appointment => ({
id: appointment.id,
title: appointment.service_name,
start: appointment.start_time,
end: appointment.end_time,
client_name: appointment.client_name,
backgroundColor: appointment.background_color,
}));
}
function updateCalendarConfig() {
AppState.calendar.setOption('headerToolbar', getHeaderToolbarConfig());
AppState.calendar.setOption('height', getCalendarHeight());
}
function mobileCheck() {
return window.innerWidth < Constants.MOBILE_WIDTH;
}
function tabletCheck() {
return window.innerWidth < Constants.TABLET_WIDTH;
}
function getEventDisplayedStyle() {
if (mobileCheck()) {
return "list-item";
}
return "block";
}
function getCalendarConfig(events) {
return {
initialView: 'dayGridMonth',
headerToolbar: getHeaderToolbarConfig(),
navLinks: true,
editable: true,
dayMaxEvents: true,
height: getCalendarHeight(),
aspectRatio: 1.0,
themeSystem: 'bootstrap5',
nowIndicator: true,
bootstrapFontAwesome: {
close: 'fa-times',
prev: 'fa-chevron-left',
next: 'fa-chevron-right',
prevYear: 'fa-angle-double-left',
nextYear: 'fa-angle-double-right'
},
defaultView: mobileCheck() ? "basicDay" : "dayGridMonth",
selectable: true,
events: events,
eventDisplay: getEventDisplayedStyle(),
timeZone: timezone,
eventClick: async function (info) {
AppState.eventIdSelected = info.event.id;
await showEventModal(info.event.id, false, false);
},
dateClick: function (info) {
// Retrieve events for the clicked date
const dateEvents = appointments
.filter(event => moment(info.date).isSame(event.start_time, 'day'))
.sort((a, b) => new Date(a.start_time) - new Date(b.start_time));
// Display events in a list below the calendar
displayEventList(dateEvents, info.date);
},
selectAllow: function (info) {
},
dayCellClassNames: function (info) {
const day = info.date.getDay();
if (day === 0 || day === 6) { // 0 = Sunday, 6 = Saturday
return 'highlight-weekend';
}
return ''; // Return empty string for regular days
},
eventDrop: async function (info) {
await validateAndUpdateAppointmentDate(info.event, info.revert);
},
eventDidMount: function (info) {
// If it is a mobile view, we change the event to a dot
if (mobileCheck()) {
// Find the fc-daygrid-event-dot class within the event element
// and change its style to display as a dot
const dotEl = info.el.querySelector('.fc-daygrid-event-dot') || document.createElement('span');
dotEl.classList.add('fc-daygrid-event-dot');
dotEl.style.borderRadius = '50%';
dotEl.style.backgroundColor = info.event.backgroundColor;
// Clear the inner HTML of the event element and append the dot
info.el.innerHTML = '';
info.el.appendChild(dotEl);
}
},
dayCellDidMount: function (dayCell) {
// Check if the day is in the past
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0); // Reset time part to compare only dates
if (dayCell.date >= currentDate && !tabletCheck()) {
// Attach right-click event listener only if the day is not in the past
if (AppState.isUserStaffAdmin) {
dayCell.el.addEventListener('contextmenu', function (event) {
event.preventDefault();
handleCalendarRightClick(event, dayCell.date);
});
}
}
},
};
}
function displayEventList(events, date) {
let eventListHtml = '
' + eventsOnTxt + ' ' + moment(date).format('MMMM Do, YYYY') + ' ';
eventListHtml += ' ';
events.forEach(function (event) {
eventListHtml += `${event.service_name}
`;
eventListHtml += ` ${moment(event.start_time).format('h:mm a')} - ${moment(event.end_time).format('h:mm a')}
`;
eventListHtml += ' ';
});
const date_obj = new Date(date.toISOString())
if (events.length === 0) {
eventListHtml += `` + noEventTxt + `
`;
}
eventListHtml += `` + newEventTxt + ` `;
const eventListContainer = document.getElementById('event-list-container');
eventListContainer.innerHTML = eventListHtml;
// Add click event listeners to each event item
const eventItems = eventListContainer.getElementsByClassName('event-list-item-appt');
for (let item of eventItems) {
item.addEventListener('click', function () {
const eventId = this.getAttribute('data-event-id');
AppState.eventIdSelected = eventId;
showEventModal(eventId, false, false).then(r => r);
});
}
}
function getHeaderToolbarConfig() {
if (window.matchMedia('(max-width: 767px)').matches) {
// Mobile configuration
return {
left: 'title', right: 'prev,next,dayGridMonth,timeGridDay'
};
} else if (window.matchMedia('(max-width: 991px)').matches) {
// Tablet configuration
return {
left: 'prev,today,next', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay'
};
} else {
// Desktop configuration
return {
left: 'prevYear,prev,today,next,nextYear',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
prevYear: 'prevYear',
nextYear: 'nextYear'
};
}
}
function getCalendarHeight() {
if (window.innerWidth <= Constants.MOBILE_WIDTH_SMALL) return '400px';
if (window.innerWidth <= Constants.MOBILE_WIDTH) return '450px';
if (window.innerWidth <= Constants.SMALL_TABLET_WIDTH) return '600px';
if (window.innerWidth <= Constants.TABLET_WIDTH) return '650px';
if (window.innerWidth <= Constants.MEDIUM_WIDTH) return '767px';
return '850px';
}
function setUserStaffAdminFlag() {
return fetch(isUserStaffAdminURL)
.then(response => response.json())
.then(data => {
if (data.is_staff_admin) {
AppState.isUserStaffAdmin = true;
} else {
console.error(data.message || "Error fetching user details.");
AppState.isUserStaffAdmin = false;
}
})
.catch(error => {
console.error("Error checking user's staff admin status: ", error);
AppState.isUserStaffAdmin = false;
});
}
function handleCalendarRightClick(event, date) {
if (!AppState.isUserStaffAdmin) {
showErrorModal(notStaffMemberTxt)
return;
}
const contextMenu = document.getElementById("customContextMenu");
contextMenu.style.top = `${event.pageY}px`;
contextMenu.style.left = `${event.pageX}px`;
contextMenu.style.display = 'block';
const newAppointmentOption = document.getElementById("newAppointmentOption");
newAppointmentOption.onclick = () => createNewAppointment(date);
// Hide context menu on any click
document.addEventListener('click', () => contextMenu.style.display = 'none', {once: true});
}
function goToEvent() {
// Get the event URL
const event = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
if (event && event.url) {
closeModal()
window.location.href = event.url;
} else {
console.error("Event not found or doesn't have a URL.");
}
}
function closeModal() {
const modal = document.getElementById("eventDetailsModal");
const editButton = document.getElementById("eventEditBtn");
const submitButton = document.getElementById("eventSubmitBtn");
const closeButton = modal.querySelector(".btn-secondary[data-dismiss='modal']");
const cancelButton = document.getElementById("eventCancelBtn");
// Reset the modal buttons to their default state
editButton.style.display = "";
closeButton.style.display = "";
submitButton.style.display = "none";
cancelButton.style.display = "none";
// Reset the editing flag
AppStateProxy.isEditingAppointment = false;
// Close the modal
$('#eventDetailsModal').modal('hide');
}
// ################################################################ //
// Generic //
// ################################################################ //
async function cancelEdit() {
// Retrieve the appointment that matches the eventIdSelected
const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
if (!appointment) {
return;
}
// Extract only the time using Moment.js
const endTime = moment(appointment.end_time).format('HH:mm:ss');
// Find the modal, end time label, and end time input
const modal = document.getElementById("eventDetailsModal");
const endTimeLabel = modal.querySelector("label[for='endTime']");
const endTimeInput = modal.querySelector("input[name='endTime']");
// Set and display the end time label and input
endTimeInput.value = endTime;
endTimeLabel.style.display = "";
endTimeInput.style.display = "";
// Re-show the event modal with the original data
await showEventModal(appointment.id, false, false);
toggleEditMode(); // Turn off edit mode
}
function confirmDeleteAppointment(appointmentId) {
const deleteURL = deleteAppointmentURLTemplate
const data = {appointment_id: appointmentId};
fetch(deleteURL, {
method: 'POST', headers: {
'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'),
}, body: JSON.stringify(data)
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
$('#eventDetailsModal').modal('hide');
let event = AppState.calendar.getEventById(appointmentId);
if (event) {
event.remove();
}
showErrorModal(data.message, successTxt);
closeConfirmModal(); // Close the confirmation modal
// Remove the deleted appointment from the global appointments array
appointments = appointments.filter(appointment => appointment.id !== appointmentId);
// Refresh the event list for the current date
const currentDate = AppState.calendar.getDate();
const dateEvents = appointments
.filter(event => moment(currentDate).isSame(event.start_time, 'day'))
.sort((a, b) => new Date(a.start_time) - new Date(b.start_time));
displayEventList(dateEvents, currentDate);
})
.catch(error => {
console.error('Error:', error);
showErrorModal(updateApptErrorTitleTxt);
});
}
function deleteAppointment() {
showModal(confirmDeletionTxt, confirmDeletionTxt, deleteBtnTxt, null, () => confirmDeleteAppointment(AppState.eventIdSelected));
}
function fetchServices(isEditMode = false) {
let url = isEditMode && AppState.eventIdSelected ? `${fetchServiceListForStaffURL}?appointmentId=${AppState.eventIdSelected}` : fetchServiceListForStaffURL;
return fetch(url)
.then(response => response.json())
.then(data => data.services_offered)
.catch(error => console.error("Error fetching services: ", error));
}
function fetchStaffMembers(isEditMode = false) {
let url = isEditMode && AppState.eventIdSelected ? `${fetchStaffListURL}?appointmentId=${AppState.eventIdSelected}` : fetchStaffListURL;
return fetch(url)
.then(response => response.json())
.then(data => data.staff_members)
.catch(error => console.error("Error fetching staff members: ", error));
}
async function populateServices(selectedServiceId, isEditMode = false) {
const services = await fetchServices(isEditMode);
if (!services) {
showErrorModal(noServiceOfferedTxt)
}
const selectElement = document.createElement('select');
services.forEach(service => {
const option = document.createElement('option');
option.value = service.id; // Accessing the id
option.textContent = service.name; // Accessing the name
if (service.id === selectedServiceId) {
option.defaultSelected = true;
}
selectElement.appendChild(option);
});
return selectElement;
}
async function populateStaffMembers(selectedStaffId, isEditMode = false) {
const staffMembers = await fetchStaffMembers(isEditMode);
if (!staffMembers) {
showErrorModal(noStaffMemberTxt)
}
const selectElement = document.createElement('select');
staffMembers.forEach(staff => {
const option = document.createElement('option');
option.value = staff.id; // Accessing the id
option.textContent = staff.name; // Accessing the name
if (staff.id === selectedStaffId) {
option.defaultSelected = true;
}
selectElement.appendChild(option);
});
return selectElement;
}
// Function to fetch services for a specific staff member
async function fetchServicesForStaffMember(staffId) {
const url = `${fetchServiceListForStaffURL}?staff_member=${staffId}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
return data.services_offered || [];
} catch (error) {
console.error("Error fetching services: ", error);
return []; // Return an empty array in case of error
}
}
// Function to update services dropdown options
function updateServicesDropdown(dropdown, services) {
// Clear existing options
dropdown.innerHTML = '';
// Populate with new options
services.forEach(service => {
const option = new Option(service.name, service.id); // Assuming service object has id and name properties
dropdown.add(option);
});
}
/*
function getCSRFToken() {
const metaTag = document.querySelector('meta[name="csrf-token"]');
if (metaTag) {
return metaTag.getAttribute('content');
} else {
console.error("CSRF token meta tag not found!");
return null;
}
}
*/
async function validateAndUpdateAppointmentDate(event, revertFunction) {
const updatedStartTime = event.start.toISOString();
const updatedEndTime = event.end ? event.end.toISOString() : null;
const data = {
appointment_id: event.id, start_time: updatedStartTime, date: event.start.toISOString().split('T')[0]
};
try {
const validationResponse = await fetch(validateApptDateURL, {
method: 'POST', headers: {
'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'),
}, body: JSON.stringify(data)
});
if (validationResponse.ok) {
await updateAppointmentDate(event, revertFunction);
} else {
const responseData = await validationResponse.json();
showErrorModal(responseData.message);
revertFunction();
}
} catch (error) {
console.error('Failed to validate data:', error);
revertFunction();
}
}
async function updateAppointmentDate(event, revertFunction) {
const updatedStartTime = event.start.toISOString().split('T')[1];
const updatedDate = event.start.toISOString().split('T')[0];
const data = {
appointment_id: event.id, start_time: updatedStartTime, date: updatedDate,
};
try {
const response = await fetch(updateApptDateURL, {
method: 'POST', headers: {
'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'),
}, body: JSON.stringify(data)
});
const responseData = await response.json();
if (response.ok) {
showErrorModal(responseData.message, successTxt)
} else {
console.error('Failed to update appointment date. Server responded with:', response.statusText);
showErrorModal(responseData.message, updateApptErrorTitleTxt);
revertFunction();
}
} catch (error) {
console.error('Failed to send data:', error);
revertFunction();
}
}
// ################################################################ //
// Create new Appt //
// ################################################################ //
function createNewAppointment(dateInput) {
let date;
if (typeof dateInput === 'string' || dateInput instanceof String) {
date = new Date(dateInput);
} else {
date = dateInput;
}
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // getMonth() returns 0-11
const year = date.getFullYear();
const formattedDate = `${year}-${month}-${day}`;
const defaultStartTime = `${formattedDate}T09:00:00`;
showCreateAppointmentModal(defaultStartTime, formattedDate).then(() => {
});
}
async function showCreateAppointmentModal(defaultStartTime, formattedDate) {
const servicesDropdown = await populateServices(null, false);
let staffDropdown = null;
if (isUserSuperUser) {
staffDropdown = await populateStaffMembers(null, false);
staffDropdown.id = "staffSelect";
staffDropdown.disabled = false; // Enable dropdown
attachEventListenersToDropdown(); // Attach event listener
}
servicesDropdown.id = "serviceSelect";
servicesDropdown.disabled = false; // Enable dropdown
document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate);
adjustCreateAppointmentModalButtons();
AppStateProxy.isCreating = true;
$('#eventDetailsModal').modal('show');
}
function adjustCreateAppointmentModalButtons() {
document.getElementById("eventSubmitBtn").style.display = "";
document.getElementById("eventCancelBtn").style.display = "none";
document.getElementById("eventEditBtn").style.display = "none";
document.getElementById("eventDeleteBtn").style.display = "none";
document.getElementById("eventGoBtn").style.display = "none";
}
// ################################################################ //
// Show Event Modal //
// ################################################################ //
// Extract Appointment Data
async function getAppointmentData(eventId, isCreatingMode, defaultStartTime) {
if (eventId && !isCreatingMode) {
const appointment = appointments.find(app => Number(app.id) === Number(eventId));
if (!appointment) {
showErrorModal(apptNotFoundTxt, errorTxt);
return null;
}
return appointment;
}
return {
id: null,
service_name: '',
start_time: defaultStartTime,
end_time: '',
client_name: '',
client_email: '',
client_phone: '',
client_address: '',
additional_info: '',
want_reminder: false,
background_color: '',
timezone: '',
};
}
// Populate Services Dropdown
async function getServiceDropdown(serviceId, isEditMode) {
const servicesDropdown = await populateServices(serviceId, !isEditMode);
servicesDropdown.id = "serviceSelect";
servicesDropdown.disabled = !isEditMode;
return servicesDropdown;
}
// Populate Staff Dropdown
async function getStaffDropdown(staffId, isEditMode) {
const staffDropdown = await populateStaffMembers(staffId, !isEditMode);
staffDropdown.id = "staffSelect";
staffDropdown.disabled = !isEditMode;
return staffDropdown;
}
// Show Event Modal
async function showEventModal(eventId = null, isEditMode, isCreatingMode = false, defaultStartTime = '') {
const appointment = await getAppointmentData(eventId, isCreatingMode, defaultStartTime);
if (!appointment) return;
const servicesDropdown = await getServiceDropdown(appointment.service_id, isEditMode);
let staffDropdown = null;
if (isUserSuperUser) {
staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
attachEventListenersToDropdown(); // Attach event listener
}
document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown);
adjustModalButtonsVisibility(isEditMode, isCreatingMode);
$('#eventDetailsModal').modal('show');
}
// Adjust Modal Buttons Visibility
function adjustModalButtonsVisibility(isEditMode, isCreatingMode) {
const editButton = document.getElementById("eventEditBtn");
const submitButton = document.getElementById("eventSubmitBtn");
const deleteButton = document.getElementById("eventDeleteBtn");
const goButton = document.getElementById("eventGoBtn");
editButton.style.display = !isEditMode && !isCreatingMode ? "" : "none";
submitButton.style.display = isCreatingMode || isEditMode ? "" : "none";
deleteButton.style.display = !isEditMode && !isCreatingMode ? "" : "none";
goButton.style.display = isCreatingMode ? "none" : "";
}
// ################################################################ //
// Edit Logic //
// ################################################################ //
function toggleEditMode() {
const modal = document.getElementById("eventDetailsModal");
const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
AppStateProxy.isCreating = false; // Turn off creating mode
// Proceed only if an appointment is found
if (appointment) {
AppStateProxy.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state
updateModalUIForEditMode(modal, AppState.isEditingAppointment);
} else {
console.error("Appointment not found!");
}
}
function updateModalUIForEditMode(modal, isEditingAppointment) {
const inputs = modal.querySelectorAll("input");
const staffDropdown = document.getElementById("staffSelect");
const servicesDropdown = document.getElementById("serviceSelect");
const editButton = document.getElementById("eventEditBtn");
const submitButton = document.getElementById("eventSubmitBtn");
const closeButton = modal.querySelector(".btn-secondary[data-dismiss='modal']");
const cancelButton = document.getElementById("eventCancelBtn");
const deleteButton = document.getElementById("eventDeleteBtn");
const goButton = document.getElementById("eventGoBtn");
const endTimeLabel = modal.querySelector("label[for='endTime']");
const endTimeInput = modal.querySelector("input[name='endTime']");
// Toggle input and dropdown enable/disable state
inputs.forEach(input => input.disabled = !isEditingAppointment);
staffDropdown.disabled = !isEditingAppointment;
servicesDropdown.disabled = !isEditingAppointment;
// Toggle visibility of UI elements
toggleElementVisibility(editButton, !isEditingAppointment);
toggleElementVisibility(submitButton, isEditingAppointment);
toggleElementVisibility(cancelButton, isEditingAppointment);
toggleElementVisibility(deleteButton, !isEditingAppointment);
toggleElementVisibility(closeButton, !isEditingAppointment);
toggleElementVisibility(endTimeLabel, !isEditingAppointment); // Show end time in view mode
toggleElementVisibility(endTimeInput, !isEditingAppointment); // Show end time in view mode
toggleElementVisibility(goButton, !isEditingAppointment);
}
function toggleElementVisibility(element, isVisible) {
if (element) {
element.style.display = isVisible ? "" : "none";
}
}
// ################################################################ //
// Submit Logic //
// ################################################################ //
async function submitChanges() {
const modal = document.getElementById("eventDetailsModal");
const formData = collectFormDataFromModal(modal);
if (!validateFormData(formData)) return;
const response = await sendAppointmentData(formData);
if (response.ok) {
const responseData = await response.json();
if (AppState.isCreating) {
addNewAppointmentToCalendar(responseData.appt[0]);
} else {
updateExistingAppointmentInCalendar(responseData.appt);
}
AppState.calendar.render();
} else {
const responseData = await response.json();
showErrorModal(responseData.message);
}
closeModal();
}
// Collect form data from modal
function collectFormDataFromModal(modal) {
const inputs = modal.querySelectorAll("input");
const serviceId = modal.querySelector("#serviceSelect").value;
let staffId = null;
if (isUserSuperUser) {
// If the user is a superuser, get the staff ID from the dropdown
const staffDropdown = modal.querySelector("#staffSelect");
if (staffDropdown) {
staffId = staffDropdown.value;
}
}
const data = {
isCreating: AppState.isCreating,
service_id: serviceId,
appointment_id: AppState.eventIdSelected
};
if (staffId) {
data.staff_member = staffId;
}
inputs.forEach(input => {
if (input.name !== "date") {
let key = input.name.replace(/([A-Z])/g, '_$1').toLowerCase();
data[key] = input.value;
}
});
if (AppState.isCreating) {
data["date"] = modal.querySelector('input[name="date"]').value;
}
// Special handling for checkbox
const wantReminderCheckbox = modal.querySelector('input[name="want_reminder"]');
if (!wantReminderCheckbox.checked) {
data['want_reminder'] = 'false';
} else {
data['want_reminder'] = 'true';
}
return data;
}
// Validate form data
function validateFormData(data) {
return validateEmail(data["client_email"]);
}
// Validate email
function validateEmail(email) {
const emailInput = document.querySelector('input[name="clientEmail"]');
const emailError = document.getElementById("emailError");
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(emailInput.value)) {
emailInput.style.border = "1px solid red";
emailError.textContent = "Invalid email address, yeah.";
emailError.style.color = "red";
emailError.style.display = "inline";
return false;
} else {
emailInput.style.border = "";
emailError.textContent = "";
emailError.style.display = "none";
return true;
}
}
// Send appointment data to server
async function sendAppointmentData(data) {
const headers = {
'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'),
};
return fetch(updateApptMinInfoURL, {
method: 'POST', headers: headers, body: JSON.stringify(data)
});
}
// Add new appointment to calendar
function addNewAppointmentToCalendar(newAppointment) {
const newEvent = formatAppointmentsForCalendar([newAppointment])[0];
appointments.push(newAppointment);
AppState.calendar.addEvent(newEvent);
}
// Update existing appointment in calendar
function updateExistingAppointmentInCalendar(appointment) {
let eventToUpdate = AppState.calendar.getEventById(AppState.eventIdSelected);
if (eventToUpdate) {
updateEventProperties(eventToUpdate, appointment);
}
// update appointment in appointments array
const index = appointments.findIndex(app => Number(app.id) === Number(AppState.eventIdSelected));
if (index !== -1) {
appointments[index] = appointment;
}
}
// Update event properties
function updateEventProperties(event, appointment) {
event.setProp('title', appointment.service_name);
event.setStart(moment(appointment.start_time).format('YYYY-MM-DDTHH:mm:ss'));
event.setEnd(appointment.end_time);
event.setExtendedProp('client_name', appointment.client_name);
event.setProp('backgroundColor', appointment.background_color);
}