(function ($) {
$(function () {
// http://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
if (!immediate)
func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow)
func.apply(context, args);
};
};
/*____________________________________________________________________________________*/
class People{
constructor(selector, template, data){
this.selector = selector;
this.data = data;
this.template = template;
// this.sample_people = {
// login: '01515b52-6936-46b2-a000-9ad4cd7a5b50',
// firstname: "first",
// lastname: "last",
// phone: '041122221',
// email: 'abc@gmail.com',
// pay: 0,
// hour: 12,
// OT: 3,
// petrol: 50,
// rating: 1,
// };
this.load_data(this.data);
}
load_data(data){
var template = $(this.template).html();
var html = Mustache.render(template, data);
$(this.selector).html(html);
//save it
$(this.selector).data({obj:this, data:data});
//draw rating star
//this.set_ratings(this.data.rating);
this.set_unconfirmed_job(this.data.unconfirmedjob);
}
set_ratings(num){
for (var i=1; i<= 5; i++){
if (i <=num){
$(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").addClass('checked');
}else{
$(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").removeClass('checked');
}
}
this.data.rating = num;
}
set_km(km)
{
var str = "petrol:" + km.toFixed(2) + " km";
$(this.selector + ' div[name="petrol"]').html(str);
}
set_unconfirmed_job(num){
if( num == 0 )
$(this.selector + " span[name='badge']").hide();
else
$(this.selector + " span[name='badge']").show();
this.data.unconfirmedjob = num;
}
reset_summary() {
this.summary = {
wages : 0,
normal_hour : 0,
ot_hour : 0,
petrol : 0,
};
this.update_summary_in_gui();
}
add_payment_summary(ps)
{
//{ot: false, hour: "2.67", money: "76.90"}
this.summary.wages += ps.money;
if (! ps.ot )
this.summary.normal_hour += ps.hour;
else
this.summary.ot_hour += ps.hour;
this.update_summary_in_gui();
}
update_summary_in_gui()
{
var msg = '$' + this.summary.wages.toFixed(2);
$(this.selector).find('div[name="wages"]').html(msg);
msg = this.summary.normal_hour.toFixed(2) + '+' +this.summary.ot_hour.toFixed(2) + 'hr';
$(this.selector).find('div[name="hours"]').html(msg);
msg = 'petrol:' + this.summary.petrol.toFixed(2) + 'km';
$(this.selector).find('div[name="petrol"]').html(msg);
}
}//end of class People
function bts_staff_html(data){
var template = $('#staff_item').html();
var head = '
';
r = head + '
' ;
return r;
}
function bts_client_html(data){
var template = $('#client_item').html();
var head = '';
r = head + '
' ;
return r;
}
function sample_staff(){
for (var i=1; i<100; i++){
var sample_people = {
login: '01515b52-6936-46b2-a000-9ad4cd7a5b50' +i,
firstname: "first"+i,
lastname: "last",
mobile: '041122221' +i,
email: 'abc@gmail.com' + i,
wages: 0,
hour: i,
OT: 3,
petrol: 50 +i,
rating: Math.floor(Math.random() * Math.floor(5)),
unconfirmedjob: Math.floor(Math.random() * Math.floor(30)),
};
var html = bts_staff_html(sample_people);
jQuery('div.stafflist').append(html);
new People("#p" + sample_people.login, sample_people);
}
}
function list_staff() {
show_loading_staff();
$('div.stafflist div.peopleitem').remove();
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "list_staff", // action
}).done(function(response, status, xhr){
if (response.status =='success'){
bts().staff = response.users;
bts().staff_map = {};
bts().staff_people= {};
response.users.forEach(function(u){
bts().staff_map[u.login] = u;
var html = bts_staff_html(u);
jQuery('div.stafflist').append(html);
var staff_obj = new People("#p" + u.login,'#staff_item', u);
bts().staff_people[u.login] = staff_obj;
});
hide_loading_staff();
}else{
alert('error getting staff list');
}
});
}
function list_clients() {
show_loading_client();
$('div.clientlist div.peopleitem').remove(); //clear it
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "list_client", // action
}, function(response, status, xhr){
if (response.status =='success'){
bts().client = response.users;
bts().client_map = {};
response.users.forEach(function(u){
bts().client_map[u.login] = u;
hide_loading_client();
var html = bts_client_html(u);
jQuery('div.clientlist').append(html);
new People("#p" + u.login, '#client_item' ,u);
});
}else{
alert('error getting Client list');
}
});
}
function list_tos() {
wifi(true);
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "list_tos", // action
}, function(response, status, xhr){
if (response.status =='success'){
bts().tos = response.tos;
wifi(false);
}else{
alert('error getting Type of Service list');
}
});
}
function show_loading_staff(){
jQuery('div.stafflist img').attr('src', bts().load_user_img).show();
}
function show_loading_client(){
jQuery('div.clientlist img').attr('src', bts().load_user_img).show();
}
function hide_loading_staff(){
jQuery('div.stafflist img').hide();
}
function hide_loading_client(){
jQuery('div.clientlist img').hide();
}
function show_loading_jobs(){
jQuery('div.workspace img').attr('src', bts().load_job_img).show();
}
function hide_loading_jobs(){
jQuery('div.workspace img').hide();
}
function xero(t){
if (t)
$('div.xero i').show();
else
$('div.xero i').hide();
}
function wifi(t){
if (t)
$('div.wifi i').show();
else
$('div.wifi i').hide();
}
function csv(t){
if (t)
$('div.csv i').show();
else
$('div.csv i').hide();
}
function init_user_search(){
$('div.b_search input').keyup(debounce(function(e){
filter_user(e.target);
}, 500));
}
function filter_user(input){
var value = $(input).attr('value');
value = value.toLowerCase();
var selector = get_selector_for_filter_people(input);
$.each( $(selector).find('div.peopleitem'), function(index, e){
//uncheck everyone
$(e).find('input[type="checkbox"]').prop('checked', false);
var html = $(e).find('div[name="title"] a').html();
html = html.toLowerCase();
if (-1 != html.indexOf(value)){//we find it;
$(e).show();
}else{
$(e).hide();
}
});
}
function get_selector_for_filter_people(input){
var selector='';
var role = $(input).attr('placeholder');
if (role == 'staff') //we filter staff
selector = 'div.stafflist';
else if (role = 'client')
selector = 'div.clientlist';
return selector;
}
$(document).on('click', 'div.jobTable div.brate.error', function(){
var msg = $(this).attr('title');
if (msg != "")
alert(msg);
});
$(document).on('click', 'div.workspace div.bedit span.ticon-edit', function(){
var el = $(this).closest('div.jobTable');
var newjob_id = el.attr('data-newjob_id');
if (typeof newjob_id != 'undefined' && newjob_id != ''){
do_edit_new_job(newjob_id);
}else{
var id = el.attr('data-id');
do_edit_job(id);
}
});
function get_workspace_start_date(){
return new Date($('span[name="w1d1"]').data().date) ;
}
function get_payroll_start()
{
return new Date(bts().pay_calendar.start + " 00:00:00");
}
function allow_editing(date){
var begin = new Date(date);
var pay_begin = get_payroll_start();
return begin > pay_begin;
}
// $(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(){
var el = $('.Editing');
el.addClass("blink_me").removeClass('Editing');
setTimeout(function(){
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,data){
var editor = data.editor;
var job = data.job;
//console.log('close_editor event %o, %o', editor, job);
editor.off_event_handler();//remove all events handler;
var templ = $("#jobv1_item").html();
var html = Mustache.render(templ, {jobs:job}); //job id should be available;
if ( $('div.jobTable.Editing').length == 0){
$('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);
debounced_calculate();
});
$(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){
check_duplicate();
setTimeout(do_delete_duplicate, 200);//200ms make GUI refresh
});
function fade_and_delete(el)
{
$(el).fadeOut(300);
setTimeout(function(){
$(el).remove();
},300);
}
function add_new_empty_job(job){
var title = "";
if (typeof job == 'undefined' || ! (job instanceof Job) ){
job = new Job({empty:true});
job.is_new = true;
title = "Create New Job ";
}else if (job instanceof Job){
title = "Create new Job by Copy Existing one";
}
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', title);
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();
add_new_empty_job();
});
function remove_job_from_gui(el)
{
el = $(el);
var newjob_id = el.data().newjob_id;
var id = el.data().id;
if (typeof newjob_id != 'undefined' && newjob_id !=''){
delete bts().job_map_new[newjob_id];
}else if (typeof id != 'undefined' && id != ''){
delete bts().job_map[id];
}
el.addClass('blink_me');
el.fadeOut(500);
setTimeout(function(){
el.remove();
debounced_calculate();
}, 500);
}
$(document).on('click', 'div.divTableCell.bdelete', function(){
var el = $(this).closest('div.jobTable');
var data = el.data();
if (typeof data.id =='undefined' || data.id == ''){//not saved
remove_job_from_gui(el);//remove direclty
}else{
var id = data.id;
if (confirm('delete this job?')){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "delete_job", // action
jobid: id,
}, function(response, status, xhr){
if (response.status=='success'){
remove_job_from_gui(el);
}else{
alert( 'error deleting job, please check your network');
}
});
}
}
});
$(document).on('mouseenter', 'div.divTableCell', function(){
$(this).closest('div.divTable').addClass('highlight');
});
$(document).on('mouseleave', 'div.divTableCell', function(){
$(this).closest('div.divTable').removeClass('highlight');
});
$(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
var table = $(this).closest('div.jobTable');
var data = table.data();
if (data.id == "" && data.newjob_id != "" && data.newjob_id.substring(0,4) == "new_" ){
job = bts().job_map_new[data.newjob_id];
console.assert(typeof job != 'undefined');
do_save_new_job(job.get_record(), table);
return;
}
alert("Error occured");
$(this).fadeOut();
});
$(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
//save all
$('div.workspace span.ticon.ticon-save').each(function (i,e){
if ($(this).is(":visible"))
$(this).trigger('click');
});
});
$(document).on('click', 'span.ticon.ticon-copy', function(){
var table = $(this).closest('div.jobTable');
var record = bts().job_map[table.data().id].get_record();
//create new job;
var newj = clone_data_create_new_job(record);
add_new_empty_job(newj);
});
function do_save_new_job(job_tobe_saved, table){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "save_job", // action
record: job_tobe_saved,
}, function(response, status, xhr){
if (response.status=='success'){
var job = new Job(response.newdata);
job.saved = true;
job.is_new = response.isNew;
bts().job_map[job.id] = job;
//delete that old job and display the new one
var jobs=[job];
var html = Mustache.render($('#jobv1_item').html(), {jobs:jobs});
delete bts().job_map_new[table.data().newjob_id];
table.after(html);
table.remove();
}else{
console.error("error saving job %o, response=%o", job_tobe_saved, response);
alert( 'error saving data, please check your network');
}
});
}
function mark_highlight_me(el, ms){
el.addClass('blink_me');
el.addClass('highlight');
el.addClass('newcopy');
setTimeout(function(){
el.removeClass('blink_me');
el.removeClass('highlight');
el.removeClass('newcopy');
},ms);
}
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);
this.job_disabled(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_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 (e.ack != 0){
e.is_confirmed = true;
}
}
job_disabled(e){
var allow = allow_editing(e.start);
if (!allow)
e.disabled =true;
else
delete e.disabled;
}
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;
}
}
allow_edit(){
return allow_editing(this.start);
}
};
class JobEditor{ //save data for the record, and display it as GUI
constructor(selector, job){
this.el = $(selector);
this.load_data(job);
console.assert(job instanceof Job);
this.init_event_handler();
}
load_data(data)
{
//save to html element
this.data = data;
this.el.data({job:this, data:data});
//draw GUI
this.clear_err_msg();
this.set_job_id(data.id);
this.set_tos(data.tos);
this.set_start(data.start);
this.set_finish(data.finish);
this.set_rate(data.rate);
this.set_staff(data.staff);
this.set_client(data.client);
this.set_ack(data.ack);
this.set_rating(data.rating);
//draw GUI by other
this.mark_dirty_on_new_record(data);
this.mark_week_color();
//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(){
if(self.validate_ack()){
self.data.ack = self.get_ack();
}
});
this.el.find("div.brating select").change(function(){
if( self.validate_rating()){
self.data.rating =self.get_rating();
}
});
this.el.find("div.bsave span.ticon-save").click(function(e){
if ( self.validate() ){
self.do_save_record();
}else{
self.set_err_msg_save('Data Error');
}
});
}
off_event_handler(){
this.el.off('change',"div.btos select");
this.el.off('change',"div.bstart input");
this.el.off('change',"div.bfinish input");
this.el.off('change',"div.bstaff select");
this.el.off('change',"div.bclient select");
this.el.off('change',"div.brate select");
this.el.off('change',"div.bconfirmed input");
this.el.off('change',"div.brating select");
this.el.off('change',"div.bsave span.ticon-save");
}
get_job_id(){
return this.el.attr('data-id');
}
set_job_id(val){
if (typeof val == 'undefined' || val == '')
{//remove data-id and id
this.el.removeAttr('data-id');
this.el.removeAttr('id');
}else{
this.el.attr('data-id', val);
this.el.attr('id', 'job_' + val);
}
}
get_tos()
{
return this.el.find('div.btos select').children("option:selected").val();
}
set_tos(val)
{
if (typeof(val) =="undefined")
return;
this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
if ( this.get_tos() != val ){
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);
}
}
get_start(){
return this.el.find('div.bstart input').attr('value');
}
set_start(val)
{
if (typeof(val) =="undefined"){
this.set_err_msg_start("need start");
return;
}
this.el.find('div.bstart input').attr('value', val);
}
get_finish()
{
return this.el.find('div.bfinish input').attr('value');
}
set_finish(val)
{
if (typeof(val) == "undefined"){
this.set_err_msg_finish("need finish");
return;
}
this.el.find('div.bfinish input').attr('value', val);
}
get_rate()
{
return this.el.find('div.brate select').children("option:selected").val();
}
set_rate(val)
{
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()
{
return this.el.find('div.bstaff select').children("option:selected").val();
}
set_staff(val)
{
if (typeof(val) =="undefined")
return;
this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
if ( this.get_staff() != val ){
var o = new Option("(deleted)", this.data.staff);
this.el.find('div.bstaff select').prepend(o);
this.set_err_msg_staff('Missing:' + this.data.staff);
this.el.find('div.bstaff select option[value="'+this.data.staff+'"]').prop('selected',true);
}
}
get_client()
{
return this.el.find('div.bclient select').children("option:selected").val();
}
set_client(val)
{
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()
{
return this.el.find('div.bconfirmed input:checked').length > 0? 1:0;
}
set_ack(val)
{
if (typeof(val) =="undefined")
return;
return this.el.find('div.bconfirmed input').prop('checked', val!=0);
}
get_rating(){
return this.el.find('div.brating select').children("option:selected").val();
}
set_rating(num){
if (!(0 <= num && num <=5))
return;
this.el.find('div.brating select option[value="'+num+'"]').prop('selected',true);
}
get_record_from_ui(){
var record = {};
record.id = this.get_job_id();
record.tos = this.get_tos();
record.start = this.get_start();
record.finish = this.get_finish();
record.rate = this.get_rate();
record.staff = this.get_staff();
record.client = this.get_client();
record.ack = this.get_ack();
record.rating = this.get_rating();
return record;
}
do_save_record(){
var self = this;
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "save_job", // action
record: self.get_record_from_ui(),
}, function(response, status, xhr){
if (response.status=='success'){
self.load_data(response.newdata);
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');
}
});
}
mark_dirty_on_new_record(data){
if (typeof(data.id) === 'undefined' || data.id == ''){
this.mark_dirty();
this.mark_new();
}
else{
this.mark_saved();
}
}
mark_dirty() //need save
{
var d = this.el.find('.bsave');
d.removeClass('saved');
d.addClass('blink_me');
setTimeout(function(){
d.removeClass('blink_me');
d.removeClass('saved');
},1000);
}
mark_saved()
{
var d = this.el.find('.bsave');
d.addClass('blink_me');
setTimeout(function(){
d.removeClass('blink_me');
d.addClass('saved');
},1000);
}
//newly created empty record
mark_new()
{
this.el.addClass('emptyrecord');
}
mark_old()
{
this.el.removeClass('emptyrecord');
}
is_start_valid(){
var s = this.get_start();
return is_valid_date_str(s);
}
is_finish_valid(){
var f = this.get_finish();
if (!is_valid_date_str(f))
return false;
}
is_finish_resonable(){
var f = this.get_finish();
if (!is_valid_date_str(f))
return false;
var s = this.get_start();
s = new Date(s);
f = new Date(f);
return (s < f);
}
validate()
{
var ok_tos = this.validate_tos();
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
var ok = ok_tos && ok_time && ok_staff && ok_client && ok_rate;
if (ok){
this.el.removeClass('invalidjob');
}else{
this.el.addClass('invalidjob');
}
return ok;
}
validate_start(){
var str = this.get_start();
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_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;
}
}
validate_rate()
{
var rate_info = this.get_rate_info_by_id(this.get_rate());
if ( rate_info.RatePerUnit <= 0){
this.set_err_msg_rate('bad rate');
this.mark_rate_invalid();
return false;
}
this.set_err_msg_rate('');
this.mark_rate_valid();
return true;
}
validate_tos(){
if ( typeof bts().tos[this.get_tos()] == 'undefined') {
this.set_err_msg_tos('missing ' + this.get_tos());
return false;
}else{
this.set_err_msg_tos('');
return true;
}
}
validate_staff(){
if ( typeof bts().staff_map[this.get_staff()] == 'undefined') {
this.set_err_msg_staff('missing ' + this.get_staff());
return false;
}else{
this.set_err_msg_staff('');
return true;
}
}
validate_client(){
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;
}
clear_err_msg(){
this.el.find('.divTableRow.errmsg > div').html('');
}
set_err_msg_start(str)
{
this.el.find('div.bstart_err').html(str);
}
set_err_msg_finish(str)
{
this.el.find('div.bfinish_err').html(str);
}
set_err_msg_staff(str)
{
this.el.find('div.bstaff_err').html(str);
}
set_err_msg_client(str)
{
this.el.find('div.bclient_err').html(str);
}
set_err_msg_rate(str)
{
this.el.find('div.brate_err').html(str);
}
set_err_msg_save(str)
{
this.el.find('div.bsave_err').html(str);
}
set_err_msg_tos(str)
{
this.el.find('div.btos_err').html(str);
}
mark_tos_valid(){
this.el.find('div.btos select').removeClass('invalid');
}
mark_tos_invalid(){
this.el.find('div.btos select').addClass('invalid');
}
mark_start_valid(){
this.el.find('div.bstart input').removeClass('invalid');
}
mark_start_invalid(){
this.el.find('div.bstart input').addClass('invalid');
}
mark_finish_valid(){
this.el.find('div.bfinish input').removeClass('invalid');
}
mark_finish_invalid(){
this.el.find('div.bfinish input').addClass('invalid');
}
mark_rate_valid(){
this.el.find('div.brate select').removeClass('invalid');
}
mark_rate_invalid(){
this.el.find('div.brate select').addClass('invalid');
}
mark_week_color(){
this.el.find('div.brating').removeClass('week1color');
this.el.find('div.brating').removeClass('week2color');
if (this.is_week1()){
this.el.find('div.brating').addClass('week1color');
}else if (this.is_week2()){
this.el.find('div.brating').addClass('week2color');
}
}
is_week1()
{
var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
var w1_end = new Date($('span[name="w1d7"]').data().date);
w1_begin.setHours(0,0,0,0);
w1_end.setHours(23,59,59);
//console.log("week1 begin %o, end %o", w1_begin, w1_end);
//w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
var me = new Date(this.data.start);
return (w1_begin <= me && me <= w1_end );
}
is_week2()
{
var w2_begin = new Date($('span[name="w2d1"]').data().date);
var w2_end = new Date($('span[name="w2d7"]').data().date);
w2_begin.setHours(0,0,0,0);
w2_end.setHours(23,59,59);
var me = new Date(this.data.start);
return (w2_begin <= me && me <= w2_end );
}
get_payment_summary(){
var result ={};
result.ot = this.get_is_high_pay();
result.hour = this.get_working_duration();
result.money = this.get_wages();
return result;
}
get_is_high_pay()
{
var rate_info = this.get_rate_info_by_id(this.get_rate());
return this.is_high_pay_hour(rate_info);
}
get_working_duration()
{
//finish - start
var f = new Date(this.get_finish());
var s = new Date(this.get_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 hour = this.get_working_duration();
var rate_info = this.get_rate_info_by_id(this.get_rate());
return hour * rate_info.RatePerUnit;
}
get_rate_info_by_id(id){
var rate_info = {};
var rates = bts().earnings_rate;
for(var i =0; i< rates.length; i++){
var r = rates[i];
if(r.EarningsRateID == id){
rate_info = $.extend(true,{}, r);//make a copy
break;
}
}
return rate_info;
}
is_high_pay_hour(rate_info){
var keywords =bts().high_pay_keywords;
var found = false;
return false;
keywords.forEach(function(e){
if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
found = true;
});
return found;
}
}//end of class Job
//global GUI summary
function get_wages()
{
var txt = $('div.wages div').html();
return parseInt(txt);
}
function set_wages(num){
$('div.wages div').html(num);
}
function set_working_hours(num){
$('input#woh').attr('value', num);
}
function get_working_hours(){
var txt = $('input#woh').attr('value');
return parseFloat(txt);
}
//modal box
function set_modal_title(selector, title){
var s = 'div.bts_'+ selector +' .ult_modal-title';
$(s).html(title);
}
function set_modal_content(selector, content){
var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
$(s).html(content);
}
function open_modal (selector){
var s='div.bts_'+selector+'_button';
$(s).trigger('click');
}
function set_modal_data(selector, data){
var s = 'div.bts_'+ selector;
$(s).data(data);
}
function get_modal_data(selector, data){
var s = 'div.bts_'+ selector;
$(s).data();
}
var blink_by_date_timer;
$(document).on('mouseenter', 'div.week1 > div, div.week2 > div', function(){
var self = this;
blink_by_date_timer = setTimeout (function(){
$(self).addClass('blink_me');
get_week2_partner(self).addClass('blink_me');
blink_same_date_by_div(self);
}, 1500);
});
$(document).on('mouseleave', 'div.week1 div, div.week2 > div', function(){
clearTimeout(blink_by_date_timer);
$(this).removeClass('blink_me');
get_week2_partner(this).removeClass('blink_me');
unblink_all_date();
});
function get_week2_partner(div){
var index = $(div).index()+1;
return $('div.week2 div:nth-child('+index+')');
}
function init_weekdays(){
var curr = new Date; // get current date
init_weekdays_by_anchor(curr, true);
return;
}
function init_weekdays_by_anchor(anchor, is_week1){
var curr = new Date(anchor); // get the date;
if (!is_week1){ //it is week2, shift for 7 days;
curr.setDate(curr.getDate() -7); //curr will be changed;
}
var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
var last = first + 6; // last day is the first day + 6
if (curr.getDay() == 0 ){// it's Sunday;
last = curr.getDate();
first = last - 6;
}
var pos = 1; //first lot
for (var i=first; i<=last; i++)
{
var now = new Date(curr);
var d1 = new Date(now.setDate(i));
now = new Date(curr);
var d2 = new Date(now.setDate(i+7));
set_day_number(1,pos, d1); //week 1
set_day_number(2,pos, d2); //week 2
pos +=1;
}
}
function set_day_number(week, index, date){
var selector = 'span[name="w'+week+'d'+index+'"]';
$(selector).html(date.getDate());
$(selector).data({date:date});
//console.log('set w%d-d%d %o', week,index,date);
}
function set_today(){
var selector = 'div.sheettitle span[name="today"]';
var curr = new Date;
$(selector).html(format_date(curr));
}
Date.prototype.get_week_number = function(){
var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
var dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
};
function set_week_number(){
var date = $('span[name="w1d1"]').data().date;
//console.log("date %o", date);
var num = date.get_week_number();
$('div.weekly span[name="week1"]').html(num);
$('div.weekly span[name="week2"]').html(num+1);
set_week_boundry();
}
function set_week_boundry()
{
var date = $('span[name="w1d1"]').data().date;
$('#week1b').attr('value', format_date(date));
var date = $('span[name="w2d7"]').data().date;
$('#week2b').attr('value', format_date(date));
}
function number_of_unsaved_job(){
var count =0;
var total_job = $('div.jobTable').length;
var total_saved = $('div.jobTable.saved').length;
var empty = $('div.emptyrecord').length;
count = total_job - total_saved - empty;
return count;
}
$('div.prevweek.left').click(function(){
if (number_of_unsaved_job() > 0){
if(!confirm("you have unsaved jobs, proceed will lost them"))
return;
}
$('div.weekdays span.weekday').each(function(i, e){
var date = $(e).data().date;
var newdate = new Date(date.setDate(date.getDate() -7 ));
$(e).html(newdate.getDate());
$(e).data({data:newdate});
});
set_week_number();
debounced_load_timesheet();
});
$('div.nextweek.right').click(function(){
if (number_of_unsaved_job() > 0){
if(!confirm("you have unsaved jobs,proceed will lost them"))
return;
}
$('div.weekdays span.weekday').each(function(i, e){
var date = $(e).data().date;
var newdate = new Date(date.setDate(date.getDate() +7 ));
$(e).html(newdate.getDate());
$(e).data({data:newdate});
});
set_week_number();
debounced_load_timesheet();
});
$('div.weekly div.weekname.prev >input ').click(function(e){
e.stopPropagation();
});
$('div.weekly div.weekname.prev >input ').change(function(e){
var date = $('#week1b').attr('value');
init_weekdays_by_anchor(date, true);
set_week_number();
debounced_load_timesheet();
});
$('div.weekly div.weekname.prev').click(function(){
if (!confirm ('copy entire week to next week?'))
return;
var jobs = [];
$('div.week1 >div').each(function(i,e){
var date = new Date($(e).find('span.weekday').data().date);
var strDate = format_date(date); //yyyy-mm-dd
$('div.bstart:visible').each(function(i,e){
var value = $(e).html();
if( -1 != value.indexOf(strDate) ) //found
{
var el = $(e).closest('div.jobTable');
if (el.is(":visible") && el.hasClass('saved') && ! el.hasClass('disabled')){
var id = el.data().id;
var j = bts().job_map[id];
var newj = clone_data_create_new_job(j.get_record(),7);//add 7 days
add_new_job_to_map(newj);
jobs.push(newj);
}
}
});
});
show_jobs(jobs);
debounced_calculate();
alert("Copied " + jobs.length + " jobs");
});
$('div.weekly div.weekname.next > input').click(function(e){
e.stopPropagation();
});
$('div.weekly div.weekname.next >input ').change(function(e){
e.stopPropagation();
var date = $('#week2b').attr('value');
init_weekdays_by_anchor(date, false);
set_week_number();
debounced_load_timesheet();
});
$('div.weekly div.weekname.next').click(function(e){
$(e).find('input').trigger('click');
});
$('div.week1 > div').click(function(e){
e.stopPropagation();
blink_same_date_by_div(this);
if ($('div.bstart.blink_me').length == 0){
alert("nothing to copy");
return;
}
if (!confirm ('copy to next week'))
return;
var new_jobs = [];
$('div.bstart.blink_me').each(function(i,e){
var r = copy_single_day_to_next_week(e);
if (r != false){
add_new_job_to_map(r);
new_jobs.push(r);
}
});
show_jobs(new_jobs);
unblink_all_date();
alert("Copied " + new_jobs.length + " jobs");
});
$('div.week1,div.week2').click(function(e){
e.stopPropagation();
$(this).toggleClass('filtered');
do_filter_workspace();
});
function copy_single_day_to_next_week(el){
var tb = $(el).closest('div.jobTable');
if (tb.is(':visible') && tb.hasClass('saved')){
var id = tb.data().id;
var j = bts().job_map[id];
if (! j.allow_edit())
return false;
var newj = clone_data_create_new_job(j.get_record() , 7); // +7 days
return newj;
}
return false;
}
function clone_data_create_new_job(val, num_of_shifted_days){
var data = $.extend(true, {}, val);//make a copy
num_of_shifted_days = typeof num_of_shifted_days !=='undefined'? num_of_shifted_days: 0;// 0 days
//reset
data.id='';
data.ack = 0;
data.rating = 0;
if (is_valid_date_str(data.start)){
var s = new Date(data.start);
var s1 = s.getDate() + num_of_shifted_days;
s = new Date(s.setDate(s1));
data.start = format_date_time(s);
}
if (is_valid_date_str(data.finish)){
var f = new Date(data.finish);
var f1 = f.getDate() + num_of_shifted_days;
f = new Date(f.setDate(f1));
data.finish = format_date_time(f);
}
var newj = new Job(data);
//return;
return newj;
}
function add_new_job_to_map(newj)
{
//add to job map
newj.newjob_id = "new_" + bts_unique_ID();
if (typeof bts().job_map_new == 'undefined'){
bts().job_map_new = [];
}
bts().job_map_new[newj.newjob_id] = newj;
}
function is_valid_date_str(val){
var d = new Date(val);
if (d.toString()== 'Invalid Date')
return false;
return true;
}
function blink_same_date_by_div(div){
var date = new Date($(div).find('span.weekday').data().date);
blink_same_date(date);
}
function blink_same_date(date){
var strDate = format_date(date); //yyyy-mm-dd
var els=[];
unblink_all_date();
var first_into_view = false; //make sure first row in match is visible
$('div.workspace div.bstart:visible').each(function(i,e){
var value = $(e).html();
if( -1 != value.indexOf(strDate) ) //found
{
if ( !first_into_view ){ //scroll to top
first_into_view = true;
ensure_visible(e);
}
els.push($(e));
$(e).addClass('blink_me');
}
});
}
function ensure_visible(el){
$(el).get(0).scrollIntoView();
}
function unblink_all_date(){
$('div.bstart').removeClass('blink_me');
}
$('div.sheettitle h1 span').click(function(){
reset_title_to_today();
load_timesheet();
});
function reset_title_to_today(){
set_today();
init_weekdays();
set_week_number();
}
var debounced_load_timesheet = debounce(load_timesheet,1000);
function load_timesheet()
{
clear_workspace();
var first = $('span[name="w1d1"]').data().date;
var last = $('span[name="w2d7"]').data().date;
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "list_jobv1", // action
start: format_date(first),
finish: format_date(last),
}, function(response, status, xhr){
if (response.status =='success'){
display_jobs_after_staff_client_tos_info_ready(response);
}else{
alert('error loading job');
hide_loading_jobs();
}
});
}
function display_jobs_after_staff_client_tos_info_ready(response)
{
var b = bts();
if ((typeof b.staff_map != "undefined" && Object.keys(b.staff_map).length > 0) &&
(typeof b.client_map != "undefined" && Object.keys(b.client_map).length > 0) &&
(typeof b.tos != "undefined" && Object.keys(b.tos).length > 0 ) &&
(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);
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, {jobs:jobs});
$('div.workspace').append(html);
hide_loading_jobs();
//filter it if reqired
debounced_filter_workspace();
return;
}
//console.log('wating staff/client/tos info to be ready');
setTimeout(function(){
display_jobs_after_staff_client_tos_info_ready(response);
}, 500); //try it half seconds later
}
function has_txt_hour(str){
if (str == null){
console.warn('null');
return;
}
var s = str.toLowerCase();
return s.indexOf('hour') != -1;
}
function show_jobs(jobs){
if (jobs.length >0){
var templ = $("#jobv1_item").html();
var html = Mustache.render(templ, {jobs:jobs}); //job id should be available;
var el = $(html);
$('div.workspace').append(el);
el.get(0).scrollIntoView();
}
debounced_calculate();
}
function format_date(date){
var dd = date.getDate();
var mm = date.getMonth() + 1; //January is 0!
var yyyy = date.getFullYear();
if (dd < 10) {
dd = '0' + dd;
}
if (mm < 10) {
mm = '0' + mm;
}
return yyyy + '-' + mm + '-' +dd ;
}
function format_date_time(date){
var strdate = format_date(date);
var hh = date.getHours();
if (hh<10){
hh= '0' + hh;
}
var mm = date.getMinutes();
if (mm<10){
mm='0' + mm;
}
return strdate + ' ' + hh + ":" + mm;
}
function clear_workspace()//clear all timesheet jobs
{
$('div.workspace > div.divTable').remove();
//clear datetime picker
$('div.xdsoft_datetimepicker').remove();
//
bts().job_map = {};
bts().job_map_new = {};
show_loading_jobs();
}
$('div.workinghours').click(function(){
$('div.bts_message_button').trigger('click');
});
function check_workspace_error(){
var els = $('div.workspace').find('.error');
if(els.length >0){
els.get(0).scrollIntoView();
return true;
}
return false;
}
$('button[name="confirmschedule"]').click(function(){
if( check_workspace_error() ){
return;
}
if (!confirm('sending email to each staff for their job arrangement?'))
return;
$('div.bts_message .ult-overlay-close-inside').hide();
$('div.bts_message_button').trigger('click');
setTimeout(do_email_jobs, 2000);//2 seconds for dialog to popup
});
$('button[name="confirmschedule"]').mouseenter(function(){
$('div.week2').addClass('blink_me');
})
$('button[name="confirmschedule"]').mouseleave(function(){
$('div.week2').removeClass('blink_me');
})
var debounced_filter_workspace = debounce(do_filter_workspace, 1000);
$(document).on('click','div.userlist', debounced_filter_workspace);
function do_filter_workspace(){
var staffs =[];
$('div.stafflist div.peopleitem :checked').each(function(i, e){
if ($(e).parent().is(':visible')){
var id = $(e).parent().attr('data-id');
//console.log("%o, id=%s", e, id);
staffs.push(id.substring(1));
}
});
var clients =[];
$('div.clientlist div.peopleitem :checked').each(function(i, e){
if ($(e).parent().is(':visible')){
var id = $(e).parent().attr('data-id');
//console.log("%o, id=%s", e, id);
clients.push(id.substring(1));
}
});
filter_workspace(staffs, clients);
filter_workspace_by_weeks();
debounced_calculate();
}
function filter_workspace(staffs, clients){
//if both array is empty
if( (staffs === undefined || staffs.length ==0) &&
(clients===undefined || clients.length ==0)){
//show all
$('div.workspace div.divTable').show();
return;
}
//if staffs is empty, we only filter by client
if (staffs === undefined || staffs.length ==0){
filter_workspace_by_client(clients);
return;
}
//if clients is empty, we only filter by staff
if (clients===undefined || clients.length ==0){
filter_workspace_by_staff(staffs);
return;
}
//filter by both
filter_workspace_by_both(staffs, clients);
}
function filter_workspace_by_staff(staffs)
{
var class_name='to_be_shown';
//filter some of them;
staffs.forEach(function(e){
$('div.workspace div.jobTable[data-staff="' + e + '"]').addClass(class_name);
$('div.workspace div.jobTable[data-driver="' + e + '"]').addClass(class_name);
});
$('div.workspace div.jobTable.' + class_name).fadeIn();
$('div.workspace div.jobTable:not(.'+ class_name +')').hide();
$('.' + class_name).removeClass(class_name);
}
function filter_workspace_by_client(clients)
{
var class_name='to_be_shown';
//filter some of them;
clients.forEach(function(e){
$('div.workspace div.jobTable[data-client="' + e + '"]').addClass(class_name);
});
$('div.workspace div.jobTable.' + class_name).fadeIn();
$('div.workspace div.jobTable:not(.'+ class_name +')').hide();
$('.' + class_name).removeClass(class_name);
}
function filter_workspace_by_both(staffs, clients)
{
var class_name='hide';
//filter some of them;
clients.forEach(function(e){
$('div.workspace div.jobTable:not([data-client="' + e + '"])').addClass(class_name);
});
staffs.forEach(function(e){
$('div.workspace div.jobTable:not([data-staff="' + e + '"])').addClass(class_name);
});
$('div.workspace div.jobTable.' + class_name).hide();
$('div.workspace div.jobTable:not(.'+ class_name +')').show();
$('.' + class_name).removeClass(class_name);
}
function filter_workspace_by_weeks(){
var hide_week1 = $('div.week1').hasClass('filtered');
var hide_week2 = $('div.week2').hasClass('filtered');
if (hide_week1 && hide_week2 ){
alert("You are hiding both weeks");
$('div.jobTable').show();//show non-week1 and none-week2
$('div.jobTable.week1job,div.jobTable.week2job').hide(); //hide week1 or week2;
}else if (hide_week1){
$('div.jobTable:not(.week1job)').show();//show non-week1
$('div.jobTable.week1job').hide(); //hide week1;
}else if (hide_week2){
$('div.jobTable.week2job').hide(); //show non-week2
$('div.jobTable:not(.week2job)').show(); //hide week2
}
}
var debounced_calculate = debounce(calculate_total_hour_and_money, 2000);
function calculate_total_hour_and_money()
{
//init pays for all staff;
var pays={
total: 0,
hours: 0,
};
$('.stafflist > div.peopleitem').each(function(i,e){
var people = $(this).data().obj;
people.reset_summary();
});
$('div.workspace > .divTable.jobTable:visible').each(function(i,e){
job = get_job_by_jobTable(e);
if (typeof job === 'undefined' || !job.is_job_valid() )
return;
if (is_dummy_driving(job.staff))
return;
var ps = job.get_payment_summary();
pays.total += ps.money;
pays.hours += ps.hour;
var staff = job.staff;
var people = bts().staff_people[staff]; //class People
if (people !=false)
people.add_payment_summary(ps);
});
set_wages(pays.total.toFixed(2));
set_working_hours(pays.hours.toFixed(2));
calculate_driving();
}
function is_dummy_driving(staff){
return staff == bts().driving;
}
function get_job_by_jobTable(div)
{
var e = div;
var id = $(e).attr('data-id');
var job = bts().job_map[id];
if (id == ''){
//is this a new Job without id?
var newjob_id = $(e).attr('data-newjob_id');
if ( typeof newjob_id != "undefined"){
id = newjob_id;
job = bts().job_map_new[newjob_id];
}
}
return job;
}
function calculate_driving(){
var id = bts().driving;
var kms={};
$('div.jobTable[data-staff="' + id + '"]:visible').each(function(){
var el = this;
var match = find_driving_partner_job(el);
if (match == false)
return;
var staff = $('#' + match).data().staff;
if (typeof kms[staff] =='undefined'){
kms[staff] = 0;
}
kms[staff] += convert_driving_to_km(el);
//console.log($(this).attr('id'), matches, kms);
});
//console.log(kms);
for (var staff in kms ){
bts().staff_people[staff].set_km(kms[staff]);
//console.log(bts().staff_map[staff]);
ensure_visible(bts().staff_people[staff].selector);
}
}
function find_driving_partner_job(selector){
if (typeof $(selector).attr('data-parent') != "undefined" && $(selector).attr('data-parent') !="")
return $(selector).attr('data-parent');
var job = $(selector).data();
var start = new Date(job.start);
var client = job.client;
var matches = [];
$('div.workspace > .divTable.jobTable:visible').each(function(i,e){
var match = $(this).data();
staff = match.staff;
s = new Date(match.start);
c = match.client;
if ((start - s == 0) && (client == c) && staff != bts().driving){
matches.push({parent:$(this).attr('id'), driver: staff});
}
});
if (matches.length != 1){
$(selector).find('.bstart').addClass("error");
$(selector).find(".bstart_err").html("No matching Job");
ensure_visible(selector);
console.warn("1 driving job has more than 1 matching", $(selector).attr('id'), matches);
return false;
}
$(selector).attr('data-driver', matches[0].driver);
$(selector).attr('data-parent', matches[0].parent);
$(selector).find('.bstaff').html("Driving/" + bts().staff_map[matches[0].driver].display_name);
return matches[0].parent;
}
function convert_driving_to_km(selector){
var data = $(selector).data();
var tos = data.tos;
var start = new Date(data.start);
var finish = new Date(data.finish);
var hours = Math.abs(finish - start) / 36e5; //in hours
var rate = parseFloat(bts().tos[tos].price);
var pay = hours * rate;
var km = pay / 1.2; //$1.2 per km
return km;
}
function find_staff(login)
{
var d = $('#p'+login).data();
if (typeof d === 'undefined')
return false;
return $('#p'+login).data().obj;
}
$(document).on('change', '.divTableRow input[name="ack"]', function(e) {
var el = $(this).closest('.jobTable');
var data = el.data();
data.ack = e.checked? 1: 0;
job_mark_dirty(el);
});
function job_mark_dirty(el)
{
el.removeClass('saved');
el.addClass('dirty');
}
function job_mark_clean(el)
{
el.addClass('saved');
el.removeClass('dirty');
}
function job_is_week1(t)
{
var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
var w1_end = new Date($('span[name="w1d7"]').data().date);
w1_begin.setHours(0,0,0,0);
w1_end.setHours(23,59,59);
//console.log("week1 begin %o, end %o", w1_begin, w1_end);
//w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
var me = new Date(t);
return (w1_begin <= me && me <= w1_end );
}
function job_is_week2(t)
{
var w2_begin = new Date($('span[name="w2d1"]').data().date);
var w2_end = new Date($('span[name="w2d7"]').data().date);
w2_begin.setHours(0,0,0,0);
w2_end.setHours(23,59,59);
var me = new Date(t);
return (w2_begin <= me && me <= w2_end );
}
function init_ts(){
xero(false);
wifi(false);
csv(false);
show_loading_jobs();
list_staff();
list_clients();
list_tos();
ajax_earning_rate();
//setTimeout(list_staff, 5000); // for testing delayed loading of jobs only
//setTimeout(list_clients, 8000); // for testing delayed loading of jobs only
//setTimeout(list_tos, 10000); // for testing delayed loading of jobs only
init_user_search();
reset_title_to_today();
load_timesheet();
}
function do_email_jobs()
{
var selector = 'div.bts_message div.ult_modal-body';
$(selector).html('Analysis staff jobs ... ok');
var staff = bts().staff.slice(0);//copy this array;
var s = staff.pop();
//week2 start
var w2_begin = new Date($('span[name="w2d1"]').data().date);
var w2_end = new Date($('span[name="w2d7"]').data().date);
var start = format_date(w2_begin);
var finish = format_date(w2_end);
function do_staff(){
var el = $(' Checking ' + s.firstname + "....
");
$(selector).append(el);
el[0].scrollIntoView();
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "email_job", // action
staff : s.login,
start : start,
finish: finish,
}, function(response, status, xhr){
if (response.status == 'success'){
if (response.sent){
el.append('' + response.emailstatus + '');
}else{
el.append('' + response.emailstatus + '');
}
}else{
el.append(' Error[' + response.error + ' ...]');
}
}).fail(function(){
el.append('' + 'Network Error occured' + '');
//clear staff pending list, stop further processing
s = [];
}).always(function(){//next staff
if (staff.length >0){
s = staff.pop();
setTimeout(do_staff, 100); //a short delay makes it looks nice
}else{
$('div.bts_message .ult-overlay-close-inside').show();
$('div.bts_message .ult-overlay-close-inside').addClass('blink_me');
$('div.week2').removeClass('blink_me');
$(selector).append('All staff confirmed! ');
}
});
}
//execute
do_staff();
}
function ajax_earning_rate(){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "earnings_rate", // action
}, function(response, status, xhr){
bts().earnings_rate = {};
response.options.forEach(function(e){
bts().earnings_rate[e.EarningsRateID]=e;
});
//console.log("%o", bts().earnings_rate);
});
}
function do_delete_duplicate()
{
var len = $('div.jobTable.to_be_deleted_duplicate').length;
if (len <= 0){
return;
}
if (!confirm("Delete " + len + " duplicates? ")){
return;
}
$('div.jobTable.to_be_deleted_duplicate:not(.saved)').each(function(){
remove_job_from_gui(this);
});
var ids = [];
$('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
var id = $(this).attr('data-id');
if (id != '')
ids.push(id);
});
if ( ids.length >0 ){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "delete_jobv1", // action
jobs: ids, //delete multiple ids
}, function(response, status, xhr){
if (response.status=='success'){
$('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
remove_job_from_gui(this);
});
debounced_calculate();
}else{
alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n");
}
});
}
}
function check_duplicate()
{
var to_be_deleted=[];
//make a copy of all jobs
var alljobs = {};
$('div.jobTable:visible').each(function(e){
alljobs[$(this).attr('id')] = $.extend({}, $(this).data());
});
//loop through jobs
for(var id1 in alljobs){
var job1 = alljobs[id1];
if (typeof job1.parent != 'undefined')
continue; //bypass it it has already found parents
job1.compared_as_master = true;//mark it
job1.duplicates={};
//console.log('investigating %s' , job1.id);
//match job2
for(var id2 in alljobs){
if (id1 == id2)
continue;
var job2 = alljobs[id2];
if (typeof job2.compared_as_master != 'undefined')
continue;
if (typeof job2.parent != "undefined")
continue; //it has already parent;
//console.log('comareing %s vs %s', job1.id, job2.id);
if (is_same_job(job1,job2)){
job2.parent = job1.id;
job1.duplicates[id2] = job2;
//console.warn("found: %s = %s", job1.id, job2.id);
to_be_deleted.push(id2);
}
}
}
if (to_be_deleted.length == 0){
alert("No duplicate found!");
return;
}
//console.log('all-done, found %d duplicates: %o', to_be_deleted.length, to_be_deleted);
mark_duplicate(to_be_deleted);
return to_be_deleted;
}
function is_same_job(job1, job2)
{
var s1 = new Date(job1.start);
var s2 = new Date(job2.start);
var f1 = new Date(job1.finish);
var f2 = new Date(job2.finish);
if ( (job1.tos == job2.tos) &&
(job1.staff == job2.staff) &&
(job1.client == job2.client) &&
(s1 - s2 == 0) &&
(f1 - f2 == 0) )
{
return true;
}
return false;
}
function mark_duplicate(ids)
{
ids.forEach(function(id){
var selector = '#' + id;
ensure_visible(selector);
$(selector).addClass('to_be_deleted_duplicate');
});
}
function do_edit_new_job(id)
{
open_modal('editor');
set_modal_title('editor', "Editing New Job ");
var new_job = bts().job_map_new[id];
new_job.editorid = id;
//set_modal_data('editor', {jobid: id, job_copy:job_copy});
var html = $('#jobv1_editor').html();
html = Mustache.render(html, new_job);
set_modal_content('editor', html);
//update GUI
dtp_init();
//init editor
var e = new JobEditor('#editor_' + id, new_job);
//console.log("e is instance of JobEditor %o", e instanceof JobEditor);
}
function do_edit_job(id)
{
//make a copy of the job
var child = bts().job_map[id];
if (! child.allow_edit()){
var msg =`You should not Edit this job,
it's locked by Xero,
Unless you insist to proceed
Are you sure you want edit it?
`;
if(!confirm(msg))
return;
}
var el = $("#job_" + id);
el.addClass('Editing');
open_modal('editor');
set_modal_title('editor', "Editing Job: " + id);
var job_copy = Object.assign(Object.create(Object.getPrototypeOf(child)), child); //a shallow copy only;
job_copy.editorid = bts_random_number();
//set_modal_data('editor', {jobid: id, job_copy:job_copy});
var html = $('#jobv1_editor').html();
html = Mustache.render(html, job_copy);
set_modal_content('editor', html);
//update GUI
dtp_init();
//init editor
var e = new JobEditor('#editor_' + job_copy.editorid, job_copy);
//console.log("e is instance of JobEditor %o", e instanceof JobEditor);
}
$( ".boundary_datepicker" ).datepicker();
$( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd");
$(document).on('click', 'div.clientlist div[name="title"] a', function(e){
e.preventDefault();
e.stopPropagation();
var id = $(this).closest('label.peopleitem').attr('data-id').substring(1);
var str = 'https://acaresydncy.com.au/feedback_card/' + id;
var name = $(this).html();
if ( confirm ("Email feedback link of : " + name + "\n\n\n" + str + "\n\n\n to helen@acaresydney.com.au?")){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "email_feedback_url", // action
client : id,
}, function(response, status, xhr){
//alert('please check your email on the phone and SMS the link to your client');
}).fail(function(){
alert('network error ');
});
}
});
init_ts();
$('div.divTableHeading div.bsave span.ticon-search').mouseenter(function(){//highlight unsaved
var el = $('div.jobTable.dirty .bsave');
if (el.length > 0){
el.addClass('blink_me');
el.get(0).scrollIntoView();
}
})
$('div.divTableHeading div.bsave span.ticon-search').mouseleave(function(){//highlight unsaved
var el = $('div.jobTable.dirty .bsave');
if (el.length > 0 ) {
el.removeClass('blink_me');
}
})
$(document).on('click', 'div.jobTable.to_be_deleted_duplicate div.bedit span.ticon-check-circle',function(){//highlight unsaved
var el = $(this).closest('div.jobTable');
if (!confirm ("Mark this is none duplicate?")){
return;
}
el.removeClass('to_be_deleted_duplicate');
});
$('div.divTableHeading div.bsave span.ticon-search').click(do_delete_unsaved_copy);
function do_delete_unsaved_copy()
{ //TODO: remove this search function
var num = $('div.jobTable.dirty').length;
if (num > 0){
if ( !confirm('delete all '+ num + ' unsaved?')){
return;
}
$('div.jobTable.dirty').each(function(){
var newjob_id = $(this).data().newjob_id;
delete bts().job_map_new[newjob_id]
$(this).remove();
})
}else{
alert("nothing to clean up");
}
}
/*________________________________________________________________________*/
});
})(jQuery);
/*______________scrolling______________________________________________*/
jQuery(document).ready(function(){
var timeoutid =0;
jQuery('button.peoplelist[name="down"]').mousedown(function(){
var button = this;
timeoutid = setInterval(function(){
//console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
}, 100);
}).on('mouseup mouseleave', function(){
clearTimeout(timeoutid);
});
jQuery('button.peoplelist[name="up"]').mousedown(function(){
var button = this;
timeoutid = setInterval(function(){
//console.log("up scrotop %d ",jQuery(button).parent().find(".userlist").get(0).scrollTop );
jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
}, 100);
}).on('mouseup mouseleave', function(){
clearTimeout(timeoutid);
});
});