Просмотр исходного кода

editor works add new job works

master
patrick 6 лет назад
Родитель
Сommit
37baac6f78
4 измененных файлов: 329 добавлений и 181 удалений
  1. +6
    -2
      css/bts_office.css
  2. +5
    -5
      html/jobv1.html
  3. +11
    -12
      html/jobv1_editor.html
  4. +307
    -162
      js/bts_office.js

+ 6
- 2
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;

+ 5
- 5
html/jobv1.html Просмотреть файл

@@ -6,13 +6,13 @@
<div class="divTableCell btos {{#tos_err}}error{{/tos_err}}" title="{{tos_err}}">
{{tos_name}}
</div>
<div class="divTableCell bstart">{{start}}</div>
<div class="divTableCell bfinish">{{finish}}</div>
<div class="divTableCell brate {{#rate_non_hour}} error {{/rate_non_hour}}" {{#non_hour}} title="{{rate_err}}" {{/non_hour}} >{{rate_name}}</div>
<div class="divTableCell bstart {{#start_err}} error {{/start_err}}">{{start}}</div>
<div class="divTableCell bfinish {{#finish_err}} error {{/finish_err}}">{{finish}}</div>
<div class="divTableCell brate {{#rate_err}} error {{/rate_err}}" {{#non_hour}} title="{{rate_err}}" {{/non_hour}} >{{rate_name}}</div>
<div class="divTableCell bstaff {{#staff_err}} error {{/staff_err}}" title="{{staff_err}}">{{staff_name}}</div>
<div class="divTableCell bclient {{#is_week1}}week1color{{/is_week1}} {{#is_week2}}week2color{{/is_week2}}">{{client_name}}</div>
<div class="divTableCell bclient {{#client_err}} error {{/client_err}} {{#is_week1}}week1color{{/is_week1}} {{#is_week2}}week2color{{/is_week2}}">{{client_name}}</div>
<div class="divTableCell bconfirmed"><input name="ack" type=checkbox {{#is_confirmed}}checked{{/is_confirmed}} onclick="return false;"></div>
<div class="divTableCell brating">{{rating}}</div>
<div class="divTableCell brating {{#rating_err}} error {{/rating_err}}">{{rating}}</div>
<div class="divTableCell bedit">
<span class="ticon ticon-edit"></span>
</div>

+ 11
- 12
html/jobv1_editor.html Просмотреть файл

@@ -37,7 +37,7 @@
<option value="3">3-Standard</option>
<option value="2">2-Bad</option>
<option value="1">1-Worst</option>
<option value="0">0-No Feedback</option>
<option value="0" selected>0-No Feedback</option>
</select>
</div>
<div class="divTableCell bdelete">
@@ -48,17 +48,16 @@
</div>
</div>
<div class="divTableRow errmsg">
<div class="divTableCell btos_err"> e tos
</div>
<div class="divTableCell bstart_err">es</div>
<div class="divTableCell bfinish_err">ef</div>
<div class="divTableCell brate_err">er</div>
<div class="divTableCell bstaff_err">estaf</div>
<div class="divTableCell bclient_err">ecli</div>
<div class="divTableCell bconfirmed_err">econfirm</div>
<div class="divTableCell brating_err">erat</div>
<div class="divTableCell bdelete_err">edel</div>
<div class="divTableCell bsave_err">eeave</div>
<div class="divTableCell btos_err"></div>
<div class="divTableCell bstart_err">when to start</div>
<div class="divTableCell bfinish_err">when to finish</div>
<div class="divTableCell brate_err"></div>
<div class="divTableCell bstaff_err"></div>
<div class="divTableCell bclient_err"></div>
<div class="divTableCell bconfirmed_err"></div>
<div class="divTableCell brating_err"></div>
<div class="divTableCell bdelete_err"></div>
<div class="divTableCell bsave_err">click to save</div>
</div>
</div>

+ 307
- 162
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);
}


Загрузка…
Отмена
Сохранить