taskeffort.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. $(function()
  2. {
  3. var $tasksTable = $('#tasksTable');
  4. if(!rowsCount) return $tasksTable.removeClass('loading');
  5. var $tableContainer = $('#tableContainer');
  6. var $tableHeader = $('#tableHeader');
  7. var $tableFooter = $('#tableFooter');
  8. var $tableBody = $('#tableBody');
  9. var $cells = $('#cells');
  10. var $scrollbarContainer = $('#scrollbarContainer');
  11. var $timeline = $('#timeline');
  12. var $timeList = $('#timeList');
  13. var $totalDays = $('#totalDays');
  14. var $scrollbar = $('#scrollbar');
  15. var $window = $(window);
  16. var cellWidth = 56;
  17. var dayWidth = cellWidth * 2;
  18. var lastScrollLeft = 0;
  19. var lastScrollTop = 0;
  20. var lastTimelineWidth;
  21. var isHeaderFixed = false;
  22. var isFooterFixed = false;
  23. var winHeight = $window.height();
  24. var headerHeight = $tableHeader.outerHeight();
  25. var footerHeight = $tableFooter.outerHeight();
  26. var pageFooterHeight = $('#footer').outerHeight();
  27. var currentYear = new Date().getFullYear();
  28. var firstVisibleDayIndex = 0;
  29. var rowLayouts = [];
  30. var maxRowHeight = 0;
  31. var lastHoverRow, lastHoverDay, hoverDelayTimer;
  32. /* Init body bounds */
  33. for (var i = 0; i < groupsCount; ++i)
  34. {
  35. var $group = $('#group-' + i);
  36. var height = $group.outerHeight();
  37. $group.find('.group-tasks').css('min-height', height - 1);
  38. }
  39. bodyBounds = $.extend({}, $tableBody[0].getBoundingClientRect());
  40. /* Init layout of rows */
  41. for (var i = 0; i < rowsCount; ++i)
  42. {
  43. var $task = $('#task-' + i);
  44. var bounds = $task[0].getBoundingClientRect();
  45. var layout = {top: bounds.top - bodyBounds.top, height: bounds.height, bottom: bounds.bottom - bodyBounds.top};
  46. rowLayouts.push(layout);
  47. maxRowHeight = Math.max(maxRowHeight, layout.height);
  48. }
  49. var scrollTopOnLoad = $window.scrollTop();
  50. if(scrollTopOnLoad > 0)
  51. {
  52. bodyBounds.y += scrollTopOnLoad;
  53. bodyBounds.top += scrollTopOnLoad;
  54. bodyBounds.bottom += scrollTopOnLoad;
  55. }
  56. /* Init layout of cells */
  57. var cellsWidth = dayWidth * days.length;
  58. $scrollbar.css('width', cellsWidth);
  59. $timeList.css('width', cellsWidth);
  60. $totalDays.css('width', cellsWidth);
  61. /* Layout timeline */
  62. function layoutTimeline(force)
  63. {
  64. var scrollLeft = $scrollbarContainer.scrollLeft();
  65. var timeLineWidth = $timeline.width();
  66. if(!force && lastScrollLeft === scrollLeft && timeLineWidth === lastTimelineWidth) return;
  67. lastScrollLeft = scrollLeft;
  68. lastTimelineWidth = timeLineWidth;
  69. firstVisibleDayIndex = Math.floor(scrollLeft / dayWidth);
  70. var index = firstVisibleDayIndex;
  71. var visibleWidth = timeLineWidth + dayWidth;
  72. var width = 0;
  73. var $days = $timeList.children('.day-cell').addClass('expired');
  74. var $tDays = $totalDays.children('.day-cell').addClass('expired');
  75. while(width < visibleWidth && days[index])
  76. {
  77. var day = days[index];
  78. var $day = $('#day-' + index);
  79. if($day.length) $day.removeClass('expired');
  80. else
  81. {
  82. $day = $(
  83. [
  84. '<div class="day-cell" id="day-' + index + '" data-day="' + index + '">',
  85. '<div class="day-name">' + (day.indexOf(currentYear + '-') === 0 ? day.substr(5) : day) + '</div>',
  86. '<div class="day-consumed">' + consumedText + '</div>',
  87. '<div class="day-left">' + leftText + '</div>',
  88. '</div>'
  89. ].join('')).css({left: index * dayWidth, width: dayWidth}).appendTo($timeList);
  90. }
  91. var $totalDay = $('#day-total-' + index);
  92. if($totalDay.length) $totalDay.removeClass('expired');
  93. else
  94. {
  95. var totalCounts = counts[day];
  96. $totalDay = $(
  97. [
  98. '<div class="day-cell" id="day-total-' + index + '" data-day="' + index + '">',
  99. '<div class="day-consumed">' + (totalCounts ? totalCounts.countConsumed.toFixed(1) : '') + '</div>',
  100. '<div class="day-left">' + (totalCounts ? totalCounts.countLeft.toFixed(1) : '') + '</div>',
  101. '</div>'
  102. ].join('')).css({left: index * dayWidth, width: dayWidth}).appendTo($totalDays);
  103. }
  104. width += dayWidth;
  105. index++;
  106. }
  107. $days.filter('.expired').remove();
  108. $tDays.filter('.expired').remove();
  109. $timeList.css('left', 0 -scrollLeft);
  110. $totalDays.css('left', 0 -scrollLeft);
  111. }
  112. /* Layout table cells */
  113. function layoutCells(scrollTop)
  114. {
  115. if(typeof scrollTop !== 'number') scrollTop = $window.scrollTop();
  116. var firstVisibleRowIndex = 0;
  117. if(scrollTop > (bodyBounds.top - headerHeight))
  118. {
  119. for(var i = 0; i < rowLayouts.length; ++i)
  120. {
  121. var layout = rowLayouts[i];
  122. firstVisibleRowIndex++;
  123. if(scrollTop < layout.bottom + bodyBounds.top) break;
  124. }
  125. }
  126. var $oldCellList = $cells.children('.data-cell').addClass('expired');
  127. var height = 0;
  128. var visibleHeight = winHeight - headerHeight - pageFooterHeight - footerHeight + maxRowHeight;
  129. var rowIndex = firstVisibleRowIndex < 3 ? 0 : firstVisibleRowIndex;
  130. while(height < visibleHeight && rowLayouts[rowIndex])
  131. {
  132. var layout = rowLayouts[rowIndex];
  133. var dayIndex = firstVisibleDayIndex;
  134. var visibleWidth = lastTimelineWidth + dayWidth;
  135. var width = 0;
  136. var task = taskList[rowIndex];
  137. while(width < visibleWidth && days[dayIndex])
  138. {
  139. var day = days[dayIndex];
  140. var $cell = $('#cell-' + rowIndex + '-' + dayIndex);
  141. if($cell.length) $cell.removeClass('expired');
  142. else
  143. {
  144. var data = task ? task[day] : null;
  145. var consumed = (data ? data.consumed : '') || '';
  146. var left = (data ? data.left : '') || '';
  147. $cell = $(
  148. [
  149. '<div class="data-cell" id="cell-' + rowIndex + '-' + dayIndex + '" data-row="' + rowIndex + '" data-day="' + dayIndex + '">',
  150. '<div class="day-consumed">' + consumed + '</div>',
  151. '<div class="day-left">' + left + '</div>',
  152. '</div>'
  153. ].join(''));
  154. $cell.css(
  155. {
  156. left: dayIndex * dayWidth,
  157. top: layout.top + (rowIndex ? 1 : 0),
  158. width: dayWidth,
  159. height: layout.height + (rowIndex ? 0 : 1)
  160. }).appendTo($cells);
  161. }
  162. width += dayWidth;
  163. dayIndex++;
  164. }
  165. height += layout.height;
  166. rowIndex++;
  167. }
  168. $oldCellList.filter('.expired').remove();
  169. $cells.css('left', 0 -lastScrollLeft);
  170. }
  171. /* Layout table cells */
  172. function fixedHeaderFooter(scrollTop, force)
  173. {
  174. if(typeof scrollTop !== 'number')
  175. {
  176. force = scrollTop;
  177. scrollTop = $window.scrollTop();
  178. }
  179. var needFixedHeader = scrollTop > (bodyBounds.top - headerHeight);
  180. if(force || needFixedHeader !== isHeaderFixed)
  181. {
  182. isHeaderFixed = needFixedHeader;
  183. $tableHeader.toggleClass('is-fixed', isHeaderFixed);
  184. $tableContainer.css('padding-top', isHeaderFixed ? headerHeight : 0);
  185. $tableHeader.css(isHeaderFixed ? {left: bodyBounds.left, width: bodyBounds.width} : {left: 'auto', width: 'auto'});
  186. }
  187. var needFixedFooter = (scrollTop + winHeight - pageFooterHeight) < bodyBounds.bottom;
  188. if(force || needFixedFooter !== isFooterFixed)
  189. {
  190. isFooterFixed = needFixedFooter;
  191. if(isFooterFixed)
  192. {
  193. $('#scrollbarContainer').css('top', '-12px');
  194. $('#scrollbarContainer').css('bottom', 'unset');
  195. }
  196. else
  197. {
  198. $('#scrollbarContainer').css('top', 'unset');
  199. $('#scrollbarContainer').css('bottom', '0');
  200. }
  201. $tableFooter.toggleClass('is-fixed', isFooterFixed);
  202. $tableContainer.css('padding-bottom', isFooterFixed ? footerHeight : 0);
  203. $tableFooter.css(isFooterFixed ? {left: bodyBounds.left, width: bodyBounds.width, bottom: pageFooterHeight} : {left: 'auto', width: 'auto', bottom: 0});
  204. }
  205. }
  206. /* Listen events to refresh layout */
  207. $scrollbarContainer.on('scroll', function()
  208. {
  209. layoutTimeline(true);
  210. layoutCells(lastScrollTop);
  211. });
  212. $window.on('scroll', function()
  213. {
  214. var scrollTop = $window.scrollTop();
  215. if(scrollTop === lastScrollTop) return;
  216. lastScrollTop = scrollTop;
  217. fixedHeaderFooter(scrollTop);
  218. layoutCells(scrollTop);
  219. clearHoverEffections();
  220. }).on('resize', function()
  221. {
  222. var bounds = $tableBody[0].getBoundingClientRect();
  223. bodyBounds.width = bounds.width;
  224. bodyBounds.left = bounds.left;
  225. bodyBounds.right = bounds.right;
  226. bodyBounds.x = bounds.x;
  227. winHeight = $window.height();
  228. fixedHeaderFooter(true);
  229. layoutTimeline(true);
  230. clearHoverEffections();
  231. });
  232. /* Clear hover effections */
  233. function clearHoverEffections(row, day)
  234. {
  235. if(row !== false && lastHoverRow > -1)
  236. {
  237. $tableContainer.find('.hover-row').removeClass('hover-row');
  238. lastHoverRow = -1;
  239. }
  240. if(day !== false && lastHoverDay > -1)
  241. {
  242. lastHoverDay = -1;
  243. $tableContainer.find('.hover-day').removeClass('hover-day');
  244. }
  245. };
  246. /* Update hover effections */
  247. function updateHoverEffections()
  248. {
  249. var $cell = $(this);
  250. var row = $cell.data('row');
  251. var day = $cell.data('day');
  252. if(row !== lastHoverRow)
  253. {
  254. clearHoverEffections(1, false);
  255. $tableContainer.find('.day-cell,.data-cell,.group-task').filter('[data-row="' + row + '"]').addClass('hover-row');
  256. lastHoverRow = row;
  257. }
  258. if(day !== lastHoverDay)
  259. {
  260. clearHoverEffections(false, 1);
  261. $tableContainer.find('.day-cell,.data-cell,.group-task').filter('[data-day="' + day + '"]').addClass('hover-day');
  262. lastHoverDay = day;
  263. }
  264. hoverDelayTimer = 0;
  265. }
  266. /* Add mouse hover effections */
  267. $tableContainer.on('mouseenter', '.day-cell,.data-cell,.group-task', function()
  268. {
  269. if(hoverDelayTimer) clearTimeout(hoverDelayTimer);
  270. hoverDelayTimer = setTimeout(updateHoverEffections.bind(this), 20);
  271. }).on('mouseleave', '.day-cell,.data-cell,.group-task', clearHoverEffections);
  272. /* Init layouts */
  273. fixedHeaderFooter(true);
  274. layoutTimeline(true);
  275. layoutCells();
  276. /* Remove loading status */
  277. $tasksTable.removeClass('loading');
  278. $('#saveButton').on('click', function(){
  279. let begin = $('#begin').val();
  280. let end = $('#end').val();
  281. if(begin > end) return alert(beginMoreThanEnd);
  282. if(end > today) return alert(endMoreThanToday);
  283. begin = begin.replace(/-/g, '');
  284. end = end.replace(/-/g, '');
  285. const link = $.createLink('execution', 'computeTaskEffort', 'begin=' + begin + '&end=' + end);
  286. $.get(link, function(data){
  287. if(data == 'success') location.reload();
  288. });
  289. });
  290. });