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

1378 lines
37KB

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