diff --git a/src/application/controllers/backend.php b/src/application/controllers/backend.php index 961a8f59..84e2a192 100644 --- a/src/application/controllers/backend.php +++ b/src/application/controllers/backend.php @@ -17,10 +17,11 @@ class Backend extends CI_Controller { $this->load->model('Settings_Model'); // Display the main backend page. - $view_data['base_url'] = $this->config->item('base_url'); + $view_data['base_url'] = $this->config->item('base_url'); + $view_data['book_advance_timeout'] = $this->Settings_Model->get_setting('book_advance_timeout'); $view_data['company_name'] = $this->Settings_Model->get_setting('company_name'); $view_data['available_providers'] = $this->Providers_Model->get_available_providers(); - $view_data['available_services'] = $this->Services_Model->get_available_services(); + $view_data['available_services'] = $this->Services_Model->get_available_services(); $this->load->view('backend/header', $view_data); $this->load->view('backend/calendar', $view_data); @@ -96,7 +97,7 @@ class Backend extends CI_Controller { * @param array $_POST['customer_data'] (OPTIONAL) Array with the customer * data. */ - public function ajax_save_appointment_changes() { + public function ajax_save_appointment() { try { $this->load->model('Appointments_Model'); $this->load->model('Providers_Model'); @@ -104,16 +105,23 @@ class Backend extends CI_Controller { $this->load->model('Customers_Model'); $this->load->model('Settings_Model'); - // :: SAVE APPOINTMENT CHANGES TO DATABASE - if (isset($_POST['appointment_data'])) { - $appointment_data = json_decode(stripcslashes($_POST['appointment_data']), true); - $this->Appointments_Model->add($appointment_data); - } - // :: SAVE CUSTOMER CHANGES TO DATABASE if (isset($_POST['customer_data'])) { $customer_data = json_decode(stripcslashes($_POST['customer_data']), true); - $this->Customers_Model->add($customer_data); + $customer_data['id'] = $this->Customers_Model->add($customer_data); + } + + // :: SAVE APPOINTMENT CHANGES TO DATABASE + if (isset($_POST['appointment_data'])) { + $appointment_data = json_decode(stripcslashes($_POST['appointment_data']), true); + + // If the appointment does not contain the customer record id, then it + // means that is is going to be inserted. Get the customer's record id. + if (!isset($appointment_data['id_users_customer'])) { + $appointment_data['id_users_customer'] = $customer_data['id']; + } + + $appointment_data['id'] = $this->Appointments_Model->add($appointment_data); } $appointment_data = $this->Appointments_Model->get_row($appointment_data['id']); @@ -128,20 +136,24 @@ class Backend extends CI_Controller { ); // :: SYNC APPOINTMENT CHANGES WITH GOOGLE CALENDAR - if ($appointment_data['id_google_calendar'] != NULL) { - $google_sync = $this->Providers_Model - ->get_setting('google_sync', $appointment_data['id_users_provider']); + $google_sync = $this->Providers_Model + ->get_setting('google_sync', $appointment_data['id_users_provider']); + + if ($google_sync == TRUE) { + $google_token = json_decode($this->Providers_Model + ->get_setting('google_token', $appointment_data['id_users_provider'])); + + $this->load->library('Google_Sync'); + + $this->google_sync->refresh_token($google_token->refresh_token); - if ($google_sync == TRUE) { - $google_token = json_decode($this->Providers_Model - ->get_setting('google_token', $appointment_data['id_users_provider'])); - - $this->load->library('Google_Sync'); - - $this->google_sync->refresh_token($google_token->refresh_token); - $this->google_sync->update_appointment($appointment_data, $provider_data, - $service_data, $customer_data, $company_settings); - } + if ($appointment_data['id_google_calendar'] == NULL) { + $this->google_sync->add_appointment($appointment_data, $provider_data, + $service_data, $customer_data, $company_settings); + } else { + $this->google_sync->update_appointment($appointment_data, $provider_data, + $service_data, $customer_data, $company_settings); + } } // :: SEND EMAIL NOTIFICATIONS TO PROVIDER AND CUSTOMER diff --git a/src/application/views/appointments/book.php b/src/application/views/appointments/book.php index 91448ab4..f9fdc0eb 100644 --- a/src/application/views/appointments/book.php +++ b/src/application/views/appointments/book.php @@ -225,7 +225,7 @@

- Fields with * are mandatory. + Fields with * are required!
diff --git a/src/application/views/backend/calendar.php b/src/application/views/backend/calendar.php index 03b51d7a..be214393 100644 --- a/src/application/views/backend/calendar.php +++ b/src/application/views/backend/calendar.php @@ -14,7 +14,8 @@ var GlobalVariables = { 'availableProviders' : , 'availableServices' : , - 'baseUrl' : + 'baseUrl' : , + 'bookAdvanceTimeout' : }; $(document).ready(function() { @@ -73,6 +74,7 @@

Edit Appointment

+
- +
- +
- +
- +
- +
- +
diff --git a/src/application/views/backend/header.php b/src/application/views/backend/header.php index af58fe26..e9183613 100644 --- a/src/application/views/backend/header.php +++ b/src/application/views/backend/header.php @@ -1,8 +1,9 @@ - Easy!Appointments Admin + Easy!Appointments Backend + diff --git a/src/assets/css/backend.css b/src/assets/css/backend.css index 57ef77b7..fb31aebb 100644 --- a/src/assets/css/backend.css +++ b/src/assets/css/backend.css @@ -35,6 +35,8 @@ root { background: rgba(255, 255, 255, 0.75);} #loading img { margin: auto; display: block; } +#modal-message { margin: 10px 0px; } + /* BACKEND CALENDAR PAGE -------------------------------------------------------------------- */ #calendar-page #calendar-toolbar { margin: 15px 10px 20px 10px; padding-bottom: 10px; diff --git a/src/assets/css/libs/jquery/images/ui-icons_ffffff_256x240.png b/src/assets/css/libs/jquery/images/ui-icons_ffffff_256x240.png new file mode 100644 index 00000000..4f624bb2 Binary files /dev/null and b/src/assets/css/libs/jquery/images/ui-icons_ffffff_256x240.png differ diff --git a/src/assets/js/backend_calendar.js b/src/assets/js/backend_calendar.js index 16e9e87b..db8fdcd2 100644 --- a/src/assets/js/backend_calendar.js +++ b/src/assets/js/backend_calendar.js @@ -161,72 +161,38 @@ var BackendCalendar = { $(this).parents().eq(2).remove(); // Hide the popover var appointmentData = BackendCalendar.lastFocusedEventData.data; - var modalHandle = $('#manage-appointment'); + var dialogHandle = $('#manage-appointment'); + + BackendCalendar.resetAppointmentDialog(); // :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG - modalHandle.find('input, textarea').val(''); - modalHandle.find('#appointment-id').val(appointmentData['id']); - - // Fill the services listbox and select the appointment service. - $.each(GlobalVariables.availableServices, function(index, service) { - var option = new Option(service['name'], service['id']); - modalHandle.find('#select-service').append(option); - }); - - $('#manage-appointment #select-service').val( - appointmentData['id_services']); - - // Fill the providers listbox with providers that can serve the appointment's - // service and then select the user's provider. - $.each(GlobalVariables.availableProviders, function(index, provider) { - var canProvideService = false; - - $.each(provider['services'], function(index, service) { - if (service === appointmentData['id_services']) { - canProvideService = true; - return; - } - }); - - if (canProvideService) { - var option = new Option(provider['first_name'] + ' ' - + provider['last_name'], provider['id']); - modalHandle.find('#select-provider').append(option); - } - }); - - modalHandle.find('#select-provider').val(appointmentData['id_users_provider']); + dialogHandle.find('.modal-header h3').text('Edit Appointment'); + dialogHandle.find('#appointment-id').val(appointmentData['id']); + dialogHandle.find('#select-service').val(appointmentData['id_services']); + dialogHandle.find('#select-provider').val(appointmentData['id_users_provider']); // Set the start and end datetime of the appointment.\ var startDatetime = Date.parseExact(appointmentData['start_datetime'], - 'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm'); - modalHandle.find('#start-datetime').datetimepicker({ - dateFormat : 'dd/mm/yy', - defaultValue: startDatetime - }); - modalHandle.find('#start-datetime').val(startDatetime); + 'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm'); + dialogHandle.find('#start-datetime').val(startDatetime); var endDatetime = Date.parseExact(appointmentData['end_datetime'], 'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm'); - modalHandle.find('#end-datetime').datetimepicker({ - dateFormat : 'dd/mm/yy', - defaultValue: endDatetime - }); - modalHandle.find('#end-datetime').val(endDatetime); + dialogHandle.find('#end-datetime').val(endDatetime); var customerData = appointmentData['customer']; - modalHandle.find('#customer-id').val(appointmentData['id_users_customer']); - modalHandle.find('#first-name').val(customerData['first_name']); - modalHandle.find('#last-name').val(customerData['last_name']); - modalHandle.find('#email').val(customerData['email']); - modalHandle.find('#phone-number').val(customerData['phone_number']); - modalHandle.find('#address').val(customerData['address']); - modalHandle.find('#city').val(customerData['city']); - modalHandle.find('#zip-code').val(customerData['zip_code']); - modalHandle.find('#notes').val(appointmentData['notes']); + dialogHandle.find('#customer-id').val(appointmentData['id_users_customer']); + dialogHandle.find('#first-name').val(customerData['first_name']); + dialogHandle.find('#last-name').val(customerData['last_name']); + dialogHandle.find('#email').val(customerData['email']); + dialogHandle.find('#phone-number').val(customerData['phone_number']); + dialogHandle.find('#address').val(customerData['address']); + dialogHandle.find('#city').val(customerData['city']); + dialogHandle.find('#zip-code').val(customerData['zip_code']); + dialogHandle.find('#notes').val(appointmentData['notes']); // :: DISPLAY THE MANAGE APPOINTMENTS MODAL DIALOG - $('#manage-appointment').modal('show'); + dialogHandle.modal('show'); }); /** @@ -249,7 +215,7 @@ var BackendCalendar = { $.post(postUrl, postData, function(response) { ///////////////////////////////////////////////////////// - //console.log('Delete Appointment Response :', response); + console.log('Delete Appointment Response :', response); ///////////////////////////////////////////////////////// $('#message_box').dialog('close'); @@ -279,7 +245,7 @@ var BackendCalendar = { /** * Event: Manage Appointments Dialog Cancel Button "Click" * - * Closes the dialog without making any actions. + * Closes the dialog without saving any changes to the database. */ $('#manage-appointment #cancel-button').click(function() { $('#manage-appointment').modal('hide'); @@ -288,9 +254,15 @@ var BackendCalendar = { /** * Event: Manage Appointments Dialog Save Button "Click" * - * Stores the appointment changes. + * Stores the appointment changes or inserts a new appointment depending the dialog + * mode. */ $('#manage-appointment #save-button').click(function() { + // Before doing anything the appointment data need to be validated. + if (!BackendCalendar.validateAppointmentForm()) { + return; // validation failed + } + // :: PREPARE APPOINTMENT DATA FOR AJAX CALL var modalHandle = $('#manage-appointment'); @@ -303,17 +275,21 @@ var BackendCalendar = { 'dd/MM/yyyy HH:mm').toString('yyyy-MM-dd HH:mm:ss'); var appointmentData = { - 'id' : modalHandle.find('#appointment-id').val(), 'id_services' : modalHandle.find('#select-service').val(), 'id_users_provider' : modalHandle.find('#select-provider').val(), - 'id_users_customer' : modalHandle.find('#customer-id').val(), 'start_datetime' : startDatetime, 'end_datetime' : endDatetime, 'notes' : modalHandle.find('#notes').val() }; + if (modalHandle.find('#appointment-id').val() !== '') { + // Set the id value, only if we are editing an appointment. + appointmentData['id'] = modalHandle.find('#appointment-id').val(); + } + + + var customerData = { - 'id' : modalHandle.find('#customer-id').val(), 'first_name' : modalHandle.find('#first-name').val(), 'last_name' : modalHandle.find('#last-name').val(), 'email' : modalHandle.find('#email').val(), @@ -323,6 +299,12 @@ var BackendCalendar = { 'zip_code' : modalHandle.find('#zip-code').val() }; + if (modalHandle.find('#customer-id').val() !== '') { + // Set the id value, only if we are editing an appointment. + customerData['id'] = modalHandle.find('#customer-id').val(); + appointmentData['id_users_customer'] = customerData['id']; + } + // :: DEFINE SUCCESS EVENT CALLBACK var successCallback = function(response) { if (response.error) { @@ -359,7 +341,7 @@ var BackendCalendar = { }; // :: CALL THE UPDATE APPOINTMENT METHOD - BackendCalendar.updateAppointmentData(appointmentData, customerData, + BackendCalendar.saveAppointmentData(appointmentData, customerData, successCallback, errorCallback); }); @@ -419,6 +401,33 @@ var BackendCalendar = { }); } }); + + /** + * 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() { + var dialogHandle = $('#manage-appointment'); + BackendCalendar.resetAppointmentDialog(); + + // :: PREPARE THE MANAGE APPOINTMENT DIALOG FOR INSERTION + dialogHandle.find('.modal-header h3').text('New Appointment'); + + // :: DISPLAY THE MANAGE APPOINTMENT MODAL DIALOG + dialogHandle.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() { + // @task Implement this event handler. + }); }, /** @@ -495,10 +504,10 @@ var BackendCalendar = { * @param {function} errorCallback (OPTIONAL) If defined, this function is * going to be executed on post failure. */ - updateAppointmentData : function(appointmentData, customerData, + saveAppointmentData : function(appointmentData, customerData, successCallback, errorCallback) { // :: MAKE AN AJAX CALL TO SERVER - STORE APPOINTMENT DATA - var postUrl = GlobalVariables.baseUrl + 'backend/ajax_save_appointment_changes'; + var postUrl = GlobalVariables.baseUrl + 'backend/ajax_save_appointment'; var postData = {}; postData['appointment_data'] = JSON.stringify(appointmentData); @@ -514,7 +523,7 @@ var BackendCalendar = { dataType : 'json', success : function(response) { ///////////////////////////////////////////////////////////// - console.log('Update Appointment Data Response:', response); + console.log('Save Appointment Data Response:', response); ///////////////////////////////////////////////////////////// if (successCallback !== undefined) { @@ -523,7 +532,7 @@ var BackendCalendar = { }, error : function(jqXHR, textStatus, errorThrown) { ////////////////////////////////////////////////////////////////// - console.log('Update Appointment Data Error:', jqXHR, textStatus, + console.log('Save Appointment Data Error:', jqXHR, textStatus, errorThrown); ////////////////////////////////////////////////////////////////// @@ -579,7 +588,7 @@ var BackendCalendar = { .toString('yyyy-MM-dd HH:mm:ss'); var postUrl = GlobalVariables.baseUrl - + 'backend/ajax_save_appointment_changes'; + + 'backend/ajax_save_appointment'; var postData = { 'appointment_data' : JSON.stringify(appointmentData) @@ -601,7 +610,7 @@ var BackendCalendar = { }; // :: UPDATE APPOINTMENT DATA VIA AJAX CALL - BackendCalendar.updateAppointmentData(appointmentData, undefined, + BackendCalendar.saveAppointmentData(appointmentData, undefined, successCallback, undefined); }, @@ -742,7 +751,7 @@ var BackendCalendar = { event.data['end_datetime'] = appointmentData['end_datetime']; var postUrl = GlobalVariables.baseUrl - + 'backend/ajax_save_appointment_changes'; + + 'backend/ajax_save_appointment'; var postData = { 'appointment_data' : JSON.stringify(appointmentData) }; @@ -764,7 +773,7 @@ var BackendCalendar = { }; // :: UPDATE APPOINTMENT DATA VIA AJAX CALL - BackendCalendar.updateAppointmentData(appointmentData, undefined, + BackendCalendar.saveAppointmentData(appointmentData, undefined, successCallback, undefined); }, @@ -816,5 +825,117 @@ var BackendCalendar = { } }, 'json'); + }, + + /** + * 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. + */ + resetAppointmentDialog: function() { + var dialogHandle = $('#manage-appointment'); + + // :: EMPTY FORM FIELDS + dialogHandle.find('input, textarea').val(''); + dialogHandle.find('#modal-message').hide(); + dialogHandle.find('#select-service, #select-provider').empty(); + + // :: PREPARE SERVICE AND PROVIDER LISTBOXES + $.each(GlobalVariables.availableServices, function(index, service) { + var option = new Option(service['name'], service['id']); + dialogHandle.find('#select-service').append(option); + }); + dialogHandle.find('#select-service').val( + dialogHandle.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. + $.each(GlobalVariables.availableProviders, function(index, provider) { + var canProvideService = false; + + $.each(provider['services'], function(index, service) { + if (service == dialogHandle.find('#select-service').val()) { + canProvideService = true; + return; + } + }); + + if (canProvideService) { // Add the provider to the listbox. + var option = new Option(provider['first_name'] + + ' ' + provider['last_name'], provider['id']); + dialogHandle.find('#select-provider').append(option); + } + }); + + // :: SETUP START AND END DATETIME PICKERS + // 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'] == dialogHandle.find('#select-service').val()) { + serviceDuration = service['duration']; + return; + } + }); + + var startDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout) + .toString('dd/MM/yyyy HH:mm'); + var endDatetime = new Date().addMinutes(GlobalVariables.bookAdvanceTimeout) + .addMinutes(serviceDuration).toString('dd/MM/yyyy HH:mm'); + + dialogHandle.find('#start-datetime').datetimepicker({ + dateFormat : 'dd/mm/yy', + minDate : 0 + }); + dialogHandle.find('#start-datetime').val(startDatetime); + + dialogHandle.find('#end-datetime').datetimepicker({ + dateFormat : 'dd/mm/yy', + minDate : 0 + }); + dialogHandle.find('#end-datetime').val(endDatetime); + }, + + /** + * Validate the manage appointment dialog data. Validation checks need to + * run every time the data are going to be saved. + * + * @returns {bool} Returns the validation result. + */ + validateAppointmentForm: function() { + var dialogHandle = $('#manage-appointment'); + + // Reset previous validation css formating. + dialogHandle.find('.control-group').removeClass('error'); + dialogHandle.find('#modal-message').hide(); + + try { + // :: CHECK REQUIRED FIELDS + var missingRequiredField = false; + dialogHandle.find('.required').each(function() { + if ($(this).val() === '') { + $(this).parents().eq(1).addClass('error'); + missingRequiredField = true; + } + }); + if (missingRequiredField) { + throw 'Fields with * are required!'; + } + + // :: CHECK EMAIL ADDRESS + if (!GeneralFunctions.validateEmail(dialogHandle.find('#email').val())) { + dialogHandle.find('#email').parents().eq(1).addClass('error'); + throw 'Invalid email address!'; + } + + return true; + + } catch(exc) { + dialogHandle.find('#modal-message') + .addClass('alert-error') + .text(exc) + .show('fade'); + return false; + } } }; \ No newline at end of file diff --git a/src/assets/js/frontend_book.js b/src/assets/js/frontend_book.js index 1b9324b4..86fb068c 100644 --- a/src/assets/js/frontend_book.js +++ b/src/assets/js/frontend_book.js @@ -215,6 +215,8 @@ var FrontendBook = { * Before the form is submitted to the server we need to make sure that * in the meantime the selected appointment date/time wasn't reserved by * another customer or event. + * + * @task Fix the problem with this event handler. Book does not work anymore. */ $('#book-appointment-form').submit(function() { event.preventDefault(); @@ -245,7 +247,7 @@ var FrontendBook = { GeneralFunctions.displayMessageBox('Appointment Hour Taken', 'Unfortunately ' + 'the selected appointment hour is not available anymore. Please select ' + 'another hour.'); - FrontendBook.getAvailableHours($('#select-date').datepicker('getDate')); + FrontendBook.getAvailableHours($('#select-date').val()); } }, 'json'); }); @@ -341,23 +343,38 @@ var FrontendBook = { }, /** - * This function validates the customer's data input. - * It only checks for empty fields by the time. + * This function validates the customer's data input. The user cannot contiue + * without passing all the validation checks. * * @return {bool} Returns the validation result. */ validateCustomerForm : function() { - var validationResult = true; - $('.required').css('border', ''); - - $('.required').each(function() { - if ($(this).val() == '') { - validationResult = false; - $(this).css('border', '2px solid red'); + $('#wizard-frame-3 input').css('border', ''); + + try { + // :: CHECK REQUIRED FIELDS + var missingRequiredField = false; + $('.required').each(function() { + if ($(this).val() == '') { + $(this).css('border', '2px solid red'); + missingRequiredField = true; + } + }); + if (missingRequiredField) { + throw 'Fields with * are required!'; } - }); - - return validationResult; + + // :: CHECK EMAIL ADDRESS + if (!GeneralFunctions.validateEmail($('#email').val())) { + $('#email').css('border', '2px solid red'); + throw 'Invalid email address!'; + } + + return true; + } catch(exc) { + $('#form-message').text(exc); + return false; + } }, /** diff --git a/src/assets/js/general_functions.js b/src/assets/js/general_functions.js index 2446c985..2d51fa64 100644 --- a/src/assets/js/general_functions.js +++ b/src/assets/js/general_functions.js @@ -163,5 +163,17 @@ var GeneralFunctions = { } throw new Error("Unable to copy obj! Its type isn't supported."); + }, + + /** + * This method validates an email address. If the address is not on the proper + * form then the result is FALSE. + * + * @param {string} email The email address to be checked. + * @returns {bool} Returns the validation result. + */ + validateEmail: function(email) { + var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; + return reg.test(email); } }; \ No newline at end of file