diff --git a/src/application/controllers/backend_api.php b/src/application/controllers/backend_api.php index 42476282..08c02f42 100644 --- a/src/application/controllers/backend_api.php +++ b/src/application/controllers/backend_api.php @@ -311,24 +311,44 @@ class Backend_api extends CI_Controller { */ public function ajax_filter_customers() { try { + $this->load->model('appointments_model'); + $this->load->model('services_model'); + $this->load->model('providers_model'); $this->load->model('customers_model'); - + $key = $_POST['key']; // @task $this->db->escape($_POST['key']); $where_clause = - 'first_name LIKE "%' . $key . '%" OR ' . + '(first_name LIKE "%' . $key . '%" OR ' . 'last_name LIKE "%' . $key . '%" OR ' . 'email LIKE "%' . $key . '%" OR ' . 'phone_number LIKE "%' . $key . '%" OR ' . 'address LIKE "%' . $key . '%" OR ' . 'city LIKE "%' . $key . '%" OR ' . - 'zip_code LIKE "%' . $key . '%" '; + 'zip_code LIKE "%' . $key . '%")'; - echo json_encode($this->customers_model->get_batch($where_clause)); + $customers = $this->customers_model->get_batch($where_clause); + + foreach($customers as &$customer) { + $appointments = $this->appointments_model + ->get_batch(array('id_users_customer' => $customer['id'])); + + foreach($appointments as &$appointment) { + $appointment['service'] = $this->services_model + ->get_row($appointment['id_services']); + $appointment['provider'] = $this->providers_model + ->get_row($appointment['id_users_provider']); + } + + $customer['appointments'] = $appointments; + } + + echo json_encode($customers); + } catch(Exception $exc) { echo json_encode(array( - 'exceptions' => array($exc) - )); + 'exceptions' => array(exceptionToJavascript($exc)) + )); } } @@ -382,7 +402,7 @@ class Backend_api extends CI_Controller { } catch(Exception $exc) { echo json_encode(array( - 'exceptions' => array($exc) + 'exceptions' => array(exceptionToJavascript($exc)) )); } } @@ -426,7 +446,42 @@ class Backend_api extends CI_Controller { } catch(Exception $exc) { echo json_encode(array( - 'exceptions' => array($exc) + 'exceptions' => array(exceptionToJavascript($exc)) + )); + } + } + + /** + * Save (insert or update) a customer record. + * + * @param array $_POST['customer'] JSON encoded array that contains the customer's data. + */ + public function ajax_save_customer() { + try { + $this->load->model('customers_model'); + $customer = json_decode($_POST['customer'], true); + $this->customers_model->add($customer); + echo json_encode('SUCCESS'); + } catch(Exception $exc) { + echo json_encode(array( + 'exceptions' => array(exceptionToJavascript($exc)) + )); + } + } + + /** + * Delete customer from database. + * + * @param numeric $_POST['customer_id'] Customer record id to be deleted. + */ + public function ajax_delete_customer() { + try { + $this->load->model('customers_model'); + $this->customers_model->delete($_POST['customer_id']); + echo json_encode('SUCCESS'); + } catch(Exception $exc) { + echo json_encode(array( + 'exceptions' => array(exceptionToJavascript($exc)) )); } } diff --git a/src/application/models/customers_model.php b/src/application/models/customers_model.php index 4e118247..c1072141 100644 --- a/src/application/models/customers_model.php +++ b/src/application/models/customers_model.php @@ -60,12 +60,12 @@ class Customers_Model extends CI_Model { // This method shouldn't depend on another method of this class. $num_rows = $this->db - ->select('*') - ->from('ea_users') - ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') - ->where('ea_users.email', $customer['email']) - ->where('ea_roles.slug', DB_SLUG_CUSTOMER) - ->get()->num_rows(); + ->select('*') + ->from('ea_users') + ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') + ->where('ea_users.email', $customer['email']) + ->where('ea_roles.slug', DB_SLUG_CUSTOMER) + ->get()->num_rows(); return ($num_rows > 0) ? TRUE : FALSE; } @@ -81,10 +81,10 @@ class Customers_Model extends CI_Model { // Before inserting the customer we need to get the customer's role id // from the database and assign it to the new record as a foreign key. $customer_role_id = $this->db - ->select('id') - ->from('ea_roles') - ->where('slug', DB_SLUG_CUSTOMER) - ->get()->row()->id; + ->select('id') + ->from('ea_roles') + ->where('slug', DB_SLUG_CUSTOMER) + ->get()->row()->id; $customer['id_roles'] = $customer_role_id; @@ -135,12 +135,12 @@ class Customers_Model extends CI_Model { // Get customer's role id $result = $this->db - ->select('ea_users.id') - ->from('ea_users') - ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') - ->where('ea_users.email', $customer['email']) - ->where('ea_roles.slug', DB_SLUG_CUSTOMER) - ->get(); + ->select('ea_users.id') + ->from('ea_users') + ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') + ->where('ea_users.email', $customer['email']) + ->where('ea_roles.slug', DB_SLUG_CUSTOMER) + ->get(); if ($result->num_rows() == 0) { throw new Exception('Could not find appointment record id.'); diff --git a/src/application/views/backend/customers.php b/src/application/views/backend/customers.php index c984488e..08e78c98 100644 --- a/src/application/views/backend/customers.php +++ b/src/application/views/backend/customers.php @@ -18,15 +18,78 @@
-
-
- - -
+
+
+ +
+ +

Customers

+
-
- +
+
+
+ + + +
+ + +
+ + + +
+

Details

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

+
Fields with * are required!
+
+ +
+

Appointments

+
+
+
\ No newline at end of file diff --git a/src/assets/css/backend.css b/src/assets/css/backend.css index 5225d227..ed8d0617 100644 --- a/src/assets/css/backend.css +++ b/src/assets/css/backend.css @@ -55,6 +55,25 @@ root { /* BACKEND CUSTOMERS PAGE -------------------------------------------------------------------- */ +#customers-page #filter { margin: 15px 0px 15px 15px; } +#customers-page #filter-results { overflow-y: auto; } +#customers-page #filter-results .customer-row { padding: 10px 7px; border-radius: 3px; } +#customers-page #filter-results .customer-row:hover { background-color: #C6E7D5; cursor: pointer; } +#customers-page #filter-results hr { margin: 5px 0; } + +#customers-page #details { margin: 15px 0 15px 15px; } +#customers-page #details .btn-toolbar { margin-top: 0px; } +#customers-page #details div.span5 { margin-left: 0; } + +#customers-page #customer-appointments { height: 250px; border: 1px solid #CCC; border-radius: 3px; + margin-bottom: 20px; overflow-y: auto; } +#customers-page #customer-appointments .appointment-row { padding: 7px; border-bottom: 1px solid #CCC; } +#customers-page #customer-appointments .appointment-row:hover { background-color: #C6E7D5; cursor: pointer; } + +#customers-page #details input, +#customers-page #details textarea { background-color: white; cursor: default; } + +#customers-page .selected-row { background-color: #EFFDF7; } /* BACKEND SERVICES PAGE diff --git a/src/assets/js/backend_customers.js b/src/assets/js/backend_customers.js index 83ac8c18..99d0abf1 100644 --- a/src/assets/js/backend_customers.js +++ b/src/assets/js/backend_customers.js @@ -7,6 +7,10 @@ */ var BackendCustomers = { + filterResults: {}, + selectedCustomer: {}, + selectedAppointment: {}, + /** * This method initializes the backend customers page. If you use this namespace * in a different page do not use this method. @@ -18,11 +22,10 @@ var BackendCustomers = { if (bindDefaultEventHandlers === undefined) { bindDefaultEventHandlers = false; // default value } - - // :: INITIALIZE BACKEND CUSTOMERS PAGE + BackendCustomers.filterCustomers(''); - - // :: BIND DEFAULT EVENT HANDLERS (IF NEEDED) + $('#details').find('input, textarea').prop('readonly', true); + if (bindDefaultEventHandlers) { BackendCustomers.bindEventHandlers(); } @@ -32,7 +35,134 @@ var BackendCustomers = { * Default event handlers declaration for backend customers page. */ bindEventHandlers: function() { - + /** + * Event: Customer Row "Click" + * + * Display the customer data of the selected row. + */ + $(document).on('click', '.customer-row', function() { + $('#filter-results .selected-row').removeClass('selected-row'); + $(this).addClass('selected-row'); + + var customerId = $(this).attr('data-id'); + var customer; + + $.each(BackendCustomers.filterResults, function(index, item) { + if (item.id === customerId) { + customer = item; + return; + } + }); + + BackendCustomers.selectedCustomer = customer; + BackendCustomers.displayCustomer(customer); + $('#edit-customer, #delete-customer').prop('disabled', false); + }); + + /** + * Event: Appointment Row "Click" + * + * Display appointment data of the selected row. + */ + $(document).on('click', '.appointment-row', function() { + $('#customer-appointments .selected-row').removeClass('selected-row'); + $(this).addClass('selected-row'); + + var appointmentId = $(this).attr('data-id'); + var appointment; + + $.each(BackendCustomers.selectedCustomer.appointments, function(index, item) { + if (item.id === appointmentId) { + appointment = item; + return; + } + }); + + BackendCustomers.selectedAppointment = appointment; + BackendCustomers.displayAppointment(appointment); + }); + + /** + * Event: Filter Customers Button "Click" + * + * Filter customer rows with given string. + */ + $('#filter-customers').click(function() { + BackendCustomers.filterCustomers($('#filter-key').val()); + }); + + /** + * Event: Add Customer Button "Click" + */ + $('#add-customer').click(function() { + BackendCustomers.resetForm(); + $('#add-edit-delete-group').hide(); + $('#save-cancel-group').show(); + $('#details').find('input, textarea').prop('readonly', false); + $('#filter-customers').prop('disabled', true); + $('.selected-row').removeClass('selected-row'); + }); + + /** + * Event: Edit Customer Button "Click" + */ + $('#edit-customer').click(function() { + $('#details').find('input, textarea').prop('readonly', false); + $('#add-edit-delete-group').hide(); + $('#save-cancel-group').show(); + $('#filter-customers').prop('disabled', true); + }); + + /** + * Event: Cancel Customer Add/Edit Operation Button "Click" + */ + $('#cancel-customer').click(function() { + $('#details').find('input, textarea').prop('readonly', true); + $('#save-cancel-group').hide(); + $('#add-edit-delete-group').show(); + $('#filter-customers').prop('disabled', true); + }); + + /** + * Event: Save Add/Edit Customer Operation "Click" + */ + $('#save-customer').click(function() { + var customer = { + 'first_name': $('#first-name').val(), + 'last_name': $('#last-name').val(), + 'email': $('#email').val(), + 'phone_number': $('#phone-number').val(), + 'address': $('#address').val(), + 'city': $('#city').val(), + 'zip_code': $('#zip-code').val(), + 'notes': $('#notes').val() + }; + + if ($('#customer-id').val() != '') { + customer.id = $('#customer-id').val(); + } + + BackendCustomers.saveCustomer(customer); + }); + + /** + * Event: Delete Customer Button "Click" + */ + $('#delete-customer').click(function() { + var messageBtns = { + 'Delete': function() { + var customerId = BackendCustomers.selectedCustomer.id; + BackendCustomers.deleteCustomer(customerId); + }, + + 'Cancel': function() { + $('#message_box').dialog('close'); + } + }; + + GeneralFunctions.displayMessageBox('Delete Customer', 'Are you sure that you want ' + + 'to delete this customer? This action cannot be undone.', messageBtns); + }); }, /** @@ -42,8 +172,34 @@ var BackendCustomers = { * * @param {int} customerId Selected customer's record id. */ - displayCustomer: function(customerId) { - + displayCustomer: function(customer) { + if (customer === undefined) { + throw 'DisplayCustomer: customer is undefined'; + } + + BackendCustomers.resetForm(); + + $('#customer-id').val(customer.id); + $('#first-name').val(customer.first_name); + $('#last-name').val(customer.last_name); + $('#email').val(customer.email); + $('#phone-number').val(customer.phone_number); + $('#address').val(customer.address); + $('#city').val(customer.city); + $('#zip-code').val(customer.zip_code); + $('#notes').val(customer.notes); + + $.each(customer.appointments, function(index, appointment) { + var start = Date.parse(appointment.start_datetime).toString('dd/MM/yyyy HH:mm'); + var end = Date.parse(appointment.end_datetime).toString('dd/MM/yyyy HH:mm'); + var html = + '
' + + start + ' - ' + end + '
' + + appointment.service.name + ', ' + + appointment.provider.first_name + ' ' + appointment.provider.last_name + + '
'; + $('#customer-appointments').append(html); + }); }, /** @@ -53,11 +209,48 @@ var BackendCustomers = { * * NOTICE: User the "deleteCustomer" method to delete a customer record. * - * @param {object} customerData Contains the customer data. If "id" is not + * @param {object} customer Contains the customer data. If "id" is not * provided then the record is going to be inserted. */ - saveCustomer: function(customerData) { - + saveCustomer: function(customer) { + if (!BackendCustomers.validateForm()) return; + + var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_save_customer'; + var postData = { 'customer': JSON.stringify(customer) }; + + $.post(postUrl, postData, 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; + } + + if (response.warnings) { + response.warnings = GeneralFunctions.parseExceptions(response.warnings); + GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); + } + + $('#add-edit-delete-group').show(); + $('#save-cancel-group').hide(); + $('#filter-customers').prop('disabled', false); + $('#details').find('input, textarea').prop('readonly', true); + + BackendCustomers.filterCustomers($('#filter-key').val()); + + // On edit mode keep the customer data on form. + if (customer.id) { + $.each(BackendCustomers.filterResults, function(index, item) { + if (item.id == customer.id) { + customer.appointments = item.appointments; // w/ appointments + return; + } + }); + BackendCustomers.displayCustomer(customer); + $('#edit-customer, #delete-customer').prop('disabled', false); + } + }, 'json'); }, /** @@ -67,7 +260,29 @@ var BackendCustomers = { * @param {int} customerId The customer record id to be deleted. */ deleteCustomer: function(customerId) { - + var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_delete_customer'; + var postData = { 'customer_id': BackendCustomers.selectedCustomer.id }; + + $.post(postUrl, postData, function(response) { + if (response.exceptions) { + response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); + GeneralFunctions.displayMessageBox('Unexpected Issues', 'Unfortunately the ' + + 'filter operation could not complete successfully. The following ' + + 'issues occured.'); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.exceptions)); + return; + } + + if (response.warnings) { + response.warnings = GeneralFunctions.parseExceptions(response.warnings); + GeneralFunctions.displayMessageBox('Unexpected Warnings', 'The filter operation ' + + 'complete with the following warnings.'); + $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); + } + + $('#message_box').dialog('close'); + BackendCustomers.filterCustomers($('#filter-key').val()); + }, 'json'); }, /** @@ -77,11 +292,15 @@ var BackendCustomers = { * @param {string} key The filter key string. */ filterCustomers: function(key) { - var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_filter_customers'; + $('#filter-results').html(''); + BackendCustomers.resetForm(); + + var postUrl = GlobalVariables.baseUrl + 'backend_api/ajax_filter_customers'; var postData = { 'key': key }; - $.post(postUrl, postData, function(response) { + + $.post(postUrl, postData, function(response) { if (response.exceptions) { - response.exceptions = GeneralFunctions.parseExcpetions(response.exceptions); + response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); GeneralFunctions.displayMessageBox('Unexpected Issues', 'Unfortunately the ' + 'filter operation could not complete successfully. The following ' + 'issues occured.'); @@ -90,21 +309,23 @@ var BackendCustomers = { } if (response.warnings) { - response.warnings = GeneralFunctions.parseExcpetions(response.warnings); + response.warnings = GeneralFunctions.parseExceptions(response.warnings); GeneralFunctions.displayMessageBox('Unexpected Warnings', 'The filter operation ' + 'complete with the following warnings.'); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); } + + BackendCustomers.filterResults = response; $.each(response, function(index, customer) { var html = - '
' + + '
' + '' + - customer['first_name'] + ' ' + customer['last_name'] + + customer.first_name + ' ' + customer.last_name + '
' + - '' + customer['email'] + ' | ' + - '' + customer['phone_number'] + '
' + - '
'; + '' + customer.email + ' | ' + + '' + customer.phone_number + '' + + '

'; $('#filter-results').append(html); }); }, 'json'); @@ -116,7 +337,63 @@ var BackendCustomers = { * * @return {bool} Returns the validation result. */ - validateCustomerForm: function() { - - } + validateForm: function() { + try { + $('#form-message').hide(); + $('.required').css('border', ''); + + // :: 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!'; + } + + // :: 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).show(); + return false; + } + }, + + /** + * Bring the customer data form back to its initial state. + */ + resetForm: function() { + $('#details').find('input, textarea').val(''); + $('#customer-appointments').html(''); + $('#appointment-details').html(''); + $('#edit-customer, #delete-customer').prop('disabled', true); + }, + + /** + * Display appointment details on customers backend page. + * + * @param {object} appointment Appointment data + */ + displayAppointment: function(appointment) { + var start = Date.parse(appointment.start_datetime).toString('dd/MM/yyyy HH:mm'); + var end = Date.parse(appointment.end_datetime).toString('dd/MM/yyyy HH:mm'); + + var html = + '
' + + '' + appointment.service.name + '
' + + appointment.provider.first_name + ' ' + appointment.provider.last_name + '
' + + start + ' - ' + end + '
' + + '
'; + + $('#appointment-details').html(html); + } }; \ No newline at end of file