From 37baac6f782e79403677ec3eaa74265b659642cc Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Sep 2019 04:30:21 +1000 Subject: [PATCH] editor works add new job works --- css/bts_office.css | 8 +- html/jobv1.html | 10 +- html/jobv1_editor.html | 23 +- js/bts_office.js | 469 +++++++++++++++++++++++++++-------------- 4 files changed, 329 insertions(+), 181 deletions(-) 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); }