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

1193 lines
32KB

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