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

1442 lines
40KB

  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. response.users.forEach(function(u){
  138. var html = bts_staff_html(u);
  139. jQuery('div.stafflist').append(html);
  140. new People("#p" + u.login,'#staff_item', u);
  141. });
  142. hide_loading_staff();
  143. calculate_total_hour_and_money();
  144. }else{
  145. alert('error getting staff list');
  146. }
  147. });
  148. }
  149. function list_clients() {
  150. show_loading_client();
  151. $('div.clientlist div.peopleitem').remove(); //clear it
  152. $.post(bts().ajax_url, { // POST request
  153. _ajax_nonce: bts().nonce, // nonce
  154. action: "list_client", // action
  155. }, function(response, status, xhr){
  156. if (response.status =='success'){
  157. bts().client = response.users;
  158. response.users.forEach(function(u){
  159. hide_loading_client();
  160. var html = bts_client_html(u);
  161. jQuery('div.clientlist').append(html);
  162. new People("#p" + u.login, '#client_item' ,u);
  163. });
  164. }else{
  165. alert('error getting Client list');
  166. }
  167. });
  168. }
  169. function show_loading_staff(){
  170. jQuery('div.stafflist img').attr('src', bts().load_user_img).show();
  171. }
  172. function show_loading_client(){
  173. jQuery('div.clientlist img').attr('src', bts().load_user_img).show();
  174. }
  175. function hide_loading_staff(){
  176. jQuery('div.stafflist img').hide();
  177. }
  178. function hide_loading_client(){
  179. jQuery('div.clientlist img').hide();
  180. }
  181. function show_loading_jobs(){
  182. jQuery('div.workspace img').attr('src', bts().load_job_img).show();
  183. }
  184. function hide_loading_jobs(){
  185. jQuery('div.workspace img').hide();
  186. }
  187. function xero(t){
  188. if (t)
  189. $('div.xero i').show();
  190. else
  191. $('div.xero i').hide();
  192. }
  193. function wifi(t){
  194. if (t)
  195. $('div.wifi i').show();
  196. else
  197. $('div.wifi i').hide();
  198. }
  199. function init_user_search(){
  200. $('div.b_search input').keyup(debounce(function(e){
  201. filter_user(e.target);
  202. }, 500));
  203. }
  204. function filter_user(input){
  205. var value = $(input).attr('value');
  206. value = value.toLowerCase();
  207. var selector = get_selector_for_filter_people(input);
  208. $.each( $(selector).find('div.peopleitem'), function(index, e){
  209. //uncheck everyone
  210. $(e).find('input[type="checkbox"]').prop('checked', false);
  211. var html = $(e).find('div[name="title"] a').html();
  212. html = html.toLowerCase();
  213. if (-1 != html.indexOf(value)){//we find it;
  214. $(e).show();
  215. }else{
  216. $(e).hide();
  217. }
  218. });
  219. }
  220. function get_selector_for_filter_people(input){
  221. var selector='';
  222. var role = $(input).attr('placeholder');
  223. if (role == 'staff') //we filter staff
  224. selector = 'div.stafflist';
  225. else if (role = 'client')
  226. selector = 'div.clientlist';
  227. return selector;
  228. }
  229. $(document).on('click', 'div.divTableHead.bdelete', function(){
  230. add_new_empty_job();
  231. });
  232. function add_new_empty_job(){
  233. var o = new Job({empty:true});
  234. $('div.workspace').append(o.el);
  235. o.el.get(0).scrollIntoView();
  236. dtp_init();
  237. }
  238. $(document).on('click', 'div[name="copyschedule"]', function(e){
  239. e.stopPropagation();
  240. add_new_empty_job();
  241. });
  242. $(document).on('click', 'div.divTableCell.bdelete', function(){
  243. var job = $(this).closest('.divTable').data().job;
  244. var el = $(this).closest('div.divTable');
  245. if ( job.get_job_id() == '')
  246. el.remove();
  247. else{
  248. if (confirm('delete this job?')){
  249. $.post(bts().ajax_url, { // POST request
  250. _ajax_nonce: bts().nonce, // nonce
  251. action: "delete_job", // action
  252. jobid: job.data.id,
  253. }, function(response, status, xhr){
  254. if (response.status=='success'){
  255. el.addClass('blink_me');
  256. el.fadeOut(900);
  257. setTimeout(function(){
  258. el.remove();
  259. }, 900);
  260. }else{
  261. alert( 'error saving data, please check your network');
  262. }
  263. });
  264. }
  265. }
  266. });
  267. $(document).on('mouseenter', 'div.divTableCell', function(){
  268. $(this).closest('div.divTable').addClass('highlight');
  269. });
  270. $(document).on('mouseleave', 'div.divTableCell', function(){
  271. $(this).closest('div.divTable').removeClass('highlight');
  272. });
  273. $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
  274. var table = $(this).closest('div.divTable');
  275. table.data().job.do_save_record();
  276. });
  277. $(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
  278. //save all
  279. $('div.workspace span.ticon.ticon-save').each(function (i,e){
  280. if ($(this).is(":visible"))
  281. $(this).trigger('click');
  282. });
  283. });
  284. $(document).on('click', 'span.ticon.ticon-copy', function(){
  285. if (!confirm("make a copy of this job?"))
  286. return;
  287. var table = $(this).closest('div.divTable');
  288. var job = table.data().job;
  289. var newj = clone_data_create_new_job(job.get_record_from_ui());
  290. $('div.workspace').append(newj.el);
  291. newj.el.get(0).scrollIntoView();//make sure it's visible;
  292. newj.mark_highlight_me(1000);//for 1 second;
  293. dtp_init();
  294. });
  295. class Job{ //save data for the record, and display it as GUI
  296. constructor(data){
  297. var html = jQuery("#job_item").html();
  298. this.el = $(html);
  299. //jQuery('div.workspace').append(this.el);
  300. this.load_data(data);
  301. this.init_start_rating();
  302. }
  303. init_start_rating(){
  304. var self = this;
  305. this.el.find("div.brating span").click(function(){
  306. var r = $(this).attr('data-rating');
  307. self.mark_dirty();
  308. self.set_rating(r);
  309. })
  310. this.el.find("div.brating").mouseenter(function(){
  311. //change to all hollow star
  312. $(this).find('span').html('☆');
  313. });
  314. this.el.find("div.brating").mouseleave(function(){
  315. self.set_rating(self.data.rating);
  316. });
  317. }
  318. load_data(data)
  319. {
  320. //save to html element
  321. this.data = data;
  322. this.el.data({job:this, data:data});
  323. //draw GUI
  324. this.clear_err_msg();
  325. this.set_job_id(data.id);
  326. this.set_tos(data.tos);
  327. this.set_start(data.start);
  328. this.set_finish(data.finish);
  329. this.set_rate(data.rate);
  330. this.set_staff(data.staff);
  331. this.set_client(data.client);
  332. this.set_ack(data.ack);
  333. this.set_rating(data.rating);
  334. //draw GUI by other
  335. this.mark_dirty_on_new_record(data);
  336. this.mark_week_color();
  337. this.validate(); //also triggers mark errors
  338. }
  339. get_job_id(){
  340. return this.el.find('input[name="id"]').attr('value');
  341. }
  342. set_job_id(val){
  343. return this.el.find('input[name="id"]').attr('value', val);
  344. }
  345. get_tos()
  346. {
  347. return this.el.find('div.btos select').children("option:selected").val();
  348. }
  349. set_tos(val)
  350. {
  351. if (typeof(val) =="undefined")
  352. return;
  353. this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
  354. }
  355. get_start(){
  356. return this.el.find('div.bstart input').attr('value');
  357. }
  358. set_start(val)
  359. {
  360. if (typeof(val) =="undefined")
  361. return;
  362. this.el.find('div.bstart input').attr('value', val);
  363. }
  364. get_finish()
  365. {
  366. return this.el.find('div.bfinish input').attr('value');
  367. }
  368. set_finish(val)
  369. {
  370. if (typeof(val) == "undefined")
  371. return;
  372. this.el.find('div.bfinish input').attr('value', val);
  373. }
  374. get_rate()
  375. {
  376. return this.el.find('div.brate select').children("option:selected").val();
  377. }
  378. set_rate(val)
  379. {
  380. if (typeof(val) =="undefined")
  381. return;
  382. this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
  383. }
  384. get_staff()
  385. {
  386. return this.el.find('div.bstaff select').children("option:selected").val();
  387. }
  388. set_staff(val)
  389. {
  390. if (typeof(val) =="undefined")
  391. return;
  392. this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
  393. }
  394. get_client()
  395. {
  396. return this.el.find('div.bclient select').children("option:selected").val();
  397. }
  398. set_client(val)
  399. {
  400. if (typeof(val) =="undefined")
  401. return;
  402. this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
  403. }
  404. get_ack()
  405. {
  406. return this.el.find('div.bconfirmed input:checked').length > 0;
  407. }
  408. set_ack(val)
  409. {
  410. if (typeof(val) =="undefined")
  411. return;
  412. return this.el.find('div.bconfirmed input').prop('checked', val!=0);
  413. }
  414. get_rating(){
  415. var count =0;
  416. this.el.find('div.brating span').each(function(i,e){
  417. if ($(e).html()=='★')
  418. count +=1;
  419. });
  420. return count;
  421. }
  422. set_rating(num){
  423. if (!(1 <= num && num <=5))
  424. return;
  425. this.el.find('div.brating span').each(function(i,e){
  426. var rating = $(e).attr('data-rating');
  427. var rating = parseInt(rating);
  428. if (rating <= num)
  429. $(e).html('★');
  430. else
  431. $(e).html('☆');
  432. });
  433. }
  434. get_record_from_ui(){
  435. var record = {};
  436. record.id = this.get_job_id();
  437. record.tos = this.get_tos();
  438. record.start = this.get_start();
  439. record.finish = this.get_finish();
  440. record.rate = this.get_rate();
  441. record.staff = this.get_staff();
  442. record.client = this.get_client();
  443. record.ack = this.get_ack();
  444. record.rating = this.get_rating();
  445. return record;
  446. }
  447. do_save_record(){
  448. var self = this;
  449. $.post(bts().ajax_url, { // POST request
  450. _ajax_nonce: bts().nonce, // nonce
  451. action: "save_job", // action
  452. record: this.get_record_from_ui(),
  453. }, function(response, status, xhr){
  454. if (response.status=='success'){
  455. self.load_data(response.newdata);
  456. self.mark_saved();
  457. self.mark_old();
  458. }else{
  459. alert( 'error saving data, please check your network');
  460. }
  461. });
  462. }
  463. mark_dirty_on_new_record(data){
  464. if (typeof(data.id) === 'undefined' || data.id == ''){
  465. this.mark_dirty();
  466. this.mark_new();
  467. }
  468. else{
  469. this.mark_saved();
  470. }
  471. }
  472. mark_dirty() //need save
  473. {
  474. var d = this.el.find('.bsave');
  475. d.removeClass('saved');
  476. d.addClass('blink_me');
  477. setTimeout(function(){
  478. d.removeClass('blink_me');
  479. d.removeClass('saved');
  480. },1000);
  481. }
  482. mark_saved()
  483. {
  484. var d = this.el.find('.bsave');
  485. d.addClass('blink_me');
  486. setTimeout(function(){
  487. d.removeClass('blink_me');
  488. d.addClass('saved');
  489. },1000);
  490. }
  491. //newly created empty record
  492. mark_new()
  493. {
  494. this.el.addClass('emptyrecord');
  495. }
  496. mark_old()
  497. {
  498. this.el.removeClass('emptyrecord');
  499. }
  500. mark_highlight_me(ms){
  501. this.el.addClass('blink_me');
  502. this.el.addClass('highlight');
  503. this.el.addClass('newcopy');
  504. var self = this;
  505. setTimeout(function(){
  506. self.el.removeClass('blink_me');
  507. self.el.removeClass('highlight');
  508. self.el.removeClass('newcopy');
  509. },ms);
  510. }
  511. is_start_valid(){
  512. var s = this.get_start();
  513. return is_valid_date_str(s);
  514. }
  515. is_finish_valid(){
  516. var f = this.get_finish();
  517. if (!is_valid_date_str(f))
  518. return false;
  519. }
  520. is_finish_resonable(){
  521. var f = this.get_finish();
  522. if (!is_valid_date_str(f))
  523. return false;
  524. var s = this.get_start();
  525. s = new Date(s);
  526. f = new Date(f);
  527. return (s < f);
  528. }
  529. validate()
  530. {
  531. var ok_time = this.validate_start() &&
  532. this.validate_finish(); //finish might not be executed, if start is wrong
  533. var ok_tos = this.validate_tos(); //make sure this validate is executed;
  534. var ok_rate = this.validate_rate() ; //make sure this validate is executed
  535. var ok = ok_time && ok_tos && ok_rate;
  536. if (ok){
  537. this.el.removeClass('invalidjob');
  538. }else{
  539. this.el.addClass('invalidjob');
  540. }
  541. return ok;
  542. }
  543. validate_start(){
  544. var str = this.get_start();
  545. if ( is_valid_date_str(str) ){
  546. this.mark_start_valid();
  547. this.set_err_msg_start('');
  548. return true;
  549. }else{
  550. this.mark_start_invalid();
  551. this.set_err_msg_start('wrong date');
  552. return false;
  553. }
  554. }
  555. validate_finish()
  556. {
  557. var str = this.get_finish();
  558. if (! is_valid_date_str(str)){
  559. this.set_err_msg_finish('wrong date');
  560. this.mark_finish_invalid();
  561. return false;
  562. }
  563. if (!this.is_finish_resonable()){
  564. this.set_err_msg_finish("older than start")
  565. this.mark_finish_invalid();
  566. return false;
  567. }
  568. this.mark_finish_valid();
  569. this.set_err_msg_finish('');
  570. return true;
  571. }
  572. validate_rate()
  573. {
  574. var rate_info = this.get_rate_info_by_id(this.get_rate());
  575. if ( rate_info.RatePerUnit <= 0){
  576. this.set_err_msg_rate('bad rate');
  577. this.mark_rate_invalid();
  578. return false;
  579. }
  580. // if (this.get_rate() != this.data.rate){
  581. // this.set_err_msg_rate('rate@Xero inactive ' + this.data.rate);
  582. // this.mark_rate_invalid();
  583. // this.mark_dirty();
  584. // return false;
  585. // }
  586. this.set_err_msg_rate('');
  587. this.mark_rate_valid();
  588. return true;
  589. }
  590. validate_tos(){
  591. // if (this.get_tos() != this.data.tos){
  592. // this.set_err_msg_tos('require NDIS ' + this.data.tos);
  593. // this.mark_tos_invalid();
  594. // this.mark_dirty();
  595. // console.log('tos mark dirty');
  596. // return false;
  597. // }
  598. this.set_err_msg_tos('');
  599. this.mark_tos_valid();
  600. return true;
  601. }
  602. clear_err_msg(){
  603. this.el.find('.divTableRow.errmsg > div').html('');
  604. }
  605. set_err_msg_start(str)
  606. {
  607. this.el.find('div.bstart_err').html(str);
  608. }
  609. set_err_msg_finish(str)
  610. {
  611. this.el.find('div.bfinish_err').html(str);
  612. }
  613. set_err_msg_rate(str)
  614. {
  615. this.el.find('div.brate_err').html(str);
  616. }
  617. set_err_msg_save(str)
  618. {
  619. this.el.find('div.bsave_err').html(str);
  620. }
  621. set_err_msg_tos(str)
  622. {
  623. this.el.find('div.btos_err').html(str);
  624. }
  625. mark_tos_valid(){
  626. this.el.find('div.btos select').removeClass('invalid');
  627. }
  628. mark_tos_invalid(){
  629. this.el.find('div.btos select').addClass('invalid');
  630. }
  631. mark_start_valid(){
  632. this.el.find('div.bstart input').removeClass('invalid');
  633. }
  634. mark_start_invalid(){
  635. this.el.find('div.bstart input').addClass('invalid');
  636. }
  637. mark_finish_valid(){
  638. this.el.find('div.bfinish input').removeClass('invalid');
  639. }
  640. mark_finish_invalid(){
  641. this.el.find('div.bfinish input').addClass('invalid');
  642. }
  643. mark_rate_valid(){
  644. this.el.find('div.brate select').removeClass('invalid');
  645. }
  646. mark_rate_invalid(){
  647. this.el.find('div.brate select').addClass('invalid');
  648. }
  649. mark_week_color(){
  650. this.el.find('div.brating').removeClass('week1color');
  651. this.el.find('div.brating').removeClass('week2color');
  652. if (this.is_week1()){
  653. this.el.find('div.brating').addClass('week1color');
  654. }else if (this.is_week2()){
  655. this.el.find('div.brating').addClass('week2color');
  656. }
  657. }
  658. is_week1()
  659. {
  660. var w1_begin = new Date($('span[name="w1d1"]').data().date) ;
  661. var w1_end = new Date($('span[name="w1d7"]').data().date);
  662. w1_begin.setHours(0,0,0,0);
  663. w1_end.setHours(23,59,59);
  664. //console.log("week1 begin %o, end %o", w1_begin, w1_end);
  665. //w1_end = new Date (w1_end.setDate(w1_end.getDate()+1)); //from 00:00 to 23:59;
  666. var me = new Date(this.data.start);
  667. return (w1_begin <= me && me <= w1_end );
  668. }
  669. is_week2()
  670. {
  671. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  672. var w2_end = new Date($('span[name="w2d7"]').data().date);
  673. w2_begin.setHours(0,0,0,0);
  674. w2_end.setHours(23,59,59);
  675. var me = new Date(this.data.start);
  676. return (w2_begin <= me && me <= w2_end );
  677. }
  678. get_payment_summary(){
  679. var result ={};
  680. result.ot = this.get_is_high_pay();
  681. result.hour = this.get_working_duration();
  682. result.money = this.get_wages();
  683. return result;
  684. }
  685. get_is_high_pay()
  686. {
  687. var rate_info = this.get_rate_info_by_id(this.get_rate());
  688. return this.is_high_pay_hour(rate_info);
  689. }
  690. get_working_duration()
  691. {
  692. //finish - start
  693. var f = new Date(this.get_finish());
  694. var s = new Date(this.get_start());
  695. var diff = f.getTime() - s.getTime();
  696. var hours = Math.floor(diff / 1000 / 60 / 60);
  697. diff -= hours * 1000 * 60 * 60;
  698. var minutes = Math.floor(diff / 1000 / 60);
  699. var minute_to_hour = minutes/60;
  700. return (hours + minute_to_hour);
  701. }
  702. get_wages(){
  703. var hour = this.get_working_duration();
  704. var rate_info = this.get_rate_info_by_id(this.get_rate());
  705. return hour * rate_info.RatePerUnit;
  706. }
  707. get_rate_info_by_id(id){
  708. var rate_info = {};
  709. var rates = bts().earnings_rate;
  710. for(var i =0; i< rates.length; i++){
  711. var r = rates[i];
  712. if(r.EarningsRateID == id){
  713. rate_info = $.extend(true,{}, r);//make a copy
  714. break;
  715. }
  716. }
  717. return rate_info;
  718. }
  719. is_high_pay_hour(rate_info){
  720. var keywords =bts().high_pay_keywords;
  721. var found = false;
  722. keywords.forEach(function(e){
  723. if (-1 != rate_info.Name.toLowerCase().indexOf(e.toLowerCase()) )
  724. found = true;
  725. });
  726. return found;
  727. }
  728. }//end of class Job
  729. //global GUI summary
  730. function get_wages()
  731. {
  732. var txt = $('div.wages div').html();
  733. return parseInt(txt);
  734. }
  735. function set_wages(num){
  736. $('div.wages div').html(num);
  737. }
  738. function set_working_hours(num){
  739. $('input#woh').attr('value', num);
  740. }
  741. function get_working_hours(){
  742. var txt = $('input#woh').attr('value');
  743. return parseFloat(txt);
  744. }
  745. //modal box
  746. function set_modal_title(selector, title){
  747. var s = 'div.bts_'+ selector +' .ult_modal-title';
  748. $(s).html(title);
  749. }
  750. function set_modal_content(selector, content){
  751. var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
  752. $(s).html(content);
  753. }
  754. function open_modal (selector){
  755. var s='div.bts_'+selector+'_button';
  756. $(s).trigger('click');
  757. }
  758. // setTimeout(function(){
  759. // set_modal_title('warning', 'suck title');
  760. // set_modal_content('warning', 'fucking details');
  761. // //open_modal('warning');
  762. // }, 1000);
  763. //
  764. // setTimeout(function(){
  765. // set_modal_title('error', 'error title');
  766. // set_modal_content('error', 'error details');
  767. // //open_modal('error');
  768. // }, 5000);
  769. $(document).on('mouseenter', 'div.week1 div', function(){
  770. $(this).addClass('blink_me');
  771. get_week2_partner(this).addClass('blink_me');
  772. blink_same_date_by_div(this);
  773. });
  774. $(document).on('mouseleave', 'div.week1 div', function(){
  775. $(this).removeClass('blink_me');
  776. get_week2_partner(this).removeClass('blink_me');
  777. unblink_all_date();
  778. });
  779. function get_week2_partner(div){
  780. var index = $(div).index()+1;
  781. return $('div.week2 div:nth-child('+index+')');
  782. }
  783. function init_weekdays(){
  784. var curr = new Date; // get current date
  785. // First day is the day of the month - the day of the week
  786. var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
  787. var last = first + 6; // last day is the first day + 6
  788. //var firstday = new Date(curr.setDate(first)); //Mon
  789. //var lastday = new Date(curr.setDate(last)); //Sun
  790. var pos = 1; //first lot
  791. for (var i=first; i<=last; i++)
  792. {
  793. var now = new Date;
  794. var d1 = new Date(now.setDate(i));
  795. now = new Date;
  796. var d2 = new Date(now.setDate(i+7));
  797. set_day_number(1,pos, d1); //week 1
  798. set_day_number(2,pos, d2); //week 2
  799. pos +=1;
  800. }
  801. }
  802. function set_day_number(week, index, date){
  803. var selector = 'span[name="w'+week+'d'+index+'"]';
  804. $(selector).html(date.getDate());
  805. $(selector).data({date:date});
  806. console.log('set w%d-d%d %o', week,index,date);
  807. }
  808. function set_today(){
  809. var selector = 'div.sheettitle span[name="today"]';
  810. var curr = new Date;
  811. $(selector).html(format_date(curr));
  812. }
  813. Date.prototype.get_week_number = function(){
  814. var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
  815. var dayNum = d.getUTCDay() || 7;
  816. d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  817. var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
  818. return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
  819. };
  820. function set_week_number(){
  821. var date = $('span[name="w1d1"]').data().date;
  822. //console.log("date %o", date);
  823. var num = date.get_week_number();
  824. $('div.weekly span[name="week1"]').html(num);
  825. $('div.weekly span[name="week2"]').html(num+1);
  826. }
  827. function number_of_unsaved_job(){
  828. var count =0;
  829. var total_job = $('div.bsave').length -1;//remove table header
  830. var total_saved = $('div.bsave.saved').length;
  831. var empty = $('div.emptyrecord').length;
  832. count = total_job - total_saved - empty;
  833. return count;
  834. }
  835. $('div.prevweek.left').click(function(){
  836. if (number_of_unsaved_job() > 0){
  837. if(!confirm("you have unsaved jobs, proceed will lost them"))
  838. return;
  839. }
  840. $('div.weekdays span.weekday').each(function(i, e){
  841. var date = $(e).data().date;
  842. var newdate = new Date(date.setDate(date.getDate() -7 ));
  843. $(e).html(newdate.getDate());
  844. $(e).data({data:newdate});
  845. });
  846. set_week_number();
  847. load_timesheet();
  848. });
  849. $('div.nextweek.right').click(function(){
  850. if (number_of_unsaved_job() > 0){
  851. if(!confirm("you have unsaved jobs,proceed will lost them"))
  852. return;
  853. }
  854. $('div.weekdays span.weekday').each(function(i, e){
  855. var date = $(e).data().date;
  856. var newdate = new Date(date.setDate(date.getDate() +7 ));
  857. $(e).html(newdate.getDate());
  858. $(e).data({data:newdate});
  859. });
  860. set_week_number();
  861. load_timesheet();
  862. });
  863. $('div.weekly div.weekname.prev').click(function(){
  864. if (!confirm ('copy entire week to next week? '))
  865. return;
  866. var jobs = [];
  867. var job_els =[];
  868. $('div.week1 >div').each(function(i,e){
  869. var date = new Date($(e).find('span.weekday').data().date);
  870. var strDate = format_date(date); //yyyy-mm-dd
  871. $('div.bstart input').each(function(i,e){
  872. var value = $(e).attr('value');
  873. if( -1 != value.indexOf(strDate) ) //found
  874. {
  875. var el = $(e).closest('div.divTable');
  876. if (el.is(":visible")){
  877. var j = el.data().job;
  878. var newj = clone_data_create_new_job(j.get_record_from_ui(),7);//add 7 days
  879. job_els.push(newj.el);
  880. }
  881. }
  882. });
  883. });
  884. show_jobs(job_els);
  885. debounced_calculate();
  886. });
  887. $('div.weekly div.weekname.next').click(function(){
  888. alert('you can only copy from past to future (left to right)');
  889. });
  890. $('div.week1 > div').click(function(e){
  891. e.stopPropagation();
  892. if ($('div.bstart input.blink_me').length == 0){
  893. alert("nothing to copy");
  894. return;
  895. }
  896. if (!confirm ('copy to next week'))
  897. return;
  898. var jobs_el = [];
  899. $('div.bstart input.blink_me').each(function(i,e){
  900. var r = copy_single_day_to_next_week(e);
  901. if (r != false)
  902. jobs_el.push(r.el);
  903. });
  904. show_jobs(jobs_el);
  905. unblink_all_date();
  906. });
  907. $('div.week1,div.week2').click(function(e){
  908. e.stopPropagation();
  909. $(this).toggleClass('filtered');
  910. do_filter_workspace();
  911. });
  912. function copy_single_day_to_next_week(el){
  913. var tb = $(el).closest('div.divTable');
  914. if (tb.is(':visible')){
  915. var j = $(tb).data().job;
  916. var newj = clone_data_create_new_job(j.get_record_from_ui() , 7); // +7 days
  917. return newj;
  918. }
  919. return false;
  920. }
  921. function clone_data_create_new_job(val, num_of_shifted_days){
  922. var data = $.extend(true, {}, val);//make a copy
  923. num_of_shifted_days = typeof num_of_shifted_days !=='undefined'? num_of_shifted_days: 0;// 0 days
  924. //reset
  925. data.id='';
  926. data.ack = 0;
  927. data.rating = 0;
  928. if (is_valid_date_str(data.start)){
  929. var s = new Date(data.start);
  930. var s1 = s.getDate() + num_of_shifted_days;
  931. s = new Date(s.setDate(s1));
  932. data.start = format_date_time(s);
  933. }
  934. if (is_valid_date_str(data.finish)){
  935. var f = new Date(data.finish);
  936. var f1 = f.getDate() + num_of_shifted_days;
  937. f = new Date(f.setDate(f1));
  938. data.finish = format_date_time(f);
  939. }
  940. var newj = new Job(data);
  941. return newj;
  942. }
  943. function is_valid_date_str(val){
  944. var d = new Date(val);
  945. if (d.toString()== 'Invalid Date')
  946. return false;
  947. return true;
  948. }
  949. function blink_same_date_by_div(div){
  950. var date = new Date($(div).find('span.weekday').data().date);
  951. blink_same_date(date);
  952. }
  953. function blink_same_date(date){
  954. var strDate = format_date(date); //yyyy-mm-dd
  955. var els=[];
  956. unblink_all_date();
  957. $('div.bstart input').each(function(i,e){
  958. if ( $(e).is(":visible") ){
  959. var value = $(e).attr('value');
  960. if( -1 != value.indexOf(strDate) ) //found
  961. {
  962. els.push(e);
  963. $(e).addClass('blink_me');
  964. }
  965. }
  966. });
  967. }
  968. function unblink_all_date(){
  969. $('div.bstart input').removeClass('blink_me');
  970. }
  971. $('div.sheettitle h1').click(function(){
  972. reset_title_to_today();
  973. load_timesheet();
  974. });
  975. function reset_title_to_today(){
  976. set_today();
  977. init_weekdays();
  978. set_week_number();
  979. }
  980. function load_timesheet()
  981. {
  982. clear_workspace();
  983. var first = $('span[name="w1d1"]').data().date;
  984. var last = $('span[name="w2d7"]').data().date;
  985. $.post(bts().ajax_url, { // POST request
  986. _ajax_nonce: bts().nonce, // nonce
  987. action: "list_job", // action
  988. start: format_date(first),
  989. finish: format_date(last),
  990. }, function(response, status, xhr){
  991. if (response.status =='success'){
  992. var job_els = [];
  993. response.jobs.forEach(function(job){
  994. //console.log('loading job... %o', job);
  995. var o = new Job(job);
  996. job_els.push(o.el);
  997. });
  998. show_jobs(job_els, 'in-ajax=true');
  999. //filter it if reqired
  1000. debounced_filter_workspace();
  1001. }else{
  1002. alert('error loading job');
  1003. }
  1004. hide_loading_jobs();
  1005. });
  1006. }
  1007. function show_jobs(job_els, in_ajax){
  1008. if (job_els.length >0){
  1009. $('div.workspace').append(job_els);
  1010. job_els[0].get(0).scrollIntoView();
  1011. console.log('loading ... %d jobs', job_els.length);
  1012. }
  1013. if (typeof in_ajax =='undefined')
  1014. dtp_init();
  1015. }
  1016. function format_date(date){
  1017. var dd = date.getDate();
  1018. var mm = date.getMonth() + 1; //January is 0!
  1019. var yyyy = date.getFullYear();
  1020. if (dd < 10) {
  1021. dd = '0' + dd;
  1022. }
  1023. if (mm < 10) {
  1024. mm = '0' + mm;
  1025. }
  1026. return yyyy + '-' + mm + '-' +dd ;
  1027. }
  1028. function format_date_time(date){
  1029. var strdate = format_date(date);
  1030. var hh = date.getHours();
  1031. if (hh<10){
  1032. hh= '0' + hh;
  1033. }
  1034. var mm = date.getMinutes();
  1035. if (mm<10){
  1036. mm='0' + mm;
  1037. }
  1038. return strdate + ' ' + hh + ":" + mm;
  1039. }
  1040. function clear_workspace()//clear all timesheet jobs
  1041. {
  1042. $('div.workspace > div.divTable').remove();
  1043. //clear datetime picker
  1044. $('div.xdsoft_datetimepicker').remove();
  1045. //
  1046. show_loading_jobs();
  1047. }
  1048. $('button[name="confirmschedule"]').click(function(){
  1049. if (!confirm('sending email to each staff for their job arrangement?'))
  1050. return;
  1051. $('div.bts_message .ult-overlay-close-inside').hide();
  1052. $('div.bts_message_button').trigger('click');
  1053. setTimeout(do_email_jobs, 2000);//2 seconds for dialog to popup
  1054. });
  1055. $('button[name="confirmschedule"]').mouseenter(function(){
  1056. $('div.week2').addClass('blink_me');
  1057. })
  1058. $('button[name="confirmschedule"]').mouseleave(function(){
  1059. $('div.week2').removeClass('blink_me');
  1060. })
  1061. var debounced_filter_workspace = debounce(do_filter_workspace, 1000);
  1062. $(document).on('click','div.userlist', debounced_filter_workspace);
  1063. function do_filter_workspace(){
  1064. var staffs =[];
  1065. $('div.stafflist div.peopleitem :checked').each(function(i, e){
  1066. if ($(e).parent().is(':visible')){
  1067. var id = $(e).parent().attr('data-id');
  1068. //console.log("%o, id=%s", e, id);
  1069. staffs.push(id.substring(1));
  1070. }
  1071. });
  1072. var clients =[];
  1073. $('div.clientlist div.peopleitem :checked').each(function(i, e){
  1074. if ($(e).parent().is(':visible')){
  1075. var id = $(e).parent().attr('data-id');
  1076. //console.log("%o, id=%s", e, id);
  1077. clients.push(id.substring(1));
  1078. }
  1079. });
  1080. filter_workspace(staffs, clients);
  1081. filter_workspace_by_weeks();
  1082. debounced_calculate();
  1083. }
  1084. function filter_workspace(staffs, clients){
  1085. //if both array is empty
  1086. if( (staffs === undefined || staffs.length ==0) &&
  1087. (clients===undefined || clients.length ==0)){
  1088. //show all
  1089. $('div.workspace div.divTable').show();
  1090. return;
  1091. }
  1092. //if staffs is empty, we only filter by client
  1093. if (staffs === undefined || staffs.length ==0){
  1094. filter_workspace_by_client(clients);
  1095. return;
  1096. }
  1097. //if clients is empty, we only filter by staff
  1098. if (clients===undefined || clients.length ==0){
  1099. filter_workspace_by_staff(staffs);
  1100. return;
  1101. }
  1102. //filter by both
  1103. filter_workspace_by_both(staffs, clients);
  1104. }
  1105. function filter_workspace_by_staff(staffs)
  1106. {
  1107. //filter some of them;
  1108. $('div.workspace div.divTable').each(function(i,e){
  1109. var job = $(e).data().job;
  1110. var s = job.get_staff();
  1111. if (staffs.indexOf(s) ==-1)
  1112. $(this).fadeOut();
  1113. else
  1114. $(this).fadeIn();
  1115. });
  1116. }
  1117. function filter_workspace_by_client(clients)
  1118. {
  1119. //filter some of them;
  1120. $('div.workspace div.divTable').each(function(i,e){
  1121. var job = $(e).data().job;
  1122. var c = job.get_client();
  1123. if (clients.indexOf(c) ==-1)
  1124. $(this).fadeOut();
  1125. else
  1126. $(this).fadeIn();
  1127. });
  1128. }
  1129. function filter_workspace_by_both(staffs, clients)
  1130. {
  1131. //filter some of them;
  1132. $('div.workspace div.divTable').each(function(i,e){
  1133. var job = $(e).data().job;
  1134. var s = job.get_staff();
  1135. var c = job.get_client();
  1136. if (staffs.indexOf(s) ==-1 || clients.indexOf(c) ==-1)
  1137. $(this).fadeOut();
  1138. else
  1139. $(this).fadeIn();
  1140. });
  1141. }
  1142. function filter_workspace_by_weeks(){
  1143. var hide_week1 = $('div.week1').hasClass('filtered');
  1144. var hide_week2 = $('div.week2').hasClass('filtered');
  1145. if (hide_week1 && hide_week2 ){
  1146. alert("You are hiding both weeks");
  1147. }
  1148. $('div.workspace div.divTable').each(function(i,e){
  1149. var job = $(e).data().job;
  1150. if ((hide_week1 && job.is_week1()) ||
  1151. (hide_week2 && job.is_week2()) ){
  1152. $(e).fadeOut();
  1153. }
  1154. });
  1155. }
  1156. var debounced_calculate = debounce(calculate_total_hour_and_money, 2000);
  1157. function calculate_total_hour_and_money()
  1158. {
  1159. //init pays for all staff;
  1160. var pays={
  1161. total: 0,
  1162. hours: 0,
  1163. };
  1164. $('.stafflist > div.peopleitem').each(function(i,e){
  1165. var people = $(this).data().obj;
  1166. people.reset_summary();
  1167. });
  1168. $('div.workspace > .divTable').each(function(i,e){
  1169. if (! $(e).is(':visible'))
  1170. return;
  1171. var job = $(e).data().job; //class Job
  1172. if (typeof job === 'undefined')
  1173. return;
  1174. var ps = job.get_payment_summary();
  1175. pays.total += ps.money;
  1176. pays.hours += ps.hour;
  1177. var staff = job.get_staff();
  1178. var people = find_staff(staff); //class People
  1179. if (people !=false)
  1180. people.add_payment_summary(ps);
  1181. });
  1182. set_wages(pays.total.toFixed(2));
  1183. set_working_hours(pays.hours.toFixed(2));
  1184. }
  1185. function find_staff(login)
  1186. {
  1187. var d = $('#p'+login).data();
  1188. if (typeof d === 'undefined')
  1189. return false;
  1190. return $('#p'+login).data().obj;
  1191. }
  1192. $(document).on('change', '.divTableRow select, .divTableRow input', function() {
  1193. var job = $(this).closest('.divTable').data().job;
  1194. job.validate();
  1195. job.mark_dirty();
  1196. debounced_calculate();
  1197. });
  1198. function init_ts(){
  1199. show_loading_jobs();
  1200. list_staff();
  1201. list_clients();
  1202. xero(false);
  1203. wifi(false);
  1204. init_user_search();
  1205. //ajax_earning_rate();
  1206. reset_title_to_today();
  1207. //load_timesheet();
  1208. }
  1209. function do_email_jobs()
  1210. {
  1211. var selector = 'div.bts_message div.ult_modal-body';
  1212. $(selector).html('Analysis staff jobs ... ok');
  1213. var staff = bts().staff.slice(0);//copy this array;
  1214. var s = staff.pop();
  1215. //week2 start
  1216. var w2_begin = new Date($('span[name="w2d1"]').data().date);
  1217. var w2_end = new Date($('span[name="w2d7"]').data().date);
  1218. var start = format_date(w2_begin);
  1219. var finish = format_date(w2_end);
  1220. function do_staff(){
  1221. var el = $('<p> Checking ' + s.firstname + "....</p>");
  1222. $(selector).append(el);
  1223. el[0].scrollIntoView();
  1224. $.post(bts().ajax_url, { // POST request
  1225. _ajax_nonce: bts().nonce, // nonce
  1226. action: "email_job", // action
  1227. staff : s.login,
  1228. start : start,
  1229. finish: finish,
  1230. }, function(response, status, xhr){
  1231. if (response.status == 'success'){
  1232. if (response.sent){
  1233. el.append('<span class="sent">' + response.emailstatus + '</span>');
  1234. }else{
  1235. el.append('<span class="nojob">' + response.emailstatus + '</span>');
  1236. }
  1237. }else{
  1238. el.append('<span class="error"> Error[' + response.error + ' ...]</span>');
  1239. }
  1240. }).fail(function(){
  1241. el.append('<span class="error">' + 'Network Error occured' + '</span>');
  1242. }).always(function(){//next staff
  1243. if (staff.length >0){
  1244. s = staff.pop();
  1245. setTimeout(do_staff, 100); //a short delay makes it looks nice
  1246. }else{
  1247. $('div.bts_message .ult-overlay-close-inside').show();
  1248. $('div.bts_message .ult-overlay-close-inside').addClass('blink_me');
  1249. $('div.week2').removeClass('blink_me');
  1250. $(selector).append('<span class="sent">All staff done! </span>');
  1251. }
  1252. });
  1253. }
  1254. //execute
  1255. do_staff();
  1256. }
  1257. // function ajax_earning_rate(){
  1258. // $.post(bts().ajax_url, { // POST request
  1259. // _ajax_nonce: bts().nonce, // nonce
  1260. // action: "earnings_rate", // action
  1261. // }, function(response, status, xhr){
  1262. // bts().earnings_rate = response;
  1263. // console.log("%o", bts().earnings_rate);
  1264. // });
  1265. // }
  1266. init_ts();
  1267. /*________________________________________________________________________*/
  1268. });
  1269. })(jQuery);
  1270. /*______________scrolling______________________________________________*/
  1271. jQuery(document).ready(function(){
  1272. var timeoutid =0;
  1273. jQuery('button.peoplelist[name="down"]').mousedown(function(){
  1274. var button = this;
  1275. timeoutid = setInterval(function(){
  1276. //console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  1277. jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
  1278. }, 100);
  1279. }).on('mouseup mouseleave', function(){
  1280. clearTimeout(timeoutid);
  1281. });
  1282. jQuery('button.peoplelist[name="up"]').mousedown(function(){
  1283. var button = this;
  1284. timeoutid = setInterval(function(){
  1285. //console.log("up scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  1286. jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
  1287. }, 100);
  1288. }).on('mouseup mouseleave', function(){
  1289. clearTimeout(timeoutid);
  1290. });
  1291. });