From 78b00711a4a2274504c8793e74940858cba79f45 Mon Sep 17 00:00:00 2001 From: Alex Tselegidis Date: Sun, 17 Jul 2016 14:43:50 +0200 Subject: [PATCH] Split the backend_calendar code to appointments modal, unavailabilities modal, AJAX API consumer. --- src/assets/js/backend_calendar.js | 930 +----------------- src/assets/js/backend_calendar_api.js | 88 ++ .../js/backend_calendar_appointments_modal.js | 505 ++++++++++ .../js/backend_calendar_default_view.js | 176 +++- ...backend_calendar_unavailabilities_modal.js | 240 +++++ 5 files changed, 1007 insertions(+), 932 deletions(-) create mode 100644 src/assets/js/backend_calendar_api.js create mode 100644 src/assets/js/backend_calendar_appointments_modal.js create mode 100644 src/assets/js/backend_calendar_unavailabilities_modal.js diff --git a/src/assets/js/backend_calendar.js b/src/assets/js/backend_calendar.js index b711e911..8a685ce2 100644 --- a/src/assets/js/backend_calendar.js +++ b/src/assets/js/backend_calendar.js @@ -22,932 +22,6 @@ window.BackendCalendar = window.BackendCalendar || {}; 'use strict'; - // Variables - var lastFocusedEventData; // Contains event data for later use. - - /** - * Bind Event Handlers - * - * This method binds the default event handlers for the backend calendar page. If you do not need the - * default handlers then initialize the page by setting the "defaultEventHandlers" argument to "false". - */ - function _bindEventHandlers() { - - var $calendarPage = $('#calendar-page'); - - /** - * Event: Reload Button "Click" - * - * When the user clicks the reload button an the calendar items need to be refreshed. - */ - $('#reload-appointments').click(function() { - $('#select-filter-item').trigger('change'); - }); - - /** - * Event: Popover Close Button "Click" - * - * Hides the open popover element. - */ - $calendarPage.on('click', '.close-popover', function() { - $(this).parents().eq(2).remove(); - }); - - /** - * Event: Popover Edit Button "Click" - * - * Enables the edit dialog of the selected calendar event. - */ - $calendarPage.on('click', '.edit-popover', function() { - $(this).parents().eq(2).remove(); // Hide the popover - - var $dialog; - - if (lastFocusedEventData.data.is_unavailable == false) { - var appointment = lastFocusedEventData.data; - $dialog = $('#manage-appointment'); - - _resetAppointmentDialog(); - - // Apply appointment data and show modal dialog. - $dialog.find('.modal-header h3').text(EALang['edit_appointment_title']); - $dialog.find('#appointment-id').val(appointment['id']); - $dialog.find('#select-service').val(appointment['id_services']).trigger('change'); - $dialog.find('#select-provider').val(appointment['id_users_provider']); - - // Set the start and end datetime of the appointment. - var startDatetime = Date.parseExact(appointment['start_datetime'], - 'yyyy-MM-dd HH:mm:ss'); - $dialog.find('#start-datetime').datetimepicker('setDate', startDatetime); - - var endDatetime = Date.parseExact(appointment['end_datetime'], - 'yyyy-MM-dd HH:mm:ss'); - $dialog.find('#end-datetime').datetimepicker('setDate', endDatetime); - - var customer = appointment['customer']; - $dialog.find('#customer-id').val(appointment['id_users_customer']); - $dialog.find('#first-name').val(customer['first_name']); - $dialog.find('#last-name').val(customer['last_name']); - $dialog.find('#email').val(customer['email']); - $dialog.find('#phone-number').val(customer['phone_number']); - $dialog.find('#address').val(customer['address']); - $dialog.find('#city').val(customer['city']); - $dialog.find('#zip-code').val(customer['zip_code']); - $dialog.find('#appointment-notes').val(appointment['notes']); - $dialog.find('#customer-notes').val(customer['notes']); - } else { - var unavailable = lastFocusedEventData.data; - - // Replace string date values with actual date objects. - unavailable.start_datetime = GeneralFunctions.clone(lastFocusedEventData.start); - unavailable.end_datetime = GeneralFunctions.clone(lastFocusedEventData.end); - - $dialog = $('#manage-unavailable'); - _resetUnavailableDialog(); - - // Apply unvailable data to dialog. - $dialog.find('.modal-header h3').text('Edit Unavailable Period'); - $dialog.find('#unavailable-start').datetimepicker('setDate', unavailable.start_datetime); - $dialog.find('#unavailable-id').val(unavailable.id); - $dialog.find('#unavailable-end').datetimepicker('setDate', unavailable.end_datetime); - $dialog.find('#unavailable-notes').val(unavailable.notes); - } - - // :: DISPLAY EDIT DIALOG - $dialog.modal('show'); - }); - - /** - * Event: Popover Delete Button "Click" - * - * Displays a prompt on whether the user wants the appoinmtent to be deleted. If he confirms the - * deletion then an ajax call is made to the server and deletes the appointment from the database. - */ - $calendarPage.on('click', '.delete-popover', function() { - $(this).parents().eq(2).remove(); // Hide the popover - - if (lastFocusedEventData.data.is_unavailable == false) { - var messageButtons = {}; - messageButtons['OK'] = function() { - var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_appointment'; - - var postData = { - 'csrfToken': GlobalVariables.csrfToken, - 'appointment_id' : lastFocusedEventData.data['id'], - 'delete_reason': $('#delete-reason').val() - }; - - $.post(postUrl, postData, function(response) { - $('#message_box').dialog('close'); - - if (response.exceptions) { - response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); - GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, - GeneralFunctions.EXCEPTIONS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); - return; - } - - if (response.warnings) { - response.warnings = GeneralFunctions.parseExceptions(response.warnings); - GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, - GeneralFunctions.WARNINGS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); - } - - // Refresh calendar event items. - $('#select-filter-item').trigger('change'); - }, 'json').fail(GeneralFunctions.ajaxFailureHandler); - }; - - messageButtons[EALang['cancel']] = function() { - $('#message_box').dialog('close'); - }; - - GeneralFunctions.displayMessageBox(EALang['delete_appointment_title'], - EALang['write_appointment_removal_reason'], messageButtons); - - $('#message_box').append(''); - $('#delete-reason').css('width', '100%'); - } else { - // Do not display confirmation promt. - var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_unavailable'; - var postData = { - csrfToken: GlobalVariables.csrfToken, - unavailable_id : lastFocusedEventData.data.id - }; - - $.post(postUrl, postData, function(response) { - $('#message_box').dialog('close'); - - if (response.exceptions) { - response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); - GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, GeneralFunctions.EXCEPTIONS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); - return; - } - - if (response.warnings) { - response.warnings = GeneralFunctions.parseExceptions(response.warnings); - GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); - } - - // Refresh calendar event items. - $('#select-filter-item').trigger('change'); - }, 'json').fail(GeneralFunctions.ajaxFailureHandler); - } - }); - - /** - * Event: Manage Appointments Dialog Cancel Button "Click" - * - * Closes the dialog without saving any changes to the database. - */ - $('#manage-appointment #cancel-appointment').click(function() { - $('#manage-appointment').modal('hide'); - }); - - /** - * Event: Manage Appointments Dialog Save Button "Click" - * - * Stores the appointment changes or inserts a new appointment depending the dialog mode. - */ - $('#manage-appointment #save-appointment').click(function() { - // Before doing anything the appointment data need to be validated. - if (!_validateAppointmentForm()) { - return; - } - - // Prepare appointment data for AJAX request. - var $dialog = $('#manage-appointment'); - - // ID must exist on the object in order for the model to update the record and not to perform - // an insert operation. - - var startDatetime = $dialog.find('#start-datetime') - .datepicker('getDate').toString('yyyy-MM-dd HH:mm:ss'); - var endDatetime = $dialog.find('#end-datetime') - .datepicker('getDate').toString('yyyy-MM-dd HH:mm:ss'); - - var appointment = { - id_services: $dialog.find('#select-service').val(), - id_users_provider: $dialog.find('#select-provider').val(), - start_datetime: startDatetime, - end_datetime: endDatetime, - notes: $dialog.find('#appointment-notes').val(), - is_unavailable: false - }; - - if ($dialog.find('#appointment-id').val() !== '') { - // Set the id value, only if we are editing an appointment. - appointment['id'] = $dialog.find('#appointment-id').val(); - } - - var customer = { - first_name: $dialog.find('#first-name').val(), - last_name: $dialog.find('#last-name').val(), - email: $dialog.find('#email').val(), - phone_number: $dialog.find('#phone-number').val(), - address: $dialog.find('#address').val(), - city: $dialog.find('#city').val(), - zip_code: $dialog.find('#zip-code').val(), - notes: $dialog.find('#customer-notes').val() - }; - - if ($dialog.find('#customer-id').val() !== '') { - // Set the id value, only if we are editing an appointment. - customer['id'] = $dialog.find('#customer-id').val(); - appointment['id_users_customer'] = customer['id']; - } - - // Define success callback. - var successCallback = function(response) { - if (!GeneralFunctions.handleAjaxExceptions(response)) { - $dialog.find('.modal-message').text(EALang['unexpected_issues_occurred']); - $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); - return false; - } - - // Display success message to the user. - $dialog.find('.modal-message').text(EALang['appointment_saved']); - $dialog.find('.modal-message').addClass('alert-success').removeClass('alert-danger hidden'); - $dialog.find('.modal-body').scrollTop(0); - - // Close the modal dialog and refresh the calendar appointments after one second. - setTimeout(function() { - $dialog.find('.alert').addClass('hidden'); - $dialog.modal('hide'); - $('#select-filter-item').trigger('change'); - }, 2000); - }; - - // Define error callback. - var errorCallback = function() { - $dialog.find('.modal-message').text(EALang['server_communication_error']); - $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); - $dialog.find('.modal-body').scrollTop(0); - }; - - // Save appointment data. - _saveAppointment(appointment, customer, successCallback, errorCallback); - }); - - /** - * Event: Manage Unavailable Dialog Save Button "Click" - * - * Stores the unavailable period changes or inserts a new record. - */ - $('#manage-unavailable #save-unavailable').click(function() { - var $dialog = $('#manage-unavailable'); - var start = $dialog.find('#unavailable-start').datetimepicker('getDate'); - var end = $dialog.find('#unavailable-end').datetimepicker('getDate'); - - if (start > end) { - // Start time is after end time - display message to user. - $dialog.find('.modal-message') - .text(EALang['start_date_before_end_error']) - .addClass('alert-danger') - .removeClass('hidden'); - return; - } - - // Unavailable period records go to the appointments table. - var unavailable = { - start_datetime: start.toString('yyyy-MM-dd HH:mm'), - end_datetime: end.toString('yyyy-MM-dd HH:mm'), - notes: $dialog.find('#unavailable-notes').val(), - id_users_provider: $('#select-filter-item').val() // curr provider - }; - - if ($dialog.find('#unavailable-id').val() !== '') { - // Set the id value, only if we are editing an appointment. - unavailable.id = $dialog.find('#unavailable-id').val(); - } - - var successCallback = function(response) { - if (response.exceptions) { - response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); - GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, - GeneralFunctions.EXCEPTIONS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); - - $dialog.find('.modal-message') - .text(EALang['unexpected_issues_occurred']) - .addClass('alert-danger') - .removeClass('hidden'); - - return; - } - - if (response.warnings) { - response.warnings = GeneralFunctions.parseExceptions(response.warnings); - GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, - GeneralFunctions.WARNINGS_MESSAGE); - $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); - } - - // Display success message to the user. - $dialog.find('.modal-message') - .text(EALang['unavailable_saved']) - .addClass('alert-success') - .removeClass('alert-danger hidden'); - - // Close the modal dialog and refresh the calendar appointments after one second. - setTimeout(function() { - $dialog.find('.alert').addClass('hidden'); - $dialog.modal('hide'); - $('#select-filter-item').trigger('change'); - }, 2000); - }; - - var errorCallback = function(jqXHR, textStatus, errorThrown) { - GeneralFunctions.displayMessageBox('Communication Error', 'Unfortunately ' + - 'the operation could not complete due to server communication errors.'); - - $dialog.find('.modal-message').txt(EALang['service_communication_error']); - $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); - }; - - _saveUnavailable(unavailable, successCallback, errorCallback); - }); - - /** - * Event: Manage Unavailable Dialog Cancel Button "Click" - * - * Closes the dialog without saveing any changes to the database. - */ - $('#manage-unavailable #cancel-unavailable').click(function() { - $('#manage-unavailable').modal('hide'); - }); - - - - /** - * Event: Insert Appointment Button "Click" - * - * When the user presses this button, the manage appointment dialog opens and lets the user to - * create a new appointment. - */ - $('#insert-appointment').click(function() { - _resetAppointmentDialog(); - var $dialog = $('#manage-appointment'); - - // Set the selected filter item and find the next appointment time as the default modal values. - if ($('#select-filter-item option:selected').attr('type') == 'provider') { - var $providerOption = $dialog.find('#select-provider option[value="' - + $('#select-filter-item').val() + '"]'); - if ($providerOption.length === 0) { // Change the services until you find the correct. - $.each($dialog.find('#select-service option'), function() { - $(this).prop('selected', true).parent().change(); - if ($providerOption.length > 0) - return false; - }); - } - $providerOption.prop('selected', true); - } else { - $dialog.find('#select-service option[value="' - + $('#select-filter-item').val() + '"]').prop('selected', true); - } - - var serviceDuration = 0; - $.each(GlobalVariables.availableServices, function(index, service) { - if (service['id'] == $dialog.find('#select-service').val()) { - serviceDuration = service['duration']; - return false; // exit loop - } - }); - - var start = new Date(); - var currentMin = parseInt(start.toString('mm')); - - if (currentMin > 0 && currentMin < 15) { - start.set({ 'minute': 15 }); - } else if (currentMin > 15 && currentMin < 30) { - start.set({ 'minute': 30 }); - } else if (currentMin > 30 && currentMin < 45) { - start.set({ 'minute': 45 }); - } else { - start.addHours(1).set({ 'minute': 0 }); - } - - $dialog.find('#start-datetime').val(GeneralFunctions.formatDate(start, GlobalVariables.dateFormat, true)); - $dialog.find('#end-datetime').val(GeneralFunctions.formatDate(start.addMinutes(serviceDuration), - GlobalVariables.dateFormat, true)); - - // Display modal form. - $dialog.find('.modal-header h3').text(EALang['new_appointment_title']); - $dialog.modal('show'); - }); - - /** - * Event : Insert Unavailable Time Period Button "Click" - * - * When the user clicks this button a popup dialog appears and the use can set a time period where - * he cannot accept any appointments. - */ - $('#insert-unavailable').click(function() { - _resetUnavailableDialog(); - var $dialog = $('#manage-unavailable'); - - // Set the default datetime values. - var start = new Date(); - var currentMin = parseInt(start.toString('mm')); - - if (currentMin > 0 && currentMin < 15) { - start.set({ 'minute': 15 }); - } else if (currentMin > 15 && currentMin < 30) { - start.set({ 'minute': 30 }); - } else if (currentMin > 30 && currentMin < 45) { - start.set({ 'minute': 45 }); - } else { - start.addHours(1).set({ 'minute': 0 }); - } - - $dialog.find('#unavailable-start').val(GeneralFunctions.formatDate(start, GlobalVariables.dateFormat, true)); - $dialog.find('#unavailable-end').val(GeneralFunctions.formatDate(start.addHours(1), GlobalVariables.dateFormat, true)); - $dialog.find('.modal-header h3').text(EALang['new_unavailable_title']); - $dialog.modal('show'); - }); - - /** - * Event: Pick Existing Customer Button "Click" - */ - $('#select-customer').click(function() { - var $list = $('#existing-customers-list'); - - if (!$list.is(':visible')) { - $(this).text(EALang['hide']); - $list.empty(); - $list.slideDown('slow'); - $('#filter-existing-customers').fadeIn('slow'); - $('#filter-existing-customers').val(''); - $.each(GlobalVariables.customers, function(index, c) { - $list.append('
' - + c.first_name + ' ' + c.last_name + '
'); - }); - } else { - $list.slideUp('slow'); - $('#filter-existing-customers').fadeOut('slow'); - $(this).text(EALang['select']); - } - }); - - /** - * Event: Select Existing Customer From List "Click" - */ - $calendarPage.on('click', '#existing-customers-list div', function() { - var id = $(this).attr('data-id'); - - $.each(GlobalVariables.customers, function(index, c) { - if (c.id == id) { - $('#customer-id').val(c.id); - $('#first-name').val(c.first_name); - $('#last-name').val(c.last_name); - $('#email').val(c.email); - $('#phone-number').val(c.phone_number); - $('#address').val(c.address); - $('#city').val(c.city); - $('#zip-code').val(c.zip_code); - $('#customer-notes').val(c.notes); - return false; - } - }); - - $('#select-customer').trigger('click'); // hide list - }); - - /** - * Event: Filter Existing Customers "Change" - */ - $('#filter-existing-customers').keyup(function() { - var key = $(this).val().toLowerCase(); - var $list = $('#existing-customers-list'); - var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_filter_customers'; - var postData = { - csrfToken: GlobalVariables.csrfToken, - key: key - }; - - // Try to get the updated customer list. - $.ajax({ - type: 'POST', - url: postUrl, - data: postData, - dataType: 'json', - timeout: 1000, - global: false, - success: function(response) { - $list.empty(); - $.each(response, function(index, c) { - $list.append('
' - + c.first_name + ' ' + c.last_name + '
'); - - // Verify if this customer is on the old customer list. - var result = $.grep(GlobalVariables.customers, - function(e){ return e.id == c.id; }); - - // Add it to the customer list. - if(result.length == 0){ - GlobalVariables.customers.push(c); - } - }); - }, - error: function(jqXHR, textStatus, errorThrown) { - // If there is any error on the request, search by the local client database. - $list.empty(); - $.each(GlobalVariables.customers, function(index, c) { - if (c.first_name.toLowerCase().indexOf(key) != -1 - || c.last_name.toLowerCase().indexOf(key) != -1 - || c.email.toLowerCase().indexOf(key) != -1 - || c.phone_number.toLowerCase().indexOf(key) != -1 - || c.address.toLowerCase().indexOf(key) != -1 - || c.city.toLowerCase().indexOf(key) != -1 - || c.zip_code.toLowerCase().indexOf(key) != -1 - || c.notes.toLowerCase().indexOf(key) != -1) { - $list.append('
' - + c.first_name + ' ' + c.last_name + '
'); - } - }); - } - }); - }); - - /** - * Event: Selected Service "Change" - * - * When the user clicks on a service, its available providers should become visible. Also we need to - * update the start and end time of the appointment. - */ - $('#select-service').change(function() { - var sid = $('#select-service').val(); - $('#select-provider').empty(); - - // Automatically update the service duration. - $.each(GlobalVariables.availableServices, function(indexService, service) { - if (service.id == sid) { - var start = $('#start-datetime').datepicker('getDate'); - $('#end-datetime').datepicker('setDate', new Date(start.getTime() + service.duration * 60000)); - return false; // break loop - } - }); - - // Update the providers select box. - $.each(GlobalVariables.availableProviders, function(indexProvider, provider) { - $.each(provider.services, function(indexService, serviceId) { - // If the current provider is able to provide the selected service, add him to the listbox. - if (serviceId == sid) { - var optionHtml = ''; - $('#select-provider').append(optionHtml); - } - }); - }); - }); - - /** - * Event: Enter New Customer Button "Click" - */ - $('#new-customer').click(function() { - $('#manage-appointment').find('#customer-id, #first-name, #last-name, #email, ' - + '#phone-number, #address, #city, #zip-code, #customer-notes').val(''); - }); - } - - /** - * Save Appointment - * - * This method stores the changes of an already registered appointment into the database, via an ajax call. - * - * @param {Object} appointment Contain the new appointment data. The ID of the appointment MUST be - * already included. The rest values must follow the database structure. - * @param {Object} customer Optional, contains the customer data. - * @param {Function} successCallback Optional, if defined, this function is going to be executed on post success. - * @param {Function} errorCallback Optional, if defined, this function is going to be executed on post failure. - */ - function _saveAppointment(appointment, customer, successCallback, errorCallback) { - var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment'; - var postData = { - csrfToken: GlobalVariables.csrfToken, - appointment_data: JSON.stringify(appointment) - }; - - if (customer !== undefined) { - postData['customer_data'] = JSON.stringify(customer); - } - - $.ajax({ - url: postUrl, - type: 'POST', - data: postData, - dataType: 'json' - }) - .done(function(response) { - if (successCallback !== undefined) { - successCallback(response); - } - }) - .fail(function(jqXHR, textStatus, errorThrown) { - if (errorCallback !== undefined) { - errorCallback(); - } - }); - } - - /** - * Save unavailable period to database. - * - * @param {Object} unavailable Containts the unavailable period data. - * @param {Function} successCallback The ajax success callback function. - * @param {Function} errorCallback The ajax failure callback function. - */ - function _saveUnavailable(unavailable, successCallback, errorCallback) { - var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable'; - var postData = { - csrfToken: GlobalVariables.csrfToken, - unavailable: JSON.stringify(unavailable) - }; - - $.ajax({ - type: 'POST', - url: postUrl, - data: postData, - success: successCallback, - error: errorCallback - }); - } - - /** - * Reset Apppointment Dialog - * - * This method resets the manage appointment dialog modal to its initial state. After that you can make - * any modification might be necessary in order to bring the dialog to the desired state. - */ - function _resetAppointmentDialog() { - var $dialog = $('#manage-appointment'); - - // Empty form fields. - $dialog.find('input, textarea').val(''); - $dialog.find('.modal-message').fadeOut(); - - // Prepare service and provider select boxes. - $dialog.find('#select-service').val( - $dialog.find('#select-service').eq(0).attr('value')); - - // Fill the providers listbox with providers that can serve the appointment's - // service and then select the user's provider. - $dialog.find('#select-provider').empty(); - $.each(GlobalVariables.availableProviders, function(index, provider) { - var canProvideService = false; - - $.each(provider['services'], function(index, serviceId) { - if (serviceId == $dialog.find('#select-service').val()) { - canProvideService = true; - return false; - } - }); - - if (canProvideService) { // Add the provider to the listbox. - var option = new Option(provider['first_name'] - + ' ' + provider['last_name'], provider['id']); - $dialog.find('#select-provider').append(option); - } - }); - - // Close existing customers-filter frame. - $('#existing-customers-list').slideUp('slow'); - $('#filter-existing-customers').fadeOut('slow'); - $('#select-customer').text(EALang['select']); - - // Setup start and datetimepickers. - // Get the selected service duration. It will be needed in order to calculate the appointment end datetime. - var serviceDuration = 0; - $.each(GlobalVariables.availableServices, function(index, service) { - if (service['id'] == $dialog.find('#select-service').val()) { - serviceDuration = service['duration']; - return false; - } - }); - - var startDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout); - var endDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout).addMinutes(serviceDuration); - var dateFormat; - - switch(GlobalVariables.dateFormat) { - case 'DMY': - dateFormat = 'dd/mm/yy'; - break; - case 'MDY': - dateFormat = 'mm/dd/yy'; - break; - case 'YMD': - dateFormat = 'yy/mm/dd'; - break; - default: - throw new Error('Invalid GlobalVariables.dateFormat value.'); - } - - $dialog.find('#start-datetime').datetimepicker({ - dateFormat: dateFormat, - - // Translation - dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], - EALang['thursday'], EALang['friday'], EALang['saturday']], - dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), - EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), - EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), - EALang['saturday'].substr(0,3)], - dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), - EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), - EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), - EALang['saturday'].substr(0,2)], - monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], - EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], - EALang['october'], EALang['november'], EALang['december']], - prevText: EALang['previous'], - nextText: EALang['next'], - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'], - firstDay: 1 - }); - $dialog.find('#start-datetime').datepicker('setDate', startDatetime); - - $dialog.find('#end-datetime').datetimepicker({ - dateFormat: dateFormat, - - // Translation - dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], - EALang['thursday'], EALang['friday'], EALang['saturday']], - dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), - EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), - EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), - EALang['saturday'].substr(0,3)], - dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), - EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), - EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), - EALang['saturday'].substr(0,2)], - monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], - EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], - EALang['october'], EALang['november'], EALang['december']], - prevText: EALang['previous'], - nextText: EALang['next'], - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'], - firstDay: 1 - }); - $dialog.find('#end-datetime').datepicker('setDate', endDatetime); - } - - /** - * Validate the manage appointment dialog data. Validation checks need to - * run every time the data are going to be saved. - * - * @returns {Boolean} Returns the validation result. - */ - function _validateAppointmentForm() { - var $dialog = $('#manage-appointment'); - - // Reset previous validation css formating. - $dialog.find('.form-group').removeClass('has-error'); - $dialog.find('.modal-message').addClass('hidden'); - - try { - // Check required fields. - var missingRequiredField = false; - - $dialog.find('.required').each(function() { - if ($(this).val() == '' || $(this).val() == null) { - $(this).parents('.form-group').addClass('has-error'); - missingRequiredField = true; - } - }); - - if (missingRequiredField) { - throw EALang['fields_are_required']; - } - - // Check email address. - if (!GeneralFunctions.validateEmail($dialog.find('#email').val())) { - $dialog.find('#email').parents('.form-group').eq(1).addClass('has-error'); - throw EALang['invalid_email']; - } - - // Check appointment start and end time. - var start = $('#start-datetime').datepicker('getDate'); - var end = $('#end-datetime').datepicker('getDate'); - if (start > end) { - $dialog.find('#start-datetime').parents('.form-group').addClass('has-error'); - $dialog.find('#end-datetime').parents('.form-group').addClass('has-error'); - throw EALang['start_date_before_end_error']; - } - - return true; - } catch(exc) { - $dialog.find('.modal-message').addClass('alert-danger').text(exc).removeClass('hidden'); - return false; - } - } - - /** - * Reset unavailable dialog form. - * - * Reset the "#manage-unavailable" dialog. Use this method to bring the dialog to the initial state - * before it becomes visible to the user. - */ - function _resetUnavailableDialog() { - var $dialog = $('#manage-unavailable'); - - $dialog.find('#unavailable-id').val(''); - - // Set default time values - var start = GeneralFunctions.formatDate(new Date(), GlobalVariables.dateFormat, true); - var end = GeneralFunctions.formatDate(new Date().addHours(1), GlobalVariables.dateFormat, true); - var dateFormat; - - switch(GlobalVariables.dateFormat) { - case 'DMY': - dateFormat = 'dd/mm/yy'; - break; - case 'MDY': - dateFormat = 'mm/dd/yy'; - break; - case 'YMD': - dateFormat = 'yy/mm/dd'; - break; - } - - - $dialog.find('#unavailable-start').datetimepicker({ - dateFormat: dateFormat, - - // Translation - dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], - EALang['thursday'], EALang['friday'], EALang['saturday']], - dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), - EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), - EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), - EALang['saturday'].substr(0,3)], - dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), - EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), - EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), - EALang['saturday'].substr(0,2)], - monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], - EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], - EALang['october'], EALang['november'], EALang['december']], - prevText: EALang['previous'], - nextText: EALang['next'], - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'], - firstDay: 1 - }); - $dialog.find('#unavailable-start').val(start); - - $dialog.find('#unavailable-end').datetimepicker({ - dateFormat: dateFormat, - - // Translation - dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], - EALang['thursday'], EALang['friday'], EALang['saturday']], - dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), - EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), - EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), - EALang['saturday'].substr(0,3)], - dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), - EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), - EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), - EALang['saturday'].substr(0,2)], - monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], - EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], - EALang['october'], EALang['november'], EALang['december']], - prevText: EALang['previous'], - nextText: EALang['next'], - currentText: EALang['now'], - closeText: EALang['close'], - timeOnlyTitle: EALang['select_time'], - timeText: EALang['time'], - hourText: EALang['hour'], - minuteText: EALang['minutes'], - firstDay: 1 - }); - $dialog.find('#unavailable-end').val(end); - - // Clear the unavailable notes field. - $dialog.find('#unavailable-notes').val(''); - } - /** * Initialize Module * @@ -965,8 +39,8 @@ window.BackendCalendar = window.BackendCalendar || {}; } BackendCalendarGoogleSync.initialize(); - - _bindEventHandlers(); // General event handling. + BackendCalendarAppointmentsModal.initialize(); + BackendCalendarUnavailabilitiesModal.initialize(); }; })(window.BackendCalendar); diff --git a/src/assets/js/backend_calendar_api.js b/src/assets/js/backend_calendar_api.js new file mode 100644 index 00000000..4a089118 --- /dev/null +++ b/src/assets/js/backend_calendar_api.js @@ -0,0 +1,88 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Open Source Web Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis + * @copyright Copyright (c) 2013 - 2016, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.2.0 + * ---------------------------------------------------------------------------- */ + +/** + * Backend Calendar API + * + * This module implements the AJAX requests for the calendar page. + * + * @module BackendCalendarApi + */ +window.BackendCalendarApi = window.BackendCalendarApi || {}; + +(function(exports) { + + 'use strict'; + + /** + * Save Appointment + * + * This method stores the changes of an already registered appointment into the database, via an ajax call. + * + * @param {Object} appointment Contain the new appointment data. The ID of the appointment MUST be + * already included. The rest values must follow the database structure. + * @param {Object} customer Optional, contains the customer data. + * @param {Function} successCallback Optional, if defined, this function is going to be executed on post success. + * @param {Function} errorCallback Optional, if defined, this function is going to be executed on post failure. + */ + exports.saveAppointment = function(appointment, customer, successCallback, errorCallback) { + var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment'; + var postData = { + csrfToken: GlobalVariables.csrfToken, + appointment_data: JSON.stringify(appointment) + }; + + if (customer !== undefined) { + postData['customer_data'] = JSON.stringify(customer); + } + + $.ajax({ + url: postUrl, + type: 'POST', + data: postData, + dataType: 'json' + }) + .done(function(response) { + if (successCallback !== undefined) { + successCallback(response); + } + }) + .fail(function(jqXHR, textStatus, errorThrown) { + if (errorCallback !== undefined) { + errorCallback(); + } + }); + }; + + /** + * Save unavailable period to database. + * + * @param {Object} unavailable Containts the unavailable period data. + * @param {Function} successCallback The ajax success callback function. + * @param {Function} errorCallback The ajax failure callback function. + */ + exports.saveUnavailable = function(unavailable, successCallback, errorCallback) { + var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable'; + var postData = { + csrfToken: GlobalVariables.csrfToken, + unavailable: JSON.stringify(unavailable) + }; + + $.ajax({ + type: 'POST', + url: postUrl, + data: postData, + success: successCallback, + error: errorCallback + }); + }; + +})(window.BackendCalendarApi); diff --git a/src/assets/js/backend_calendar_appointments_modal.js b/src/assets/js/backend_calendar_appointments_modal.js new file mode 100644 index 00000000..1af9bf5a --- /dev/null +++ b/src/assets/js/backend_calendar_appointments_modal.js @@ -0,0 +1,505 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Open Source Web Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis + * @copyright Copyright (c) 2013 - 2016, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.2.0 + * ---------------------------------------------------------------------------- */ + +/** + * Backend Calendar Appointments Modal + * + * This module implements the appointments modal functionality. + * + * @module BackendCalendarAppointmentsModal + */ +window.BackendCalendarAppointmentsModal = window.BackendCalendarAppointmentsModal || {}; + +(function(exports) { + + 'use strict'; + + function _bindEventHandlers() { + /** + * Event: Manage Appointments Dialog Cancel Button "Click" + * + * Closes the dialog without saving any changes to the database. + */ + $('#manage-appointment #cancel-appointment').click(function() { + $('#manage-appointment').modal('hide'); + }); + + /** + * Event: Manage Appointments Dialog Save Button "Click" + * + * Stores the appointment changes or inserts a new appointment depending the dialog mode. + */ + $('#manage-appointment #save-appointment').click(function() { + // Before doing anything the appointment data need to be validated. + if (!_validateAppointmentForm()) { + return; + } + + // Prepare appointment data for AJAX request. + var $dialog = $('#manage-appointment'); + + // ID must exist on the object in order for the model to update the record and not to perform + // an insert operation. + + var startDatetime = $dialog.find('#start-datetime') + .datepicker('getDate').toString('yyyy-MM-dd HH:mm:ss'); + var endDatetime = $dialog.find('#end-datetime') + .datepicker('getDate').toString('yyyy-MM-dd HH:mm:ss'); + + var appointment = { + id_services: $dialog.find('#select-service').val(), + id_users_provider: $dialog.find('#select-provider').val(), + start_datetime: startDatetime, + end_datetime: endDatetime, + notes: $dialog.find('#appointment-notes').val(), + is_unavailable: false + }; + + if ($dialog.find('#appointment-id').val() !== '') { + // Set the id value, only if we are editing an appointment. + appointment['id'] = $dialog.find('#appointment-id').val(); + } + + var customer = { + first_name: $dialog.find('#first-name').val(), + last_name: $dialog.find('#last-name').val(), + email: $dialog.find('#email').val(), + phone_number: $dialog.find('#phone-number').val(), + address: $dialog.find('#address').val(), + city: $dialog.find('#city').val(), + zip_code: $dialog.find('#zip-code').val(), + notes: $dialog.find('#customer-notes').val() + }; + + if ($dialog.find('#customer-id').val() !== '') { + // Set the id value, only if we are editing an appointment. + customer['id'] = $dialog.find('#customer-id').val(); + appointment['id_users_customer'] = customer['id']; + } + + // Define success callback. + var successCallback = function(response) { + if (!GeneralFunctions.handleAjaxExceptions(response)) { + $dialog.find('.modal-message').text(EALang['unexpected_issues_occurred']); + $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); + return false; + } + + // Display success message to the user. + $dialog.find('.modal-message').text(EALang['appointment_saved']); + $dialog.find('.modal-message').addClass('alert-success').removeClass('alert-danger hidden'); + $dialog.find('.modal-body').scrollTop(0); + + // Close the modal dialog and refresh the calendar appointments after one second. + setTimeout(function() { + $dialog.find('.alert').addClass('hidden'); + $dialog.modal('hide'); + $('#select-filter-item').trigger('change'); + }, 2000); + }; + + // Define error callback. + var errorCallback = function() { + $dialog.find('.modal-message').text(EALang['server_communication_error']); + $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); + $dialog.find('.modal-body').scrollTop(0); + }; + + // Save appointment data. + BackendCalendarApi.saveAppointment(appointment, customer, successCallback, errorCallback); + }); + + /** + * Event: Insert Appointment Button "Click" + * + * When the user presses this button, the manage appointment dialog opens and lets the user to + * create a new appointment. + */ + $('#insert-appointment').click(function() { + _resetAppointmentDialog(); + var $dialog = $('#manage-appointment'); + + // Set the selected filter item and find the next appointment time as the default modal values. + if ($('#select-filter-item option:selected').attr('type') == 'provider') { + var $providerOption = $dialog.find('#select-provider option[value="' + + $('#select-filter-item').val() + '"]'); + if ($providerOption.length === 0) { // Change the services until you find the correct. + $.each($dialog.find('#select-service option'), function() { + $(this).prop('selected', true).parent().change(); + if ($providerOption.length > 0) + return false; + }); + } + $providerOption.prop('selected', true); + } else { + $dialog.find('#select-service option[value="' + + $('#select-filter-item').val() + '"]').prop('selected', true); + } + + var serviceDuration = 0; + $.each(GlobalVariables.availableServices, function(index, service) { + if (service['id'] == $dialog.find('#select-service').val()) { + serviceDuration = service['duration']; + return false; // exit loop + } + }); + + var start = new Date(); + var currentMin = parseInt(start.toString('mm')); + + if (currentMin > 0 && currentMin < 15) { + start.set({ 'minute': 15 }); + } else if (currentMin > 15 && currentMin < 30) { + start.set({ 'minute': 30 }); + } else if (currentMin > 30 && currentMin < 45) { + start.set({ 'minute': 45 }); + } else { + start.addHours(1).set({ 'minute': 0 }); + } + + $dialog.find('#start-datetime').val(GeneralFunctions.formatDate(start, GlobalVariables.dateFormat, true)); + $dialog.find('#end-datetime').val(GeneralFunctions.formatDate(start.addMinutes(serviceDuration), + GlobalVariables.dateFormat, true)); + + // Display modal form. + $dialog.find('.modal-header h3').text(EALang['new_appointment_title']); + $dialog.modal('show'); + }); + + /** + * Event: Pick Existing Customer Button "Click" + */ + $('#select-customer').click(function() { + var $list = $('#existing-customers-list'); + + if (!$list.is(':visible')) { + $(this).text(EALang['hide']); + $list.empty(); + $list.slideDown('slow'); + $('#filter-existing-customers').fadeIn('slow'); + $('#filter-existing-customers').val(''); + $.each(GlobalVariables.customers, function(index, c) { + $list.append('
' + + c.first_name + ' ' + c.last_name + '
'); + }); + } else { + $list.slideUp('slow'); + $('#filter-existing-customers').fadeOut('slow'); + $(this).text(EALang['select']); + } + }); + + /** + * Event: Select Existing Customer From List "Click" + */ + $('#manage-appointment').on('click', '#existing-customers-list div', function() { + var id = $(this).attr('data-id'); + + $.each(GlobalVariables.customers, function(index, c) { + if (c.id == id) { + $('#customer-id').val(c.id); + $('#first-name').val(c.first_name); + $('#last-name').val(c.last_name); + $('#email').val(c.email); + $('#phone-number').val(c.phone_number); + $('#address').val(c.address); + $('#city').val(c.city); + $('#zip-code').val(c.zip_code); + $('#customer-notes').val(c.notes); + return false; + } + }); + + $('#select-customer').trigger('click'); // hide list + }); + + /** + * Event: Filter Existing Customers "Change" + */ + $('#filter-existing-customers').keyup(function() { + var key = $(this).val().toLowerCase(); + var $list = $('#existing-customers-list'); + var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_filter_customers'; + var postData = { + csrfToken: GlobalVariables.csrfToken, + key: key + }; + + // Try to get the updated customer list. + $.ajax({ + type: 'POST', + url: postUrl, + data: postData, + dataType: 'json', + timeout: 1000, + global: false, + success: function(response) { + $list.empty(); + $.each(response, function(index, c) { + $list.append('
' + + c.first_name + ' ' + c.last_name + '
'); + + // Verify if this customer is on the old customer list. + var result = $.grep(GlobalVariables.customers, + function(e){ return e.id == c.id; }); + + // Add it to the customer list. + if(result.length == 0){ + GlobalVariables.customers.push(c); + } + }); + }, + error: function(jqXHR, textStatus, errorThrown) { + // If there is any error on the request, search by the local client database. + $list.empty(); + $.each(GlobalVariables.customers, function(index, c) { + if (c.first_name.toLowerCase().indexOf(key) != -1 + || c.last_name.toLowerCase().indexOf(key) != -1 + || c.email.toLowerCase().indexOf(key) != -1 + || c.phone_number.toLowerCase().indexOf(key) != -1 + || c.address.toLowerCase().indexOf(key) != -1 + || c.city.toLowerCase().indexOf(key) != -1 + || c.zip_code.toLowerCase().indexOf(key) != -1 + || c.notes.toLowerCase().indexOf(key) != -1) { + $list.append('
' + + c.first_name + ' ' + c.last_name + '
'); + } + }); + } + }); + }); + + /** + * Event: Selected Service "Change" + * + * When the user clicks on a service, its available providers should become visible. Also we need to + * update the start and end time of the appointment. + */ + $('#select-service').change(function() { + var sid = $('#select-service').val(); + $('#select-provider').empty(); + + // Automatically update the service duration. + $.each(GlobalVariables.availableServices, function(indexService, service) { + if (service.id == sid) { + var start = $('#start-datetime').datepicker('getDate'); + $('#end-datetime').datepicker('setDate', new Date(start.getTime() + service.duration * 60000)); + return false; // break loop + } + }); + + // Update the providers select box. + $.each(GlobalVariables.availableProviders, function(indexProvider, provider) { + $.each(provider.services, function(indexService, serviceId) { + // If the current provider is able to provide the selected service, add him to the listbox. + if (serviceId == sid) { + var optionHtml = ''; + $('#select-provider').append(optionHtml); + } + }); + }); + }); + + /** + * Event: Enter New Customer Button "Click" + */ + $('#new-customer').click(function() { + $('#manage-appointment').find('#customer-id, #first-name, #last-name, #email, ' + + '#phone-number, #address, #city, #zip-code, #customer-notes').val(''); + }); + } + + /** + * Reset Apppointment Dialog + * + * This method resets the manage appointment dialog modal to its initial state. After that you can make + * any modification might be necessary in order to bring the dialog to the desired state. + */ + function _resetAppointmentDialog() { + var $dialog = $('#manage-appointment'); + + // Empty form fields. + $dialog.find('input, textarea').val(''); + $dialog.find('.modal-message').fadeOut(); + + // Prepare service and provider select boxes. + $dialog.find('#select-service').val( + $dialog.find('#select-service').eq(0).attr('value')); + + // Fill the providers listbox with providers that can serve the appointment's + // service and then select the user's provider. + $dialog.find('#select-provider').empty(); + $.each(GlobalVariables.availableProviders, function(index, provider) { + var canProvideService = false; + + $.each(provider['services'], function(index, serviceId) { + if (serviceId == $dialog.find('#select-service').val()) { + canProvideService = true; + return false; + } + }); + + if (canProvideService) { // Add the provider to the listbox. + var option = new Option(provider['first_name'] + + ' ' + provider['last_name'], provider['id']); + $dialog.find('#select-provider').append(option); + } + }); + + // Close existing customers-filter frame. + $('#existing-customers-list').slideUp('slow'); + $('#filter-existing-customers').fadeOut('slow'); + $('#select-customer').text(EALang['select']); + + // Setup start and datetimepickers. + // Get the selected service duration. It will be needed in order to calculate the appointment end datetime. + var serviceDuration = 0; + $.each(GlobalVariables.availableServices, function(index, service) { + if (service['id'] == $dialog.find('#select-service').val()) { + serviceDuration = service['duration']; + return false; + } + }); + + var startDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout); + var endDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout).addMinutes(serviceDuration); + var dateFormat; + + switch(GlobalVariables.dateFormat) { + case 'DMY': + dateFormat = 'dd/mm/yy'; + break; + case 'MDY': + dateFormat = 'mm/dd/yy'; + break; + case 'YMD': + dateFormat = 'yy/mm/dd'; + break; + default: + throw new Error('Invalid GlobalVariables.dateFormat value.'); + } + + $dialog.find('#start-datetime').datetimepicker({ + dateFormat: dateFormat, + + // Translation + dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], + EALang['thursday'], EALang['friday'], EALang['saturday']], + dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), + EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), + EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), + EALang['saturday'].substr(0,3)], + dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), + EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), + EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), + EALang['saturday'].substr(0,2)], + monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], + EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], + EALang['october'], EALang['november'], EALang['december']], + prevText: EALang['previous'], + nextText: EALang['next'], + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'], + firstDay: 1 + }); + $dialog.find('#start-datetime').datepicker('setDate', startDatetime); + + $dialog.find('#end-datetime').datetimepicker({ + dateFormat: dateFormat, + + // Translation + dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], + EALang['thursday'], EALang['friday'], EALang['saturday']], + dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), + EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), + EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), + EALang['saturday'].substr(0,3)], + dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), + EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), + EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), + EALang['saturday'].substr(0,2)], + monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], + EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], + EALang['october'], EALang['november'], EALang['december']], + prevText: EALang['previous'], + nextText: EALang['next'], + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'], + firstDay: 1 + }); + $dialog.find('#end-datetime').datepicker('setDate', endDatetime); + } + + /** + * Validate the manage appointment dialog data. Validation checks need to + * run every time the data are going to be saved. + * + * @returns {Boolean} Returns the validation result. + */ + function _validateAppointmentForm() { + var $dialog = $('#manage-appointment'); + + // Reset previous validation css formating. + $dialog.find('.form-group').removeClass('has-error'); + $dialog.find('.modal-message').addClass('hidden'); + + try { + // Check required fields. + var missingRequiredField = false; + + $dialog.find('.required').each(function() { + if ($(this).val() == '' || $(this).val() == null) { + $(this).parents('.form-group').addClass('has-error'); + missingRequiredField = true; + } + }); + + if (missingRequiredField) { + throw EALang['fields_are_required']; + } + + // Check email address. + if (!GeneralFunctions.validateEmail($dialog.find('#email').val())) { + $dialog.find('#email').parents('.form-group').eq(1).addClass('has-error'); + throw EALang['invalid_email']; + } + + // Check appointment start and end time. + var start = $('#start-datetime').datepicker('getDate'); + var end = $('#end-datetime').datepicker('getDate'); + if (start > end) { + $dialog.find('#start-datetime').parents('.form-group').addClass('has-error'); + $dialog.find('#end-datetime').parents('.form-group').addClass('has-error'); + throw EALang['start_date_before_end_error']; + } + + return true; + } catch(exc) { + $dialog.find('.modal-message').addClass('alert-danger').text(exc).removeClass('hidden'); + return false; + } + } + + exports.initialize = function() { + _bindEventHandlers(); + }; + +})(window.BackendCalendarAppointmentsModal); diff --git a/src/assets/js/backend_calendar_default_view.js b/src/assets/js/backend_calendar_default_view.js index 4594057e..e8e6e99c 100644 --- a/src/assets/js/backend_calendar_default_view.js +++ b/src/assets/js/backend_calendar_default_view.js @@ -26,10 +26,178 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {}; var FILTER_TYPE_PROVIDER = 'provider'; var FILTER_TYPE_SERVICE = 'service'; + // Variables + var lastFocusedEventData; // Contains event data for later use. + /** * Bind event handlers for the calendar view. */ function _bindEventHandlers() { + var $calendarPage = $('#calendar-page'); + + /** + * Event: Reload Button "Click" + * + * When the user clicks the reload button an the calendar items need to be refreshed. + */ + $('#reload-appointments').click(function() { + $('#select-filter-item').trigger('change'); + }); + + /** + * Event: Popover Close Button "Click" + * + * Hides the open popover element. + */ + $calendarPage.on('click', '.close-popover', function() { + $(this).parents().eq(2).remove(); + }); + + /** + * Event: Popover Edit Button "Click" + * + * Enables the edit dialog of the selected calendar event. + */ + $calendarPage.on('click', '.edit-popover', function() { + $(this).parents().eq(2).remove(); // Hide the popover + + var $dialog; + + if (lastFocusedEventData.data.is_unavailable == false) { + var appointment = lastFocusedEventData.data; + $dialog = $('#manage-appointment'); + + _resetAppointmentDialog(); + + // Apply appointment data and show modal dialog. + $dialog.find('.modal-header h3').text(EALang['edit_appointment_title']); + $dialog.find('#appointment-id').val(appointment['id']); + $dialog.find('#select-service').val(appointment['id_services']).trigger('change'); + $dialog.find('#select-provider').val(appointment['id_users_provider']); + + // Set the start and end datetime of the appointment. + var startDatetime = Date.parseExact(appointment['start_datetime'], + 'yyyy-MM-dd HH:mm:ss'); + $dialog.find('#start-datetime').datetimepicker('setDate', startDatetime); + + var endDatetime = Date.parseExact(appointment['end_datetime'], + 'yyyy-MM-dd HH:mm:ss'); + $dialog.find('#end-datetime').datetimepicker('setDate', endDatetime); + + var customer = appointment['customer']; + $dialog.find('#customer-id').val(appointment['id_users_customer']); + $dialog.find('#first-name').val(customer['first_name']); + $dialog.find('#last-name').val(customer['last_name']); + $dialog.find('#email').val(customer['email']); + $dialog.find('#phone-number').val(customer['phone_number']); + $dialog.find('#address').val(customer['address']); + $dialog.find('#city').val(customer['city']); + $dialog.find('#zip-code').val(customer['zip_code']); + $dialog.find('#appointment-notes').val(appointment['notes']); + $dialog.find('#customer-notes').val(customer['notes']); + } else { + var unavailable = lastFocusedEventData.data; + + // Replace string date values with actual date objects. + unavailable.start_datetime = GeneralFunctions.clone(lastFocusedEventData.start); + unavailable.end_datetime = GeneralFunctions.clone(lastFocusedEventData.end); + + $dialog = $('#manage-unavailable'); + _resetUnavailableDialog(); + + // Apply unvailable data to dialog. + $dialog.find('.modal-header h3').text('Edit Unavailable Period'); + $dialog.find('#unavailable-start').datetimepicker('setDate', unavailable.start_datetime); + $dialog.find('#unavailable-id').val(unavailable.id); + $dialog.find('#unavailable-end').datetimepicker('setDate', unavailable.end_datetime); + $dialog.find('#unavailable-notes').val(unavailable.notes); + } + + // :: DISPLAY EDIT DIALOG + $dialog.modal('show'); + }); + + /** + * Event: Popover Delete Button "Click" + * + * Displays a prompt on whether the user wants the appoinmtent to be deleted. If he confirms the + * deletion then an ajax call is made to the server and deletes the appointment from the database. + */ + $calendarPage.on('click', '.delete-popover', function() { + $(this).parents().eq(2).remove(); // Hide the popover + + if (lastFocusedEventData.data.is_unavailable == false) { + var messageButtons = {}; + messageButtons['OK'] = function() { + var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_appointment'; + var postData = { + csrfToken: GlobalVariables.csrfToken, + appointment_id : lastFocusedEventData.data['id'], + delete_reason: $('#delete-reason').val() + }; + + $.post(postUrl, postData, function(response) { + $('#message_box').dialog('close'); + + if (response.exceptions) { + response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); + GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, + GeneralFunctions.EXCEPTIONS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); + return; + } + + if (response.warnings) { + response.warnings = GeneralFunctions.parseExceptions(response.warnings); + GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, + GeneralFunctions.WARNINGS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); + } + + // Refresh calendar event items. + $('#select-filter-item').trigger('change'); + }, 'json').fail(GeneralFunctions.ajaxFailureHandler); + }; + + messageButtons[EALang['cancel']] = function() { + $('#message_box').dialog('close'); + }; + + GeneralFunctions.displayMessageBox(EALang['delete_appointment_title'], + EALang['write_appointment_removal_reason'], messageButtons); + + $('#message_box').append(''); + $('#delete-reason').css('width', '100%'); + } else { + // Do not display confirmation promt. + var postUrl = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_unavailable'; + var postData = { + csrfToken: GlobalVariables.csrfToken, + unavailable_id : lastFocusedEventData.data.id + }; + + $.post(postUrl, postData, function(response) { + $('#message_box').dialog('close'); + + if (response.exceptions) { + response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); + GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, GeneralFunctions.EXCEPTIONS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); + return; + } + + if (response.warnings) { + response.warnings = GeneralFunctions.parseExceptions(response.warnings); + GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); + } + + // Refresh calendar event items. + $('#select-filter-item').trigger('change'); + }, 'json').fail(GeneralFunctions.ajaxFailureHandler); + } + }); + /** * Event: Calendar Filter Item "Change" * @@ -257,7 +425,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {}; }; // Update appointment data. - _saveAppointment(appointment, undefined, successCallback, undefined); + BackendCalendarApi.saveAppointment(appointment, undefined, successCallback, undefined); } else { // Update unvailable time period. var unavailable = { @@ -311,7 +479,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {}; $('#footer').css('position', 'static'); // Footer position fix. }; - _saveUnavailable(unavailable, successCallback, undefined); + BackendCalendarApi.saveUnavailable(unavailable, successCallback, undefined); } } @@ -433,7 +601,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {}; }; // Update appointment data. - _saveAppointment(appointment, undefined, successCallback, undefined); + BackendCalendarApi.saveAppointment(appointment, undefined, successCallback, undefined); } else { // Update unavailable time period. var unavailable = { @@ -493,7 +661,7 @@ window.BackendCalendarDefaultView = window.BackendCalendarDefaultView || {}; $('#footer').css('position', 'static'); // Footer position fix. }; - _saveUnavailable(unavailable, successCallback, undefined); + BackendCalendarApi.saveUnavailable(unavailable, successCallback, undefined); } } diff --git a/src/assets/js/backend_calendar_unavailabilities_modal.js b/src/assets/js/backend_calendar_unavailabilities_modal.js new file mode 100644 index 00000000..eb056a19 --- /dev/null +++ b/src/assets/js/backend_calendar_unavailabilities_modal.js @@ -0,0 +1,240 @@ +/* ---------------------------------------------------------------------------- + * Easy!Appointments - Open Source Web Scheduler + * + * @package EasyAppointments + * @author A.Tselegidis + * @copyright Copyright (c) 2013 - 2016, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.2.0 + * ---------------------------------------------------------------------------- */ + +/** + * Backend Calendar Unavailabilities Modal + * + * This module implements the unavailabilities modal functionality. + * + * @module BackendCalendarUnavailabilitiesModal + */ +window.BackendCalendarUnavailabilitiesModal = window.BackendCalendarUnavailabilitiesModal || {}; + +(function(exports) { + + 'use strict'; + + function _bindEventHandlers() { + /** + * Event: Manage Unavailable Dialog Save Button "Click" + * + * Stores the unavailable period changes or inserts a new record. + */ + $('#manage-unavailable #save-unavailable').click(function() { + var $dialog = $('#manage-unavailable'); + var start = $dialog.find('#unavailable-start').datetimepicker('getDate'); + var end = $dialog.find('#unavailable-end').datetimepicker('getDate'); + + if (start > end) { + // Start time is after end time - display message to user. + $dialog.find('.modal-message') + .text(EALang['start_date_before_end_error']) + .addClass('alert-danger') + .removeClass('hidden'); + return; + } + + // Unavailable period records go to the appointments table. + var unavailable = { + start_datetime: start.toString('yyyy-MM-dd HH:mm'), + end_datetime: end.toString('yyyy-MM-dd HH:mm'), + notes: $dialog.find('#unavailable-notes').val(), + id_users_provider: $('#select-filter-item').val() // curr provider + }; + + if ($dialog.find('#unavailable-id').val() !== '') { + // Set the id value, only if we are editing an appointment. + unavailable.id = $dialog.find('#unavailable-id').val(); + } + + var successCallback = function(response) { + if (response.exceptions) { + response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); + GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, + GeneralFunctions.EXCEPTIONS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); + + $dialog.find('.modal-message') + .text(EALang['unexpected_issues_occurred']) + .addClass('alert-danger') + .removeClass('hidden'); + + return; + } + + if (response.warnings) { + response.warnings = GeneralFunctions.parseExceptions(response.warnings); + GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, + GeneralFunctions.WARNINGS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); + } + + // Display success message to the user. + $dialog.find('.modal-message') + .text(EALang['unavailable_saved']) + .addClass('alert-success') + .removeClass('alert-danger hidden'); + + // Close the modal dialog and refresh the calendar appointments after one second. + setTimeout(function() { + $dialog.find('.alert').addClass('hidden'); + $dialog.modal('hide'); + $('#select-filter-item').trigger('change'); + }, 2000); + }; + + var errorCallback = function(jqXHR, textStatus, errorThrown) { + GeneralFunctions.displayMessageBox('Communication Error', 'Unfortunately ' + + 'the operation could not complete due to server communication errors.'); + + $dialog.find('.modal-message').txt(EALang['service_communication_error']); + $dialog.find('.modal-message').addClass('alert-danger').removeClass('hidden'); + }; + + BackendCalendarApi.saveUnavailable(unavailable, successCallback, errorCallback); + }); + + /** + * Event: Manage Unavailable Dialog Cancel Button "Click" + * + * Closes the dialog without saveing any changes to the database. + */ + $('#manage-unavailable #cancel-unavailable').click(function() { + $('#manage-unavailable').modal('hide'); + }); + + /** + * Event : Insert Unavailable Time Period Button "Click" + * + * When the user clicks this button a popup dialog appears and the use can set a time period where + * he cannot accept any appointments. + */ + $('#insert-unavailable').click(function() { + _resetUnavailableDialog(); + var $dialog = $('#manage-unavailable'); + + // Set the default datetime values. + var start = new Date(); + var currentMin = parseInt(start.toString('mm')); + + if (currentMin > 0 && currentMin < 15) { + start.set({ 'minute': 15 }); + } else if (currentMin > 15 && currentMin < 30) { + start.set({ 'minute': 30 }); + } else if (currentMin > 30 && currentMin < 45) { + start.set({ 'minute': 45 }); + } else { + start.addHours(1).set({ 'minute': 0 }); + } + + $dialog.find('#unavailable-start').val(GeneralFunctions.formatDate(start, GlobalVariables.dateFormat, true)); + $dialog.find('#unavailable-end').val(GeneralFunctions.formatDate(start.addHours(1), GlobalVariables.dateFormat, true)); + $dialog.find('.modal-header h3').text(EALang['new_unavailable_title']); + $dialog.modal('show'); + }); + } + + /** + * Reset unavailable dialog form. + * + * Reset the "#manage-unavailable" dialog. Use this method to bring the dialog to the initial state + * before it becomes visible to the user. + */ + function _resetUnavailableDialog() { + var $dialog = $('#manage-unavailable'); + + $dialog.find('#unavailable-id').val(''); + + // Set default time values + var start = GeneralFunctions.formatDate(new Date(), GlobalVariables.dateFormat, true); + var end = GeneralFunctions.formatDate(new Date().addHours(1), GlobalVariables.dateFormat, true); + var dateFormat; + + switch(GlobalVariables.dateFormat) { + case 'DMY': + dateFormat = 'dd/mm/yy'; + break; + case 'MDY': + dateFormat = 'mm/dd/yy'; + break; + case 'YMD': + dateFormat = 'yy/mm/dd'; + break; + } + + + $dialog.find('#unavailable-start').datetimepicker({ + dateFormat: dateFormat, + + // Translation + dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], + EALang['thursday'], EALang['friday'], EALang['saturday']], + dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), + EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), + EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), + EALang['saturday'].substr(0,3)], + dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), + EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), + EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), + EALang['saturday'].substr(0,2)], + monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], + EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], + EALang['october'], EALang['november'], EALang['december']], + prevText: EALang['previous'], + nextText: EALang['next'], + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'], + firstDay: 1 + }); + $dialog.find('#unavailable-start').val(start); + + $dialog.find('#unavailable-end').datetimepicker({ + dateFormat: dateFormat, + + // Translation + dayNames: [EALang['sunday'], EALang['monday'], EALang['tuesday'], EALang['wednesday'], + EALang['thursday'], EALang['friday'], EALang['saturday']], + dayNamesShort: [EALang['sunday'].substr(0,3), EALang['monday'].substr(0,3), + EALang['tuesday'].substr(0,3), EALang['wednesday'].substr(0,3), + EALang['thursday'].substr(0,3), EALang['friday'].substr(0,3), + EALang['saturday'].substr(0,3)], + dayNamesMin: [EALang['sunday'].substr(0,2), EALang['monday'].substr(0,2), + EALang['tuesday'].substr(0,2), EALang['wednesday'].substr(0,2), + EALang['thursday'].substr(0,2), EALang['friday'].substr(0,2), + EALang['saturday'].substr(0,2)], + monthNames: [EALang['january'], EALang['february'], EALang['march'], EALang['april'], + EALang['may'], EALang['june'], EALang['july'], EALang['august'], EALang['september'], + EALang['october'], EALang['november'], EALang['december']], + prevText: EALang['previous'], + nextText: EALang['next'], + currentText: EALang['now'], + closeText: EALang['close'], + timeOnlyTitle: EALang['select_time'], + timeText: EALang['time'], + hourText: EALang['hour'], + minuteText: EALang['minutes'], + firstDay: 1 + }); + $dialog.find('#unavailable-end').val(end); + + // Clear the unavailable notes field. + $dialog.find('#unavailable-notes').val(''); + } + + exports.initialize = function() { + _bindEventHandlers(); + }; + +})(window.BackendCalendarUnavailabilitiesModal);