* Finished backend customers page.

This commit is contained in:
alextselegidis@gmail.com 2013-07-15 14:27:19 +00:00
parent c139df2135
commit b53add71b6
5 changed files with 468 additions and 54 deletions

View file

@ -311,24 +311,44 @@ class Backend_api extends CI_Controller {
*/ */
public function ajax_filter_customers() { public function ajax_filter_customers() {
try { try {
$this->load->model('appointments_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$this->load->model('customers_model'); $this->load->model('customers_model');
$key = $_POST['key']; // @task $this->db->escape($_POST['key']); $key = $_POST['key']; // @task $this->db->escape($_POST['key']);
$where_clause = $where_clause =
'first_name LIKE "%' . $key . '%" OR ' . '(first_name LIKE "%' . $key . '%" OR ' .
'last_name LIKE "%' . $key . '%" OR ' . 'last_name LIKE "%' . $key . '%" OR ' .
'email LIKE "%' . $key . '%" OR ' . 'email LIKE "%' . $key . '%" OR ' .
'phone_number LIKE "%' . $key . '%" OR ' . 'phone_number LIKE "%' . $key . '%" OR ' .
'address LIKE "%' . $key . '%" OR ' . 'address LIKE "%' . $key . '%" OR ' .
'city 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) { } catch(Exception $exc) {
echo json_encode(array( echo json_encode(array(
'exceptions' => array($exc) 'exceptions' => array(exceptionToJavascript($exc))
)); ));
} }
} }
@ -382,7 +402,7 @@ class Backend_api extends CI_Controller {
} catch(Exception $exc) { } catch(Exception $exc) {
echo json_encode(array( echo json_encode(array(
'exceptions' => array($exc) 'exceptions' => array(exceptionToJavascript($exc))
)); ));
} }
} }
@ -426,7 +446,42 @@ class Backend_api extends CI_Controller {
} catch(Exception $exc) { } catch(Exception $exc) {
echo json_encode(array( 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))
)); ));
} }
} }

View file

