timesheet source code
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1755 lines
49KB

  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.divTableHead.bdelete', function(){
  256. add_new_empty_job();
  257. });
  258. function add_new_empty_job(){
  259. var o = new Job({empty:true});
  260. $('div.workspace').append(o.el);
  261. o.el.get(0).scrollIntoView();
  262. dtp_init();
  263. }
  264. $(document).on('click', 'div[name="copyschedule"]', function(e){
  265. e.stopPropagation();
  266. add_new_empty_job();
  267. });
  268. $(document).on('click', 'div.divTableCell.bdelete', function(){
  269. var el = $(this).closest('div.jobTable');
  270. var data = el.data();
  271. if (typeof data.id =='undefined' || data.id == '')
  272. el.remove();
  273. else{
  274. var id = data.id;
  275. if (confirm('delete this job?')){
  276. $.post(bts().ajax_url, { // POST request
  277. _ajax_nonce: bts().nonce, // nonce
  278. action: "delete_job", // action
  279. jobid: id,
  280. }, function(response, status, xhr){
  281. if (response.status=='success'){
  282. var id = el.attr('data-id');
  283. delete bts().job_map[id];
  284. console.log("delete %s , job_map[%d]=%o ", id, id, bts().job_map[id]);
  285. el.addClass('blink_me');
  286. el.fadeOut(900);
  287. setTimeout(function(){
  288. el.remove();
  289. }, 900);
  290. }else{
  291. alert( 'error saving data, please check your network');
  292. }
  293. });
  294. }
  295. }
  296. });
  297. $(document).on('mouseenter', 'div.divTableCell', function(){
  298. $(this).closest('div.divTable').addClass('highlight');
  299. });
  300. $(document).on('mouseleave', 'div.divTableCell', function(){
  301. $(this).closest('div.divTable').removeClass('highlight');
  302. });
  303. $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
  304. var table = $(this).closest('div.divTable');
  305. table.data().job.do_save_record();
  306. });
  307. $(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
  308. //save all
  309. $('div.workspace span.ticon.ticon-save').each(function (i,e){
  310. if ($(this).is(":visible"))
  311. $(this).trigger('click');
  312. });
  313. });
  314. $(document).on('click', 'span.ticon.ticon-copy', function(){
  315. if (!confirm("make a copy of this job?"))
  316. return;
  317. var table = $(this).closest('div.divTable');
  318. var job = table.data().job;
  319. var newj = clone_data_create_new_job(job.get_record_from_ui());
  320. $('div.workspace').append(newj.el);
  321. newj.el.get(0).scrollIntoView();//make sure it's visible;
  322. newj.mark_highlight_me(1000);//for 1 second;
  323. dtp_init();
  324. });
  325. class Job{ //save data for the record, and display it as GUI
  326. constructor(selector, data){
  327. this.el = $(selector);
  328. //this.load_data(data);
  329. //this.init_start_rating();
  330. }
  331. init_start_rating(){
  332. var self = this;
  333. this.el.find("div.brating span").click(function(){
  334. var r = $(this).attr('data-rating');
  335. self.mark_dirty();
  336. self.set_rating(r);
  337. })
  338. this.el.find("div.brating").mouseenter(function(){
  339. //change to all hollow star
  340. $(this).find('span').html('☆');
  341. });
  342. this.el.find("div.brating").mouseleave(function(){
  343. self.set_rating(self.data.rating);
  344. });
  345. }
  346. load_data(data)
  347. {
  348. //save to html element
  349. this.data = data;
  350. this.el.data({job:this, data:data});
  351. //draw GUI
  352. this.clear_err_msg();
  353. this.set_job_id(data.id);
  354. this.set_tos(data.tos);
  355. this.set_start(data.start);
  356. this.set_finish(data.finish);
  357. this.set_rate(data.rate);
  358. this.set_staff(data.staff);
  359. this.set_client(data.client);
  360. this.set_ack(data.ack);
  361. this.set_rating(data.rating);
  362. //draw GUI by other
  363. this.mark_dirty_on_new_record(data);
  364. this.mark_week_color();
  365. this.validate(); //also triggers mark errors
  366. }
  367. get_job_id(){
  368. return this.el.find('input[name="id"]').attr('value');
  369. }
  370. set_job_id(val){
  371. if (typeof val == 'undefined' || val == '')
  372. {//remove data-id and id
  373. this.el.removeAttr('data-id');
  374. this.el.removeAttr('id');
  375. }else{
  376. this.el.attr('data-id', val);
  377. this.el.attr('id', 'job_' + val);
  378. }
  379. }
  380. get_tos()
  381. {
  382. this.el.find('div.btos select').children("option:selected").val();
  383. }
  384. set_tos(val)
  385. {
  386. if (typeof(val) =="undefined")
  387. return;
  388. this.el.find('input.tos_name').attr('value', bts().tos[val].tos_full_str);
  389. }
  390. get_start(){
  391. return this.el.find('div.bstart').html();
  392. }
  393. set_start(val)
  394. {
  395. this.el.find('div.bstart').html(val);
  396. }
  397. get_finish()
  398. {
  399. return this.el.find('div.bfinish').html();
  400. }
  401. set_finish(val)
  402. {
  403. if (typeof(val) == "undefined")
  404. return;
  405. this.el.find('div.bfinish').html(val);
  406. }
  407. get_rate()
  408. {
  409. return this.el.find('div.brate').children("option:selected").val();
  410. }
  411. set_rate(val)
  412. {
  413. if (typeof(val) =="undefined")
  414. return;
  415. this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
  416. }
  417. get_staff()
  418. {
  419. return this.el.find('div.bstaff select').children("option:selected").val();
  420. }
  421. set_staff(val)
  422. {
  423. if (typeof(val) =="undefined")
  424. return;
  425. this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
  426. }
  427. get_client()
  428. {
  429. return this.el.find('div.bclient select').children("option:selected").val();
  430. }
  431. set_client(val)
  432. {
  433. if (typeof(val) =="undefined")
  434. return;
  435. this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
  436. }
  437. get_ack()
  438. {
  439. return this.el.find('div.bconfirmed input:checked').length > 0;
  440. }
  441. set_ack(val)
  442. {
  443. if (typeof(val) =="undefined")
  444. return;
  445. return this.el.find('div.bconfirmed input').prop('checked', val!=0);
  446. }
  447. get_rating(){
  448. var count =0;
  449. this.el.find('div.brating span').each(function(i,e){
  450. if ($(e).html()=='★')
  451. count +=1;
  452. });
  453. return count;
  454. }
  455. set_rating(num){
  456. if (!(1 <= num && num <=5))
  457. return;
  458. this.el.find('div.brating span').each(function(i,e){
  459. var rating = $(e).attr('data-rating');
  460. var rating = parseInt(rating);
  461. if (rating <= num)
  462. $(e).html('★');
  463. else
  464. $(e).html('☆');
  465. });
  466. }
  467. get_record_from_ui(){
  468. var record = {};
  469. record.id = this.get_job_id();
  470. record.tos = this.get_tos();
  471. record.start = this.get_start();
  472. record.finish = this.get_finish();
  473. record.rate = this.get_rate();
  474. record.staff = this.get_staff();
  475. record.client = this.get_client();
  476. record.ack = this.get_ack();
  477. record.rating = this.get_rating();
  478. return record;
  479. }
  480. do_save_record(){
  481. var self = this;
  482. $.post(bts().ajax_url, { // POST request
  483. _ajax_nonce: bts().nonce, // nonce
  484. action: "save_job", // action
  485. record: this.get_record_from_ui(),
  486. }, function(response, status, xhr){
  487. if (response.status=='success'){
  488. self.load_data(response.newdata);
  489. self.mark_saved();
  490. self.mark_old();
  491. }else{
  492. alert( 'error saving data, please check your network');
  493. }
  494. });
  495. }
  496. mark_dirty_on_new_record(data){
  497. if (typeof(data.id) === 'undefined' || data.id == ''){
  498. this.mark_dirty();
  499. this.mark_new();
  500. }
  501. else{
  502. this.mark_saved();
  503. }
  504. }
  505. mark_dirty() //need save
  506. {
  507. var d = this.el.find('.bsave');
  508. d.removeClass('saved');
  509. d.addClass('blink_me');
  510. setTimeout(function(){
  511. d.removeClass('blink_me');
  512. d.removeClass('saved');
  513. },1000);
  514. }
  515. mark_saved()
  516. {
  517. var d = this.el.find('.bsave');
  518. d.addClass('blink_me');
  519. setTimeout(function(){
  520. d.removeClass('blink_me');
  521. d.addClass('saved');
  522. },1000);
  523. }
  524. //newly created empty record
  525. mark_new()
  526. {
  527. this.el.addClass('emptyrecord');
  528. }
  529. mark_old()
  530. {
  531. this.el.removeClass('emptyrecord');
  532. }
  533. mark_highlight_me(ms){
  534. this.el.addClass('blink_me');
  535. this.el.addClass('highlight');
  536. this.el.addClass('newcopy');
  537. var self = this;
  538. setTimeout(function(){
  539. self.el.removeClass('blink_me');
  540. self.el.removeClass('highlight');
  541. self.el.removeClass('newcopy');
  542. },ms);
  543. }
  544. is_start_valid(){
  545. var s = this.get_start();
  546. return is_valid_date_str(s);
  547. }
  548. is_finish_valid(){
  549. var f = this.get_finish();
  550. if (!is_valid_date_str(f))
  551. return false;
  552. }
  553. is_finish_resonable(){
  554. var f = this.get_finish();
  555. if (!is_valid_date_str(f))
  556. return false;
  557. var s = this.get_start();
  558. s = new Date(s);
  559. f = new Date(f);
  560. return (s < f);
  561. }
  562. validate()
  563. {
  564. var ok_time = this.validate_start() &&
  565. this.validate_finish(); //finish might not be executed, if start is wrong
  566. var ok_tos = this.validate_tos(); //make sure this validate is executed;
  567. var ok_rate = this.validate_rate() ; //make sure this validate is executed
  568. var ok = ok_time && ok_tos && ok_rate;
  569. if (ok){
  570. this.el.removeClass('invalidjob');
  571. }else{
  572. this.el.addClass('invalidjob');
  573. }
  574. return ok;
  575. }
  576. validate_start(){
  577. var str = this.get_start();
  578. if ( is_valid_date_str(str) ){
  579. this.mark_start_valid();
  580. this.set_err_msg_start('');
  581. return true;
  582. }else{
  583. this.mark_start_invalid();
  584. this.set_err_msg_start('wrong date');
  585. return false;
  586. }
  587. }
  588. validate_finish()
  589. {
  590. var str = this.get_finish();
  591. if (! is_valid_date_str(str)){
  592. this.set_err_msg_finish('wrong date');
  593. this.mark_finish_invalid();
  594. return false;
  595. }
  596. if (!this.is_finish_resonable()){
  597. this.set_err_msg_finish("older than start")
  598. this.mark_finish_invalid();
  599. return false;
  600. }
  601. this.mark_finish_valid();
  602. this.set_err_msg_finish('');
  603. return true;
  604. }
  605. validate_rate()
  606. {
  607. var rate_info = this.get_rate_info_by_id(this.get_rate());
  608. if ( rate_info.RatePerUnit <= 0){
  609. this.set_err_msg_rate('bad rate');
  610. this.mark_rate_invalid();
  611. return false;
  612. }
  613. // if (this.get_rate() != this.data.rate){
  614. // this.set_err_msg_rate('rate@Xero inactive ' + this.data.rate);
  615. // this.mark_rate_invalid();
  616. // this.mark_dirty();
  617. // return false;
  618. // }
  619. this.set_err_msg_rate('');
  620. this.mark_rate_valid();
  621. return true;
  622. }
  623. validate_tos(){
  624. // if (this.get_tos() != this.data.tos){
  625. // this.set_err_msg_tos('require NDIS ' + this.data.tos);
  626. // this.mark_tos_invalid();
  627. // this.mark_dirty();
  628. // console.log('tos mark dirty');
  629. // return false;
  630. // }
  631. this.set_err_msg_tos('');
  632. this.mark_tos_valid();
  633. return true;
  634. }
  635. clear_err_msg(){
  636. this.el.find('.divTableRow.errmsg > div').html('');
  637. }
  638. set_err_msg_start(str)
  639. {
  640. this.el.find('div.bstart_err').html(str);
  641. }
  642. set_err_msg_finish(str)
  643. {
  644. this.el.find('div.bfinish_err').html(str);
  645. }
  646. set_err_msg_rate(str)
  647. {
  648. this.el.find('div.brate_err').html(str);
  649. }
  650. set_err_msg_save(str)
  651. {
  652. this.el.find('div.bsave_err').html(str);
  653. }
  654. set_err_msg_tos(str)
  655. {
  656. this.el.find('div.btos_err').html(str);
  657. }
  658. mark_tos_valid(){
  659. this.el.find('div.btos select').removeClass('invalid');
  660. }
  661. mark_tos_invalid(){
  662. this.el.find('div.btos select').addClass('invalid');
  663. }
  664. mark_start_valid(){
  665. this.el.find('div.bstart input').removeClass('invalid');
  666. }
  667. mark_start_invalid(){
  668. this.el.find('div.bstart input').addClass('invalid');
  669. }
  670. mark_finish_valid(){
  671. this.el.find('div.bfinish input').removeClass('invalid');
  672. }
  673. mark_finish_invalid(){
  674. this.el.find('div.bfinish input').addClass('invalid');
  675. }
  676. mark_rate_valid(){
  677. this.el.find('div.brate select').removeClass('invalid');
  678. }
  679. mark_rate_invalid(){
  680. this.el.find('div.brate select').addClass('invalid');
  681. }
  682. mark_week_color(){
  683. this.el.find('div.brating').removeClass('week1color');
  684. this.el.find('div.brating').removeClass('week2color');
  685. if (this.is_week1()){
  686. this.el.find('div.brating').addClass('week1color');
  687. }else if (this.is_week2()){
  688. this.el.find('div.brating').addClass('week2color');
  689. }
  690. }
  691. is_week1()
  692. {
  693. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  694. var w1_end = new Date($('span[name="w1d7"]').data().date);
  695. w1_begin.setHours(0,0,0,0);
  696. w1_end.setHours(23,59,59);
  697. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  698. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  699. var me = new Date(this.data.start);
  700. return (w1_begin <= me && me <= w1_end );
  701. }
  702. is_week2()
  703. {
  704. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  705. var w2_end = new Date($('span[name="w2d7"]').data().date);
  706. w2_begin.setHours(0,0,0,0);
  707. w2_end.setHours(23,59,59);
  708. var me = new Date(this.data.start);
  709. return (w2_begin <= me && me <= w2_end );
  710. }
  711. get_payment_summary(){
  712. var result ={};
  713. result.ot = this.get_is_high_pay();
  714. result.hour = this.get_working_duration();
  715. result.money = this.get_wages();
  716. return result;
  717. }
  718. get_is_high_pay()
  719. {
  720. var rate_info = this.get_rate_info_by_id(this.get_rate());
  721. return this.is_high_pay_hour(rate_info);
  722. }
  723. get_working_duration()
  724. {
  725. //finish - start
  726. var f = new Date(this.get_finish());
  727. var s = new Date(this.get_start());
  728. var diff = f.getTime() - s.getTime();
  729. var hours = Math.floor(diff / 1000 / 60 / 60);
  730. diff -= hours * 1000 * 60 * 60;
  731. var minutes = Math.floor(diff / 1000 / 60);
  732. var minute_to_hour = minutes/60;
  733. return (hours + minute_to_hour);
  734. }
  735. get_wages(){
  736. var hour = this.get_working_duration();
  737. var rate_info = this.get_rate_info_by_id(this.get_rate());
  738. return hour * rate_info.RatePerUnit;
  739. }
  740. get_rate_info_by_id(id){
  741. var rate_info = {};
  742. var rates = bts().earnings_rate;
  743. for(var i =0; i< rates.length; i++){
  744. var r = rates[i];
  745. if(r.EarningsRateID == id){
  746. rate_info = $.extend(true,{}, r);//make a copy
  747. break;
  748. }
  749. }
  750. return rate_info;
  751. }
  752. is_high_pay_hour(rate_info){
  753. var keywords =bts().high_pay_keywords;
  754. var found = false;
  755. return false;
  756. keywords.forEach(function(e){
  757. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  758. found = true;
  759. });
  760. return found;
  761. }
  762. }//end of class Job
  763. //global GUI summary
  764. function get_wages()
  765. {
  766. var txt = $('div.wages div').html();
  767. return parseInt(txt);
  768. }
  769. function set_wages(num){
  770. $('div.wages div').html(num);
  771. }
  772. function set_working_hours(num){
  773. $('input#woh').attr('value', num);
  774. }
  775. function get_working_hours(){
  776. var txt = $('input#woh').attr('value');
  777. return parseFloat(txt);
  778. }
  779. //modal box
  780. function set_modal_title(selector, title){
  781. var s = 'div.bts_'+ selector +' .ult_modal-title';
  782. $(s).html(title);
  783. }
  784. function set_modal_content(selector, content){
  785. var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
  786. $(s).html(content);
  787. }
  788. function open_modal (selector){
  789. var s='div.bts_'+selector+'_button';
  790. $(s).trigger('click');
  791. }
  792. // setTimeout(function(){
  793. // set_modal_title('warning', 'suck title');
  794. // set_modal_content('warning', 'fucking details');
  795. // //open_modal('warning');
  796. // }, 1000);
  797. //
  798. // setTimeout(function(){
  799. // set_modal_title('error', 'error title');
  800. // set_modal_content('error', 'error details');
  801. // //open_modal('error');
  802. // }, 5000);
  803. $(document).on('mouseenter', 'div.week1 div', function(){
  804. $(this).addClass('blink_me');
  805. get_week2_partner(this).addClass('blink_me');
  806. blink_same_date_by_div(this);
  807. });
  808. $(document).on('mouseleave', 'div.week1 div', function(){
  809. $(this).removeClass('blink_me');
  810. get_week2_partner(this).removeClass('blink_me');
  811. unblink_all_date();
  812. });
  813. function get_week2_partner(div){
  814. var index = $(div).index()+1;
  815. return $('div.week2 div:nth-child('+index+')');
  816. }
  817. function init_weekdays(){
  818. var curr = new Date; // get current date
  819. init_weekdays_by_anchor(curr, true);
  820. return;
  821. }
  822. function init_weekdays_by_anchor(anchor, is_week1){
  823. var curr = new Date(anchor); // get the date;
  824. if (!is_week1){ //it is week2, shift for 7 days;
  825. curr.setDate(curr.getDate() -7); //curr will be changed;
  826. }
  827. var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
  828. var last = first + 6; // last day is the first day + 6
  829. if (curr.getDay() == 0 ){// it's Sunday;
  830. last = curr.getDate();
  831. first = last - 6;
  832. }
  833. var pos = 1; //first lot
  834. for (var i=first; i<=last; i++)
  835. {
  836. var now = new Date(curr);
  837. var d1 = new Date(now.setDate(i));
  838. now = new Date(curr);
  839. var d2 = new Date(now.setDate(i+7));
  840. set_day_number(1,pos, d1); //week 1
  841. set_day_number(2,pos, d2); //week 2
  842. pos +=1;
  843. }
  844. }
  845. function set_day_number(week, index, date){
  846. var selector = 'span[name="w'+week+'d'+index+'"]';
  847. $(selector).html(date.getDate());
  848. $(selector).data({date:date});
  849. //console.log('set w%d-d%d %o', week,index,date);
  850. }
  851. function set_today(){
  852. var selector = 'div.sheettitle span[name="today"]';
  853. var curr = new Date;
  854. $(selector).html(format_date(curr));
  855. }
  856. Date.prototype.get_week_number = function(){
  857. var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
  858. var dayNum = d.getUTCDay() || 7;
  859. d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  860. var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
  861. return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
  862. };
  863. function set_week_number(){
  864. var date = $('span[name="w1d1"]').data().date;
  865. //console.log("date %o", date);
  866. var num = date.get_week_number();
  867. $('div.weekly span[name="week1"]').html(num);
  868. $('div.weekly span[name="week2"]').html(num+1);
  869. set_week_boundry();
  870. }
  871. function set_week_boundry()
  872. {
  873. var date = $('span[name="w1d1"]').data().date;
  874. $('#week1b').attr('value', format_date(date));
  875. var date = $('span[name="w2d7"]').data().date;
  876. $('#week2b').attr('value', format_date(date));
  877. }
  878. function number_of_unsaved_job(){
  879. var count =0;
  880. var total_job = $('div.jobTable').length;
  881. var total_saved = $('div.jobTable.saved').length;
  882. var empty = $('div.emptyrecord').length;
  883. count = total_job - total_saved - empty;
  884. return count;
  885. }
  886. $('div.prevweek.left').click(function(){
  887. if (number_of_unsaved_job() > 0){
  888. if(!confirm("you have unsaved jobs, proceed will lost them"))
  889. return;
  890. }
  891. $('div.weekdays span.weekday').each(function(i, e){
  892. var date = $(e).data().date;
  893. var newdate = new Date(date.setDate(date.getDate() -7 ));
  894. $(e).html(newdate.getDate());
  895. $(e).data({data:newdate});
  896. });
  897. set_week_number();
  898. debounced_load_timesheet();
  899. });
  900. $('div.nextweek.right').click(function(){
  901. if (number_of_unsaved_job() > 0){
  902. if(!confirm("you have unsaved jobs,proceed will lost them"))
  903. return;
  904. }
  905. $('div.weekdays span.weekday').each(function(i, e){
  906. var date = $(e).data().date;
  907. var newdate = new Date(date.setDate(date.getDate() +7 ));
  908. $(e).html(newdate.getDate());
  909. $(e).data({data:newdate});
  910. });
  911. set_week_number();
  912. debounced_load_timesheet();
  913. });
  914. $('div.weekly div.weekname.prev >input ').click(function(e){
  915. e.stopPropagation();
  916. });
  917. $('div.weekly div.weekname.prev >input ').change(function(e){
  918. var date = $('#week1b').attr('value');
  919. init_weekdays_by_anchor(date, true);
  920. set_week_number();
  921. debounced_load_timesheet();
  922. });
  923. $('div.weekly div.weekname.prev').click(function(){
  924. if (!confirm ('copy entire week to next week? '))
  925. return;
  926. var jobs = [];
  927. var job_els =[];
  928. $('div.week1 >div').each(function(i,e){
  929. var date = new Date($(e).find('span.weekday').data().date);
  930. var strDate = format_date(date); //yyyy-mm-dd
  931. $('div.bstart input').each(function(i,e){
  932. var value = $(e).attr('value');
  933. if( -1 != value.indexOf(strDate) ) //found
  934. {
  935. var el = $(e).closest('div.divTable');
  936. if (el.is(":visible")){
  937. var j = el.data().job;
  938. var newj = clone_data_create_new_job(j.get_record_from_ui(),7);//add 7 days
  939. job_els.push(newj.el);
  940. }
  941. }
  942. });
  943. });
  944. show_jobs(job_els);
  945. debounced_calculate();
  946. });
  947. $('div.weekly div.weekname.next > input').click(function(e){
  948. e.stopPropagation();
  949. });
  950. $('div.weekly div.weekname.next >input ').change(function(e){
  951. e.stopPropagation();
  952. var date = $('#week2b').attr('value');
  953. init_weekdays_by_anchor(date, false);
  954. set_week_number();
  955. debounced_load_timesheet();
  956. });
  957. $('div.weekly div.weekname.next').click(function(e){
  958. $(e).find('input').trigger('click');
  959. });
  960. $('div.week1 > div').click(function(e){
  961. e.stopPropagation();
  962. if ($('div.bstart input.blink_me').length == 0){
  963. alert("nothing to copy");
  964. return;
  965. }
  966. if (!confirm ('copy to next week'))
  967. return;
  968. var jobs_el = [];
  969. $('div.bstart input.blink_me').each(function(i,e){
  970. var r = copy_single_day_to_next_week(e);
  971. if (r != false)
  972. jobs_el.push(r.el);
  973. });
  974. show_jobs(jobs_el);
  975. unblink_all_date();
  976. });
  977. $('div.week1,div.week2').click(function(e){
  978. e.stopPropagation();
  979. $(this).toggleClass('filtered');
  980. do_filter_workspace();
  981. });
  982. function copy_single_day_to_next_week(el){
  983. var tb = $(el).closest('div.divTable');
  984. if (tb.is(':visible')){
  985. var j = $(tb).data().job;
  986. var newj = clone_data_create_new_job(j.get_record_from_ui() , 7); // +7 days
  987. return newj;
  988. }
  989. return false;
  990. }
  991. function clone_data_create_new_job(val, num_of_shifted_days){
  992. var data = $.extend(true, {}, val);//make a copy
  993. num_of_shifted_days = typeof num_of_shifted_days !=='undefined'? num_of_shifted_days: 0;// 0 days
  994. //reset
  995. data.id='';
  996. data.ack = 0;
  997. data.rating = 0;
  998. if (is_valid_date_str(data.start)){
  999. var s = new Date(data.start);
  1000. var s1 = s.getDate() + num_of_shifted_days;
  1001. s = new Date(s.setDate(s1));
  1002. data.start = format_date_time(s);
  1003. }
  1004. if (is_valid_date_str(data.finish)){
  1005. var f = new Date(data.finish);
  1006. var f1 = f.getDate() + num_of_shifted_days;
  1007. f = new Date(f.setDate(f1));
  1008. data.finish = format_date_time(f);
  1009. }
  1010. var newj = new Job(data);
  1011. return newj;
  1012. }
  1013. function is_valid_date_str(val){
  1014. var d = new Date(val);
  1015. if (d.toString()== 'Invalid Date')
  1016. return false;
  1017. return true;
  1018. }
  1019. function blink_same_date_by_div(div){
  1020. var date = new Date($(div).find('span.weekday').data().date);
  1021. blink_same_date(date);
  1022. }
  1023. function blink_same_date(date){
  1024. var strDate = format_date(date); //yyyy-mm-dd
  1025. var els=[];
  1026. unblink_all_date();
  1027. $('div.bstart input').each(function(i,e){
  1028. if ( $(e).is(":visible") ){
  1029. var value = $(e).attr('value');
  1030. if( -1 != value.indexOf(strDate) ) //found
  1031. {
  1032. els.push(e);
  1033. $(e).addClass('blink_me');
  1034. }
  1035. }
  1036. });
  1037. }
  1038. function unblink_all_date(){
  1039. $('div.bstart input').removeClass('blink_me');
  1040. }
  1041. $('div.sheettitle h1 span').click(function(){
  1042. reset_title_to_today();
  1043. load_timesheet();
  1044. });
  1045. function reset_title_to_today(){
  1046. set_today();
  1047. init_weekdays();
  1048. set_week_number();
  1049. }
  1050. var debounced_load_timesheet = debounce(load_timesheet,1000);
  1051. function load_timesheet()
  1052. {
  1053. clear_workspace();
  1054. var first = $('span[name="w1d1"]').data().date;
  1055. var last = $('span[name="w2d7"]').data().date;
  1056. $.post(bts().ajax_url, { // POST request
  1057. _ajax_nonce: bts().nonce, // nonce
  1058. action: "list_jobv1", // action
  1059. start: format_date(first),
  1060. finish: format_date(last),
  1061. }, function(response, status, xhr){
  1062. if (response.status =='success'){
  1063. display_jobs_after_staff_client_tos_info_ready(response);
  1064. }else{
  1065. alert('error loading job');
  1066. hide_loading_jobs();
  1067. }
  1068. });
  1069. }
  1070. function display_jobs_after_staff_client_tos_info_ready(response)
  1071. {
  1072. var b = bts();
  1073. if ((typeof b.staff_map != "undefined" && Object.keys(b.staff_map).length > 0) &&
  1074. (typeof b.client_map != "undefined" && Object.keys(b.client_map).length > 0) &&
  1075. (typeof b.tos != "undefined" && Object.keys(b.tos).length > 0 ) &&
  1076. (typeof b.earnings_rate != "undefined" && Object.keys(b.earnings_rate).length > 0 ))
  1077. {
  1078. var job_map={};
  1079. //map data for each jobTable
  1080. response.jobs.forEach(function(e){
  1081. job_derive_attr(e);
  1082. job_map[e.id] = e;
  1083. });
  1084. bts().job_map = job_map;
  1085. //we do works, load timesheets
  1086. var template = $("#jobv1_item").html();
  1087. var html = Mustache.render(template, response);
  1088. $('div.workspace').append(html);
  1089. hide_loading_jobs();
  1090. //filter it if reqired
  1091. debounced_filter_workspace();
  1092. return;
  1093. }
  1094. //console.log('wating staff/client/tos info to be ready');
  1095. setTimeout(function(){
  1096. display_jobs_after_staff_client_tos_info_ready(response);
  1097. }, 500); //try it half seconds later
  1098. }
  1099. function job_derive_attr(e)
  1100. {
  1101. e.saved = true;
  1102. e.tos_name = bts().tos[e.tos].tos_full_str;
  1103. e.staff_name = bts().staff_map[e.staff].display_name;
  1104. e.client_name = bts().client_map[e.client].display_name;
  1105. e.rate_name = bts().earnings_rate[e.rate].RatePerUnit + "-" + bts().earnings_rate[e.rate].Name;
  1106. if (! has_txt_hour( bts().earnings_rate[e.rate].TypeOfUnits )){
  1107. e.non_hour = true;
  1108. e.brate_err = "Rate type is not Hours - Not allowed";
  1109. }
  1110. if (job_is_week1(e.start)){
  1111. e.is_week1=true;
  1112. }else if (job_is_week2(e.start)){
  1113. e.is_week2=true;
  1114. }
  1115. if (e.ack != 0){
  1116. e.is_confirmed = true;
  1117. }
  1118. }
  1119. function has_txt_hour(str){
  1120. var s = str.toLowerCase();
  1121. return s.indexOf('hour') != -1;
  1122. }
  1123. function show_jobs(job_els, in_ajax){
  1124. if (job_els.length >0){
  1125. $('div.workspace').append(job_els);
  1126. job_els[0].get(0).scrollIntoView();
  1127. console.log('loading ... %d jobs', job_els.length);
  1128. }
  1129. if (typeof in_ajax =='undefined')
  1130. dtp_init();
  1131. }
  1132. function format_date(date){
  1133. var dd = date.getDate();
  1134. var mm = date.getMonth() + 1; //January is 0!
  1135. var yyyy = date.getFullYear();
  1136. if (dd < 10) {
  1137. dd = '0' + dd;
  1138. }
  1139. if (mm < 10) {
  1140. mm = '0' + mm;
  1141. }
  1142. return yyyy + '-' + mm + '-' +dd ;
  1143. }
  1144. function format_date_time(date){
  1145. var strdate = format_date(date);
  1146. var hh = date.getHours();
  1147. if (hh<10){
  1148. hh= '0' + hh;
  1149. }
  1150. var mm = date.getMinutes();
  1151. if (mm<10){
  1152. mm='0' + mm;
  1153. }
  1154. return strdate + ' ' + hh + ":" + mm;
  1155. }
  1156. function clear_workspace()//clear all timesheet jobs
  1157. {
  1158. $('div.workspace > div.divTable').remove();
  1159. //clear datetime picker
  1160. $('div.xdsoft_datetimepicker').remove();
  1161. //
  1162. show_loading_jobs();
  1163. }
  1164. $('div.workinghours').click(function(){
  1165. $('div.bts_message_button').trigger('click');
  1166. });
  1167. $('button[name="confirmschedule"]').click(function(){
  1168. if (!confirm('sending email to each staff for their job arrangement?'))
  1169. return;
  1170. $('div.bts_message .ult-overlay-close-inside').hide();
  1171. $('div.bts_message_button').trigger('click');
  1172. setTimeout(do_email_jobs, 2000);//2 seconds for dialog to popup
  1173. });
  1174. $('button[name="confirmschedule"]').mouseenter(function(){
  1175. $('div.week2').addClass('blink_me');
  1176. })
  1177. $('button[name="confirmschedule"]').mouseleave(function(){
  1178. $('div.week2').removeClass('blink_me');
  1179. })
  1180. var debounced_filter_workspace = debounce(do_filter_workspace, 1000);
  1181. $(document).on('click','div.userlist', debounced_filter_workspace);
  1182. function do_filter_workspace(){
  1183. var staffs =[];
  1184. $('div.stafflist div.peopleitem :checked').each(function(i, e){
  1185. if ($(e).parent().is(':visible')){
  1186. var id = $(e).parent().attr('data-id');
  1187. //console.log("%o, id=%s", e, id);
  1188. staffs.push(id.substring(1));
  1189. }
  1190. });
  1191. var clients =[];
  1192. $('div.clientlist div.peopleitem :checked').each(function(i, e){
  1193. if ($(e).parent().is(':visible')){
  1194. var id = $(e).parent().attr('data-id');
  1195. //console.log("%o, id=%s", e, id);
  1196. clients.push(id.substring(1));
  1197. }
  1198. });
  1199. filter_workspace(staffs, clients);
  1200. filter_workspace_by_weeks();
  1201. debounced_calculate();
  1202. }
  1203. function filter_workspace(staffs, clients){
  1204. //if both array is empty
  1205. if( (staffs === undefined || staffs.length ==0) &&
  1206. (clients===undefined || clients.length ==0)){
  1207. //show all
  1208. $('div.workspace div.divTable').show();
  1209. return;
  1210. }
  1211. //if staffs is empty, we only filter by client
  1212. if (staffs === undefined || staffs.length ==0){
  1213. filter_workspace_by_client(clients);
  1214. return;
  1215. }
  1216. //if clients is empty, we only filter by staff
  1217. if (clients===undefined || clients.length ==0){
  1218. filter_workspace_by_staff(staffs);
  1219. return;
  1220. }
  1221. //filter by both
  1222. filter_workspace_by_both(staffs, clients);
  1223. }
  1224. function filter_workspace_by_staff(staffs)
  1225. {
  1226. var class_name='to_be_shown';
  1227. //filter some of them;
  1228. staffs.forEach(function(e){
  1229. $('div.workspace div.jobTable[data-staff="' + e + '"]').addClass(class_name);
  1230. });
  1231. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1232. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1233. $('.' + class_name).removeClass(class_name);
  1234. }
  1235. function filter_workspace_by_client(clients)
  1236. {
  1237. var class_name='to_be_shown';
  1238. //filter some of them;
  1239. clients.forEach(function(e){
  1240. $('div.workspace div.jobTable[data-client="' + e + '"]').addClass(class_name);
  1241. });
  1242. $('div.workspace div.jobTable.' + class_name).fadeIn();
  1243. $('div.workspace div.jobTable:not(.'+ class_name +')').hide();
  1244. $('.' + class_name).removeClass(class_name);
  1245. }
  1246. function filter_workspace_by_both(staffs, clients)
  1247. {
  1248. var class_name='hide';
  1249. //filter some of them;
  1250. clients.forEach(function(e){
  1251. $('div.workspace div.jobTable:not([data-client="' + e + '"])').addClass(class_name);
  1252. });
  1253. staffs.forEach(function(e){
  1254. $('div.workspace div.jobTable:not([data-staff="' + e + '"])').addClass(class_name);
  1255. });
  1256. $('div.workspace div.jobTable.' + class_name).hide();
  1257. $('div.workspace div.jobTable:not(.'+ class_name +')').show();
  1258. $('.' + class_name).removeClass(class_name);
  1259. }
  1260. function filter_workspace_by_weeks(){
  1261. var hide_week1 = $('div.week1').hasClass('filtered');
  1262. var hide_week2 = $('div.week2').hasClass('filtered');
  1263. if (hide_week1 && hide_week2 ){
  1264. alert("You are hiding both weeks");
  1265. $('div.jobTable').show();//show non-week1 and none-week2
  1266. $('div.jobTable.week1job,div.jobTable.week2job').hide(); //hide week1 or week2;
  1267. }else if (hide_week1){
  1268. $('div.jobTable:not(.week1job)').show();//show non-week1
  1269. $('div.jobTable.week1job').hide(); //hide week1;
  1270. }else if (hide_week2){
  1271. $('div.jobTable.week2job').hide(); //show non-week2
  1272. $('div.jobTable:not(.week2job)').show(); //hide week2
  1273. }
  1274. }
  1275. var debounced_calculate = debounce(calculate_total_hour_and_money, 500);
  1276. function calculate_total_hour_and_money()
  1277. {
  1278. //init pays for all staff;
  1279. var pays={
  1280. total: 0,
  1281. hours: 0,
  1282. };
  1283. $('.stafflist > div.peopleitem').each(function(i,e){
  1284. var people = $(this).data().obj;
  1285. people.reset_summary();
  1286. });
  1287. $('div.workspace > .divTable.jobTable').each(function(i,e){
  1288. if (! $(e).is(':visible'))
  1289. return;
  1290. var id = $(e).attr('data-id');
  1291. var job = bts().job_map[id];
  1292. if (typeof job === 'undefined')
  1293. return;
  1294. var ps = job_get_payment_summary(job);
  1295. pays.total += ps.money;
  1296. pays.hours += ps.hour;
  1297. var staff = job.staff;
  1298. var people = bts().staff_people[staff]; //class People
  1299. if (people !=false)
  1300. people.add_payment_summary(ps);
  1301. });
  1302. set_wages(pays.total.toFixed(2));
  1303. set_working_hours(pays.hours.toFixed(2));
  1304. }
  1305. function job_get_payment_summary(job)
  1306. {
  1307. var result ={};
  1308. result.ot = job_get_is_high_pay(job);
  1309. result.hour = job_get_working_duration(job);
  1310. result.money = job_get_wages(job);
  1311. return result;
  1312. }
  1313. function job_get_is_high_pay(job)
  1314. {
  1315. var rate_info = bts().earnings_rate[job.rate];
  1316. var keywords =bts().high_pay_keywords;
  1317. var found = false;
  1318. keywords.forEach(function(e){
  1319. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  1320. found = true;
  1321. });
  1322. return found;
  1323. }
  1324. function job_get_working_duration(job)
  1325. {
  1326. //finish - start
  1327. var f = new Date(job.finish);
  1328. var s = new Date(job.start);
  1329. var diff = f.getTime() - s.getTime();
  1330. var hours = Math.floor(diff / 1000 / 60 / 60);
  1331. diff -= hours * 1000 * 60 * 60;
  1332. var minutes = Math.floor(diff / 1000 / 60);
  1333. var minute_to_hour = minutes/60;
  1334. return (hours + minute_to_hour);
  1335. }
  1336. function job_get_wages(job)
  1337. {
  1338. var hour = job_get_working_duration(job);
  1339. var rate_info = bts().earnings_rate[job.rate];
  1340. if ( has_txt_hour( bts().earnings_rate[job.rate].TypeOfUnits ) )
  1341. {
  1342. return hour * rate_info.RatePerUnit;
  1343. }else{
  1344. return 1 * rate_info.RatePerUnit;
  1345. }
  1346. }
  1347. function find_staff(login)
  1348. {
  1349. var d = $('#p'+login).data();
  1350. if (typeof d === 'undefined')
  1351. return false;
  1352. return $('#p'+login).data().obj;
  1353. }
  1354. $(document).on('change', '.divTableRow input[name="ack"]', function(e) {
  1355. var el = $(this).closest('.jobTable');
  1356. var data = el.data();
  1357. data.ack = e.checked? 1: 0;
  1358. job_mark_dirty(el);
  1359. });
  1360. function job_mark_dirty(el)
  1361. {
  1362. el.removeClass('saved');
  1363. el.addClass('dirty');
  1364. }
  1365. function job_mark_clean(el)
  1366. {
  1367. el.addClass('saved');
  1368. el.removeClass('dirty');
  1369. }
  1370. function job_is_week1(t)
  1371. {
  1372. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  1373. var w1_end = new Date($('span[name="w1d7"]').data().date);
  1374. w1_begin.setHours(0,0,0,0);
  1375. w1_end.setHours(23,59,59);
  1376. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  1377. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  1378. var me = new Date(t);
  1379. return (w1_begin <= me && me <= w1_end );
  1380. }
  1381. function job_is_week2(t)
  1382. {
  1383. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1384. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1385. w2_begin.setHours(0,0,0,0);
  1386. w2_end.setHours(23,59,59);
  1387. var me = new Date(t);
  1388. return (w2_begin <= me && me <= w2_end );
  1389. }
  1390. function init_ts(){
  1391. xero(false);
  1392. wifi(false);
  1393. csv(false);
  1394. show_loading_jobs();
  1395. list_staff();
  1396. list_clients();
  1397. list_tos();
  1398. ajax_earning_rate();
  1399. //setTimeout(list_staff, 5000); // for testing delayed loading of jobs only
  1400. //setTimeout(list_clients, 8000); // for testing delayed loading of jobs only
  1401. //setTimeout(list_tos, 10000); // for testing delayed loading of jobs only
  1402. init_user_search();
  1403. reset_title_to_today();
  1404. load_timesheet();
  1405. }
  1406. function do_email_jobs()
  1407. {
  1408. var selector = 'div.bts_message div.ult_modal-body';
  1409. $(selector).html('Analysis staff jobs ... ok');
  1410. var staff = bts().staff.slice(0);//copy this array;
  1411. var s = staff.pop();
  1412. //week2 start
  1413. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1414. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1415. var start = format_date(w2_begin);
  1416. var finish = format_date(w2_end);
  1417. function do_staff(){
  1418. var el = $('<p> Checking ' + s.firstname + "....</p>");
  1419. $(selector).append(el);
  1420. el[0].scrollIntoView();
  1421. $.post(bts().ajax_url, { // POST request
  1422. _ajax_nonce: bts().nonce, // nonce
  1423. action: "email_job", // action
  1424. staff : s.login,
  1425. start : start,
  1426. finish: finish,
  1427. }, function(response, status, xhr){
  1428. if (response.status == 'success'){
  1429. if (response.sent){
  1430. el.append('<span class="sent">' + response.emailstatus + '</span>');
  1431. }else{
  1432. el.append('<span class="nojob">' + response.emailstatus + '</span>');
  1433. }
  1434. }else{
  1435. el.append('<span class="error"> Error[' + response.error + ' ...]</span>');
  1436. }
  1437. }).fail(function(){
  1438. el.append('<span class="error">' + 'Network Error occured' + '</span>');
  1439. //clear staff pending list, stop further processing
  1440. s = [];
  1441. }).always(function(){//next staff
  1442. if (staff.length >0){
  1443. s = staff.pop();
  1444. setTimeout(do_staff, 100); //a short delay makes it looks nice
  1445. }else{
  1446. $('div.bts_message .ult-overlay-close-inside').show();
  1447. $('div.bts_message .ult-overlay-close-inside').addClass('blink_me');
  1448. $('div.week2').removeClass('blink_me');
  1449. $(selector).append('<span class="sent">All staff confirmed! </span>');
  1450. }
  1451. });
  1452. }
  1453. //execute
  1454. do_staff();
  1455. }
  1456. function ajax_earning_rate(){
  1457. $.post(bts().ajax_url, { // POST request
  1458. _ajax_nonce: bts().nonce, // nonce
  1459. action: "earnings_rate", // action
  1460. }, function(response, status, xhr){
  1461. bts().earnings_rate = {};
  1462. response.options.forEach(function(e){
  1463. bts().earnings_rate[e.EarningsRateID]=e;
  1464. });
  1465. //console.log("%o", bts().earnings_rate);
  1466. });
  1467. }
  1468. function check_duplicate()
  1469. {
  1470. var count= 0;
  1471. var to_be_delete=[];
  1472. //loop through jobs
  1473. for(var id1 in bts().job_map){
  1474. var job1 = bts().job_map[id1];
  1475. if (typeof job1.parent != 'undefined')
  1476. continue; //bypass it it has already found parents
  1477. job1.compared_as_master = true;//mark it
  1478. job1.duplicates={};
  1479. //console.log('investigating %s' , job1.id);
  1480. //match job2
  1481. for(var id2 in bts().job_map){
  1482. var job2 = bts().job_map[id2];
  1483. if (typeof job2.compared_as_master != 'undefined')
  1484. continue;
  1485. if (typeof job2.parent != "undefined")
  1486. continue; //it has already parent;
  1487. //console.log('comareing %s vs %s', job1.id, job2.id);
  1488. if (is_same_job(job1,job2)){
  1489. job2.parent = job1.id;
  1490. job1.duplicates[id2] = job2;
  1491. console.warn("found: %s = %s", job1.id, job2.id);
  1492. count++;
  1493. to_be_delete.push(id2);
  1494. }
  1495. }
  1496. }
  1497. console.log('all-done, found %d duplicates: %o', count, to_be_delete);
  1498. }
  1499. $('div.wages').click(check_duplicate);
  1500. function is_same_job(job1, job2)
  1501. {
  1502. if ( (job1.tos == job2.tos) &&
  1503. (job1.staff == job2.staff) &&
  1504. (job1.client == job2.client) &&
  1505. (job1.start == job2.start) &&
  1506. (job1.finish == job2.finish) )
  1507. {
  1508. return true;
  1509. }
  1510. return false;
  1511. }
  1512. $( ".boundary_datepicker" ).datepicker();
  1513. $( ".boundary_datepicker" ).datepicker("option", "dateFormat", "yy-mm-dd");
  1514. $(document).on('click', 'div.clientlist div[name="title"] a', function(e){
  1515. e.preventDefault();
  1516. e.stopPropagation();
  1517. var id = $(this).closest('label.peopleitem').attr('data-id').substring(1);
  1518. var str = 'https://acaresydncy.com.au/feedback_card/' + id;
  1519. var name = $(this).html();
  1520. if ( confirm ("Email feedback link of : " + name + "\n\n\n" + str + "\n\n\n to helen@acaresydney.com.au?")){
  1521. $.post(bts().ajax_url, { // POST request
  1522. _ajax_nonce: bts().nonce, // nonce
  1523. action: "email_feedback_url", // action
  1524. client : id,
  1525. }, function(response, status, xhr){
  1526. //alert('please check your email on the phone and SMS the link to your client');
  1527. }).fail(function(){
  1528. alert('network error ');
  1529. });
  1530. }
  1531. });
  1532. init_ts();
  1533. /*________________________________________________________________________*/
  1534. });
  1535. })(jQuery);
  1536. /*______________scrolling______________________________________________*/
  1537. jQuery(document).ready(function(){
  1538. var timeoutid =0;
  1539. jQuery('button.peoplelist[name="down"]').mousedown(function(){
  1540. var button = this;
  1541. timeoutid = setInterval(function(){
  1542. //console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  1543. jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
  1544. }, 100);
  1545. }).on('mouseup mouseleave', function(){
  1546. clearTimeout(timeoutid);
  1547. });
  1548. jQuery('button.peoplelist[name="up"]').mousedown(function(){
  1549. var button = this;
  1550. timeoutid = setInterval(function(){
  1551. //console.log("up scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  1552. jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
  1553. }, 100);
  1554. }).on('mouseup mouseleave', function(){
  1555. clearTimeout(timeoutid);
  1556. });
  1557. });