diff --git a/css/bts_office.css b/css/bts_office.css
index 44943ce..d8e640c 100644
--- a/css/bts_office.css
+++ b/css/bts_office.css
@@ -872,10 +872,14 @@ div.brate {
max-width: 20px;
}
div.btos.error,
+div.bstart.error,
+div.bfinish.error,
+div.bstaff.error,
+div.bclient.error,
div.brate.error{
position:relative;
- background-color: red !important;
- animation: flash 1s linear infinite;
+ background-color: yellow !important;
+ /* animation: flash 1s linear infinite; */
}
div.error::after{
position:absolute;
diff --git a/html/jobv1.html b/html/jobv1.html
index 69ee2ad..63f2379 100644
--- a/html/jobv1.html
+++ b/html/jobv1.html
@@ -6,13 +6,13 @@
{{tos_name}}
- {{start}}
- {{finish}}
- {{rate_name}}
+ {{start}}
+ {{finish}}
+ {{rate_name}}
{{staff_name}}
- {{client_name}}
+ {{client_name}}
- {{rating}}
+ {{rating}}
diff --git a/html/jobv1_editor.html b/html/jobv1_editor.html
index 37f6f24..823f13e 100644
--- a/html/jobv1_editor.html
+++ b/html/jobv1_editor.html
@@ -37,7 +37,7 @@
-
+
@@ -48,17 +48,16 @@
-
e tos
-
-
es
-
ef
-
er
-
estaf
-
ecli
-
econfirm
-
erat
-
edel
-
eeave
+
+
when to start
+
when to finish
+
+
+
+
+
+
+
click to save
diff --git a/js/bts_office.js b/js/bts_office.js
index 4c3fa02..838d780 100644
--- a/js/bts_office.js
+++ b/js/bts_office.js
@@ -289,22 +289,44 @@
do_edit_job(id);
});
+// $(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
+// $('.Editing').addClass('blink_me');
+// setTimeout(function(){
+// $(".Editing").removeClass('Editing blink_me');
+// }, 1000);
+//
+// });
+
$(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
- $('.Editing').addClass('blink_me');
+ var el = $('.Editing');
+ el.addClass("blink_me").removeClass('Editing');
setTimeout(function(){
- $(".Editing").removeClass('Editing blink_me');
- }, 1000);
-
- });
-
- $(document).on('click', 'div.bts_editor div.bsave span.ticon-save', function(){
- close_editor();
+ el.removeClass('blink_me');
+ }, 600);
});
function close_editor(){
$('div.bts_editor div.ult-overlay-close').trigger('click');
}
- $(document).on('jobEditor:close', 'div.divTable.Editor', function(e,job){
- console.log('close_editor event %o', job);
+ $(document).on('jobEditor:close', 'div.divTable.Editor', function(e,data){
+ var editor = data.editor;
+ var job = data.job;
+ //console.log('close_editor event %o, %o', editor, job);
+
+ var templ = $("#jobv1_item").html();
+ var html = Mustache.render(templ, {jobs:job}); //job id should be available;
+ if (job.is_new){
+ $('div.workspace').append(html);
+ $('#job_'+job.id).get(0).scrollIntoView();
+ }else{
+ //create new job underneath it
+ var templ = $("#jobv1_item").html();
+ var html = Mustache.render(templ, {jobs:job}); //job id should be available;
+ $('div.jobTable.Editing').after(html).remove();
+ }
+ var el = $('#job_' + job.id).addClass("blink_me");
+ setTimeout(function(){
+ el.removeClass('blink_me');
+ }, 1500);
});
$(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){
@@ -354,10 +376,21 @@
}
function add_new_empty_job(){
- var o = new Job({empty:true});
- $('div.workspace').append(o.el);
- o.el.get(0).scrollIntoView();
- dtp_init();
+ var job = new Job({empty:true});
+ job.is_new = true;
+ job.editorid = Math.floor(Math.random() * Math.floor(99999)); // a random number;
+
+ //show editor
+ var html = $('#jobv1_editor').html();
+ html = Mustache.render(html, job);
+ set_modal_content('editor', html);
+ //update GUI
+ open_modal('editor');
+ set_modal_title('editor', "Create New Job ");
+ dtp_init();
+ //init editor
+ var e = new JobEditor('#editor_' + job.editorid, job);
+ console.log("%o is instance of JobEditor %o", e, e instanceof JobEditor);
}
$(document).on('click', 'div[name="copyschedule"]', function(e){
e.stopPropagation();
@@ -426,11 +459,172 @@
newj.mark_highlight_me(1000);//for 1 second;
dtp_init();
});
- class Job{};
+ class Job{
+ constructor(record)
+ {
+ this.original_attr = Object.keys(record);
+ $.extend(this, record);
+ this.derive_attr();
+ }
+
+ get_record()
+ {//without extended attributes;
+ var self = this;
+ var r = {};
+ this.original_attr.forEach(function(attr){
+ r[attr] = self[attr];
+ });
+ return r;
+ }
+
+ derive_attr()
+ {
+ this.tos_name(this);
+ this.staff_name(this);
+ this.client_name(this);
+ this.rate_name(this);
+ this.rating_range(this);
+ this.identify_job_week(this);
+ this.job_acked(this);
+ }
+
+ tos_name(e){
+ if (typeof bts().tos[e.tos] != 'undefined'){
+ e.tos_name = bts().tos[e.tos].tos_full_str;
+ }else{
+ e.tos_name = "(deleted) ";
+ e.tos_err="Missing: " + e.tos;
+ }
+ }
+
+ staff_name(e){
+ if (typeof bts().staff_map[e.staff] != 'undefined'){
+ e.staff_name = bts().staff_map[e.staff].display_name;
+ }else{
+ e.staff_name = "(deleted) ";
+ e.staff_err="Missing: " + e.staff;
+ }
+ }
+
+ client_name(e){
+ if (typeof bts().client_map[e.client] != 'undefined'){
+ e.client_name = bts().client_map[e.client].display_name;
+ }else{
+ e.client_name ="(deleted)";
+ e.client_err ="Missing: " + e.client;
+ }
+ }
+
+ rate_name(e){
+ if (typeof bts().earnings_rate[e.rate] != 'undefined') {
+ e.rate_name = bts().earnings_rate[e.rate].RatePerUnit + "-" + bts().earnings_rate[e.rate].Name;
+ if (! has_txt_hour( bts().earnings_rate[e.rate].TypeOfUnits )){
+ e.rate_non_hour = true;
+ e.rate_err = `Rate unit must be ⟦ Hours ⟧
+ Possible solution:
+ 1. Change it in Xero
+ 2. Delete this job`;
+ }
+ }else{
+ e.rate_name = "(deleted)";
+ e.rate_err = "Missing: " + e.rate;
+ }
+ }
+
+ rating_range(e){
+ if (e.rating <0 || e.rating >5){
+ e.rating_err = "Rating 0-5 Only";
+ }
+ }
+
+ identify_job_week(e){
+ if (job_is_week1(e.start)){
+ e.is_week1=true;
+ }else if (job_is_week2(e.start)){
+ e.is_week2=true;
+ }
+ }
+
+ job_acked(e){
+ if (typeof e == 'undefined'){
+ console.log('break;');
+ }
+ if (e.ack != 0){
+ e.is_confirmed = true;
+ }
+ }
+
+ is_job_valid(){
+
+ var error_found = false;
+ var self = this;
+ this.original_attr.forEach(function(attr){
+ var col = attr + "_err";
+ if (typeof self[col] == 'undefined')
+ return;
+ if (self[attr+"_err"] != "")
+ error_found = true;
+ });
+
+ return !error_found;
+ }
+
+ is_high_pay()
+ {
+ var job = this;
+ var rate_info = bts().earnings_rate[job.rate];
+
+ var keywords =bts().high_pay_keywords;
+ var found = false;
+ keywords.forEach(function(e){
+ if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
+ found = true;
+ });
+ return found;
+ }
+
+ get_payment_summary()
+ {
+ var job = this;
+ var result ={};
+ result.ot = job.is_high_pay();
+ result.hour = job.get_working_duration();
+ result.money = job.get_wages();
+ return result;
+ }
+
+ get_working_duration()
+ {
+ var job = this;
+ //finish - start
+ var f = new Date(job.finish);
+ var s = new Date(job.start);
+ var diff = f.getTime() - s.getTime();
+ var hours = Math.floor(diff / 1000 / 60 / 60);
+ diff -= hours * 1000 * 60 * 60;
+ var minutes = Math.floor(diff / 1000 / 60);
+ var minute_to_hour = minutes/60;
+ return (hours + minute_to_hour);
+ }
+
+ get_wages()
+ {
+ var job = this;
+ var hour = job.get_working_duration(job);
+ var rate_info = bts().earnings_rate[job.rate];
+ if ( has_txt_hour( bts().earnings_rate[job.rate].TypeOfUnits ) )
+ {
+ return hour * rate_info.RatePerUnit;
+ }else{
+ return 1 * rate_info.RatePerUnit;
+ }
+ }
+ };
class JobEditor{ //save data for the record, and display it as GUI
- constructor(selector, data){
+ constructor(selector, job){
this.el = $(selector);
- this.load_data(data);
+ this.load_data(job);
+ console.assert(job instanceof Job);
this.init_event_handler();
}
@@ -455,39 +649,47 @@
//draw GUI by other
this.mark_dirty_on_new_record(data);
this.mark_week_color();
- this.validate(); //also triggers mark errors
+ //this.validate(); //also triggers mark errors
}
init_event_handler(){
var self = this;
this.el.find("div.btos select").change(function(){
if ( self.validate_tos() ) {
self.data.tos = self.get_tos();
+ self.set_err_msg_tos('');
}
});
this.el.find("div.bstart input").change(function(){
if (self.validate_start()){
self.data.start = self.get_start();
+ self.set_err_msg_start('');
+ self.validate_start_and_finish();
}
});
this.el.find("div.bfinish input").change(function(){
if (self.validate_finish()){
self.data.finish = self.get_finish();
+ self.set_err_msg_finish('');
+ self.validate_start_and_finish();
}
});
this.el.find("div.bstaff select").change(function(){
if (self.validate_staff()){
self.data.staff = self.get_staff();
+ self.set_err_msg_staff('');
}
});
this.el.find("div.bclient select").change(function(){
if (self.validate_client()){
self.data.client = self.get_client();
+ self.set_err_msg_client('');
}
});
this.el.find("div.brate select").change(function(){
if (self.validate_rate()){
self.data.rate = self.get_rate();
+ self.set_err_msg_rate('');
}
});
this.el.find("div.bconfirmed input").change(function(){
@@ -502,8 +704,9 @@
});
this.el.find("div.bsave span.ticon-save").click(function(e){
if ( self.validate() ){
- job_derive_attr(self.data);
- self.el.trigger('jobEditor:close', self.data);
+ self.do_save_record();
+ }else{
+ self.set_err_msg_save('Data Error');
}
});
}
@@ -531,9 +734,11 @@
return;
this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
if ( this.get_tos() != val ){
- var o = new Option("(missing:" + this.data.tos +")", this.data.tos);
+ this.set_err_msg_tos("Missing:" + this.data.tos)
+ var o = new Option("(deleted)", this.data.tos);
this.el.find('div.btos select').prepend(o);
- this.el.find('div.btos select option[value="'+this.data.tos+'"]').prop('selected',true);
+ this.el.find('div.btos select option[value="'+this.data.tos+'"]').prop('selected',true);
+
}
}
get_start(){
@@ -541,8 +746,10 @@
}
set_start(val)
{
- if (typeof(val) =="undefined")
+ if (typeof(val) =="undefined"){
+ this.set_err_msg_start("need start");
return;
+ }
this.el.find('div.bstart input').attr('value', val);
}
get_finish()
@@ -551,10 +758,10 @@
}
set_finish(val)
{
- if (typeof(val) == "undefined")
- return;
- if (typeof(val) == "undefined")
+ if (typeof(val) == "undefined"){
+ this.set_err_msg_finish("need finish");
return;
+ }
this.el.find('div.bfinish input').attr('value', val);
}
get_rate()
@@ -566,6 +773,13 @@
if (typeof(val) =="undefined")
return;
this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
+
+ if ( this.get_rate() != val ){
+ var o = new Option("(deleted)", this.data.rate);
+ this.el.find('div.brate select').prepend(o);
+ this.set_err_msg_rate('Missing:' + this.data.rate);
+ this.el.find('div.brate select option[value="'+this.data.rate+'"]').prop('selected',true);
+ }
}
get_staff()
{
@@ -577,9 +791,9 @@
return;
this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
if ( this.get_staff() != val ){
- var o = new Option("(missing:" + this.data.staff +")", this.data.staff);
+ var o = new Option("(deleted)", this.data.staff);
this.el.find('div.bstaff select').prepend(o);
- this.set_err_msg_tos('missing' + this.data.staff);
+ this.set_err_msg_staff('Missing:' + this.data.staff);
this.el.find('div.bstaff select option[value="'+this.data.staff+'"]').prop('selected',true);
}
}
@@ -592,6 +806,12 @@
if (typeof(val) =="undefined")
return;
this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
+ if ( this.get_client() != val ){
+ var o = new Option("(deleted)", this.data.client);
+ this.el.find('div.bclient select').prepend(o);
+ this.set_err_msg_client('Missing:' + this.data.client);
+ this.el.find('div.bclient select option[value="'+this.data.client+'"]').prop('selected',true);
+ }
}
get_ack()
{
@@ -631,13 +851,19 @@
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "save_job", // action
- record: this.get_record_from_ui(),
+ record: self.get_record_from_ui(),
}, function(response, status, xhr){
if (response.status=='success'){
self.load_data(response.newdata);
- self.mark_saved();
- self.mark_old();
+ var job = new Job(response.newdata);
+ job.saved = true;
+ job.is_new = response.isNew;
+ bts().job_map[job.id] = job;
+ var data = {editor:self, job:job};
+ self.el.trigger('jobEditor:close', data);
+ close_editor();
}else{
+ self.self.set_err_msg_save('Not saved');
alert( 'error saving data, please check your network');
}
});
@@ -723,7 +949,7 @@
validate()
{
var ok_tos = this.validate_tos();
- var ok_time = this.validate_start() && this.validate_finish(); //finish might not be executed, if start is wrong
+ var ok_time = this.validate_start() && this.validate_finish() && this.validate_start_and_finish();
var ok_staff = this.validate_staff();
var ok_client = this.validate_client();
var ok_rate = this.validate_rate() ; //make sure this validate is executed
@@ -739,33 +965,49 @@
validate_start(){
var str = this.get_start();
- if ( is_valid_date_str(str) ){
- this.mark_start_valid();
- this.set_err_msg_start('');
- return true;
- }else{
+ if (str == ""){
+ this.set_err_msg_start('need start');
+ return false;
+ }
+ if (!is_valid_date_str(str) ){
this.mark_start_invalid();
this.set_err_msg_start('wrong date');
return false;
}
+ return true;
}
validate_finish()
{
var str = this.get_finish();
+ if (str == ""){
+ this.set_err_msg_finish('need finish');
+ return false;
+ }
if (! is_valid_date_str(str)){
this.set_err_msg_finish('wrong date');
this.mark_finish_invalid();
return false;
}
-
+ return true;
+ }
+
+ validate_start_and_finish()
+ {
+ if (! this.validate_start() || ! this.validate_finish())
+ return;
if (!this.is_finish_resonable()){
- this.set_err_msg_finish("older than start")
+ this.set_err_msg_finish("older than start");
+ this.set_err_msg_start("after finish");
+ this.mark_start_invalid();
this.mark_finish_invalid();
return false;
+ }else{
+ this.mark_start_valid();
+ this.mark_finish_valid();
+ this.set_err_msg_finish("");
+ this.set_err_msg_start("");
+ return true;
}
- this.mark_finish_valid();
- this.set_err_msg_finish('');
- return true;
}
validate_rate()
{
@@ -775,12 +1017,6 @@
this.mark_rate_invalid();
return false;
}
-// if (this.get_rate() != this.data.rate){
-// this.set_err_msg_rate('rate@Xero inactive ' + this.data.rate);
-// this.mark_rate_invalid();
-// this.mark_dirty();
-// return false;
-// }
this.set_err_msg_rate('');
this.mark_rate_valid();
return true;
@@ -806,10 +1042,16 @@
}
}
validate_client(){
- return true;
+ if ( typeof bts().client_map[this.get_client()] == 'undefined') {
+ this.set_err_msg_client('missing ' + this.get_client());
+ return false;
+ }else{
+ this.set_err_msg_client('');
+ return true;
+ }
}
validate_rating(){
- return true;
+ return;
}
clear_err_msg(){
@@ -1334,16 +1576,20 @@
(typeof b.earnings_rate != "undefined" && Object.keys(b.earnings_rate).length > 0 ))
{
var job_map={};
+ var jobs = [];
//map data for each jobTable
response.jobs.forEach(function(e){
- job_derive_attr(e);
- job_map[e.id] = e;
+ //job_derive_attr(e);
+ var j = new Job(e)
+ j.saved=true; //this is a record from database;
+ job_map[e.id] = j;
+ jobs.push(j);
});
bts().job_map = job_map;
//we do works, load timesheets
var template = $("#jobv1_item").html();
- var html = Mustache.render(template, response);
+ var html = Mustache.render(template, {jobs:jobs});
$('div.workspace').append(html);
hide_loading_jobs();
//filter it if reqired
@@ -1356,63 +1602,8 @@
}, 500); //try it half seconds later
}
- function job_derive_attr(e)
- {
- clear_non_derive_attr(e);
- e.saved = true;
-
- if (typeof bts().tos[e.tos] != 'undefined'){
- e.tos_name = bts().tos[e.tos].tos_full_str;
- }else{
- e.tos_name = "(deleted) ";
- e.tos_err="Missing: " + e.tos;
- }
-
- if (typeof bts().staff_map[e.staff] != 'undefined'){
- e.staff_name = bts().staff_map[e.staff].display_name;
- }else{
- e.staff_name = "(deleted) ";
- e.staff_err="Missing: " + e.staff;
- }
-
-
- e.client_name = bts().client_map[e.client].display_name;
- e.rate_name = bts().earnings_rate[e.rate].RatePerUnit + "-" + bts().earnings_rate[e.rate].Name;
-
- if (! has_txt_hour( bts().earnings_rate[e.rate].TypeOfUnits )){
- e.rate_non_hour = true;
- e.rate_err = `Rate unit must be ⟦ Hours ⟧
- Possible solution:
- 1. Change it in Xero
- 2. Delete this job`;
- }
-
- if (job_is_week1(e.start)){
- e.is_week1=true;
- }else if (job_is_week2(e.start)){
- e.is_week2=true;
- }
-
- if (e.ack != 0){
- e.is_confirmed = true;
- }
- }
- function clear_non_derive_attr(e)
- {
- delete e.saved;
- delete e.tos_name;
- delete e.tos_err;
- delete e.staff_name;
- delete e.staff_err;
- delete e.client_name;
- delete e.client_err;
- delete e.rate_name;
- delete e.rate_err;
- delete e.rate_non_hour;
- delete e.is_week1;
- delete e.is_week2;
- delete e.is_confirmed;
- }
+
+
function has_txt_hour(str){
var s = str.toLowerCase();
@@ -1618,9 +1809,9 @@
var id = $(e).attr('data-id');
var job = bts().job_map[id];
- if (typeof job === 'undefined')
+ if (typeof job === 'undefined' || !job.is_job_valid() )
return;
- var ps = job_get_payment_summary(job);
+ var ps = job.get_payment_summary();
pays.total += ps.money;
pays.hours += ps.hour;
@@ -1634,54 +1825,6 @@
set_working_hours(pays.hours.toFixed(2));
}
- function job_get_payment_summary(job)
- {
- var result ={};
- result.ot = job_get_is_high_pay(job);
- result.hour = job_get_working_duration(job);
- result.money = job_get_wages(job);
- return result;
- }
-
- function job_get_is_high_pay(job)
- {
- var rate_info = bts().earnings_rate[job.rate];
-
- var keywords =bts().high_pay_keywords;
- var found = false;
- keywords.forEach(function(e){
- if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
- found = true;
- });
- return found;
- }
-
- function job_get_working_duration(job)
- {
- //finish - start
- var f = new Date(job.finish);
- var s = new Date(job.start);
- var diff = f.getTime() - s.getTime();
- var hours = Math.floor(diff / 1000 / 60 / 60);
- diff -= hours * 1000 * 60 * 60;
- var minutes = Math.floor(diff / 1000 / 60);
- var minute_to_hour = minutes/60;
- return (hours + minute_to_hour);
- }
-
- function job_get_wages(job)
- {
- var hour = job_get_working_duration(job);
- var rate_info = bts().earnings_rate[job.rate];
- if ( has_txt_hour( bts().earnings_rate[job.rate].TypeOfUnits ) )
- {
- return hour * rate_info.RatePerUnit;
- }else{
- return 1 * rate_info.RatePerUnit;
- }
- }
-
-
function find_staff(login)
@@ -1884,7 +2027,8 @@
set_modal_title('editor', "Editing Job: " + id);
//make a copy of the job
- var job_copy = $.extend({}, bts().job_map[id]);
+ var child = bts().job_map[id];
+ var job_copy = Object.assign(Object.create(Object.getPrototypeOf(child)), child); //a shallow copy only;
job_copy.editorid = Math.floor(Math.random() * Math.floor(99999)); // a random number;
//set_modal_data('editor', {jobid: id, job_copy:job_copy});
var html = $('#jobv1_editor').html();
@@ -1893,7 +2037,8 @@
//update GUI
dtp_init();
//init editor
- new JobEditor('#editor_' + job_copy.editorid, job_copy);
+ var e = new JobEditor('#editor_' + job_copy.editorid, job_copy);
+ //console.log("e is instance of JobEditor %o", e instanceof JobEditor);
}