@ -60,12 +60,12 @@ class Customers_Model extends CI_Model {
// This method shouldn't depend on another method of this class. // This method shouldn't depend on another method of this class.
$num_rows = $this->db $num_rows = $this->db
->select('*') ->select('*')
->from('ea_users') ->from('ea_users')
->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
->where('ea_users.email', $customer['email']) ->where('ea_users.email', $customer['email'])
->where('ea_roles.slug', DB_SLUG_CUSTOMER) ->where('ea_roles.slug', DB_SLUG_CUSTOMER)
->get()->num_rows(); ->get()->num_rows();
return ($num_rows > 0) ? TRUE : FALSE; 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 // 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. // from the database and assign it to the new record as a foreign key.
$customer_role_id = $this->db $customer_role_id = $this->db
->select('id') ->select('id')
->from('ea_roles') ->from('ea_roles')
->where('slug', DB_SLUG_CUSTOMER) ->where('slug', DB_SLUG_CUSTOMER)
->get()->row()->id; ->get()->row()->id;
$customer['id_roles'] = $customer_role_id; $customer['id_roles'] = $customer_role_id;
@ -135,12 +135,12 @@ class Customers_Model extends CI_Model {
// Get customer's role id // Get customer's role id
$result = $this->db $result = $this->db
->select('ea_users.id') ->select('ea_users.id')
->from('ea_users') ->from('ea_users')
->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner') ->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
->where('ea_users.email', $customer['email']) ->where('ea_users.email', $customer['email'])
->where('ea_roles.slug', DB_SLUG_CUSTOMER) ->where('ea_roles.slug', DB_SLUG_CUSTOMER)
->get(); ->get();
if ($result->num_rows() == 0) { if ($result->num_rows() == 0) {
throw new Exception('Could not find appointment record id.'); throw new Exception('Could not find appointment record id.');

View file

@ -18,15 +18,78 @@
</script> </script>
<div id="customers-page" class="row-fluid"> <div id="customers-page" class="row-fluid">
<div class="span4"> <div id="filter" class="span4">
<div class="filter-customers"> <div class="input-append">
<label for="filter-input">Filter</label> <input class="span12" type="text" id="filter-key" />
<input type="text" id="filter-key" /> <button type="button" class="btn" id="filter-customers">Filter</button>
<div id="filter-results"></div>
</div> </div>
<h2>Customers</h2>
<div id="filter-results"></div>
</div> </div>
<div class="span8"> <div id="details" class="span7 row-fluid">
<div class="btn-toolbar">
<div id="add-edit-delete-group" class="btn-group">
<button id="add-customer" class="btn">
<i class="icon-plus"></i>
Add</button>
<button id="edit-customer" class="btn" disabled="disabled">
<i class="icon-pencil"></i>
Edit</button>
<button id="delete-customer" class="btn" disabled="disabled">
<i class="icon-remove"></i>
Delete</button>
</div>
<div id="save-cancel-group" class="btn-group" style="display:none;">
<button id="save-customer" class="btn">
<i class="icon-ok"></i>
Save</button>
<button id="cancel-customer" class="btn">
<i class="icon-ban-circle"></i>
Cancel</button>
</div>
</div>
<input id="customer-id" type="hidden" />
<div class="span5">
<h2>Details</h2>
<div id="form-message" class="alert" style="display:none;"></div>
<label for="first-name">First Name</label>
<input type="text" id="first-name" class="span11" />
<label for="last-name">Last Name *</label>
<input type="text" id="last-name" class="span11 required" />
<label for="email">Email *</label>
<input type="text" id="email" class="span11 required" />
<label for="phone-number">Phone Number *</label>
<input type="text" id="phone-number" class="span11 required" />
<label for="address">Address</label>
<input type="text" id="address" class="span11" />
<label for="city">City</label>
<input type="text" id="city" class="span11" />
<label for="zip-code">Zip Code</label>
<input type="text" id="zip-code" class="span11" />
<label for="notes">Notes</label>
<textarea id="notes" rows="4" class="span11"></textarea>
<br/><br/>
<center><em id="form-message" class="text-error">Fields with * are required!</em></center>
</div>
<div class="span7">
<h2>Appointments</h2>
<div id="customer-appointments"></div>
<div id="appointment-details"></div>
</div>
</div> </div>
</div> </div>

View file

@ -55,6 +55,25 @@ root {
/* BACKEND CUSTOMERS PAGE /* 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 /* BACKEND SERVICES PAGE

View file

@ -7,6 +7,10 @@
*/ */
var BackendCustomers = { var BackendCustomers = {
filterResults: {},
selectedCustomer: {},
selectedAppointment: {},
/** /**
* This method initializes the backend customers page. If you use this namespace * This method initializes the backend customers page. If you use this namespace
* in a different page do not use this method. * in a different page do not use this method.
@ -18,11 +22,10 @@ var BackendCustomers = {
if (bindDefaultEventHandlers === undefined) { if (bindDefaultEventHandlers === undefined) {
bindDefaultEventHandlers = false; // default value bindDefaultEventHandlers = false; // default value
} }
// :: INITIALIZE BACKEND CUSTOMERS PAGE
BackendCustomers.filterCustomers(''); BackendCustomers.filterCustomers('');
$('#details').find('input, textarea').prop('readonly', true);
// :: BIND DEFAULT EVENT HANDLERS (IF NEEDED)
if (bindDefaultEventHandlers) { if (bindDefaultEventHandlers) {
BackendCustomers.bindEventHandlers(); BackendCustomers.bindEventHandlers();
} }
@ -32,7 +35,134 @@ var BackendCustomers = {
* Default event handlers declaration for backend customers page. * Default event handlers declaration for backend customers page.
*/ */
bindEventHandlers: function() { 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. * @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 =
'<div class="appointment-row" data-id="' + appointment.id + '">' +
start + ' - ' + end + '<br>' +
appointment.service.name + ', ' +
appointment.provider.first_name + ' ' + appointment.provider.last_name +
'</div>';
$('#customer-appointments').append(html);
});
}, },
/** /**
@ -53,11 +209,48 @@ var BackendCustomers = {
* *
* NOTICE: User the "deleteCustomer" method to delete a customer record. * 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. * 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. * @param {int} customerId The customer record id to be deleted.
*/ */
deleteCustomer: function(customerId) { 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. * @param {string} key The filter key string.
*/ */
filterCustomers: function(key) { 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 }; var postData = { 'key': key };
$.post(postUrl, postData, function(response) {
$.post(postUrl, postData, function(response) {
if (response.exceptions) { if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExcpetions(response.exceptions); response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox('Unexpected Issues', 'Unfortunately the ' GeneralFunctions.displayMessageBox('Unexpected Issues', 'Unfortunately the '
+ 'filter operation could not complete successfully. The following ' + 'filter operation could not complete successfully. The following '
+ 'issues occured.'); + 'issues occured.');
@ -90,21 +309,23 @@ var BackendCustomers = {
} }
if (response.warnings) { if (response.warnings) {
response.warnings = GeneralFunctions.parseExcpetions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox('Unexpected Warnings', 'The filter operation ' GeneralFunctions.displayMessageBox('Unexpected Warnings', 'The filter operation '
+ 'complete with the following warnings.'); + 'complete with the following warnings.');
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
BackendCustomers.filterResults = response;
$.each(response, function(index, customer) { $.each(response, function(index, customer) {
var html = var html =
'<div class="customer-data" data-id="' + customer['id'] + '">' + '<div class="customer-row" data-id="' + customer.id + '">' +
'<strong>' + '<strong>' +
customer['first_name'] + ' ' + customer['last_name'] + customer.first_name + ' ' + customer.last_name +
'</strong><br>' + '</strong><br>' +
'<span>' + customer['email'] + '</span> | ' + '<span>' + customer.email + '</span> | ' +
'<span>' + customer['phone_number'] + '</span><hr>' + '<span>' + customer.phone_number + '</span>' +
'</div>'; '</div><hr>';
$('#filter-results').append(html); $('#filter-results').append(html);
}); });
}, 'json'); }, 'json');
@ -116,7 +337,63 @@ var BackendCustomers = {
* *
* @return {bool} Returns the validation result. * @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 =
'<div>' +
'<strong>' + appointment.service.name + '</strong><br>' +
appointment.provider.first_name + ' ' + appointment.provider.last_name + '<br>' +
start + ' - ' + end + '<br>' +
'</div>';
$('#appointment-details').html(html);
}
}; };