diff --git a/src/application/controllers/backend_api.php b/src/application/controllers/backend_api.php index 674441bb..79a08689 100644 --- a/src/application/controllers/backend_api.php +++ b/src/application/controllers/backend_api.php @@ -807,13 +807,17 @@ class Backend_api extends CI_Controller { public function ajax_save_settings() { try { if ($_POST['type'] == SETTINGS_SYSTEM) { - // @task Implement save settings. + $this->load->model('settings_model'); + $settings = json_decode($_POST['settings'], true); + $this->settings_model->save_settings($settings); } else if ($_POST['type'] == SETTINGS_USER) { $this->load->library('session'); $this->load->model('user_model'); $user_id = $this->session->userdata('user_id'); $this->user_model->save_settings($_POST['settings'], $user_id); } + + echo json_encode(AJAX_SUCCESS); } catch(Exception $exc) { echo json_encode(array( 'exceptions' => array(exceptionToJavaScript($exc)) diff --git a/src/application/models/settings_model.php b/src/application/models/settings_model.php index 98b3acd3..48bf3e75 100644 --- a/src/application/models/settings_model.php +++ b/src/application/models/settings_model.php @@ -107,9 +107,11 @@ class Settings_Model extends CI_Model { throw new Exception('$settings argument is invalid: '. print_r($settings, TRUE)); } - foreach($settings as $name=>$value) { - if (!$this->db->update('ea_settings', array('value' => $value), array('name' => $name))) { - throw new Exception('Could not save setting (' . $name . ' - ' . $value . ')'); + foreach($settings as $setting) { + $this->db->where('name', $setting['name']); + if (!$this->db->update('ea_settings', array('value' => $setting['value']))) { + throw new Exception('Could not save setting (' . $setting['name'] + . ' - ' . $setting['value'] . ')'); } } diff --git a/src/application/views/backend/settings.php b/src/application/views/backend/settings.php index 1ad43951..9a86721c 100644 --- a/src/application/views/backend/settings.php +++ b/src/application/views/backend/settings.php @@ -1,5 +1,9 @@ + + -
+
@@ -187,7 +197,7 @@
Personal Info - + diff --git a/src/assets/css/backend.css b/src/assets/css/backend.css index a6b2efac..c61dfd64 100644 --- a/src/assets/css/backend.css +++ b/src/assets/css/backend.css @@ -371,4 +371,52 @@ body .modal-header h3 { /* BACKEND SETTINGS PAGE - -------------------------------------------------------------------- */ \ No newline at end of file + -------------------------------------------------------------------- */ +#settings-page .tab-content { + margin: 15px; +} + +#settings-page .nav { + margin: 15px; +} + +#settings-page .nav li { + cursor: pointer; +} + +#business-logic .working-hours td input[type="text"] { + margin-bottom: 0; + cursor: pointer; +} + +#business-logic .working-hours label.checkbox { + margin-top: 5px; + margin-bottom: 0; +} + +#business-logic #breaks .btn { + margin-right: 5px; + padding: 4px 7px; +} + +#business-logic #breaks input { + margin-bottom: 0; +} + +#business-logic #breaks .editable form { + margin: 0; +} + +#business-logic #breaks .editable select, +#business-logic #breaks .editable input { + margin: 0; + cursor: pointer; +} + +#business-logic #book-advance-timeout { + margin: 0; +} + +#business-logic .ui-spinner { + border: none; +} \ No newline at end of file diff --git a/src/assets/js/backend.js b/src/assets/js/backend.js index 99b32ed7..22ca9270 100644 --- a/src/assets/js/backend.js +++ b/src/assets/js/backend.js @@ -36,21 +36,23 @@ var Backend = { /** * Place the backend footer always on the bottom of the page. + * + * @task Re-enable this method. */ placeFooterToBottom: function() { - var $footer = $('#footer'); - - if (window.innerHeight > $('body').height()) { - $footer.css({ - 'position': 'absolute', - 'width': '100%', - 'bottom': '0px' - }); - } else { - $footer.css({ - 'position': 'static' - }); - } +// var $footer = $('#footer'); +// +// if (window.innerHeight > $('body').height()) { +// $footer.css({ +// 'position': 'absolute', +// 'width': '100%', +// 'bottom': '0px' +// }); +// } else { +// $footer.css({ +// 'position': 'static' +// }); +// } }, /** diff --git a/src/assets/js/backend_settings.js b/src/assets/js/backend_settings.js index 46686cc8..cfc7728c 100644 --- a/src/assets/js/backend_settings.js +++ b/src/assets/js/backend_settings.js @@ -9,6 +9,22 @@ var BackendSettings = { SETTINGS_SYSTEM: 'SETTINGS_SYSTEM', SETTINGS_USER: 'SETTINGS_USER', + /** + * This flag is used when trying to cancel row editing. It is + * true only whenever the user presses the cancel button. + * + * @type {bool} + */ + enableCancel: false, + + /** + * This flag determines whether the jeditables are allowed to submit. It is + * true only whenever the user presses the save button. + * + * @type {bool} + */ + enableSubmit: false, + /** * Tab settings object. * @@ -28,9 +44,71 @@ var BackendSettings = { // Apply values from database. $.each(GlobalVariables.settings.system, function(index, setting) { - $('#general input[data-field="' + setting.name + '"]').val(setting.value); + $('input[data-field="' + setting.name + '"]').val(setting.value); }); + var workingPlan = {}; + $.each(GlobalVariables.settings.system, function(index, setting) { + if (setting.name == 'company_working_plan') { + workingPlan = $.parseJSON(setting.value); + return false; + } + }); + + $.each(workingPlan, function(index, workingDay) { + if (workingDay != null) { + $('#' + index).prop('checked', true); + $('#' + index + '-start').val(workingDay.start); + $('#' + index + '-end').val(workingDay.end); + + // Add the day's breaks on the breaks table. + $.each(workingDay.breaks, function(i, brk) { + var tr = + '' + + '' + GeneralFunctions.ucaseFirstLetter(index) + '' + + '' + brk.start + '' + + '' + brk.end + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + $('#breaks').append(tr); + }); + } else { + $('#' + index).prop('checked', false); + $('#' + index + '-start').prop('disabled', true); + $('#' + index + '-end').prop('disabled', true); + } + }); + + // Make break cells editable. + BackendSettings.breakDayEditable($('#breaks .break-day')); + BackendSettings.breakTimeEditable($('#breaks').find('.break-start, .break-end')); + + // Set timepickers where needed. + $('.working-hours input').timepicker({ + timeFormat: 'HH:mm' + }); + + // Book Advance Timeout Spinner + $('#book-advance-timeout').spinner({ + min: 0 + }); + + // Set default settings helper. + BackendSettings.settings = new SystemSettings(); + if (bindEventHandlers) { BackendSettings.bindEventHandlers(); } @@ -66,7 +144,171 @@ var BackendSettings = { */ $('.save-settings').click(function() { var settings = BackendSettings.settings.get(); - BackendSettings.settings.save(); + BackendSettings.settings.save(settings); + }); + + /** + * Event: Day Checkbox "Click" + * + * Enable or disable the time selection for each day. + */ + $('.working-hours input[type="checkbox"]').click(function() { + var id = $(this).attr('id'); + + if ($(this).prop('checked') == true) { + $('#' + id + '-start').prop('disabled', false).val('09:00'); + $('#' + id + '-end').prop('disabled', false).val('18:00'); + } else { + $('#' + id + '-start').prop('disabled', true).val(''); + $('#' + id + '-end').prop('disabled', true).val(''); + } + }); + + /** + * Event: Add Break Button "Click" + * + * A new row is added on the table and the user can enter the new break + * data. After that he can either press the save or cancel button. + */ + $('.add-break').click(function() { + var tr = + '' + + 'Monday' + + '09:00' + + '10:00' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + $('#breaks').prepend(tr); + + // Bind editable and event handlers. + tr = $('#breaks tr').get()[1]; + BackendSettings.breakDayEditable($(tr).find('.break-day')); + BackendSettings.breakTimeEditable($(tr).find('.break-start, .break-end')); + $(tr).find('.edit-break').trigger('click'); + }); + + /** + * Event: Edit Break Button "Click" + * + * Enables the row editing for the "Breaks" table rows. + */ + $(document).on('click', '.edit-break', function() { + // Reset previous editable tds + var $previousEdt = $(this).closest('table').find('.editable').get(); + $.each($previousEdt, function(index, edt) { + edt.reset(); + }); + + // Make all cells in current row editable. + $(this).parent().parent().children().trigger('edit'); + $(this).parent().parent().find('.break-start input, .break-end input').timepicker(); + $(this).parent().parent().find('.break-day select').focus(); + + // Show save - cancel buttons. + $(this).closest('table').find('.edit-break, .delete-break').addClass('hidden'); + $(this).parent().find('.save-break, .cancel-break').removeClass('hidden'); + + }); + + /** + * Event: Delete Break Button "Click" + * + * Removes the current line from the "Breaks" table. + */ + $(document).on('click', '.delete-break', function() { + $(this).parent().parent().remove(); + }); + + /** + * Event: Cancel Break Button "Click" + * + * Bring the "#breaks" table back to its initial state. + */ + $(document).on('click', '.cancel-break', function() { + BackendSettings.enableCancel = true; + $(this).parent().parent().find('.cancel-editable').trigger('click'); + BackendSettings.enableCancel = false; + + $(this).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); + $(this).parent().find('.save-break, .cancel-break').addClass('hidden'); + }); + + /** + * Event: Save Break Button "Click" + * + * Save the editable values and restore the table to its initial state. + */ + $(document).on('click', '.save-break', function() { + BackendSettings.enableSubmit = true; + $(this).parent().parent().find('.editable .submit-editable').trigger('click'); + BackendSettings.enableSubmit = false; + + $(this).closest('table').find('.edit-break, .delete-break').removeClass('hidden'); + $(this).parent().find('.save-break, .cancel-break').addClass('hidden'); + }); + }, + + /** + * Initialize the editable functionality to the break day table cells. + * + * @param {object} $selector The cells to be initialized. + */ + breakDayEditable: function($selector) { + $selector.editable(function(value, settings) { + return value; + }, { + 'type': 'select', + 'data': '{ "Monday": "Monday", "Tuesday": "Tuesday", "Wednesday": "Wednesday", ' + + '"Thursday": "Thursday", "Friday": "Friday", "Saturday": "Saturday", ' + + '"Sunday": "Sunday", "selected": "Monday"}', + 'event': 'edit', + 'height': '30px', + 'submit': '', + 'cancel': '', + 'onblur': 'ignore', + 'onreset': function(settings, td) { + if (!BackendSettings.enableCancel) return false; // disable ESC button + }, + 'onsubmit': function(settings, td) { + if (!BackendSettings.enableSubmit) return false; // disable Enter button + } + }); + }, + + /** + * Initialize the editable functionality to the break time table cells. + * + * @param {object} $selector The cells to be initialized. + */ + breakTimeEditable: function($selector) { + $selector.editable(function(value, settings) { + // Do not return the value because the user needs to press the "Save" button. + return value; + }, { + 'event': 'edit', + 'height': '25px', + 'submit': '', + 'cancel': '', + 'onblur': 'ignore', + 'onreset': function(settings, td) { + if (!BackendSettings.enableCancel) return false; // disable ESC button + }, + 'onsubmit': function(settings, td) { + if (!BackendSettings.enableSubmit) return false; // disable Enter button + } }); } }; @@ -108,21 +350,49 @@ SystemSettings.prototype.save = function(settings) { * @returns {array} Returns the system settings array. */ SystemSettings.prototype.get = function() { - var settings = [] - var tmpSetting = {}; + var settings = []; // General Settings Tab $('#general input').each(function() { - tmpSetting[$(this).attr('data-field')] = $(this).val(); - settings.push(tmpSetting); + settings.push({ + 'name': $(this).attr('data-field'), + 'value': $(this).val() + }); }); // Business Logic Tab - // @task Get business logic settings. + var workingPlan = {}; + $('.working-hours input[type="checkbox"').each(function() { + var id = $(this).attr('id'); + if ($(this).prop('checked') == true) { + workingPlan[id] = {} + workingPlan[id].start = $('#' + id + '-start').val(); + workingPlan[id].end = $('#' + id + '-end').val(); + workingPlan[id].breaks = {}; + + } else { + workingPlan[id] = null; + } + }); + + settings.push({ + 'name': 'company_working_plan', + 'value': JSON.stringify(workingPlan) + }); return settings; }; +/** + * Validate the settings data. If the validation fails then display a + * message to the user. + * + * @returns {bool} Returns the validation result. + */ +SystemSettings.prototype.validate = function() { + +}; + /** * "User Settings" Tab Helper * @class UserSettings @@ -136,7 +406,7 @@ var UserSettings = function() {}; */ UserSettings.prototype.get = function() { -} +}; /** * Store the user settings into the database. @@ -145,4 +415,14 @@ UserSettings.prototype.get = function() { */ UserSettings.prototype.save = function(settings) { -} +}; + +/** + * Validate the settings data. If the validation fails then display a + * message to the user. + * + * @returns {bool} Returns the validation result. + */ +UserSettings.prototype.validate = function() { + +}; diff --git a/src/assets/js/general_functions.js b/src/assets/js/general_functions.js index a468446f..ef5d6a41 100644 --- a/src/assets/js/general_functions.js +++ b/src/assets/js/general_functions.js @@ -224,5 +224,15 @@ var GeneralFunctions = { }); return parsedExceptions; + }, + + /** + * Makes the first letter of the string upper case. + * + * @param {string} str The string to be converted. + * @returns {string} Returns the capitalized string. + */ + ucaseFirstLetter: function(str){ + return str.charAt(0).toUpperCase() + str.slice(1); } }; \ No newline at end of file diff --git a/src/assets/js/libs/jquery/jquery.jeditable.min.js b/src/assets/js/libs/jquery/jquery.jeditable.min.js new file mode 100644 index 00000000..ef885f06 --- /dev/null +++ b/src/assets/js/libs/jquery/jquery.jeditable.min.js @@ -0,0 +1,38 @@ + +(function($){$.fn.editable=function(target,options){if('disable'==target){$(this).data('disabled.editable',true);return;} +if('enable'==target){$(this).data('disabled.editable',false);return;} +if('destroy'==target){$(this).unbind($(this).data('event.editable')).removeData('disabled.editable').removeData('event.editable');return;} +var settings=$.extend({},$.fn.editable.defaults,{target:target},options);var plugin=$.editable.types[settings.type].plugin||function(){};var submit=$.editable.types[settings.type].submit||function(){};var buttons=$.editable.types[settings.type].buttons||$.editable.types['defaults'].buttons;var content=$.editable.types[settings.type].content||$.editable.types['defaults'].content;var element=$.editable.types[settings.type].element||$.editable.types['defaults'].element;var reset=$.editable.types[settings.type].reset||$.editable.types['defaults'].reset;var callback=settings.callback||function(){};var onedit=settings.onedit||function(){};var onsubmit=settings.onsubmit||function(){};var onreset=settings.onreset||function(){};var onerror=settings.onerror||reset;if(settings.tooltip){$(this).attr('title',settings.tooltip);} +settings.autowidth='auto'==settings.width;settings.autoheight='auto'==settings.height;return this.each(function(){var self=this;var savedwidth=$(self).width();var savedheight=$(self).height();$(this).data('event.editable',settings.event);if(!$.trim($(this).html())){$(this).html(settings.placeholder);} +$(this).bind(settings.event,function(e){if(true===$(this).data('disabled.editable')){return;} +if(self.editing){return;} +if(false===onedit.apply(this,[settings,self])){return;} +e.preventDefault();e.stopPropagation();if(settings.tooltip){$(self).removeAttr('title');} +if(0==$(self).width()){settings.width=savedwidth;settings.height=savedheight;}else{if(settings.width!='none'){settings.width=settings.autowidth?$(self).width():settings.width;} +if(settings.height!='none'){settings.height=settings.autoheight?$(self).height():settings.height;}} +if($(this).html().toLowerCase().replace(/(;|")/g,'')==settings.placeholder.toLowerCase().replace(/(;|")/g,'')){$(this).html('');} +self.editing=true;self.revert=$(self).html();$(self).html('');var form=$('
');if(settings.cssclass){if('inherit'==settings.cssclass){form.attr('class',$(self).attr('class'));}else{form.attr('class',settings.cssclass);}} +if(settings.style){if('inherit'==settings.style){form.attr('style',$(self).attr('style'));form.css('display',$(self).css('display'));}else{form.attr('style',settings.style);}} +var input=element.apply(form,[settings,self]);var input_content;if(settings.loadurl){var t=setTimeout(function(){input.disabled=true;content.apply(form,[settings.loadtext,settings,self]);},100);var loaddata={};loaddata[settings.id]=self.id;if($.isFunction(settings.loaddata)){$.extend(loaddata,settings.loaddata.apply(self,[self.revert,settings]));}else{$.extend(loaddata,settings.loaddata);} +$.ajax({type:settings.loadtype,url:settings.loadurl,data:loaddata,async:false,success:function(result){window.clearTimeout(t);input_content=result;input.disabled=false;}});}else if(settings.data){input_content=settings.data;if($.isFunction(settings.data)){input_content=settings.data.apply(self,[self.revert,settings]);}}else{input_content=self.revert;} +content.apply(form,[input_content,settings,self]);input.attr('name',settings.name);buttons.apply(form,[settings,self]);$(self).append(form);plugin.apply(form,[settings,self]);$(':input:visible:enabled:first',form).focus();if(settings.select){input.select();} +input.keydown(function(e){if(e.keyCode==27){e.preventDefault();reset.apply(form,[settings,self]);}});var t;if('cancel'==settings.onblur){input.blur(function(e){t=setTimeout(function(){reset.apply(form,[settings,self]);},500);});}else if('submit'==settings.onblur){input.blur(function(e){t=setTimeout(function(){form.submit();},200);});}else if($.isFunction(settings.onblur)){input.blur(function(e){settings.onblur.apply(self,[input.val(),settings]);});}else{input.blur(function(e){});} +form.submit(function(e){if(t){clearTimeout(t);} +e.preventDefault();if(false!==onsubmit.apply(form,[settings,self])){if(false!==submit.apply(form,[settings,self])){if($.isFunction(settings.target)){var str=settings.target.apply(self,[input.val(),settings]);$(self).html(str);self.editing=false;callback.apply(self,[self.innerHTML,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}}else{var submitdata={};submitdata[settings.name]=input.val();submitdata[settings.id]=self.id;if($.isFunction(settings.submitdata)){$.extend(submitdata,settings.submitdata.apply(self,[self.revert,settings]));}else{$.extend(submitdata,settings.submitdata);} +if('PUT'==settings.method){submitdata['_method']='put';} +$(self).html(settings.indicator);var ajaxoptions={type:'POST',data:submitdata,dataType:'html',url:settings.target,success:function(result,status){if(ajaxoptions.dataType=='html'){$(self).html(result);} +self.editing=false;callback.apply(self,[result,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}},error:function(xhr,status,error){onerror.apply(form,[settings,self,xhr]);}};$.extend(ajaxoptions,settings.ajaxoptions);$.ajax(ajaxoptions);}}} +$(self).attr('title',settings.tooltip);return false;});});this.reset=function(form){if(this.editing){if(false!==onreset.apply(form,[settings,self])){$(self).html(self.revert);self.editing=false;if(!$.trim($(self).html())){$(self).html(settings.placeholder);} +if(settings.tooltip){$(self).attr('title',settings.tooltip);}}}};});};$.editable={types:{defaults:{element:function(settings,original){var input=$('');$(this).append(input);return(input);},content:function(string,settings,original){$(':input:first',this).val(string);},reset:function(settings,original){original.reset(this);},buttons:function(settings,original){var form=this;if(settings.submit){if(settings.submit.match(/>$/)){var submit=$(settings.submit).click(function(){if(submit.attr("type")!="submit"){form.submit();}});}else{var submit=$('