diff --git a/Xero.php b/Xero.php index c519055..4e2afd0 100644 --- a/Xero.php +++ b/Xero.php @@ -99,7 +99,6 @@ class Xero { $msg= "Xero API resource not found rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n"; $this->logConsole($msg); } - } private function sync_clients(){ $contacts = $this->getClients($this->clientgroup); diff --git a/css/bts_office.css b/css/bts_office.css index d8e640c..058e340 100644 --- a/css/bts_office.css +++ b/css/bts_office.css @@ -942,6 +942,7 @@ div.bsave span.ticon-save { cursor: pointer; } +div.jobTable.to_be_deleted_duplicate div.bsave span.ticon-save, div.jobTable.saved div.bsave span.ticon-save { display: none; } @@ -964,8 +965,8 @@ div.jobTable.dirty div.bsave span{ animation-name: blinker; } -div.bdelete span.ticon-trash, -div.bedit span.ticon-edit { +div.workspace div.bdelete span.ticon-trash, +div.workspace div.bedit span.ticon-edit { display: inline-block; border: 3px solid lightgrey; padding: 5px; @@ -973,7 +974,7 @@ div.bedit span.ticon-edit { color: red; cursor: pointer; } -div.bedit span.ticon-edit{ +div.workspace div.bedit span.ticon-edit{ color:blue; } @@ -989,12 +990,26 @@ div.jobTable.to_be_deleted_duplicate span.ticon-trash { animation: blinker 0.3s linear infinite; } + div.jobTable.to_be_deleted_duplicate span.ticon-copy, div.jobTable.to_be_deleted_duplicate span.ticon-edit{ display:none !important; } +div.jobTable:not(.to_be_deleted_duplicate) span.ticon-check-circle{ + display:none !important; +} +div.jobTable.to_be_deleted_duplicate span.ticon-check-circle{ + display: inline-block; + border: 3px solid green; + padding: 5px; + border-radius: 10px; + color: green; + cursor: pointer; + font-size: 1.1em; +} -div.divTableHeading div > span:hover { +div.divTableHeading div > span.ticon-trash:hover, +div.divTableHeading div > span.ticon-search:hover { cursor: pointer; animation: blinker 0.3s linear infinite; } diff --git a/html/jobv1.html b/html/jobv1.html index b22caa4..e21d67a 100644 --- a/html/jobv1.html +++ b/html/jobv1.html @@ -1,7 +1,9 @@ {{#jobs}}
+ data-id="{{id}}" {{^id}} data-newjob_id="{{newjob_id}}" {{/id}} + data-tos="{{tos}}" data-rate="{{rate}}" data-staff="{{staff}}" data-client="{{client}}" + data-start="{{start}}" data-finish="{{finish}}">
@@ -15,6 +17,7 @@
{{rating}}
+
@@ -22,6 +25,7 @@
+
diff --git a/html/jobv1_editor.html b/html/jobv1_editor.html index 823f13e..0ae4859 100644 --- a/html/jobv1_editor.html +++ b/html/jobv1_editor.html @@ -41,7 +41,7 @@
- +
diff --git a/js/bts_office.js b/js/bts_office.js index c1e6cc1..7d18ac2 100644 --- a/js/bts_office.js +++ b/js/bts_office.js @@ -340,41 +340,8 @@ }); $(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){ - var len = $('div.jobTable.to_be_deleted_duplicate').length; - if (len <= 0){ - if (confirm("No duplicates to delete, trying to find duplicates?")){ - check_duplicate(); - } - return; - }else{ - if (!confirm("Delete " + len + " duplicates? ")) - return; - } - - var ids = []; - $('div.jobTable.to_be_deleted_duplicate').each(function(){ - var id = $(this).attr('data-id'); - ids.push(id); - }); - - $.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').each(function(){ - var id = $(this).attr('data-id'); - delete bts().job_map[id]; - $(this).get(0).scrollIntoView(); - fade_and_delete(this); - }); - debounced_calculate(); - }else{ - alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n"); - } - }); - + check_duplicate(); + setTimeout(do_delete_duplicate, 200);//200ms make GUI refresh }); function fade_and_delete(el) @@ -413,12 +380,30 @@ 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 == '') - el.remove(); - else{ + 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 @@ -427,22 +412,13 @@ jobid: id, }, function(response, status, xhr){ if (response.status=='success'){ - var id = el.attr('data-id'); - delete bts().job_map[id]; - //console.log("delete %s , job_map[%s]=%o ", id, id, bts().job_map[id]); - el.addClass('blink_me'); - el.fadeOut(900); - setTimeout(function(){ - el.remove(); - }, 900); - + remove_job_from_gui(el); }else{ - alert( 'error saving data, please check your network'); + alert( 'error deleting job, please check your network'); } }); } } - debounced_calculate(); }); $(document).on('mouseenter', 'div.divTableCell', function(){ @@ -453,9 +429,18 @@ }); $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){ - var table = $(this).closest('div.divTable'); - table.data().job.do_save_record(); + 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){ @@ -472,7 +457,31 @@ 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'); @@ -1272,26 +1281,17 @@ $(s).data(); } - - -// setTimeout(function(){ -// set_modal_title('warning', 'suck title'); -// set_modal_content('warning', 'fucking details'); -// //open_modal('warning'); -// }, 1000); -// -// setTimeout(function(){ -// set_modal_title('error', 'error title'); -// set_modal_content('error', 'error details'); -// //open_modal('error'); -// }, 5000); - - $(document).on('mouseenter', 'div.week1 div', function(){ - $(this).addClass('blink_me'); - get_week2_partner(this).addClass('blink_me'); - blink_same_date_by_div(this); + 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', function(){ + $(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(); @@ -1472,6 +1472,7 @@ $('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; @@ -1559,22 +1560,23 @@ var els=[]; unblink_all_date(); var first_into_view = false; //make sure first row in match is visible - $('div.bstart').each(function(i,e){ - if ( $(e).is(":visible") ){ - var value = $(e).html(); - if( -1 != value.indexOf(strDate) ) //found - { - if ( !first_into_view ){ //scroll to top - first_into_view = true; - $(e).get(0).scrollIntoView(); - } - els.push(e); - $(e).addClass('blink_me'); - } + $('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'); @@ -1705,6 +1707,9 @@ //clear datetime picker $('div.xdsoft_datetimepicker').remove(); // + bts().job_map = {}; + bts().job_map_new = {}; + show_loading_jobs(); } @@ -1721,7 +1726,7 @@ return false; } - $('button[name="confirmschedule"]').click(function(){//TODO: check error before confirm + $('button[name="confirmschedule"]').click(function(){ if( check_workspace_error() ){ return; } @@ -2034,13 +2039,58 @@ }); } + 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 bts().job_map){ - var job1 = bts().job_map[id1]; + for(var id1 in alljobs){ + var job1 = alljobs[id1]; if (typeof job1.parent != 'undefined') continue; //bypass it it has already found parents @@ -2048,8 +2098,12 @@ job1.duplicates={}; //console.log('investigating %s' , job1.id); //match job2 - for(var id2 in bts().job_map){ - var job2 = bts().job_map[id2]; + for(var id2 in alljobs){ + if (id1 == id2) + continue; + + var job2 = alljobs[id2]; + if (typeof job2.compared_as_master != 'undefined') continue; @@ -2068,17 +2122,21 @@ alert("No duplicate found!"); return; } - bts().to_be_deleted_duplicate = to_be_deleted; //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) && - (job1.start == job2.start) && - (job1.finish == job2.finish) ) + (s1 - s2 == 0) && + (f1 - f2 == 0) ) { return true; } @@ -2087,8 +2145,8 @@ function mark_duplicate(ids) { ids.forEach(function(id){ - var selector = '#job_' +id; - $(selector).get(0).scrollIntoView(); + var selector = '#' + id; + ensure_visible(selector); $(selector).addClass('to_be_deleted_duplicate'); }); } @@ -2155,12 +2213,44 @@ }); 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'); + } + }) - $('div.divTableHeading div.bsave span.ticon-search').click(do_test); - function do_test(){ //TODO: remove this search function - open_modal('editor'); - set_modal_title('editor', "title"); - set_modal_content('editor', $('div.workspace').html()); + $(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"); + } } /*________________________________________________________________________*/