taskeffort.ui.js 12 KB

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