diff --git a/src/application/config/constants.php b/src/application/config/constants.php
index 81e46b70..3d5fb33e 100644
--- a/src/application/config/constants.php
+++ b/src/application/config/constants.php
@@ -53,5 +53,7 @@ define('DB_SLUG_SECRETARY', 'secretary');
define('FILTER_TYPE_PROVIDER', 'provider');
define('FILTER_TYPE_SERVICE', 'service');
+define('AJAX_SUCCESS', 'SUCCESS');
+define('AJAX_FAILURE', 'FAILURE');
/* End of file constants.php */
/* Location: ./application/config/constants.php */
\ No newline at end of file
diff --git a/src/application/controllers/backend.php b/src/application/controllers/backend.php
index 02612537..7f7fcb4c 100644
--- a/src/application/controllers/backend.php
+++ b/src/application/controllers/backend.php
@@ -66,7 +66,21 @@ class Backend extends CI_Controller {
}
public function services() {
- echo '
Not implemented yet. ';
+ // @task Require user to be logged in the application.
+
+ $this->load->model('providers_model');
+ $this->load->model('customers_model');
+ $this->load->model('services_model');
+ $this->load->model('settings_model');
+
+ $view['base_url'] = $this->config->item('base_url');
+ $view['company_name'] = $this->settings_model->get_setting('company_name');
+ $view['services'] = $this->services_model->get_batch();
+ $view['categories'] = $this->services_model->get_all_categories();
+
+ $this->load->view('backend/header', $view);
+ $this->load->view('backend/services', $view);
+ $this->load->view('backend/footer', $view);
}
public function providers() {
diff --git a/src/application/controllers/backend_api.php b/src/application/controllers/backend_api.php
index 135b536f..0b1f95d1 100644
--- a/src/application/controllers/backend_api.php
+++ b/src/application/controllers/backend_api.php
@@ -180,7 +180,7 @@ class Backend_api extends CI_Controller {
}
if (!isset($warnings)) {
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
} else {
echo json_encode(array(
'warnings' => $warnings
@@ -262,7 +262,7 @@ class Backend_api extends CI_Controller {
// :: SEND RESPONSE TO CLIENT BROWSER
if (!isset($warnings)) {
- echo json_encode('SUCCESS'); // Everything executed successfully.
+ echo json_encode(AJAX_SUCCESS); // Everything executed successfully.
} else {
echo json_encode(array(
'warnings' => $warnings // There were warnings during the operation.
@@ -294,7 +294,7 @@ class Backend_api extends CI_Controller {
$this->providers_model->set_setting('google_sync', FALSE, $_POST['provider_id']);
$this->providers_model->set_setting('google_token', NULL, $_POST['provider_id']);
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
} catch(Exception $exc) {
echo json_encode(array(
@@ -397,7 +397,7 @@ class Backend_api extends CI_Controller {
'warnings' => $warnings
));
} else {
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
}
} catch(Exception $exc) {
@@ -441,7 +441,7 @@ class Backend_api extends CI_Controller {
'warnings' => $warnings
));
} else {
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
}
} catch(Exception $exc) {
@@ -461,7 +461,7 @@ class Backend_api extends CI_Controller {
$this->load->model('customers_model');
$customer = json_decode($_POST['customer'], true);
$this->customers_model->add($customer);
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
} catch(Exception $exc) {
echo json_encode(array(
'exceptions' => array(exceptionToJavascript($exc))
@@ -478,7 +478,7 @@ class Backend_api extends CI_Controller {
try {
$this->load->model('customers_model');
$this->customers_model->delete($_POST['customer_id']);
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
} catch(Exception $exc) {
echo json_encode(array(
'exceptions' => array(exceptionToJavascript($exc))
@@ -487,27 +487,84 @@ class Backend_api extends CI_Controller {
}
public function ajax_save_service() {
-
+ try {
+ $this->load->model('services_model');
+ $service = json_decode($_POST['service'], true);
+ $this->services_model->add($service);
+ echo json_encode(AJAX_SUCCESS);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
public function ajax_delete_service() {
-
+ try {
+ $this->load->model('services_model');
+ $result = $this->services_model->delete($_POST['service_id']);
+ echo ($result) ? json_encode(AJAX_SUCCESS) : json_encode(AJAX_FAILURE);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
public function ajax_filter_services() {
-
+ try {
+ $this->load->model('services_model');
+ $key = $_POST['key']; // @task fix sql injection
+ $where =
+ '(name LIKE "%' . $key . '%" OR duration LIKE "%' . $key . '%" OR ' .
+ 'price LIKE "%' . $key . '%" OR currency LIKE "%' . $key . '%" OR ' .
+ 'description LIKE "%' . $key . '%")';
+ $services = $this->services_model->get_batch($where);
+ echo json_encode($services);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
public function ajax_save_service_category() {
-
+ try {
+ $this->load->model('services_model');
+ $category = json_decode($_POST['category'], true);
+ $this->services_model->add_category($category);
+ echo json_encode(AJAX_SUCCESS);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
public function ajax_delete_service_category() {
-
+ try {
+ $this->load->model('services_model');
+ $result = $this->services_model->delete($_POST['category_id']);
+ echo ($result) ? json_encode(AJAX_SUCCESS) : json_encode(AJAX_FAILURE);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
public function ajax_filter_service_categories() {
-
+ try {
+ $this->load->model('services_model');
+ $key = $_POST['key']; // @task sql injection
+ $where = '(name LIKE "%' . $key . '%" OR description LIKE "%' . $key . '%")';
+ $categories = $this->services_model->get_categories($where);
+ echo json_encode($categories);
+ } catch(Exception $exc) {
+ echo json_encode(array(
+ 'exceptions' => array(exceptionToJavascript($exc))
+ ));
+ }
}
}
diff --git a/src/application/controllers/google.php b/src/application/controllers/google.php
index 8f39fecf..51de484c 100644
--- a/src/application/controllers/google.php
+++ b/src/application/controllers/google.php
@@ -185,7 +185,7 @@ class Google extends CI_Controller {
}
}
- echo json_encode('SUCCESS');
+ echo json_encode(AJAX_SUCCESS);
} catch(Exception $exc) {
echo json_encode(array(
diff --git a/src/application/models/services_model.php b/src/application/models/services_model.php
index 31c6b852..39728d71 100644
--- a/src/application/models/services_model.php
+++ b/src/application/models/services_model.php
@@ -324,7 +324,8 @@ class Services_Model extends CI_Model {
*
* @return array Returns an array that contains all the service category records.
*/
- public function get_all_categories() {
+ public function get_all_categories($where = '') {
+ if ($where !== '') $this->db->where($where);
return $this->db->get('ea_service_categories')->result_array();
}
diff --git a/src/application/views/backend/services.php b/src/application/views/backend/services.php
index e69de29b..4ddd3b9a 100644
--- a/src/application/views/backend/services.php
+++ b/src/application/views/backend/services.php
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filter
+
+
Services
+
+
+
+
+
+
+
Details
+
+
+
+
+
Name *
+
+
+
Duration
+
+
+
Price
+
+
+
Currency
+
+
+
Description
+
+
+
Category
+
+
+
+
+
+
+
+
+
+ Filter
+
+
+
Categories
+
+
+
+
+
+
+
Details
+
+
+
Name *
+
+
+
Description
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/css/backend.css b/src/assets/css/backend.css
index ed8d0617..cfe4f66a 100644
--- a/src/assets/css/backend.css
+++ b/src/assets/css/backend.css
@@ -78,7 +78,12 @@ root {
/* BACKEND SERVICES PAGE
-------------------------------------------------------------------- */
-
+#services-page .tab-content { margin: 15px; }
+#services-page .nav { margin: 15px; }
+#services-page .nav li { cursor: pointer; }
+#services-page .service-row { padding: 10px 7px; border-radius: 3px; }
+#services-page .service-row:hover { cursor: pointer; background-color: #C6E7D5; }
+#services-page .selected-row { background-color: #EFFDF7; }
/* BACKEND PROVIDERS PAGE
-------------------------------------------------------------------- */
diff --git a/src/assets/js/backend.js b/src/assets/js/backend.js
index 17caa61d..ba9f691b 100644
--- a/src/assets/js/backend.js
+++ b/src/assets/js/backend.js
@@ -87,5 +87,31 @@ var Backend = {
$('#notification').html(notificationHtml);
$('#notification').show('blind');
+ },
+
+ /**
+ * All backend js code has the same way of dislaying exceptions that are raised on the
+ * server during an ajax call.
+ *
+ * @param {object} response Contains the server response. If exceptions or warnings are
+ * found, user friendly messages are going to be displayed to the user.
+ * @returns {bool} Returns whether the the ajax callback should continue the execution or
+ * stop, due to critical server exceptions.
+ */
+ handleAjaxExceptions: function(response) {
+ if (response.exceptions) {
+ response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
+ GeneralFunctions.displayMessageBox(Backend.EXCEPTIONS_TITLE, Backend.EXCEPTIONS_MESSAGE);
+ $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions));
+ return false;
+ }
+
+ if (response.warnings) {
+ response.warnings = GeneralFunctions.parseExceptions(response.warnings);
+ GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE);
+ $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
+ }
+
+ return true;
}
};
\ No newline at end of file
diff --git a/src/assets/js/backend_services.js b/src/assets/js/backend_services.js
new file mode 100644
index 00000000..318f9e59
--- /dev/null
+++ b/src/assets/js/backend_services.js
@@ -0,0 +1,228 @@
+/**
+ * This namespace handles the js functionality of the backend services page.
+ *
+ * @namespace BackendServices
+ */
+var BackendServices = {
+ /**
+ * Contains the basic record methods for the page.
+ *
+ * @type ServicesHelper|CategoriesHelper
+ */
+ helper: {},
+
+ /**
+ * Default initialize method of the page.
+ *
+ * @param {bool} bindEventHandlers (OPTIONAL) Determines whether to bind the
+ * default event handlers (default: true).
+ */
+ initialize: function(bindEventHandlers) {
+ if (bindEventHandlers === undefined) bindEventHandlers = true;
+
+ // Fill available service categories listbox.
+ $.each(GlobalVariables.categories, function(index, category) {
+ var option = new Option(category.name, category.value);
+ $('#service-category').append(option);
+ });
+ $('#service-category').append(new Option('- No Category -', null)).val('null');
+
+ // Instantiate helper object (service helper by default).
+ BackendServices.helper = new ServicesHelper();
+ BackendServices.helper.resetForm();
+ BackendServices.helper.filter('');
+
+ $('#service-duration').spinner({
+ 'min': 0,
+ 'numberFormat': 'n Minutes'
+ });
+
+
+ if (bindEventHandlers) BackendServices.bindEventHandlers();
+ },
+
+ /**
+ * Binds the default event handlers of the backend services page. Do not use this method
+ * if you include the "BackendServices" namespace on another page.
+ *
+ * @returns {undefined}
+ */
+ bindEventHandlers: function() {
+ /**
+ * Event: Page Tab Button "Click"
+ *
+ * Changes the displayed tab.
+ */
+ $('.tab').click(function() {
+ $('.active').removeClass('active');
+ $(this).addClass('active');
+ $('.tab-content').hide();
+
+ if ($(this).hasClass('services-tab')) { // display services tab
+ $('#services').show();
+ BackendServices.helper = new ServicesHelper();
+ } else if ($(this).hasClass('categories-tab')) { // display categories tab
+ $('#categories').show();
+ BackendServices.helper = new CategoriesHelper();
+ }
+ });
+
+ /**
+ * Event: Filter Services Button "Click"
+ */
+ $(' .filter-services').click(function() {
+ var key = $('#services .filter-key').val();
+ $('.selected-row').removeClass('selected-row');
+ BackendServices.helper.resetForm();
+ BackendServices.helper.filter(key);
+ });
+
+ /**
+ * Event: Filter Service Row "Click"
+ *
+ * Display the selected service data to the user.
+ */
+ $(document).on('click', '.service-row', function() {
+ var service = { 'id': $(this).attr('data-id') };
+
+ $.each(BackendServices.helper.filterResults, function(index, item) {
+ if (item.id === service.id) {
+ service = item;
+ return;
+ }
+ });
+
+ BackendServices.helper.display(service);
+
+ $('.selected-row').removeClass('selected-row');
+ $(this).addClass('selected-row');
+ });
+ }
+};
+
+/**
+ * This class contains the methods that will be used by the "Services" tab of the page.
+ * @class ServicesHelper
+ */
+var ServicesHelper = function() {
+ this.filterResults = {};
+};
+
+/**
+ * Save service record to database.
+ *
+ * @param {object} service Contains the service record data. If an 'id' value is provided
+ * then the update operation is going to be executed.
+ */
+ServicesHelper.prototype.save = function(service) {
+ var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_service';
+ var postData = { 'service': JSON.stringify(service) };
+
+ $.post(postUrl, postData, function(response) {
+ console.log('Save Service Response:', response);
+ if (!Backend.handleAjaxExceptions(response)) return;
+
+ $('#services .add-edit-delete-group').show();
+ $('#services .save-cancel-group').hide();
+ });
+};
+
+/**
+ * Delete a service records from database.
+ *
+ * @param {int} id Record id to be deleted.
+ */
+ServicesHelper.prototype.delete = function(id) {
+
+};
+
+/**
+ * Validates a service record.
+ *
+ * @param {object} service Contains the service data.
+ * @returns {bool} Returns the validation result.
+ */
+ServicesHelper.prototype.validate = function(service) {
+
+};
+
+/**
+ * Resets the service tab form back to its initial state.
+ */
+ServicesHelper.prototype.resetForm = function() {
+ $('#services .details').find('input, textarea').val('');
+ $('#service-category').val('null');
+ $('#services .add-edit-delete-group').show();
+ $('#services .save-cancel-group').hide();
+ $('#edit-service, #delete-service').prop('disabled', true);
+ $('#services .details').find('input, textarea').prop('readonly', true);
+ $('#service-category').prop('disabled', true);
+};
+
+/**
+ * Display a service record into the service form.
+ *
+ * @param {object} service Contains the service record data.
+ */
+ServicesHelper.prototype.display = function(service) {
+ $('#service-id').val(service.id);
+ $('#service-name').val(service.name);
+ $('#service-duration').val(service.duration + ' min');
+ $('#service-price').val(service.price);
+ $('#service-currency').val(service.currency);
+ $('#service-description').val(service.description);
+ $('#service-category').val(service.id_service_categories);
+};
+
+/**
+ * Filters service records depending a string key.
+ *
+ * @param {string} key This is used to filter the service records of the database.
+ */
+ServicesHelper.prototype.filter = function(key) {
+ var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_filter_services';
+ var postData = { 'key': key };
+
+ $.post(postUrl, postData, function(response) {
+ /////////////////////////////////////////////////////
+ console.log('Filter services response:', response);
+ /////////////////////////////////////////////////////
+
+ if (!Backend.handleAjaxExceptions(response)) return;
+
+ BackendServices.helper.filterResults = response;
+ $('#services .filter-results').html('');
+
+ $.each(response, function(index, service) {
+ var html = ServicesHelper.prototype.getFilterHtml(service);
+ $('#services .filter-results').append(html);
+ });
+ }, 'json');
+};
+
+/**
+ * Get a service row html code that is going to be displayed on the filter results list.
+ *
+ * @param {object} service Contains the service record data.
+ * @returns {string} The html code that represents the record on the filter results list.
+ */
+ServicesHelper.prototype.getFilterHtml = function(service) {
+ var html =
+ '' +
+ '' + service.name + ' ' +
+ service.duration + ' min - ' +
+ service.price + ' ' + service.currency + ' ' +
+ '
';
+
+ return html;
+};
+
+/**
+ * This class contains the core method implementations that belong to the categories tab
+ * of the backend services page.
+ *
+ * @class CategoriesHelper
+ */
+var CategoriesHelper = function() {
+ this.filterResults = {};
+};
\ No newline at end of file