timesheet source code
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

2288 lines
64KB

  1. (function ($) {
  2. $(function () {
  3. // http://davidwalsh.name/javascript-debounce-function
  4. function debounce(func, wait, immediate) {
  5. var timeout;
  6. return function () {
  7. var context = this, args = arguments;
  8. var later = function () {
  9. timeout = null;
  10. if (!immediate)
  11. func.apply(context, args);
  12. };
  13. var callNow = immediate && !timeout;
  14. clearTimeout(timeout);
  15. timeout = setTimeout(later, wait);
  16. if (callNow)
  17. func.apply(context, args);
  18. };
  19. };
  20. /*____________________________________________________________________________________*/
  21. class People{
  22. constructor(selector, template, data){
  23. this.selector = selector;
  24. this.data = data;
  25. this.template = template;
  26. // this.sample_people = {
  27. // login: '01515b52-6936-46b2-a000-9ad4cd7a5b50',
  28. // firstname: "first",
  29. // lastname: "last",
  30. // phone: '041122221',
  31. // email: 'abc@gmail.com',
  32. // pay: 0,
  33. // hour: 12,
  34. // OT: 3,
  35. // petrol: 50,
  36. // rating: 1,
  37. // };
  38. this.load_data(this.data);
  39. }
  40. load_data(data){
  41. var template = $(this.template).html();
  42. var html = Mustache.render(template, data);
  43. $(this.selector).html(html);
  44. //save it
  45. $(this.selector).data({obj:this, data:data});
  46. //draw rating star
  47. this.set_ratings(this.data.rating);
  48. this.set_unconfirmed_job(this.data.unconfirmedjob);
  49. }
  50. set_ratings(num){
  51. for (var i=1; i<= 5; i++){
  52. if (i <=num){
  53. $(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").addClass('checked');
  54. }else{
  55. $(this.selector + " div[name='rating'] span:nth-child(" +i+ ")").removeClass('checked');
  56. }
  57. }
  58. this.data.rating = num;
  59. }
  60. set_unconfirmed_job(num){
  61. if( num == 0 )
  62. $(this.selector + " span[name='badge']").hide();
  63. else
  64. $(this.selector + " span[name='badge']").show();
  65. this.data.unconfirmedjob = num;
  66. }
  67. reset_summary() {
  68. this.summary = {
  69. wages : 0,
  70. normal_hour : 0,
  71. ot_hour : 0,
  72. petrol : 0,
  73. };
  74. this.update_summary_in_gui();
  75. }
  76. add_payment_summary(ps)
  77. {
  78. //{ot: false, hour: "2.67", money: "76.90"}
  79. this.summary.wages += ps.money;
  80. if (! ps.ot )
  81. this.summary.normal_hour += ps.hour;
  82. else
  83. this.summary.ot_hour += ps.hour;
  84. this.update_summary_in_gui();
  85. }
  86. update_summary_in_gui()
  87. {
  88. var msg = '$' + this.summary.wages.toFixed(2);
  89. $(this.selector).find('div[name="wages"]').html(msg);
  90. msg = this.summary.normal_hour.toFixed(2) + '+' +this.summary.ot_hour.toFixed(2) + 'hr';
  91. $(this.selector).find('div[name="hours"]').html(msg);
  92. msg = 'petrol:' + this.summary.petrol.toFixed(2) + 'km';
  93. $(this.selector).find('div[name="petrol"]').html(msg);
  94. }
  95. }//end of class People
  96. function bts_staff_html(data){
  97. var template = $('#staff_item').html();
  98. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  99. r = head + '</div>' ;
  100. return r;
  101. }
  102. function bts_client_html(data){
  103. var template = $('#client_item').html();
  104. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  105. r = head + '</div>' ;
  106. return r;
  107. }
  108. function sample_staff(){
  109. for (var i=1; i<100; i++){
  110. var sample_people = {
  111. login: '01515b52-6936-46b2-a000-9ad4cd7a5b50' +i,
  112. firstname: "first"+i,
  113. lastname: "last",
  114. mobile: '041122221' +i,
  115. email: 'abc@gmail.com' + i,
  116. wages: 0,
  117. hour: i,
  118. OT: 3,
  119. petrol: 50 +i,
  120. rating: Math.floor(Math.random() * Math.floor(5)),
  121. unconfirmedjob: Math.floor(Math.random() * Math.floor(30)),
  122. };
  123. var html = bts_staff_html(sample_people);
  124. jQuery('div.stafflist').append(html);
  125. new People("#p" + sample_people.login, sample_people);
  126. }
  127. }
  128. function list_staff() {
  129. show_loading_staff();
  130. $('div.stafflist div.peopleitem').remove();
  131. $.post(bts().ajax_url, { // POST request
  132. _ajax_nonce: bts().nonce, // nonce
  133. action: "list_staff", // action
  134. }).done(function(response, status, xhr){
  135. if (response.status =='success'){
  136. bts().staff = response.users;
  137. bts().staff_map = {};
  138. bts().staff_people= {};
  139. response.users.forEach(function(u){
  140. bts().staff_map[u.login] = u;
  141. var html = bts_staff_html(u);
  142. jQuery('div.stafflist').append(html);
  143. var staff_obj = new People("#p" + u.login,'#staff_item', u);
  144. bts().staff_people[u.login] = staff_obj;
  145. });
  146. hide_loading_staff();
  147. calculate_total_hour_and_money();
  148. }else{
  149. alert('error getting staff list');
  150. }
  151. });
  152. }
  153. function list_clients() {
  154. show_loading_client();
  155. $('div.clientlist div.peopleitem').remove(); //clear it
  156. $.post(bts().ajax_url, { // POST request
  157. _ajax_nonce: bts().nonce, // nonce
  158. action: "list_client", // action
  159. }, function(response, status, xhr){
  160. if (response.status =='success'){
  161. bts().client = response.users;
  162. bts().client_map = {};
  163. response.users.forEach(function(u){
  164. bts().client_map[u.login] = u;
  165. hide_loading_client();
  166. var html = bts_client_html(u);
  167. jQuery('div.clientlist').append(html);
  168. new People("#p" + u.login, '#client_item' ,u);
  169. });
  170. }else{
  171. alert('error getting Client list');
  172. }
  173. });
  174. }
  175. function list_tos() {
  176. wifi(true);
  177. $.post(bts().ajax_url, { // POST request
  178. _ajax_nonce: bts().nonce, // nonce
  179. action: "list_tos", // action
  180. }, function(response, status, xhr){
  181. if (response.status =='success'){
  182. bts().tos = response.tos;
  183. wifi(false);
  184. }else{
  185. alert('error getting Type of Service list');
  186. }
  187. });
  188. }
  189. function show_loading_staff(){
  190. jQuery('div.stafflist img').attr('src', bts().load_user_img).show();
  191. }
  192. function show_loading_client(){
  193. jQuery('div.clientlist img').attr('src', bts().load_user_img).show();
  194. }
  195. function hide_loading_staff(){
  196. jQuery('div.stafflist img').hide();
  197. }
  198. function hide_loading_client(){
  199. jQuery('div.clientlist img').hide();
  200. }
  201. function show_loading_jobs(){
  202. jQuery('div.workspace img').attr('src', bts().load_job_img).show();
  203. }
  204. function hide_loading_jobs(){
  205. jQuery('div.workspace img').hide();
  206. }
  207. function xero(t){
  208. if (t)
  209. $('div.xero i').show();
  210. else
  211. $('div.xero i').hide();
  212. }
  213. function wifi(t){
  214. if (t)
  215. $('div.wifi i').show();
  216. else
  217. $('div.wifi i').hide();
  218. }
  219. function csv(t){
  220. if (t)
  221. $('div.csv i').show();
  222. else
  223. $('div.csv i').hide();
  224. }
  225. function init_user_search(){
  226. $('div.b_search input').keyup(debounce(function(e){
  227. filter_user(e.target);
  228. }, 500));
  229. }
  230. function filter_user(input){
  231. var value = $(input).attr('value');
  232. value = value.toLowerCase();
  233. var selector = get_selector_for_filter_people(input);
  234. $.each( $(selector).find('div.peopleitem'), function(index, e){
  235. //uncheck everyone
  236. $(e).find('input[type="checkbox"]').prop('checked', false);
  237. var html = $(e).find('div[name="title"] a').html();
  238. html = html.toLowerCase();
  239. if (-1 != html.indexOf(value)){//we find it;
  240. $(e).show();
  241. }else{
  242. $(e).hide();
  243. }
  244. });
  245. }
  246. function get_selector_for_filter_people(input){
  247. var selector='';
  248. var role = $(input).attr('placeholder');
  249. if (role == 'staff') //we filter staff
  250. selector = 'div.stafflist';
  251. else if (role = 'client')
  252. selector = 'div.clientlist';
  253. return selector;
  254. }
  255. $(document).on('click', 'div.jobTable div.brate.error', function(){
  256. var msg = $(this).attr('title');
  257. if (msg != "")
  258. alert(msg);
  259. });
  260. $(document).on('click', 'div.workspace div.bedit span.ticon-edit', function(){
  261. var el = $(this).closest('div.jobTable');
  262. el.addClass('Editing');
  263. var newjob_id = el.attr('data-newjob_id');
  264. if (typeof newjob_id != 'undefined' && newjob_id != ''){
  265. do_edit_new_job(newjob_id);
  266. }else{
  267. var id = el.attr('data-id');
  268. do_edit_job(id);
  269. }
  270. });
  271. // $(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
  272. // $('.Editing').addClass('blink_me');
  273. // setTimeout(function(){
  274. // $(".Editing").removeClass('Editing blink_me');
  275. // }, 1000);
  276. //
  277. // });
  278. $(document).on('click', 'div.bts_editor div.ult-overlay-close', function(){
  279. var el = $('.Editing');
  280. el.addClass("blink_me").removeClass('Editing');
  281. setTimeout(function(){
  282. el.removeClass('blink_me');
  283. }, 600);
  284. });
  285. function close_editor(){
  286. $('div.bts_editor div.ult-overlay-close').trigger('click');
  287. }
  288. $(document).on('jobEditor:close', 'div.divTable.Editor', function(e,data){
  289. var editor = data.editor;
  290. var job = data.job;
  291. //console.log('close_editor event %o, %o', editor, job);
  292. editor.off_event_handler();//remove all events handler;
  293. var templ = $("#jobv1_item").html();
  294. var html = Mustache.render(templ, {jobs:job}); //job id should be available;
  295. if ( $('div.jobTable.Editing').length == 0){
  296. $('div.workspace').append(html);
  297. $('#job_'+job.id).get(0).scrollIntoView();
  298. }else{
  299. //create new job underneath it
  300. var templ = $("#jobv1_item").html();
  301. var html = Mustache.render(templ, {jobs:job}); //job id should be available;
  302. $('div.jobTable.Editing').after(html).remove();
  303. }
  304. var el = $('#job_' + job.id).addClass("blink_me");
  305. setTimeout(function(){
  306. el.removeClass('blink_me');
  307. }, 1500);
  308. debounced_calculate();
  309. });
  310. $(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){
  311. check_duplicate();
  312. setTimeout(do_delete_duplicate, 200);//200ms make GUI refresh
  313. });
  314. function fade_and_delete(el)
  315. {
  316. $(el).fadeOut(300);
  317. setTimeout(function(){
  318. $(el).remove();
  319. },300);
  320. }
  321. function add_new_empty_job(job){
  322. var title = "";
  323. if (typeof job == 'undefined' || ! (job instanceof Job) ){
  324. job = new Job({empty:true});
  325. job.is_new = true;
  326. title = "Create New Job ";
  327. }else if (job instanceof Job){
  328. title = "Create new Job by Copy Existing one";
  329. }
  330. job.editorid = Math.floor(Math.random() * Math.floor(99999)); // a random number;
  331. //show editor
  332. var html = $('#jobv1_editor').html();
  333. html = Mustache.render(html, job);
  334. set_modal_content('editor', html);
  335. //update GUI
  336. open_modal('editor');
  337. set_modal_title('editor', title);
  338. dtp_init();
  339. //init editor
  340. var e = new JobEditor('#editor_' + job.editorid, job);
  341. console.log("%o is instance of JobEditor %o", e, e instanceof JobEditor);
  342. }
  343. $(document).on('click', 'div[name="copyschedule"]', function(e){
  344. e.stopPropagation();
  345. add_new_empty_job();
  346. });
  347. function remove_job_from_gui(el)
  348. {
  349. el = $(el);
  350. var newjob_id = el.data().newjob_id;
  351. var id = el.data().id;
  352. if (typeof newjob_id != 'undefined' && newjob_id !=''){
  353. delete bts().job_map_new[newjob_id];
  354. }else if (typeof id != 'undefined' && id != ''){
  355. delete bts().job_map[id];
  356. }
  357. el.addClass('blink_me');
  358. el.fadeOut(500);
  359. setTimeout(function(){
  360. el.remove();
  361. debounced_calculate();
  362. }, 500);
  363. }
  364. $(document).on('click', 'div.divTableCell.bdelete', function(){
  365. var el = $(this).closest('div.jobTable');
  366. var data = el.data();
  367. if (typeof data.id =='undefined' || data.id == ''){//not saved
  368. remove_job_from_gui(el);//remove direclty
  369. }else{
  370. var id = data.id;
  371. if (confirm('delete this job?')){
  372. $.post(bts().ajax_url, { // POST request
  373. _ajax_nonce: bts().nonce, // nonce
  374. action: "delete_job", // action
  375. jobid: id,
  376. }, function(response, status, xhr){
  377. if (response.status=='success'){
  378. remove_job_from_gui(el);
  379. }else{
  380. alert( 'error deleting job, please check your network');
  381. }
  382. });
  383. }
  384. }
  385. });
  386. $(document).on('mouseenter', 'div.divTableCell', function(){
  387. $(this).closest('div.divTable').addClass('highlight');
  388. });
  389. $(document).on('mouseleave', 'div.divTableCell', function(){
  390. $(this).closest('div.divTable').removeClass('highlight');
  391. });
  392. $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
  393. var table = $(this).closest('div.jobTable');
  394. var data = table.data();
  395. if (data.id == "" && data.newjob_id != "" && data.newjob_id.substring(0,4) == "new_" ){
  396. job = bts().job_map_new[data.newjob_id];
  397. console.assert(typeof job != 'undefined');
  398. do_save_new_job(job.get_record(), table);
  399. return;
  400. }
  401. alert("Error occured");
  402. $(this).fadeOut();
  403. });
  404. $(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
  405. //save all
  406. $('div.workspace span.ticon.ticon-save').each(function (i,e){
  407. if ($(this).is(":visible"))
  408. $(this).trigger('click');
  409. });
  410. });
  411. $(document).on('click', 'span.ticon.ticon-copy', function(){
  412. var table = $(this).closest('div.jobTable');
  413. var record = bts().job_map[table.data().id].get_record();
  414. //create new job;
  415. var newj = clone_data_create_new_job(record);
  416. add_new_empty_job(newj);
  417. });
  418. function do_save_new_job(job_tobe_saved, table){
  419. $.post(bts().ajax_url, { // POST request
  420. _ajax_nonce: bts().nonce, // nonce
  421. action: "save_job", // action
  422. record: job_tobe_saved,
  423. }, function(response, status, xhr){
  424. if (response.status=='success'){
  425. var job = new Job(response.newdata);
  426. job.saved = true;
  427. job.is_new = response.isNew;
  428. bts().job_map[job.id] = job;
  429. //delete that old job and display the new one
  430. var jobs=[job];
  431. var html = Mustache.render($('#jobv1_item').html(), {jobs:jobs});
  432. delete bts().job_map_new[table.data().newjob_id];
  433. table.after(html);
  434. table.remove();
  435. }else{
  436. console.error("error saving job %o, response=%o", job_tobe_saved, response);
  437. alert( 'error saving data, please check your network');
  438. }
  439. });
  440. }
  441. function mark_highlight_me(el, ms){
  442. el.addClass('blink_me');
  443. el.addClass('highlight');
  444. el.addClass('newcopy');
  445. setTimeout(function(){
  446. el.removeClass('blink_me');
  447. el.removeClass('highlight');
  448. el.removeClass('newcopy');
  449. },ms);
  450. }
  451. class Job{
  452. constructor(record)
  453. {
  454. this.original_attr = Object.keys(record);
  455. $.extend(this, record);
  456. this.derive_attr();
  457. }
  458. get_record()
  459. {//without extended attributes;
  460. var self = this;
  461. var r = {};
  462. this.original_attr.forEach(function(attr){
  463. r[attr] = self[attr];
  464. });
  465. return r;
  466. }
  467. derive_attr()
  468. {
  469. this.tos_name(this);
  470. this.staff_name(this);
  471. this.client_name(this);
  472. this.rate_name(this);
  473. this.rating_range(this);
  474. this.identify_job_week(this);
  475. this.job_acked(this);
  476. }
  477. tos_name(e){
  478. if (typeof bts().tos[e.tos] != 'undefined'){
  479. e.tos_name = bts().tos[e.tos].tos_full_str;
  480. }else{
  481. e.tos_name = "(deleted) ";
  482. e.tos_err="Missing: " + e.tos;
  483. }
  484. }
  485. staff_name(e){
  486. if (typeof bts().staff_map[e.staff] != 'undefined'){
  487. e.staff_name = bts().staff_map[e.staff].display_name;
  488. }else{
  489. e.staff_name = "(deleted) ";
  490. e.staff_err="Missing: " + e.staff;
  491. }
  492. }
  493. client_name(e){
  494. if (typeof bts().client_map[e.client] != 'undefined'){
  495. e.client_name = bts().client_map[e.client].display_name;
  496. }else{
  497. e.client_name ="(deleted)";
  498. e.client_err ="Missing: " + e.client;
  499. }
  500. }
  501. rate_name(e){
  502. if (typeof bts().earnings_rate[e.rate] != 'undefined') {
  503. e.rate_name = bts().earnings_rate[e.rate].RatePerUnit + "-" + bts().earnings_rate[e.rate].Name;
  504. if (! has_txt_hour( bts().earnings_rate[e.rate].TypeOfUnits )){
  505. e.rate_err = `Rate unit must be ⟦ Hours ⟧
  506. Possible solution:
  507. 1. Change it in Xero
  508. 2. Delete this job`;
  509. }
  510. }else{
  511. e.rate_name = "(deleted)";
  512. e.rate_err = "Missing: " + e.rate;
  513. }
  514. }
  515. rating_range(e){
  516. if (e.rating <0 || e.rating >5){
  517. e.rating_err = "Rating 0-5 Only";
  518. }
  519. }
  520. identify_job_week(e){
  521. if (job_is_week1(e.start)){
  522. e.is_week1=true;
  523. }else if (job_is_week2(e.start)){
  524. e.is_week2=true;
  525. }
  526. }
  527. job_acked(e){
  528. if (typeof e == 'undefined'){
  529. console.log('break;');
  530. }
  531. if (e.ack != 0){
  532. e.is_confirmed = true;
  533. }
  534. }
  535. is_job_valid(){
  536. var error_found = false;
  537. var self = this;
  538. this.original_attr.forEach(function(attr){
  539. var col = attr + "_err";
  540. if (typeof self[col] == 'undefined')
  541. return;
  542. if (self[attr+"_err"] != "")
  543. error_found = true;
  544. });
  545. return !error_found;
  546. }
  547. is_high_pay()
  548. {
  549. var job = this;
  550. var rate_info = bts().earnings_rate[job.rate];
  551. var keywords =bts().high_pay_keywords;
  552. var found = false;
  553. keywords.forEach(function(e){
  554. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  555. found = true;
  556. });
  557. return found;
  558. }
  559. get_payment_summary()
  560. {
  561. var job = this;
  562. var result ={};
  563. result.ot = job.is_high_pay();
  564. result.hour = job.get_working_duration();
  565. result.money = job.get_wages();
  566. return result;
  567. }
  568. get_working_duration()
  569. {
  570. var job = this;
  571. //finish - start
  572. var f = new Date(job.finish);
  573. var s = new Date(job.start);
  574. var diff = f.getTime() - s.getTime();
  575. var hours = Math.floor(diff / 1000 / 60 / 60);
  576. diff -= hours * 1000 * 60 * 60;
  577. var minutes = Math.floor(diff / 1000 / 60);
  578. var minute_to_hour = minutes/60;
  579. return (hours + minute_to_hour);
  580. }
  581. get_wages()
  582. {
  583. var job = this;
  584. var hour = job.get_working_duration(job);
  585. var rate_info = bts().earnings_rate[job.rate];
  586. if ( has_txt_hour( bts().earnings_rate[job.rate].TypeOfUnits ) )
  587. {
  588. return hour * rate_info.RatePerUnit;
  589. }else{
  590. return 1 * rate_info.RatePerUnit;
  591. }
  592. }
  593. };
  594. class JobEditor{ //save data for the record, and display it as GUI
  595. constructor(selector, job){
  596. this.el = $(selector);
  597. this.load_data(job);
  598. console.assert(job instanceof Job);
  599. this.init_event_handler();
  600. }
  601. load_data(data)
  602. {
  603. //save to html element
  604. this.data = data;
  605. this.el.data({job:this, data:data});
  606. //draw GUI
  607. this.clear_err_msg();
  608. this.set_job_id(data.id);
  609. this.set_tos(data.tos);
  610. this.set_start(data.start);
  611. this.set_finish(data.finish);
  612. this.set_rate(data.rate);
  613. this.set_staff(data.staff);
  614. this.set_client(data.client);
  615. this.set_ack(data.ack);
  616. this.set_rating(data.rating);
  617. //draw GUI by other
  618. this.mark_dirty_on_new_record(data);
  619. this.mark_week_color();
  620. //this.validate(); //also triggers mark errors
  621. }
  622. init_event_handler(){
  623. var self = this;
  624. this.el.find("div.btos select").change(function(){
  625. if ( self.validate_tos() ) {
  626. self.data.tos = self.get_tos();
  627. self.set_err_msg_tos('');
  628. }
  629. });
  630. this.el.find("div.bstart input").change(function(){
  631. if (self.validate_start()){
  632. self.data.start = self.get_start();
  633. self.set_err_msg_start('');
  634. self.validate_start_and_finish();
  635. }
  636. });
  637. this.el.find("div.bfinish input").change(function(){
  638. if (self.validate_finish()){
  639. self.data.finish = self.get_finish();
  640. self.set_err_msg_finish('');
  641. self.validate_start_and_finish();
  642. }
  643. });
  644. this.el.find("div.bstaff select").change(function(){
  645. if (self.validate_staff()){
  646. self.data.staff = self.get_staff();
  647. self.set_err_msg_staff('');
  648. }
  649. });
  650. this.el.find("div.bclient select").change(function(){
  651. if (self.validate_client()){
  652. self.data.client = self.get_client();
  653. self.set_err_msg_client('');
  654. }
  655. });
  656. this.el.find("div.brate select").change(function(){
  657. if (self.validate_rate()){
  658. self.data.rate = self.get_rate();
  659. self.set_err_msg_rate('');
  660. }
  661. });
  662. this.el.find("div.bconfirmed input").change(function(){
  663. if(self.validate_ack()){
  664. self.data.ack = self.get_ack();
  665. }
  666. });
  667. this.el.find("div.brating select").change(function(){
  668. if( self.validate_rating()){
  669. self.data.rating =self.get_rating();
  670. }
  671. });
  672. this.el.find("div.bsave span.ticon-save").click(function(e){
  673. if ( self.validate() ){
  674. self.do_save_record();
  675. }else{
  676. self.set_err_msg_save('Data Error');
  677. }
  678. });
  679. }
  680. off_event_handler(){
  681. this.el.off('change',"div.btos select");
  682. this.el.off('change',"div.bstart input");
  683. this.el.off('change',"div.bfinish input");
  684. this.el.off('change',"div.bstaff select");
  685. this.el.off('change',"div.bclient select");
  686. this.el.off('change',"div.brate select");
  687. this.el.off('change',"div.bconfirmed input");
  688. this.el.off('change',"div.brating select");
  689. this.el.off('change',"div.bsave span.ticon-save");
  690. }
  691. get_job_id(){
  692. return this.el.attr('data-id');
  693. }
  694. set_job_id(val){
  695. if (typeof val == 'undefined' || val == '')
  696. {//remove data-id and id
  697. this.el.removeAttr('data-id');
  698. this.el.removeAttr('id');
  699. }else{
  700. this.el.attr('data-id', val);
  701. this.el.attr('id', 'job_' + val);
  702. }
  703. }
  704. get_tos()
  705. {
  706. return this.el.find('div.btos select').children("option:selected").val();
  707. }
  708. set_tos(val)
  709. {
  710. if (typeof(val) =="undefined")
  711. return;
  712. this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
  713. if ( this.get_tos() != val ){
  714. this.set_err_msg_tos("Missing:" + this.data.tos)
  715. var o = new Option("(deleted)", this.data.tos);
  716. this.el.find('div.btos select').prepend(o);
  717. this.el.find('div.btos select option[value="'+this.data.tos+'"]').prop('selected',true);
  718. }
  719. }
  720. get_start(){
  721. return this.el.find('div.bstart input').attr('value');
  722. }
  723. set_start(val)
  724. {
  725. if (typeof(val) =="undefined"){
  726. this.set_err_msg_start("need start");
  727. return;
  728. }
  729. this.el.find('div.bstart input').attr('value', val);
  730. }
  731. get_finish()
  732. {
  733. return this.el.find('div.bfinish input').attr('value');
  734. }
  735. set_finish(val)
  736. {
  737. if (typeof(val) == "undefined"){
  738. this.set_err_msg_finish("need finish");
  739. return;
  740. }
  741. this.el.find('div.bfinish input').attr('value', val);
  742. }
  743. get_rate()
  744. {
  745. return this.el.find('div.brate select').children("option:selected").val();
  746. }
  747. set_rate(val)
  748. {
  749. if (typeof(val) =="undefined")
  750. return;
  751. this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
  752. if ( this.get_rate() != val ){
  753. var o = new Option("(deleted)", this.data.rate);
  754. this.el.find('div.brate select').prepend(o);
  755. this.set_err_msg_rate('Missing:' + this.data.rate);
  756. this.el.find('div.brate select option[value="'+this.data.rate+'"]').prop('selected',true);
  757. }
  758. }
  759. get_staff()
  760. {
  761. return this.el.find('div.bstaff select').children("option:selected").val();
  762. }
  763. set_staff(val)
  764. {
  765. if (typeof(val) =="undefined")
  766. return;
  767. this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
  768. if ( this.get_staff() != val ){
  769. var o = new Option("(deleted)", this.data.staff);
  770. this.el.find('div.bstaff select').prepend(o);
  771. this.set_err_msg_staff('Missing:' + this.data.staff);
  772. this.el.find('div.bstaff select option[value="'+this.data.staff+'"]').prop('selected',true);
  773. }
  774. }
  775. get_client()
  776. {
  777. return this.el.find('div.bclient select').children("option:selected").val();
  778. }
  779. set_client(val)
  780. {
  781. if (typeof(val) =="undefined")
  782. return;
  783. this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
  784. if ( this.get_client() != val ){
  785. var o = new Option("(deleted)", this.data.client);
  786. this.el.find('div.bclient select').prepend(o);
  787. this.set_err_msg_client('Missing:' + this.data.client);
  788. this.el.find('div.bclient select option[value="'+this.data.client+'"]').prop('selected',true);
  789. }
  790. }
  791. get_ack()
  792. {
  793. return this.el.find('div.bconfirmed input:checked').length > 0? 1:0;
  794. }
  795. set_ack(val)
  796. {
  797. if (typeof(val) =="undefined")
  798. return;
  799. return this.el.find('div.bconfirmed input').prop('checked', val!=0);
  800. }
  801. get_rating(){
  802. return this.el.find('div.brating select').children("option:selected").val();
  803. }
  804. set_rating(num){
  805. if (!(0 <= num && num <=5))
  806. return;
  807. this.el.find('div.brating select option[value="'+num+'"]').prop('selected',true);
  808. }
  809. get_record_from_ui(){
  810. var record = {};
  811. record.id = this.get_job_id();
  812. record.tos = this.get_tos();
  813. record.start = this.get_start();
  814. record.finish = this.get_finish();
  815. record.rate = this.get_rate();
  816. record.staff = this.get_staff();
  817. record.client = this.get_client();
  818. record.ack = this.get_ack();
  819. record.rating = this.get_rating();
  820. return record;
  821. }
  822. do_save_record(){
  823. var self = this;
  824. $.post(bts().ajax_url, { // POST request
  825. _ajax_nonce: bts().nonce, // nonce
  826. action: "save_job", // action
  827. record: self.get_record_from_ui(),
  828. }, function(response, status, xhr){
  829. if (response.status=='success'){
  830. self.load_data(response.newdata);
  831. var job = new Job(response.newdata);
  832. job.saved = true;
  833. job.is_new = response.isNew;
  834. bts().job_map[job.id] = job;
  835. var data = {editor:self, job:job};
  836. self.el.trigger('jobEditor:close', data);
  837. close_editor();
  838. }else{
  839. self.self.set_err_msg_save('Not saved');
  840. alert( 'error saving data, please check your network');
  841. }
  842. });
  843. }
  844. mark_dirty_on_new_record(data){
  845. if (typeof(data.id) === 'undefined' || data.id == ''){
  846. this.mark_dirty();
  847. this.mark_new();
  848. }
  849. else{
  850. this.mark_saved();
  851. }
  852. }
  853. mark_dirty() //need save
  854. {
  855. var d = this.el.find('.bsave');
  856. d.removeClass('saved');
  857. d.addClass('blink_me');
  858. setTimeout(function(){
  859. d.removeClass('blink_me');
  860. d.removeClass('saved');
  861. },1000);
  862. }
  863. mark_saved()
  864. {
  865. var d = this.el.find('.bsave');
  866. d.addClass('blink_me');
  867. setTimeout(function(){
  868. d.removeClass('blink_me');
  869. d.addClass('saved');
  870. },1000);
  871. }
  872. //newly created empty record
  873. mark_new()
  874. {
  875. this.el.addClass('emptyrecord');
  876. }
  877. mark_old()
  878. {
  879. this.el.removeClass('emptyrecord');
  880. }
  881. is_start_valid(){
  882. var s = this.get_start();
  883. return is_valid_date_str(s);
  884. }
  885. is_finish_valid(){
  886. var f = this.get_finish();
  887. if (!is_valid_date_str(f))
  888. return false;
  889. }
  890. is_finish_resonable(){
  891. var f = this.get_finish();
  892. if (!is_valid_date_str(f))
  893. return false;
  894. var s = this.get_start();
  895. s = new Date(s);
  896. f = new Date(f);
  897. return (s < f);
  898. }
  899. validate()
  900. {
  901. var ok_tos = this.validate_tos();
  902. var ok_time = this.validate_start() && this.validate_finish() && this.validate_start_and_finish();
  903. var ok_staff = this.validate_staff();
  904. var ok_client = this.validate_client();
  905. var ok_rate = this.validate_rate() ; //make sure this validate is executed
  906. var ok = ok_tos && ok_time && ok_staff && ok_client && ok_rate;
  907. if (ok){
  908. this.el.removeClass('invalidjob');
  909. }else{
  910. this.el.addClass('invalidjob');
  911. }
  912. return ok;
  913. }
  914. validate_start(){
  915. var str = this.get_start();
  916. if (str == ""){
  917. this.set_err_msg_start('need start');
  918. return false;
  919. }
  920. if (!is_valid_date_str(str) ){
  921. this.mark_start_invalid();
  922. this.set_err_msg_start('wrong date');
  923. return false;
  924. }
  925. return true;
  926. }
  927. validate_finish()
  928. {
  929. var str = this.get_finish();
  930. if (str == ""){
  931. this.set_err_msg_finish('need finish');
  932. return false;
  933. }
  934. if (! is_valid_date_str(str)){
  935. this.set_err_msg_finish('wrong date');
  936. this.mark_finish_invalid();
  937. return false;
  938. }
  939. return true;
  940. }
  941. validate_start_and_finish()
  942. {
  943. if (! this.validate_start() || ! this.validate_finish())
  944. return;
  945. if (!this.is_finish_resonable()){
  946. this.set_err_msg_finish("older than start");
  947. this.set_err_msg_start("after finish");
  948. this.mark_start_invalid();
  949. this.mark_finish_invalid();
  950. return false;
  951. }else{
  952. this.mark_start_valid();
  953. this.mark_finish_valid();
  954. this.set_err_msg_finish("");
  955. this.set_err_msg_start("");
  956. return true;
  957. }
  958. }
  959. validate_rate()
  960. {
  961. var rate_info = this.get_rate_info_by_id(this.get_rate());
  962. if ( rate_info.RatePerUnit <= 0){
  963. this.set_err_msg_rate('bad rate');
  964. this.mark_rate_invalid();
  965. return false;
  966. }
  967. this.set_err_msg_rate('');
  968. this.mark_rate_valid();
  969. return true;
  970. }
  971. validate_tos(){
  972. if ( typeof bts().tos[this.get_tos()] == 'undefined') {
  973. this.set_err_msg_tos('missing ' + this.get_tos());
  974. return false;
  975. }else{
  976. this.set_err_msg_tos('');
  977. return true;
  978. }
  979. }
  980. validate_staff(){
  981. if ( typeof bts().staff_map[this.get_staff()] == 'undefined') {
  982. this.set_err_msg_staff('missing ' + this.get_staff());
  983. return false;
  984. }else{
  985. this.set_err_msg_staff('');
  986. return true;
  987. }
  988. }
  989. validate_client(){
  990. if ( typeof bts().client_map[this.get_client()] == 'undefined') {
  991. this.set_err_msg_client('missing ' + this.get_client());
  992. return false;
  993. }else{
  994. this.set_err_msg_client('');
  995. return true;
  996. }
  997. }
  998. validate_rating(){
  999. return;
  1000. }
  1001. clear_err_msg(){
  1002. this.el.find('.divTableRow.errmsg > div').html('');
  1003. }
  1004. set_err_msg_start(str)
  1005. {
  1006. this.el.find('div.bstart_err').html(str);
  1007. }
  1008. set_err_msg_finish(str)
  1009. {
  1010. this.el.find('div.bfinish_err').html(str);
  1011. }
  1012. set_err_msg_staff(str)
  1013. {
  1014. this.el.find('div.bstaff_err').html(str);
  1015. }
  1016. set_err_msg_client(str)
  1017. {
  1018. this.el.find('div.bclient_err').html(str);
  1019. }
  1020. set_err_msg_rate(str)
  1021. {
  1022. this.el.find('div.brate_err').html(str);
  1023. }
  1024. set_err_msg_save(str)
  1025. {
  1026. this.el.find('div.bsave_err').html(str);
  1027. }
  1028. set_err_msg_tos(str)
  1029. {
  1030. this.el.find('div.btos_err').html(str);
  1031. }
  1032. mark_tos_valid(){
  1033. this.el.find('div.btos select').removeClass('invalid');
  1034. }
  1035. mark_tos_invalid(){
  1036. this.el.find('div.btos select').addClass('invalid');
  1037. }
  1038. mark_start_valid(){
  1039. this.el.find('div.bstart input').removeClass('invalid');
  1040. }
  1041. mark_start_invalid(){
  1042. this.el.find('div.bstart input').addClass('invalid');
  1043. }
  1044. mark_finish_valid(){
  1045. this.el.find('div.bfinish input').removeClass('invalid');
  1046. }
  1047. mark_finish_invalid(){
  1048. this.el.find('div.bfinish input').addClass('invalid');
  1049. }
  1050. mark_rate_valid(){
  1051. this.el.find('div.brate select').removeClass('invalid');
  1052. }
  1053. mark_rate_invalid(){
  1054. this.el.find('div.brate select').addClass('invalid');
  1055. }
  1056. mark_week_color(){
  1057. this.el.find('div.brating').removeClass('week1color');
  1058. this.el.find('div.brating').removeClass('week2color');
  1059. if (this.is_week1()){
  1060. this.el.find('div.brating').addClass('week1color');
  1061. }else if (this.is_week2()){
  1062. this.el.find('div.brating').addClass('week2color');
  1063. }
  1064. }
  1065. is_week1()
  1066. {
  1067. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  1068. var w1_end = new Date($('span[name="w1d7"]').data().date);
  1069. w1_begin.setHours(0,0,0,0);
  1070. w1_end.setHours(23,59,59);
  1071. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  1072. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  1073. var me = new Date(this.data.start);
  1074. return (w1_begin <= me && me <= w1_end );
  1075. }
  1076. is_week2()
  1077. {
  1078. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1079. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1080. w2_begin.setHours(0,0,0,0);
  1081. w2_end.setHours(23,59,59);
  1082. var me = new Date(this.data.start);
  1083. return (w2_begin <= me && me <= w2_end );
  1084. }
  1085. get_payment_summary(){
  1086. var result ={};
  1087. result.ot = this.get_is_high_pay();
  1088. result.hour = this.get_working_duration();
  1089. result.money = this.get_wages();
  1090. return result;
  1091. }
  1092. get_is_high_pay()
  1093. {
  1094. var rate_info = this.get_rate_info_by_id(this.get_rate());
  1095. return this.is_high_pay_hour(rate_info);
  1096. }
  1097. get_working_duration()
  1098. {
  1099. //finish - start
  1100. var f = new Date(this.get_finish());
  1101. var s = new Date(this.get_start());
  1102. var diff = f.getTime() - s.getTime();
  1103. var hours = Math.floor(diff / 1000 / 60 / 60);
  1104. diff -= hours * 1000 * 60 * 60;
  1105. var minutes = Math.floor(diff / 1000 / 60);
  1106. var minute_to_hour = minutes/60;
  1107. return (hours + minute_to_hour);
  1108. }
  1109. get_wages(){
  1110. var hour = this.get_working_duration();
  1111. var rate_info = this.get_rate_info_by_id(this.get_rate());
  1112. return hour * rate_info.RatePerUnit;
  1113. }
  1114. get_rate_info_by_id(id){
  1115. var rate_info = {};
  1116. var rates = bts().earnings_rate;
  1117. for(var i =0; i< rates.length; i++){
  1118. var r = rates[i];
  1119. if(r.EarningsRateID == id){
  1120. rate_info = $.extend(true,{}, r);//make a copy
  1121. break;
  1122. }
  1123. }
  1124. return rate_info;
  1125. }
  1126. is_high_pay_hour(rate_info){
  1127. var keywords =bts().high_pay_keywords;
  1128. var found = false;
  1129. return false;
  1130. keywords.forEach(function(e){
  1131. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  1132. found = true;
  1133. });
  1134. return found;
  1135. }
  1136. }//end of class Job
  1137. //global GUI summary
  1138. function get_wages()
  1139. {
  1140. var txt = $('div.wages div').html();
  1141. return parseInt(txt);
  1142. }
  1143. function set_wages(num){
  1144. $('div.wages div').html(num);
  1145. }
  1146. function set_working_hours(num){
  1147. $('input#woh').attr('value', num);
  1148. }
  1149. function get_working_hours(){
  1150. var txt = $('input#woh').attr('value');
  1151. return parseFloat(txt);
  1152. }
  1153. //modal box
  1154. function set_modal_title(selector, title){
  1155. var s = 'div.bts_'+ selector +' .ult_modal-title';
  1156. $(s).html(title);
  1157. }
  1158. function set_modal_content(selector, content){
  1159. var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
  1160. $(s).html(content);
  1161. }
  1162. function open_modal (selector){
  1163. var s='div.bts_'+selector+'_button';
  1164. $(s).trigger('click');
  1165. }
  1166. function set_modal_data(selector, data){
  1167. var s = 'div.bts_'+ selector;
  1168. $(s).data(data);
  1169. }
  1170. function get_modal_data(selector, data){
  1171. var s = 'div.bts_'+ selector;
  1172. $(s).data();
  1173. }
  1174. var blink_by_date_timer;
  1175. $(document).on('mouseenter', 'div.week1 > div, div.week2 > div', function(){
  1176. var self = this;
  1177. blink_by_date_timer = setTimeout (function(){
  1178. $(self).addClass('blink_me');
  1179. get_week2_partner(self).addClass('blink_me');
  1180. blink_same_date_by_div(self);
  1181. }, 1500);
  1182. });
  1183. $(document).on('mouseleave', 'div.week1 div, div.week2 > div', function(){
  1184. clearTimeout(blink_by_date_timer);
  1185. $(this).removeClass('blink_me');
  1186. get_week2_partner(this).removeClass('blink_me');
  1187. unblink_all_date();
  1188. });
  1189. function get_week2_partner(div){
  1190. var index = $(div).index()+1;
  1191. return $('div.week2 div:nth-child('+index+')');
  1192. }
  1193. function init_weekdays(){
  1194. var curr = new Date; // get current date
  1195. init_weekdays_by_anchor(curr, true);
  1196. return;
  1197. }
  1198. function init_weekdays_by_anchor(anchor, is_week1){
  1199. var curr = new Date(anchor); // get the date;
  1200. if (!is_week1){ //it is week2, shift for 7 days;
  1201. curr.setDate(curr.getDate() -7); //curr will be changed;
  1202. }
  1203. var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
  1204. var last = first + 6; // last day is the first day + 6
  1205. if (curr.getDay() == 0 ){// it's Sunday;
  1206. last = curr.getDate();
  1207. first = last - 6;
  1208. }
  1209. var pos = 1; //first lot
  1210. for (var i=first; i<=last; i++)
  1211. {
  1212. var now = new Date(curr);
  1213. var d1 = new Date(now.setDate(i));
  1214. now = new Date(curr);
  1215. var d2 = new Date(now.setDate(i+7));
  1216. set_day_number(1,pos, d1); //week 1
  1217. set_day_number(2,pos, d2); //week 2
  1218. pos +=1;
  1219. }
  1220. }
  1221. function set_day_number(week, index, date){
  1222. var selector = 'span[name="w'+week+'d'+index+'"]';
  1223. $(selector).html(date.getDate());
  1224. $(selector).data({date:date});
  1225. //console.log('set w%d-d%d %o', week,index,date);
  1226. }
  1227. function set_today(){
  1228. var selector = 'div.sheettitle span[name="today"]';
  1229. var curr = new Date;
  1230. $(selector).html(format_date(curr));
  1231. }
  1232. Date.prototype.get_week_number = function(){
  1233. var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
  1234. var dayNum = d.getUTCDay() || 7;
  1235. d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  1236. var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
  1237. return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
  1238. };
  1239. function set_week_number(){
  1240. var date = $('span[name="w1d1"]').data().date;
  1241. //console.log("date %o", date);
  1242. var num = date.get_week_number();
  1243. $('div.weekly span[name="week1"]').html(num);
  1244. $('div.weekly span[name="week2"]').html(num+1);
  1245. set_week_boundry();
  1246. }
  1247. function set_week_boundry()
  1248. {
  1249. var date = $('span[name="w1d1"]').data().date;
  1250. $('#week1b').attr('value', format_date(date));
  1251. var date = $('span[name="w2d7"]').data().date;
  1252. $('#week2b').attr('value', format_date(date));
  1253. }
  1254. function number_of_unsaved_job(){
  1255. var count =0;
  1256. var total_job = $('div.jobTable').length;
  1257. var total_saved = $('div.jobTable.saved').length;
  1258. var empty = $('div.emptyrecord').length;
  1259. count = total_job - total_saved - empty;
  1260. return count;
  1261. }
  1262. $('div.prevweek.left').click(function(){
  1263. if (number_of_unsaved_job() > 0){
  1264. if(!confirm("you have unsaved jobs, proceed will lost them"))
  1265. return;
  1266. }
  1267. $('div.weekdays span.weekday').each(function(i, e){
  1268. var date = $(e).data().date;
  1269. var newdate = new Date(date.setDate(date.getDate() -7 ));
  1270. $(e).html(newdate.getDate());
  1271. $(e).data({data:newdate});
  1272. });
  1273. set_week_number();
  1274. debounced_load_timesheet();
  1275. });
  1276. $('div.nextweek.right').click(function(){
  1277. if (number_of_unsaved_job() > 0){
  1278. if(!confirm("you have unsaved jobs,proceed will lost them"))
  1279. return;
  1280. }
  1281. $('div.weekdays span.weekday').each(function(i, e){
  1282. var date = $(e).data().date;
  1283. var newdate = new Date(date.setDate(date.getDate() +7 ));
  1284. $(e).html(newdate.getDate());
  1285. $(e).data({data:newdate});
  1286. });
  1287. set_week_number();
  1288. debounced_load_timesheet();
  1289. });
  1290. $('div.weekly div.weekname.prev >input ').click(function(e){
  1291. e.stopPropagation();
  1292. });
  1293. $('div.weekly div.weekname.prev >input ').change(function(e){
  1294. var date = $('#week1b').attr('value');
  1295. init_weekdays_by_anchor(date, true);
  1296. set_week_number();
  1297. debounced_load_timesheet();
  1298. });
  1299. $('div.weekly div.weekname.prev').click(function(){
  1300. if (!confirm ('copy entire week to next week?'))
  1301. return;
  1302. var jobs = [];
  1303. $('div.week1 >div').each(function(i,e){
  1304. var date = new Date($(e).find('span.weekday').data().date);
  1305. var strDate = format_date(date); //yyyy-mm-dd
  1306. $('div.bstart:visible').each(function(i,e){
  1307. var value = $(e).html();
  1308. if( -1 != value.indexOf(strDate) ) //found
  1309. {
  1310. var el = $(e).closest('div.jobTable');
  1311. if (el.is(":visible") && el.hasClass('saved')){
  1312. var id = el.data().id;
  1313. var j = bts().job_map[id];
  1314. var newj = clone_data_create_new_job(j.get_record(),7);//add 7 days
  1315. add_new_job_to_map(newj);
  1316. jobs.push(newj);
  1317. }
  1318. }
  1319. });
  1320. });
  1321. show_jobs(jobs);
  1322. debounced_calculate();
  1323. });
  1324. $('div.weekly div.weekname.next > input').click(function(e){
  1325. e.stopPropagation();
  1326. });
  1327. $('div.weekly div.weekname.next >input ').change(function(e){
  1328. e.stopPropagation();
  1329. var date = $('#week2b').attr('value');
  1330. init_weekdays_by_anchor(date, false);
  1331. set_week_number();
  1332. debounced_load_timesheet();
  1333. });
  1334. $('div.weekly div.weekname.next').click(function(e){
  1335. $(e).find('input').trigger('click');
  1336. });
  1337. $('div.week1 > div').click(function(e){
  1338. e.stopPropagation();
  1339. blink_same_date_by_div(this);
  1340. if ($('div.bstart.blink_me').length == 0){
  1341. alert("nothing to copy");
  1342. return;
  1343. }
  1344. if (!confirm ('copy to next week'))
  1345. return;
  1346. var new_jobs = [];
  1347. $('div.bstart.blink_me').each(function(i,e){
  1348. var r = copy_single_day_to_next_week(e);
  1349. if (r != false){
  1350. add_new_job_to_map(r);
  1351. new_jobs.push(r);
  1352. }
  1353. });
  1354. show_jobs(new_jobs);
  1355. unblink_all_date();
  1356. });
  1357. $('div.week1,div.week2').click(function(e){
  1358. e.stopPropagation();
  1359. $(this).toggleClass('filtered');
  1360. do_filter_workspace();
  1361. });
  1362. function copy_single_day_to_next_week(el){
  1363. var tb = $(el).closest('div.jobTable');
  1364. if (tb.is(':visible') && tb.hasClass('saved')){
  1365. var id = tb.data().id;
  1366. var j = bts().job_map[id];
  1367. var newj = clone_data_create_new_job(j.get_record() , 7); // +7 days
  1368. return newj;
  1369. }
  1370. return false;
  1371. }
  1372. function clone_data_create_new_job(val, num_of_shifted_days){
  1373. var data = $.extend(true, {}, val);//make a copy
  1374. num_of_shifted_days = typeof num_of_shifted_days !=='undefined'? num_of_shifted_days: 0;// 0 days
  1375. //reset
  1376. data.id='';
  1377. data.ack = 0;
  1378. data.rating = 0;
  1379. if (is_valid_date_str(data.start)){
  1380. var s = new Date(data.start);
  1381. var s1 = s.getDate() + num_of_shifted_days;
  1382. s = new Date(s.setDate(s1));
  1383. data.start = format_date_time(s);
  1384. }
  1385. if (is_valid_date_str(data.finish)){
  1386. var f = new Date(data.finish);
  1387. var f1 = f.getDate() + num_of_shifted_days;
  1388. f = new Date(f.setDate(f1));
  1389. data.finish = format_date_time(f);
  1390. }
  1391. var newj = new Job(data);
  1392. //return;
  1393. return newj;
  1394. }
  1395. function add_new_job_to_map(newj)
  1396. {
  1397. //add to job map
  1398. newj.newjob_id = "new_" + bts_unique_ID();
  1399. if (typeof bts().job_map_new == 'undefined'){
  1400. bts().job_map_new = [];
  1401. }
  1402. bts().job_map_new[newj.newjob_id] = newj;
  1403. }
  1404. function is_valid_date_str(val){
  1405. var d = new Date(val);
  1406. if (d.toString()== 'Invalid Date')
  1407. return false;
  1408. return true;
  1409. }
  1410. function blink_same_date_by_div(div){
  1411. var date = new Date($(div).find('span.weekday').data().date);
  1412. blink_same_date(date);
  1413. }
  1414. function blink_same_date(date){
  1415. var strDate = format_date(date); //yyyy-mm-dd
  1416. var els=[];
  1417. unblink_all_date();
  1418. var first_into_view = false; //make sure first row in match is visible
  1419. $('div.workspace div.bstart:visible').each(function(i,e){
  1420. var value = $(e).html();
  1421. if( -1 != value.indexOf(strDate) ) //found
  1422. {
  1423. if ( !first_into_view ){ //scroll to top
  1424. first_into_view = true;
  1425. ensure_visible(e);
  1426. }
  1427. els.push($(e));
  1428. $(e).addClass('blink_me');
  1429. }
  1430. });
  1431. }
  1432. function ensure_visible(el){
  1433. $(el).get(0).scrollIntoView();
  1434. }
  1435. function unblink_all_date(){
  1436. $('div.bstart').removeClass('blink_me');
  1437. }
  1438. $('div.sheettitle h1 span').click(function(){
  1439. reset_title_to_today();
  1440. load_timesheet();
  1441. });
  1442. function reset_title_to_today(){
  1443. set_today();
  1444. init_weekdays();
  1445. set_week_number();
  1446. }
  1447. var debounced_load_timesheet = debounce(load_timesheet,1000);
  1448. function load_timesheet()
  1449. {
  1450. clear_workspace();
  1451. var first = $('span[name="w1d1"]').data().date;
  1452. var last = $('span[name="w2d7"]').data().date;
  1453. $.post(bts().ajax_url, { // POST request
  1454. _ajax_nonce: bts().nonce, // nonce
  1455. action: "list_jobv1", // action
  1456. start: format_date(first),
  1457. finish: format_date(last),
  1458. }, function(response, status, xhr){
  1459. if (response.status =='success'){
  1460. display_jobs_after_staff_client_tos_info_ready(response);
  1461. }else{
  1462. alert('error loading job');
  1463. hide_loading_jobs();
  1464. }
  1465. });
  1466. }
  1467. function display_jobs_after_staff_client_tos_info_ready(response)
  1468. {
  1469. var b = bts();
  1470. if ((typeof b.staff_map != "undefined" && Object.keys(b.staff_map).length > 0) &&
  1471. (typeof b.client_map != "undefined" && Object.keys(b.client_map).length > 0) &&
  1472. (typeof b.tos != "undefined" && Object.keys(b.tos).length > 0 ) &&
  1473. (typeof b.earnings_rate != "undefined" && Object.keys(b.earnings_rate).length > 0 ))
  1474. {
  1475. var job_map={};
  1476. var jobs = [];
  1477. //map data for each jobTable
  1478. response.jobs.forEach(function(e){
  1479. //job_derive_attr(e);
  1480. var j = new Job(e)
  1481. j.saved=true; //this is a record from database;
  1482. job_map[e.id] = j;
  1483. jobs.push(j);
  1484. });
  1485. bts().job_map = job_map;
  1486. //we do works, load timesheets
  1487. var template = $("#jobv1_item").html();
  1488. var html = Mustache.render(template, {jobs:jobs});
  1489. $('div.workspace').append(html);
  1490. hide_loading_jobs();
  1491. //filter it if reqired
  1492. debounced_filter_workspace();
  1493. return;
  1494. }
  1495. //console.log('wating staff/client/tos info to be ready');
  1496. setTimeout(function(){
  1497. display_jobs_after_staff_client_tos_info_ready(response);
  1498. }, 500); //try it half seconds later
  1499. }
  1500. function has_txt_hour(str){
  1501. if (str == null){
  1502. console.warn('null');
  1503. return;
  1504. }
  1505. var s = str.toLowerCase();
  1506. return s.indexOf('hour') != -1;
  1507. }
  1508. function show_jobs(jobs){
  1509. if (jobs.length >0){
  1510. var templ = $("#jobv1_item").html();
  1511. var html = Mustache.render(templ, {jobs:jobs}); //job id should be available;
  1512. var el = $(html);
  1513. $('div.workspace').append(el);
  1514. el.get(0).scrollIntoView();
  1515. }
  1516. debounced_calculate();
  1517. }
  1518. function format_date(date){
  1519. var dd = date.getDate();
  1520. var mm = date.getMonth() + 1; //January is 0!
  1521. var yyyy = date.getFullYear();
  1522. if (dd < 10) {
  1523. dd = '0' + dd;
  1524. }
  1525. if (mm < 10) {
  1526. mm = '0' + mm;
  1527. }
  1528. return yyyy + '-' + mm + '-' +dd ;
  1529. }
  1530. function format_date_time(date){
  1531. var strdate = format_date(date);
  1532. var hh = date.getHours();
  1533. if (hh<10){
  1534. hh= '0' + hh;
  1535. }
  1536. var mm = date.getMinutes();
  1537. if (mm<10){
  1538. mm='0' + mm;
  1539. }
  1540. return strdate + ' ' + hh + ":" + mm;
  1541. }
  1542. function clear_workspace()//clear all timesheet jobs
  1543. {
  1544. $('div.workspace > div.divTable').remove();
  1545. //clear datetime picker
  1546. $('div.xdsoft_datetimepicker').remove();
  1547. //
  1548. bts().job_map = {};
  1549. bts().job_map_new = {};
  1550. show_loading_jobs();
  1551. }
  1552. $('div.workinghours').click(function(){
  1553. $('div.bts_message_button').trigger('click');
  1554. });
  1555. function check_workspace_error(){
  1556. var els = $('div.workspace').find('.error');
  1557. if(els.length >0){
  1558. els.get(0).scrollIntoView();
  1559. return true;
  1560. }
  1561. return false;
  1562. }
  1563. $('button[name="confirmschedule"]').click(function(){
  1564. if( check_workspace_error() ){
  1565. return;
  1566. }
  1567. if (!confirm('sending email to each staff for their job arrangement?'))
  1568. return;
  1569. $('div.bts_message .ult-overlay-close-inside').hide();
  1570. $('div.bts_message_button').trigger('click');
  1571. setTimeout(do_email_jobs, 2000);//2 seconds for dialog to popup
  1572. });
  1573. $('button[name="confirmschedule"]').mouseenter(function(){
  1574. $('div.week2').addClass('blink_me');
  1575. })
  1576. $('button[name="confirmschedule"]').mouseleave(function(){
  1577. $('div.week2').removeClass('blink_me');
  1578. })
  1579. var debounced_filter_workspace = debounce(do_filter_workspace, 1000);
  1580. $(document).on('click','div.userlist', debounced_filter_workspace);
  1581. function do_filter_workspace(){
  1582. var staffs =[];
  1583. $('div.stafflist div.peopleitem :checked').each(function(i, e){
  1584. if ($(e).parent().is(':visible')){
  1585. var id = $(e).parent().attr('data-id');
  1586. //console.log("%o, id=%s", e, id);
  1587. staffs.push(id.substring(1));
  1588. }
  1589. });
  1590. var clients =[];
  1591. $('div.clientlist div.peopleitem :checked').each(function(i, e){
  1592. if ($(e).parent().is(':visible')){
  1593. var id = $(e).parent().attr('data-id');
  1594. //console.log("%o, id=%s", e, id);
  1595. clients.push(id.substring(1));
  1596. }
  1597. });
  1598. filter_workspace(staffs, clients);
  1599. filter_workspace_by_weeks();
  1600. debounced_calculate();
  1601. }
  1602. function filter_workspace(staffs, clients){
  1603. //if both array is empty
  1604. if( (staffs === undefined || staffs.length ==0) &&
  1605. (clients===undefined || clients.length ==0)){
  1606. //show all
  1607. $('div.workspace div.divTable').show();
  1608. return;
  1609. }
  1610. //if staffs is empty, we only filter by client
  1611. if (staffs === undefined || staffs.length ==0){
  1612. filter_workspace_by_client(clients);
  1613. return;
  1614. }
  1615. //if clients is empty, we only filter by staff
  1616. if (clients===undefined || clients.length ==0){
  1617. filter_workspace_by_staff(staffs);
  1618. return;
  1619. }
  1620. //filter by both
  1621. filter_workspace_by_both(staffs, clients);
  1622. }
  1623. function filter_workspace_by_staff(staffs)
  1624. {
  1625. var class_name='to_be_shown';
  1626. //filter some of them;
  1627. staffs.forEach(function(e){
  1628. $('div.workspace div.jobTable[data-staff="' + e + '"]').addClass(class_name);
  1629. });
  1630. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1631. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1632. $('.' + class_name).removeClass(class_name);
  1633. }
  1634. function filter_workspace_by_client(clients)
  1635. {
  1636. var class_name='to_be_shown';
  1637. //filter some of them;
  1638. clients.forEach(function(e){
  1639. $('div.workspace div.jobTable[data-client="' + e + '"]').addClass(class_name);
  1640. });
  1641. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1642. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1643. $('.' + class_name).removeClass(class_name);
  1644. }
  1645. function filter_workspace_by_both(staffs, clients)
  1646. {
  1647. var class_name='hide';
  1648. //filter some of them;
  1649. clients.forEach(function(e){
  1650. $('div.workspace div.jobTable:not([data-client="' + e + '"])').addClass(class_name);
  1651. });
  1652. staffs.forEach(function(e){
  1653. $('div.workspace div.jobTable:not([data-staff="' + e + '"])').addClass(class_name);
  1654. });
  1655. $('div.workspace div.jobTable.' + class_name).hide();
  1656. $('div.workspace div.jobTable:not(.'+ class_name +')').show();
  1657. $('.' + class_name).removeClass(class_name);
  1658. }
  1659. function filter_workspace_by_weeks(){
  1660. var hide_week1 = $('div.week1').hasClass('filtered');
  1661. var hide_week2 = $('div.week2').hasClass('filtered');
  1662. if (hide_week1 && hide_week2 ){
  1663. alert("You are hiding both weeks");
  1664. $('div.jobTable').show();//show non-week1 and none-week2
  1665. $('div.jobTable.week1job,div.jobTable.week2job').hide(); //hide week1 or week2;
  1666. }else if (hide_week1){
  1667. $('div.jobTable:not(.week1job)').show();//show non-week1
  1668. $('div.jobTable.week1job').hide(); //hide week1;
  1669. }else if (hide_week2){
  1670. $('div.jobTable.week2job').hide(); //show non-week2
  1671. $('div.jobTable:not(.week2job)').show(); //hide week2
  1672. }
  1673. }
  1674. var debounced_calculate = debounce(calculate_total_hour_and_money, 2000);
  1675. function calculate_total_hour_and_money()
  1676. {
  1677. //init pays for all staff;
  1678. var pays={
  1679. total: 0,
  1680. hours: 0,
  1681. };
  1682. $('.stafflist > div.peopleitem').each(function(i,e){
  1683. var people = $(this).data().obj;
  1684. people.reset_summary();
  1685. });
  1686. $('div.workspace > .divTable.jobTable:visible').each(function(i,e){
  1687. var id = $(e).attr('data-id');
  1688. var job = bts().job_map[id];
  1689. if (id == ''){
  1690. //is this a new Job without id?
  1691. var newjob_id = $(e).attr('data-newjob_id');
  1692. if ( typeof newjob_id != "undefined"){
  1693. id = newjob_id;
  1694. job = bts().job_map_new[newjob_id];
  1695. }
  1696. }
  1697. if (typeof job === 'undefined' || !job.is_job_valid() )
  1698. return;
  1699. var ps = job.get_payment_summary();
  1700. pays.total += ps.money;
  1701. pays.hours += ps.hour;
  1702. var staff = job.staff;
  1703. var people = bts().staff_people[staff]; //class People
  1704. if (people !=false)
  1705. people.add_payment_summary(ps);
  1706. });
  1707. set_wages(pays.total.toFixed(2));
  1708. set_working_hours(pays.hours.toFixed(2));
  1709. }
  1710. function find_staff(login)
  1711. {
  1712. var d = $('#p'+login).data();
  1713. if (typeof d === 'undefined')
  1714. return false;
  1715. return $('#p'+login).data().obj;
  1716. }
  1717. $(document).on('change', '.divTableRow input[name="ack"]', function(e) {
  1718. var el = $(this).closest('.jobTable');
  1719. var data = el.data();
  1720. data.ack = e.checked? 1: 0;
  1721. job_mark_dirty(el);
  1722. });
  1723. function job_mark_dirty(el)
  1724. {
  1725. el.removeClass('saved');
  1726. el.addClass('dirty');
  1727. }
  1728. function job_mark_clean(el)
  1729. {
  1730. el.addClass('saved');
  1731. el.removeClass('dirty');
  1732. }
  1733. function job_is_week1(t)
  1734. {
  1735. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  1736. var w1_end = new Date($('span[name="w1d7"]').data().date);
  1737. w1_begin.setHours(0,0,0,0);
  1738. w1_end.setHours(23,59,59);
  1739. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  1740. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  1741. var me = new Date(t);
  1742. return (w1_begin <= me && me <= w1_end );
  1743. }
  1744. function job_is_week2(t)
  1745. {
  1746. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1747. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1748. w2_begin.setHours(0,0,0,0);
  1749. w2_end.setHours(23,59,59);
  1750. var me = new Date(t);
  1751. return (w2_begin <= me && me <= w2_end );
  1752. }
  1753. function init_ts(){
  1754. xero(false);
  1755. wifi(false);
  1756. csv(false);
  1757. show_loading_jobs();
  1758. list_staff();
  1759. list_clients();
  1760. list_tos();
  1761. ajax_earning_rate();
  1762. //setTimeout(list_staff, 5000); // for testing delayed loading of jobs only
  1763. //setTimeout(list_clients, 8000); // for testing delayed loading of jobs only
  1764. //setTimeout(list_tos, 10000); // for testing delayed loading of jobs only
  1765. init_user_search();
  1766. reset_title_to_today();
  1767. load_timesheet();
  1768. }
  1769. function do_email_jobs()
  1770. {
  1771. var selector = 'div.bts_message div.ult_modal-body';
  1772. $(selector).html('Analysis staff jobs ... ok');
  1773. var staff = bts().staff.slice(0);//copy this array;
  1774. var s = staff.pop();
  1775. //week2 start
  1776. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1777. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1778. var start = format_date(w2_begin);
  1779. var finish = format_date(w2_end);
  1780. function do_staff(){
  1781. var el = $('<p> Checking ' + s.firstname + "....</p>");
  1782. $(selector).append(el);
  1783. el[0].scrollIntoView();
  1784. $.post(bts().ajax_url, { // POST request
  1785. _ajax_nonce: bts().nonce, // nonce
  1786. action: "email_job", // action
  1787. staff : s.login,
  1788. start : start,
  1789. finish: finish,
  1790. }, function(response, status, xhr){
  1791. if (response.status == 'success'){
  1792. if (response.sent){
  1793. el.append('<span class="sent">' + response.emailstatus + '</span>');
  1794. }else{
  1795. el.append('<span class="nojob">' + response.emailstatus + '</span>');
  1796. }
  1797. }else{
  1798. el.append('<span class="error"> Error[' + response.error + ' ...]</span>');
  1799. }
  1800. }).fail(function(){
  1801. el.append('<span class="error">' + 'Network Error occured' + '</span>');
  1802. //clear staff pending list, stop further processing
  1803. s = [];
  1804. }).always(function(){//next staff
  1805. if (staff.length >0){
  1806. s = staff.pop();
  1807. setTimeout(do_staff, 100); //a short delay makes it looks nice
  1808. }else{
  1809. $('div.bts_message .ult-overlay-close-inside').show();
  1810. $('div.bts_message .ult-overlay-close-inside').addClass('blink_me');
  1811. $('div.week2').removeClass('blink_me');
  1812. $(selector).append('<span class="sent">All staff confirmed! </span>');
  1813. }
  1814. });
  1815. }
  1816. //execute
  1817. do_staff();
  1818. }
  1819. function ajax_earning_rate(){
  1820. $.post(bts().ajax_url, { // POST request
  1821. _ajax_nonce: bts().nonce, // nonce
  1822. action: "earnings_rate", // action
  1823. }, function(response, status, xhr){
  1824. bts().earnings_rate = {};
  1825. response.options.forEach(function(e){
  1826. bts().earnings_rate[e.EarningsRateID]=e;
  1827. });
  1828. //console.log("%o", bts().earnings_rate);
  1829. });
  1830. }
  1831. function do_delete_duplicate()
  1832. {
  1833. var len = $('div.jobTable.to_be_deleted_duplicate').length;
  1834. if (len <= 0){
  1835. return;
  1836. }
  1837. if (!confirm("Delete " + len + " duplicates? ")){
  1838. return;
  1839. }
  1840. $('div.jobTable.to_be_deleted_duplicate:not(.saved)').each(function(){
  1841. remove_job_from_gui(this);
  1842. });
  1843. var ids = [];
  1844. $('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
  1845. var id = $(this).attr('data-id');
  1846. if (id != '')
  1847. ids.push(id);
  1848. });
  1849. if ( ids.length >0 ){
  1850. $.post(bts().ajax_url, { // POST request
  1851. _ajax_nonce: bts().nonce, // nonce
  1852. action: "delete_jobv1", // action
  1853. jobs: ids, //delete multiple ids
  1854. }, function(response, status, xhr){
  1855. if (response.status=='success'){
  1856. $('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
  1857. remove_job_from_gui(this);
  1858. });
  1859. debounced_calculate();
  1860. }else{
  1861. alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n");
  1862. }
  1863. });
  1864. }
  1865. }
  1866. function check_duplicate()
  1867. {
  1868. var to_be_deleted=[];
  1869. //make a copy of all jobs
  1870. var alljobs = {};
  1871. $('div.jobTable:visible').each(function(e){
  1872. alljobs[$(this).attr('id')] = $.extend({}, $(this).data());
  1873. });
  1874. //loop through jobs
  1875. for(var id1 in alljobs){
  1876. var job1 = alljobs[id1];
  1877. if (typeof job1.parent != 'undefined')
  1878. continue; //bypass it it has already found parents
  1879. job1.compared_as_master = true;//mark it
  1880. job1.duplicates={};
  1881. //console.log('investigating %s' , job1.id);
  1882. //match job2
  1883. for(var id2 in alljobs){
  1884. if (id1 == id2)
  1885. continue;
  1886. var job2 = alljobs[id2];
  1887. if (typeof job2.compared_as_master != 'undefined')
  1888. continue;
  1889. if (typeof job2.parent != "undefined")
  1890. continue; //it has already parent;
  1891. //console.log('comareing %s vs %s', job1.id, job2.id);
  1892. if (is_same_job(job1,job2)){
  1893. job2.parent = job1.id;
  1894. job1.duplicates[id2] = job2;
  1895. //console.warn("found: %s = %s", job1.id, job2.id);
  1896. to_be_deleted.push(id2);
  1897. }
  1898. }
  1899. }
  1900. if (to_be_deleted.length == 0){
  1901. alert("No duplicate found!");
  1902. return;
  1903. }
  1904. //console.log('all-done, found %d duplicates: %o', to_be_deleted.length, to_be_deleted);
  1905. mark_duplicate(to_be_deleted);
  1906. return to_be_deleted;
  1907. }
  1908. function is_same_job(job1, job2)
  1909. {
  1910. var s1 = new Date(job1.start);
  1911. var s2 = new Date(job2.start);
  1912. var f1 = new Date(job1.finish);
  1913. var f2 = new Date(job2.finish);
  1914. if ( (job1.tos == job2.tos) &&
  1915. (job1.staff == job2.staff) &&
  1916. (job1.client == job2.client) &&
  1917. (s1 - s2 == 0) &&
  1918. (f1 - f2 == 0) )
  1919. {
  1920. return true;
  1921. }
  1922. return false;
  1923. }
  1924. function mark_duplicate(ids)
  1925. {
  1926. ids.forEach(function(id){
  1927. var selector = '#' + id;
  1928. ensure_visible(selector);
  1929. $(selector).addClass('to_be_deleted_duplicate');
  1930. });
  1931. }
  1932. function do_edit_new_job(id)
  1933. {
  1934. open_modal('editor');
  1935. set_modal_title('editor', "Editing New Job ");
  1936. var new_job = bts().job_map_new[id];
  1937. new_job.editorid = id;
  1938. //set_modal_data('editor', {jobid: id, job_copy:job_copy});
  1939. var html = $('#jobv1_editor').html();
  1940. html = Mustache.render(html, new_job);
  1941. set_modal_content('editor', html);
  1942. //update GUI
  1943. dtp_init();
  1944. //init editor
  1945. var e = new JobEditor('#editor_' + id, new_job);
  1946. //console.log("e is instance of JobEditor %o", e instanceof JobEditor);
  1947. }
  1948. function do_edit_job(id)
  1949. {
  1950. open_modal('editor');
  1951. set_modal_title('editor', "Editing Job: " + id);
  1952. //make a copy of the job
  1953. var child = bts().job_map[id];
  1954. var job_copy = Object.assign(Object.create(Object.getPrototypeOf(child)), child); //a shallow copy only;
  1955. job_copy.editorid = bts_random_number();
  1956. //set_modal_data('editor', {jobid: id, job_copy:job_copy});
  1957. var html = $('#jobv1_editor').html();
  1958. html = Mustache.render(html, job_copy);
  1959. set_modal_content('editor', html);
  1960. //update GUI
  1961. dtp_init();
  1962. //init editor
  1963. var e = new JobEditor('#editor_' + job_copy.editorid, job_copy);
  1964. //console.log("e is instance of JobEditor %o", e instanceof JobEditor);
  1965. }
  1966. $( ".boundary_datepicker" ).datepicker();
  1967. $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd");
  1968. $(document).on('click', 'div.clientlist div[name="title"] a', function(e){
  1969. e.preventDefault();
  1970. e.stopPropagation();
  1971. var id = $(this).closest('label.peopleitem').attr('data-id').substring(1);
  1972. var str = 'https://acaresydncy.com.au/feedback_card/' + id;
  1973. var name = $(this).html();
  1974. if ( confirm ("Email feedback link of : " + name + "\n\n\n" + str + "\n\n\n to helen@acaresydney.com.au?")){
  1975. $.post(bts().ajax_url, { // POST request
  1976. _ajax_nonce: bts().nonce, // nonce
  1977. action: "email_feedback_url", // action
  1978. client : id,
  1979. }, function(response, status, xhr){
  1980. //alert('please check your email on the phone and SMS the link to your client');
  1981. }).fail(function(){
  1982. alert('network error ');
  1983. });
  1984. }
  1985. });
  1986. init_ts();
  1987. $('div.divTableHeading div.bsave span.ticon-search').mouseenter(function(){//highlight unsaved
  1988. var el = $('div.jobTable.dirty .bsave');
  1989. if (el.length > 0){
  1990. el.addClass('blink_me');
  1991. el.get(0).scrollIntoView();
  1992. }
  1993. })
  1994. $('div.divTableHeading div.bsave span.ticon-search').mouseleave(function(){//highlight unsaved
  1995. var el = $('div.jobTable.dirty .bsave');
  1996. if (el.length > 0 ) {
  1997. el.removeClass('blink_me');
  1998. }
  1999. })
  2000. $(document).on('click', 'div.jobTable.to_be_deleted_duplicate div.bedit span.ticon-check-circle',function(){//highlight unsaved
  2001. var el = $(this).closest('div.jobTable');
  2002. if (!confirm ("Mark this is none duplicate?")){
  2003. return;
  2004. }
  2005. el.removeClass('to_be_deleted_duplicate');
  2006. });
  2007. $('div.divTableHeading div.bsave span.ticon-search').click(do_delete_unsaved_copy);
  2008. function do_delete_unsaved_copy()
  2009. { //TODO: remove this search function
  2010. var num = $('div.jobTable.dirty').length;
  2011. if (num > 0){
  2012. if ( !confirm('delete all '+ num + ' unsaved?')){
  2013. return;
  2014. }
  2015. $('div.jobTable.dirty').each(function(){
  2016. var newjob_id = $(this).data().newjob_id;
  2017. delete bts().job_map_new[newjob_id]
  2018. $(this).remove();
  2019. })
  2020. }else{
  2021. alert("nothing to clean up");
  2022. }
  2023. }
  2024. /*________________________________________________________________________*/
  2025. });
  2026. })(jQuery);
  2027. /*______________scrolling______________________________________________*/
  2028. jQuery(document).ready(function(){
  2029. var timeoutid =0;
  2030. jQuery('button.peoplelist[name="down"]').mousedown(function(){
  2031. var button = this;
  2032. timeoutid = setInterval(function(){
  2033. //console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  2034. jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
  2035. }, 100);
  2036. }).on('mouseup mouseleave', function(){
  2037. clearTimeout(timeoutid);
  2038. });
  2039. jQuery('button.peoplelist[name="up"]').mousedown(function(){
  2040. var button = this;
  2041. timeoutid = setInterval(function(){
  2042. //console.log("up scrotop %d ",jQuery(button).parent().find(".userlist").get(0).scrollTop );
  2043. jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
  2044. }, 100);
  2045. }).on('mouseup mouseleave', function(){
  2046. clearTimeout(timeoutid);
  2047. });
  2048. });