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 @@ + + + + +
+ + + +
+ +
+
+ + +
+

Services

+
+
+ +
+
+
+ + + +
+ + +
+ +

Details

+ + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
\ 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