timesheet source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

720 lines
20KB

  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(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. }//end of class People
  68. function bts_staff_html(data){
  69. var template = $('#staff_item').html();
  70. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  71. r = head + '</div>' ;
  72. return r;
  73. }
  74. function bts_client_html(data){
  75. var template = $('#client_item').html();
  76. var head = '<div class="peopleitem" id="p'+ data.login +'">';
  77. r = head + '</div>' ;
  78. return r;
  79. }
  80. function sample_staff(){
  81. for (var i=1; i<100; i++){
  82. var sample_people = {
  83. login: '01515b52-6936-46b2-a000-9ad4cd7a5b50' +i,
  84. firstname: "first"+i,
  85. lastname: "last",
  86. mobile: '041122221' +i,
  87. email: 'abc@gmail.com' + i,
  88. wages: 0,
  89. hour: i,
  90. OT: 3,
  91. petrol: 50 +i,
  92. rating: Math.floor(Math.random() * Math.floor(5)),
  93. unconfirmedjob: Math.floor(Math.random() * Math.floor(30)),
  94. };
  95. var html = bts_staff_html(sample_people);
  96. jQuery('div.stafflist').append(html);
  97. new People("#p" + sample_people.login, sample_people);
  98. }
  99. }
  100. function list_staff() {
  101. show_loading_staff();
  102. $('div.stafflist div.peopleitem').remove();
  103. $.post(bts().ajax_url, { // POST request
  104. _ajax_nonce: bts().nonce, // nonce
  105. action: "list_staff", // action
  106. }, function(response, status, xhr){
  107. if (response.status =='success'){
  108. hide_loading_staff();
  109. response.users.forEach(function(u){
  110. var html = bts_staff_html(u);
  111. jQuery('div.stafflist').append(html);
  112. new People("#p" + u.login,'#staff_item', u);
  113. });
  114. }else{
  115. alert('error getting staff list');
  116. }
  117. });
  118. }
  119. function list_clients() {
  120. show_loading_client();
  121. $('div.clientlist div.peopleitem').remove(); //clear it
  122. $.post(bts().ajax_url, { // POST request
  123. _ajax_nonce: bts().nonce, // nonce
  124. action: "list_client", // action
  125. }, function(response, status, xhr){
  126. if (response.status =='success'){
  127. response.users.forEach(function(u){
  128. hide_loading_client();
  129. var html = bts_client_html(u);
  130. jQuery('div.clientlist').append(html);
  131. new People("#p" + u.login, '#client_item' ,u);
  132. });
  133. }else{
  134. alert('error getting Client list');
  135. }
  136. });
  137. }
  138. function show_loading_staff(){
  139. jQuery('div.stafflist img').attr('src', bts().load_user_img).show();
  140. }
  141. function show_loading_client(){
  142. jQuery('div.clientlist img').attr('src', bts().load_user_img).show();
  143. }
  144. function hide_loading_staff(){
  145. jQuery('div.stafflist img').hide();;
  146. }
  147. function hide_loading_client(){
  148. jQuery('div.clientlist img').hide();
  149. }
  150. function xero(t){
  151. if (t)
  152. $('div.xero i').show();
  153. else
  154. $('div.xero i').hide();
  155. }
  156. function wifi(t){
  157. if (t)
  158. $('div.wifi i').show();
  159. else
  160. $('div.wifi i').hide();
  161. }
  162. function init_user_search(){
  163. $('div.b_search input').keyup(debounce(function(e){
  164. filter_user(e.target);
  165. }, 500));
  166. }
  167. function filter_user(input){
  168. var value = $(input).attr('value');
  169. value = value.toLowerCase();
  170. var selector = get_selector_for_filter_people(input);
  171. $.each( $(selector).find('div.peopleitem'), function(index, e){
  172. var html = $(e).find('div[name="title"] a').html();
  173. html = html.toLowerCase();
  174. if (-1 != html.indexOf(value)){//we find it;
  175. $(e).show();
  176. }else{
  177. $(e).hide();
  178. }
  179. });
  180. }
  181. function get_selector_for_filter_people(input){
  182. var selector='';
  183. var role = $(input).attr('placeholder');
  184. if (role == 'staff') //we filter staff
  185. selector = 'div.stafflist';
  186. else if (role = 'client')
  187. selector = 'div.clientlist';
  188. return selector;
  189. }
  190. function init_ts(){
  191. list_staff();
  192. list_clients();
  193. xero(false);
  194. wifi(false);
  195. init_user_search();
  196. ajax_earning_rate();
  197. }
  198. function ajax_earning_rate(){
  199. $.post(bts().ajax_url, { // POST request
  200. _ajax_nonce: bts().nonce, // nonce
  201. action: "earnings_rate", // action
  202. }, function(response, status, xhr){
  203. bts().earnings_rate = response;
  204. console.log("%o", bts().earnings_rate);
  205. });
  206. }
  207. init_ts();
  208. $(document).on('click', 'div.divTableHead.bdelete', function(){
  209. for (var i=1; i<10; i++){
  210. var o = new Job({i:i});
  211. }
  212. });
  213. $(document).on('click', 'div.divTableCell.bdelete', function(){
  214. if (confirm('delete this job?'))
  215. $(this).closest('div.divTable').remove();
  216. });
  217. $(document).on('mouseenter', 'div.divTableCell', function(){
  218. $(this).closest('div.divTable').addClass('highlight');
  219. });
  220. $(document).on('mouseleave', 'div.divTableCell', function(){
  221. $(this).closest('div.divTable').removeClass('highlight');
  222. });
  223. $(document).on('click', 'span.ticon.ticon-save', function(){
  224. var table = $(this).closest('div.divTable')
  225. table.data().job.do_save_record();
  226. });
  227. class Job{ //save data for the record, and display it as GUI
  228. constructor(data){
  229. var html = jQuery("#job_item").html();
  230. this.el = $(html);
  231. jQuery('div.workspace').append(this.el);
  232. this.load_data(data);
  233. dtp_init();
  234. this.init_start_rating();
  235. }
  236. init_start_rating(){
  237. var self = this;
  238. this.el.find("div.brating span").click(function(){
  239. var r = $(this).attr('data-rating');
  240. self.data.rating = r;
  241. self.set_rating(r);
  242. })
  243. this.el.find("div.brating").mouseenter(function(){
  244. //change to all hollow star
  245. $(this).find('span').html('☆');
  246. });
  247. this.el.find("div.brating").mouseleave(function(){
  248. self.set_rating(self.data.rating);
  249. });
  250. }
  251. load_data(data)
  252. {
  253. this.set_job_id(data.id);
  254. this.set_tos(data.tos);
  255. this.set_start(data.start);
  256. this.set_finish(data.finish);
  257. this.set_rate(data.rate);
  258. this.set_staff(data.staff);
  259. this.set_client(data.client);
  260. this.set_ack(data.ack);
  261. this.set_rating(data.rating);
  262. //save to html element
  263. this.data = data;
  264. this.el.data({job:this, data:data});
  265. }
  266. get_job_id(){
  267. return this.el.find('input[name="id"]').attr('value');
  268. }
  269. set_job_id(val){
  270. return this.el.find('input[name="id"]').attr('value', val);
  271. }
  272. get_tos()
  273. {
  274. return this.el.find('div.btos select').children("option:selected").val();
  275. }
  276. set_tos(val)
  277. {
  278. if (typeof(val) =="undefined")
  279. return;
  280. this.el.find('div.btos select option[value="'+val+'"]').prop('selected',true);
  281. }
  282. get_start(){
  283. return this.el.find('div.bstart input').attr('value');
  284. }
  285. set_start(val)
  286. {
  287. if (typeof(val) =="undefined")
  288. return;
  289. this.el.find('div.bstart input').attr('value', val);
  290. }
  291. get_finish()
  292. {
  293. return this.el.find('div.bfinish input').attr('value');
  294. }
  295. set_finish(val)
  296. {
  297. if (typeof(val) == "undefined")
  298. return;
  299. this.el.find('div.bfinish input').attr('value', val);
  300. }
  301. get_rate()
  302. {
  303. return this.el.find('div.brate select').children("option:selected").val();
  304. }
  305. set_rate(val)
  306. {
  307. if (typeof(val) =="undefined")
  308. return;
  309. this.el.find('div.brate select option[value="'+val+'"]').prop('selected',true);
  310. }
  311. get_staff()
  312. {
  313. return this.el.find('div.bstaff select').children("option:selected").val();
  314. }
  315. set_staff(val)
  316. {
  317. if (typeof(val) =="undefined")
  318. return;
  319. this.el.find('div.bstaff select option[value="'+val+'"]').prop('selected',true);
  320. }
  321. get_client()
  322. {
  323. return this.el.find('div.bclient select').children("option:selected").val();
  324. }
  325. set_client(val)
  326. {
  327. if (typeof(val) =="undefined")
  328. return;
  329. this.el.find('div.bclient select option[value="'+val+'"]').prop('selected',true);
  330. }
  331. get_ack()
  332. {
  333. return this.el.find('div.bconfirmed input:checked').length > 0;
  334. }
  335. set_ack(val)
  336. {
  337. if (typeof(val) =="undefined")
  338. return;
  339. return this.el.find('div.bconfirmed input').prop('checked', val!=0);
  340. }
  341. get_rating(){
  342. var count =0;
  343. this.el.find('div.brating span').each(function(i,e){
  344. if ($(e).html()=='★')
  345. count +=1;
  346. });
  347. return count;
  348. }
  349. set_rating(num){
  350. if (!(1 <= num && num <=5))
  351. return;
  352. this.el.find('div.brating span').each(function(i,e){
  353. var rating = $(e).attr('data-rating');
  354. var rating = parseInt(rating);
  355. if (rating <= num)
  356. $(e).html('★');
  357. else
  358. $(e).html('☆');
  359. });
  360. }
  361. get_record_from_ui(){
  362. var record = {};
  363. record.id = this.get_job_id();
  364. record.tos = this.get_tos();
  365. record.start = this.get_start();
  366. record.finish = this.get_finish();
  367. record.rate = this.get_rate();
  368. record.staff = this.get_staff();
  369. record.client = this.get_client();
  370. record.ack = this.get_ack();
  371. return record;
  372. }
  373. do_save_record(){
  374. var self = this;
  375. $.post(bts().ajax_url, { // POST request
  376. _ajax_nonce: bts().nonce, // nonce
  377. action: "save_job", // action
  378. record: this.get_record_from_ui(),
  379. }, function(response, status, xhr){
  380. console.log("response for save %o", response);
  381. self.load_data(response.newdata);
  382. });
  383. }
  384. }//end of class Job
  385. //global GUI summary
  386. function get_wages()
  387. {
  388. var txt = $('div.wages div').html();
  389. return parseInt(txt);
  390. }
  391. function set_wages(num){
  392. $('div.wages div').html(num);
  393. }
  394. function set_working_hours(num){
  395. $('input#woh').attr('value', num);
  396. }
  397. function get_working_hours(){
  398. var txt = $('input#woh').attr('value');
  399. return parseFloat(txt);
  400. }
  401. //modal box
  402. function set_modal_title(selector, title){
  403. var s = 'div.bts_'+ selector +' .ult_modal-title';
  404. $(s).html(title);
  405. }
  406. function set_modal_content(selector, content){
  407. var s = 'div.bts_'+ selector +' div.ult_modal-body.ult-html';
  408. $(s).html(content);
  409. }
  410. function open_modal (selector){
  411. var s='div.bts_'+selector+'_button';
  412. $(s).trigger('click');
  413. }
  414. // setTimeout(function(){
  415. // set_modal_title('warning', 'suck title');
  416. // set_modal_content('warning', 'fucking details');
  417. // //open_modal('warning');
  418. // }, 1000);
  419. //
  420. // setTimeout(function(){
  421. // set_modal_title('error', 'error title');
  422. // set_modal_content('error', 'error details');
  423. // //open_modal('error');
  424. // }, 5000);
  425. $(document).on('mouseenter', 'div.week1 div', function(){
  426. $(this).addClass('blink_me');
  427. get_week2_partner(this).addClass('blink_me');
  428. });
  429. $(document).on('mouseleave', 'div.week1 div', function(){
  430. $(this).removeClass('blink_me');
  431. get_week2_partner(this).removeClass('blink_me');
  432. });
  433. function get_week2_partner(div){
  434. var index = $(div).index()+1;
  435. return $('div.week2 div:nth-child('+index+')');
  436. }
  437. function init_weekdays(){
  438. var curr = new Date; // get current date
  439. // First day is the day of the month - the day of the week
  440. var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first
  441. var last = first + 6; // last day is the first day + 6
  442. //var firstday = new Date(curr.setDate(first)); //Mon
  443. //var lastday = new Date(curr.setDate(last)); //Sun
  444. var pos = 1; //first lot
  445. for (var i=first; i<=last; i++)
  446. {
  447. var d1 = new Date(curr.setDate(i));
  448. var d2 = new Date(curr.setDate(i+7));
  449. set_day_number(1,pos, d1); //week 1
  450. set_day_number(2,pos, d2); //week 2
  451. pos +=1;
  452. }
  453. }
  454. function set_day_number(week, index, date){
  455. var selector = 'span[name="w'+week+'d'+index+'"]';
  456. $(selector).html(date.getDate());
  457. $(selector).data({date:date});
  458. }
  459. function format_date(date) {
  460. var monthNames = [
  461. "January", "February", "March",
  462. "April", "May", "June", "July",
  463. "August", "September", "October",
  464. "November", "December"
  465. ];
  466. var day = date.getDate();
  467. var monthIndex = date.getMonth();
  468. var year = date.getFullYear();
  469. return day + ' ' + monthNames[monthIndex] + ' ' + year;
  470. }
  471. function set_today(){
  472. var selector = 'div.sheettitle span[name="today"]';
  473. var curr = new Date;
  474. $(selector).html(format_date(curr));
  475. }
  476. Date.prototype.get_week_number = function(){
  477. var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
  478. var dayNum = d.getUTCDay() || 7;
  479. d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  480. var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
  481. return Math.ceil((((d - yearStart) / 86400000) + 1)/7)
  482. };
  483. function set_week_number(){
  484. var date = $('span[name="w1d1"]').data().date;
  485. console.log("date %o", date);
  486. var num = date.get_week_number();
  487. $('div.weekly span[name="week1"]').html(num);
  488. $('div.weekly span[name="week2"]').html(num+1);
  489. }
  490. $('div.prevweek.left').click(function(){
  491. $('div.weekdays span.weekday').each(function(i, e){
  492. var date = $(e).data().date;
  493. var newdate = new Date(date.setDate(date.getDate() -7 ));
  494. $(e).html(newdate.getDate());
  495. $(e).data({data:newdate});
  496. });
  497. set_week_number();
  498. load_timesheet();
  499. });
  500. $('div.nextweek.right').click(function(){
  501. $('div.weekdays span.weekday').each(function(i, e){
  502. var date = $(e).data().date;
  503. var newdate = new Date(date.setDate(date.getDate() +7 ));
  504. $(e).html(newdate.getDate());
  505. $(e).data({data:newdate});
  506. });
  507. set_week_number();
  508. load_timesheet();
  509. });
  510. $('div.weekly div.weekname.prev').click(function(){
  511. if (!confirm ('copy entire week to next week? '))
  512. return;
  513. });
  514. $('div.weekly div.weekname.next').click(function(){
  515. if (!confirm ('copy entire week to previous week? '))
  516. return;
  517. });
  518. $('div.week1 > div').click(function(){
  519. if (!confirm ('copy to next week'))
  520. return;
  521. });
  522. $('div.sheettitle h1').click(function(){
  523. reset_title_to_today();
  524. })
  525. function reset_title_to_today(){
  526. set_today();
  527. init_weekdays();
  528. set_week_number();
  529. load_timesheet();
  530. }
  531. function load_timesheet()
  532. {
  533. clear_workspace();
  534. var first = $('span[name="w1d1"]').data().date;
  535. var last = $('span[name="w2d7"]').data().date;
  536. $.post(bts().ajax_url, { // POST request
  537. _ajax_nonce: bts().nonce, // nonce
  538. action: "list_job", // action
  539. start: format_date(first),
  540. finish: format_date(last),
  541. }, function(response, status, xhr){
  542. if (response.status =='success'){
  543. response.jobs.forEach(function(job){
  544. new Job(job);
  545. });
  546. //filter it if reqired
  547. do_filter_workspace();
  548. }else{
  549. alert('error loading job');
  550. }
  551. });
  552. }
  553. function format_date(date){
  554. var dd = date.getDate();
  555. var mm = date.getMonth() + 1; //January is 0!
  556. var yyyy = date.getFullYear();
  557. if (dd < 10) {
  558. dd = '0' + dd;
  559. }
  560. if (mm < 10) {
  561. mm = '0' + mm;
  562. }
  563. return yyyy + '-' + mm + '-' +dd ;
  564. }
  565. function clear_workspace()//clear all timesheet jobs
  566. {
  567. $('div.workspace > div.divTable').remove();
  568. //clear datetime picker
  569. $('div.xdsoft_datetimepicker').remove();
  570. }
  571. $('button[name="confirmschedule"]').click(function(){
  572. $('span.ticon.ticon-save').trigger('click');
  573. });
  574. $(document).on('click','div.userlist', debounce(do_filter_workspace));
  575. function do_filter_workspace(){
  576. var staffs =[];
  577. $('div.stafflist div.peopleitem :checked').each(function(i, e){
  578. var id = $(e).parent().attr('data-id');
  579. //console.log("%o, id=%s", e, id);
  580. staffs.push(id.substring(1));
  581. });
  582. var clients =[];
  583. $('div.clientlist div.peopleitem :checked').each(function(i, e){
  584. var id = $(e).parent().attr('data-id');
  585. //console.log("%o, id=%s", e, id);
  586. clients.push(id.substring(1));
  587. });
  588. console.log('staffs %o' , staffs);
  589. console.log('clients %o' , clients);
  590. filter_workspace(staffs, clients);
  591. }
  592. function filter_workspace(staffs, clients){
  593. //if both array is empty
  594. if( (staffs === undefined || staffs.length ==0) &&
  595. (clients===undefined || clients.length ==0)){
  596. //show all
  597. $('div.workspace div.divTable').show();
  598. return;
  599. }
  600. //filter some of them;
  601. $('div.workspace div.divTable').each(function(i,e){
  602. var job = $(e).data().job;
  603. var s = job.get_staff();
  604. var c = job.get_client();
  605. if (staffs.indexOf(s) ==-1 && clients.indexOf(c) ==-1)
  606. $(this).fadeOut();
  607. else
  608. $(this).fadeIn();
  609. });
  610. }
  611. reset_title_to_today();
  612. /*________________________________________________________________________*/
  613. });
  614. })(jQuery);
  615. /*______________scrolling______________________________________________*/
  616. jQuery(document).ready(function(){
  617. var timeoutid =0;
  618. jQuery('button.peoplelist[name="down"]').mousedown(function(){
  619. var button = this;
  620. timeoutid = setInterval(function(){
  621. //console.log("down scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  622. jQuery(button).parent().find(".userlist").get(0).scrollTop +=240;
  623. }, 100);
  624. }).on('mouseup mouseleave', function(){
  625. clearTimeout(timeoutid);
  626. });
  627. jQuery('button.peoplelist[name="up"]').mousedown(function(){
  628. var button = this;
  629. timeoutid = setInterval(function(){
  630. //console.log("up scrotop %d ", jQuery(button).parent().find(".userlist").get(0).scrollTop );
  631. jQuery(button).parent().find(".userlist").get(0).scrollTop -=240;
  632. }, 100);
  633. }).on('mouseup mouseleave', function(){
  634. clearTimeout(timeoutid);
  635. });
  636. });