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
+
@@ -124,23 +126,23 @@
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