/* ---------------------------------------------------------------------------- * Easy!Appointments - Open Source Web Scheduler * * @package EasyAppointments * @author A.Tselegidis * @copyright Copyright (c) Alex Tselegidis * @license https://opensource.org/licenses/GPL-3.0 - GPLv3 * @link https://easyappointments.org * @since v1.0.0 * ---------------------------------------------------------------------------- */ /** * Booking Page * * This module contains functions that implement the book appointment page functionality. Once the "initialize" method * is called the page is fully functional and can serve the appointment booking process. * * Old Name: FrontendBook */ App.Pages.Booking = (function () { /** * Contains terms and conditions consent. * * @type {Object} */ let termsAndConditionsConsent; /** * Contains privacy policy consent. * * @type {Object} */ let privacyPolicyConsent; /** * Determines the functionality of the page. * * @type {Boolean} */ let manageMode = false; /** * This method initializes the book appointment page. */ function initialize() { if (App.Vars.display_cookie_notice) { cookieconsent.initialise({ palette: { popup: { background: '#ffffffbd', text: '#666666' }, button: { background: '#429a82', text: '#ffffff' } }, content: { message: App.Lang.website_using_cookies_to_ensure_best_experience, dismiss: 'OK' } }); $('.cc-link').replaceWith( $('', { 'data-toggle': 'modal', 'data-target': '#cookie-notice-modal', 'href': '#', 'class': 'cc-link', 'text': $('.cc-link').text() }) ); } manageMode = App.Vars.manage_mode; // Initialize page's components (tooltips, datepickers etc). tippy('[data-tippy-content]'); const weekDayId = GeneralFunctions.getWeekDayId(GlobalVariables.firstWeekday); $('#select-date').datepicker({ dateFormat: 'dd-mm-yy', firstDay: weekDayId, minDate: 0, defaultDate: moment().toDate(), dayNames: [ App.Lang.sunday, App.Lang.monday, App.Lang.tuesday, App.Lang.wednesday, App.Lang.thursday, App.Lang.friday, App.Lang.saturday ], dayNamesShort: [ App.Lang.sunday.substr(0, 3), App.Lang.monday.substr(0, 3), App.Lang.tuesday.substr(0, 3), App.Lang.wednesday.substr(0, 3), App.Lang.thursday.substr(0, 3), App.Lang.friday.substr(0, 3), App.Lang.saturday.substr(0, 3) ], dayNamesMin: [ App.Lang.sunday.substr(0, 2), App.Lang.monday.substr(0, 2), App.Lang.tuesday.substr(0, 2), App.Lang.wednesday.substr(0, 2), App.Lang.thursday.substr(0, 2), App.Lang.friday.substr(0, 2), App.Lang.saturday.substr(0, 2) ], monthNames: [ App.Lang.january, App.Lang.february, App.Lang.march, App.Lang.april, App.Lang.may, App.Lang.june, App.Lang.july, App.Lang.august, App.Lang.september, App.Lang.october, App.Lang.november, App.Lang.december ], prevText: App.Lang.previous, nextText: App.Lang.next, currentText: App.Lang.now, closeText: App.Lang.close, onSelect: function (dateText, instance) { App.Http.Booking.getAvailableHours(moment($(this).datepicker('getDate')).format('YYYY-MM-DD')); updateConfirmFrame(); }, onChangeMonthYear: (year, month) => { const currentDate = new Date(year, month - 1, 1); App.Http.Booking.getUnavailableDates( $('#select-provider').val(), $('#select-service').val(), moment(currentDate).format('YYYY-MM-DD') ); } }); $('#select-timezone').val(Intl.DateTimeFormat().resolvedOptions().timeZone); // Bind the event handlers (might not be necessary every time we use this class). bindEventHandlers(); // If the manage mode is true, the appointments data should be loaded by default. if (manageMode) { applyAppointmentData( GlobalVariables.appointmentData, GlobalVariables.providerData, GlobalVariables.customerData ); } else { const $selectProvider = $('#select-provider'); const $selectService = $('#select-service'); // Check if a specific service was selected (via URL parameter). const selectedServiceId = GeneralFunctions.getUrlParameter(location.href, 'service'); if (selectedServiceId && $selectService.find('option[value="' + selectedServiceId + '"]').length > 0) { $selectService.val(selectedServiceId); } $selectService.trigger('change'); // Load the available hours. // Check if a specific provider was selected. const selectedProviderId = GeneralFunctions.getUrlParameter(location.href, 'provider'); if (selectedProviderId && $selectProvider.find('option[value="' + selectedProviderId + '"]').length === 0) { // Select a service of this provider in order to make the provider available in the select box. for (const index in GlobalVariables.availableProviders) { const provider = GlobalVariables.availableProviders[index]; if (provider.id === selectedProviderId && provider.services.length > 0) { $selectService.val(provider.services[0]).trigger('change'); } } } if (selectedProviderId && $selectProvider.find('option[value="' + selectedProviderId + '"]').length > 0) { $selectProvider.val(selectedProviderId).trigger('change'); } } } /** * This method binds the necessary event handlers for the book appointments page. */ function bindEventHandlers() { /** * Event: Timezone "Changed" */ $('#select-timezone').on('change', () => { const date = $('#select-date').datepicker('getDate'); if (!date) { return; } App.Http.Booking.getAvailableHours(moment(date).format('YYYY-MM-DD')); updateConfirmFrame(); }); /** * Event: Selected Provider "Changed" * * Whenever the provider changes the available appointment date - time periods must be updated. */ $('#select-provider').on('change', (event) => { App.Http.Booking.getUnavailableDates( $(event.target).val(), $('#select-service').val(), moment($('#select-date').datepicker('getDate')).format('YYYY-MM-DD') ); updateConfirmFrame(); }); /** * Event: Selected Service "Changed" * * When the user clicks on a service, its available providers should * become visible. */ $('#select-service').on('change', (event) => { const serviceId = $('#select-service').val(); $('#select-provider').empty(); GlobalVariables.availableProviders.forEach((provider) => { // If the current provider is able to provide the selected service, add him to the list box. const canServeService = provider.services.filter((providerServiceId) => Number(providerServiceId) === Number(serviceId)) .length > 0; if (canServeService) { $('#select-provider').append( new Option(provider.first_name + ' ' + provider.last_name, provider.id) ); } }); // Add the "Any Provider" entry. if ($('#select-provider option').length >= 1 && GlobalVariables.displayAnyProvider === '1') { $('#select-provider').prepend( new Option('- ' + App.Lang.any_provider + ' -', 'any-provider', true, true) ); } App.Http.Booking.getUnavailableDates( $('#select-provider').val(), $(event.target).val(), moment($('#select-date').datepicker('getDate')).format('YYYY-MM-DD') ); updateConfirmFrame(); updateServiceDescription(serviceId); }); /** * Event: Next Step Button "Clicked" * * This handler is triggered every time the user pressed the "next" button on the book wizard. * Some special tasks might be performed, depending on the current wizard step. */ $('.button-next').on('click', (event) => { // If we are on the first step and there is not provider selected do not continue with the next step. if ($(event.target).attr('data-step_index') === '1' && !$('#select-provider').val()) { return; } // If we are on the 2nd tab then the user should have an appointment hour selected. if ($(event.target).attr('data-step_index') === '2') { if (!$('.selected-hour').length) { if (!$('#select-hour-prompt').length) { $('
', { 'id': 'select-hour-prompt', 'class': 'text-danger mb-4', 'text': App.Lang.appointment_hour_missing }).prependTo('#available-hours'); } return; } } // If we are on the 3rd tab then we will need to validate the user's input before proceeding to the next // step. if ($(event.target).attr('data-step_index') === '3') { if (!validateCustomerForm()) { return; // Validation failed, do not continue. } else { updateConfirmFrame(); const $acceptToTermsAndConditions = $('#accept-to-terms-and-conditions'); if ($acceptToTermsAndConditions.length && $acceptToTermsAndConditions.prop('checked') === true) { const newTermsAndConditionsConsent = { first_name: $('#first-name').val(), last_name: $('#last-name').val(), email: $('#email').val(), type: 'terms-and-conditions' }; if ( JSON.stringify(newTermsAndConditionsConsent) !== JSON.stringify(termsAndConditionsConsent) ) { termsAndConditionsConsent = newTermsAndConditionsConsent; App.Http.Booking.saveConsent(termsAndConditionsConsent); } } const $acceptToPrivacyPolicy = $('#accept-to-privacy-policy'); if ($acceptToPrivacyPolicy.length && $acceptToPrivacyPolicy.prop('checked') === true) { const newPrivacyPolicyConsent = { first_name: $('#first-name').val(), last_name: $('#last-name').val(), email: $('#email').val(), type: 'privacy-policy' }; if (JSON.stringify(newPrivacyPolicyConsent) !== JSON.stringify(privacyPolicyConsent)) { privacyPolicyConsent = newPrivacyPolicyConsent; App.Http.Booking.saveConsent(privacyPolicyConsent); } } } } // Display the next step tab (uses jquery animation effect). const nextTabIndex = parseInt($(event.target).attr('data-step_index')) + 1; $(event.target) .parents() .eq(1) .hide('fade', () => { $('.active-step').removeClass('active-step'); $('#step-' + nextTabIndex).addClass('active-step'); $('#wizard-frame-' + nextTabIndex).show('fade'); }); }); /** * Event: Back Step Button "Clicked" * * This handler is triggered every time the user pressed the "back" button on the * book wizard. */ $('.button-back').on('click', (event) => { const prevTabIndex = parseInt($(event.target).attr('data-step_index')) - 1; $(event.target) .parents() .eq(1) .hide('fade', () => { $('.active-step').removeClass('active-step'); $('#step-' + prevTabIndex).addClass('active-step'); $('#wizard-frame-' + prevTabIndex).show('fade'); }); }); /** * Event: Available Hour "Click" * * Triggered whenever the user clicks on an available hour * for his appointment. */ $('#available-hours').on('click', '.available-hour', (event) => { $('.selected-hour').removeClass('selected-hour'); $(event.target).addClass('selected-hour'); updateConfirmFrame(); }); if (manageMode) { /** * Event: Cancel Appointment Button "Click" * * When the user clicks the "Cancel" button this form is going to be submitted. We need * the user to confirm this action because once the appointment is cancelled, it will be * delete from the database. * * @param {jQuery.Event} event */ $('#cancel-appointment').on('click', () => { const buttons = [ { text: App.Lang.cancel, click: function () { $('#message-box').dialog('close'); } }, { text: 'OK', click: function () { if ($('#cancel-reason').val() === '') { $('#cancel-reason').css('border', '2px solid #DC3545'); return; } $('#cancel-appointment-form textarea').val($('#cancel-reason').val()); $('#cancel-appointment-form').submit(); } } ]; App.Utils.Message.show( App.Lang.cancel_appointment_title, App.Lang.write_appointment_removal_reason, buttons ); $('