window.initTaskEffortTable = function() { let $tasksTable = $('#tasksTable'); console.log('> rowsCount', rowsCount); if(!rowsCount) return $tasksTable.removeClass('loading'); let $tableContainer = $('#tableContainer'); let $tableHeader = $('#tableHeader'); let $tableFooter = $('#tableFooter'); let $tableBody = $('#tableBody'); let $cells = $('#cells'); let $scrollbarContainer = $('#scrollbarContainer'); let $timeline = $('#timeline'); let $timeList = $('#timeList'); let $totalDays = $('#totalDays'); let $scrollbar = $('#scrollbar'); let $window = $(window); let cellWidth = 56; let dayWidth = cellWidth * 2; let lastScrollLeft = 0; let lastScrollTop = 0; let lastTimelineWidth; let isHeaderFixed = false; let isFooterFixed = false; let winHeight = $window.height(); let headerHeight = $tableHeader.outerHeight(); let footerHeight = $tableFooter.outerHeight(); let pageFooterHeight = $('#footer').outerHeight() || 0; let currentYear = new Date().getFullYear(); let firstVisibleDayIndex = 0; let rowLayouts = []; let maxRowHeight = 0; let lastHoverRow, lastHoverDay, hoverDelayTimer; /* Init body bounds */ for (let i = 0; i < groupsCount; ++i) { let $group = $('#group-' + i); let height = $group.outerHeight(); $group.find('.group-tasks').css('min-height', height - 1); } const bodyBounds = $.extend({}, $tableBody[0].getBoundingClientRect()); /* Init layout of rows */ for (let i = 0; i < rowsCount; ++i) { let $task = $('#task-' + i); let bounds = $task[0].getBoundingClientRect(); let layout = {top: bounds.top - bodyBounds.top, height: bounds.height, bottom: bounds.bottom - bodyBounds.top}; rowLayouts.push(layout); maxRowHeight = Math.max(maxRowHeight, layout.height); } let scrollTopOnLoad = window.scrollTo(0, 0); if(scrollTopOnLoad > 0) { bodyBounds.y += scrollTopOnLoad; bodyBounds.top += scrollTopOnLoad; bodyBounds.bottom += scrollTopOnLoad; } /* Init layout of cells */ let cellsWidth = dayWidth * days.length; $scrollbar.css('width', cellsWidth); $timeList.css('width', cellsWidth); $totalDays.css('width', cellsWidth); /* Layout timeline */ function layoutTimeline(force) { let scrollLeft = $scrollbarContainer[0].scrollLeft; let timeLineWidth = $timeline.width(); if(!force && lastScrollLeft === scrollLeft && timeLineWidth === lastTimelineWidth) return; lastScrollLeft = scrollLeft; lastTimelineWidth = timeLineWidth; firstVisibleDayIndex = Math.floor(scrollLeft / dayWidth); let index = firstVisibleDayIndex; let visibleWidth = timeLineWidth + dayWidth; let width = 0; let $days = $timeList.children('.day-cell').addClass('expired'); let $tDays = $totalDays.children('.day-cell').addClass('expired'); while(width < visibleWidth && days[index]) { let day = days[index]; let $day = $('#day-' + index); if($day.length) $day.removeClass('expired'); else { $day = $( [ '
', '
' + (day.indexOf(currentYear + '-') === 0 ? day.substr(5) : day) + '
', '
' + consumedText + '
', '
' + leftText + '
', '
' ].join('')).css({left: index * dayWidth, width: dayWidth}).appendTo($timeList); } let $totalDay = $('#day-total-' + index); if($totalDay.length) $totalDay.removeClass('expired'); else { let totalCounts = counts[day]; $totalDay = $( [ '
', '
' + (totalCounts ? totalCounts.countConsumed.toFixed(1) : '') + '
', '
' + (totalCounts ? totalCounts.countLeft.toFixed(1) : '') + '
', '
' ].join('')).css({left: index * dayWidth, width: dayWidth}).appendTo($totalDays); } width += dayWidth; index++; } $days.filter('.expired').remove(); $tDays.filter('.expired').remove(); $timeList.css('left', 0 -scrollLeft); $totalDays.css('left', 0 -scrollLeft); } /* Layout table cells */ function layoutCells(scrollTop) { if(typeof scrollTop !== 'number') scrollTop = window.pageYOffset; let firstVisibleRowIndex = 0; if(scrollTop > (bodyBounds.top - headerHeight)) { for(let i = 0; i < rowLayouts.length; ++i) { let layout = rowLayouts[i]; firstVisibleRowIndex++; if(scrollTop < layout.bottom + bodyBounds.top) break; } } let $oldCellList = $cells.children('.data-cell').addClass('expired'); let height = 0; let visibleHeight = winHeight - headerHeight - pageFooterHeight - footerHeight + maxRowHeight; let rowIndex = firstVisibleRowIndex < 3 ? 0 : firstVisibleRowIndex; while(height < visibleHeight && rowLayouts[rowIndex]) { let layout = rowLayouts[rowIndex]; let dayIndex = firstVisibleDayIndex; let visibleWidth = lastTimelineWidth + dayWidth; let width = 0; let task = taskList[rowIndex]; while(width < visibleWidth && days[dayIndex]) { let day = days[dayIndex]; let $cell = $('#cell-' + rowIndex + '-' + dayIndex); if($cell.length) $cell.removeClass('expired'); else { let data = task ? task[day] : null; let consumed = (data ? data.consumed : '') || ''; let left = (data ? data.left : '') || ''; $cell = $( [ '
', '
' + consumed + '
', '
' + left + '
', '
' ].join('')); $cell.css( { left: dayIndex * dayWidth, top: layout.top + (rowIndex ? 1 : 0), width: dayWidth, height: layout.height + (rowIndex ? 0 : 1) }).appendTo($cells); } width += dayWidth; dayIndex++; } height += layout.height; rowIndex++; } $oldCellList.filter('.expired').remove(); $cells.css('left', 0 -lastScrollLeft); } /* Layout table cells */ function fixedHeaderFooter(scrollTop, force) { if(typeof scrollTop !== 'number') { force = scrollTop; scrollTop = window.pageYOffset; } let needFixedHeader = scrollTop > (bodyBounds.top - headerHeight); if(force || needFixedHeader !== isHeaderFixed) { isHeaderFixed = needFixedHeader; $tableHeader.toggleClass('is-fixed', isHeaderFixed); $tableContainer.toggleClass('is-fixed-header', isHeaderFixed).css('padding-top', isHeaderFixed ? headerHeight : 0); $tableHeader.css(isHeaderFixed ? {left: bodyBounds.left, width: bodyBounds.width} : {left: 'auto', width: 'auto'}); } let needFixedFooter = (scrollTop + winHeight - pageFooterHeight) < bodyBounds.bottom; if(force || needFixedFooter !== isFooterFixed) { isFooterFixed = needFixedFooter; if(isFooterFixed) { $('#scrollbarContainer').css('top', '-12px'); $('#scrollbarContainer').css('bottom', 'unset'); } else { $('#scrollbarContainer').css('top', 'unset'); $('#scrollbarContainer').css('bottom', '0'); } $tableFooter.toggleClass('is-fixed', isFooterFixed); $tableContainer.toggleClass('is-fixed-footer', isFooterFixed).css('padding-bottom', isFooterFixed ? footerHeight : 0); $tableFooter.css(isFooterFixed ? {left: bodyBounds.left, width: bodyBounds.width, bottom: pageFooterHeight} : {left: 'auto', width: 'auto', bottom: 0}); } } const date = Date.now(); /* Listen events to refresh layout */ $scrollbarContainer.off('.zentaoTaskeffort').on('scroll.zentaoTaskeffort', function() { layoutTimeline(true); layoutCells(lastScrollTop); }); $window.off('.zentaoTaskeffort').on('scroll.zentaoTaskeffort', function() { let scrollTop = window.pageYOffset; if(scrollTop === lastScrollTop) return; lastScrollTop = scrollTop; fixedHeaderFooter(scrollTop); layoutCells(scrollTop); clearHoverEffections(); }).on('resize.zentaoTaskeffort', function() { let bounds = $tableBody[0].getBoundingClientRect(); bodyBounds.width = bounds.width; bodyBounds.left = bounds.left; bodyBounds.right = bounds.right; bodyBounds.x = bounds.x; winHeight = $window.height(); fixedHeaderFooter(true); layoutTimeline(true); clearHoverEffections(); }); /* Clear hover effections */ function clearHoverEffections(row, day) { if(row !== false && lastHoverRow > -1) { $tableContainer.find('.hover-row').removeClass('hover-row'); lastHoverRow = -1; } if(day !== false && lastHoverDay > -1) { lastHoverDay = -1; $tableContainer.find('.hover-day').removeClass('hover-day'); } }; /* Update hover effections */ function updateHoverEffections() { let $cell = $(this); let row = $cell.data('row'); let day = $cell.data('day'); if(row !== lastHoverRow) { clearHoverEffections(1, false); $tableContainer.find('.day-cell,.data-cell,.group-task').filter('[data-row="' + row + '"]').addClass('hover-row'); lastHoverRow = row; } if(day !== lastHoverDay) { clearHoverEffections(false, 1); $tableContainer.find('.day-cell,.data-cell,.group-task').filter('[data-day="' + day + '"]').addClass('hover-day'); lastHoverDay = day; } hoverDelayTimer = 0; } /* Add mouse hover effections */ $tableContainer.off('.zentaoTaskeffort').on('mouseenter.zentaoTaskeffort', '.day-cell,.data-cell,.group-task', function() { if(hoverDelayTimer) clearTimeout(hoverDelayTimer); hoverDelayTimer = setTimeout(updateHoverEffections.bind(this), 20); }).on('mouseleave.zentaoTaskeffort', '.day-cell,.data-cell,.group-task', clearHoverEffections); /* Init layouts */ fixedHeaderFooter(true); layoutTimeline(true); layoutCells(); /* Remove loading status */ $tasksTable.removeClass('loading'); }; window.computeTaskEffort = function() { let begin = $('#begin').val(); let end = $('#end').val(); if(begin > end) return zui.Modal.alert(beginMoreThanEnd); if(typeof(today) != 'undefined' && end > today) return zui.Modal.alert(endMoreThanToday); begin = begin.replace(/-/g, ''); end = end.replace(/-/g, ''); const link = $.createLink('execution', 'computeTaskEffort', 'begin=' + begin + '&end=' + end); $.get(link, function(data){if(data == 'success') reloadPage();}); };