- Added JS Gantt Chart library files including CSS and JS. - Updated existing Gantt chart styles for better visual representation. - Translated month and day names to Polish in the Gantt chart settings. - Implemented Gantt chart initialization in the main view with sample data. - Added a toggle switch for displaying tasks in the calendar. - Enhanced task edit form with a new checkbox for calendar visibility. - Improved the layout of the logged-in user template to include Gantt chart styles and scripts.
1 line
9.7 KiB
JavaScript
1 line
9.7 KiB
JavaScript
class Gantt{constructor(staticID,params){this.staticID=staticID,this.sidebarHeader=params.sidebarHeader||"Unused parameter right now",this.noDataFoundMessage=params.noDataFoundMessage||"No data found.",this.startTimeAlias=params.startTimeAlias||"startTime",this.endTimeAlias=params.endTimeAlias||"endTime",this.idAlias=params.idAlias||"id",this.rowAlias=params.rowAlias||"rowTitle",this.linkAlias=params.linkAlias,this.tooltipAlias=params.tooltipAlias||"tooltip",this.groupBy=params.groupBy?params.groupBy.split(",").map(group=>group.trim()):[],this.groupByAlias=this.groupBy?params.groupByAlias?params.groupByAlias.split(",").map(group=>group.trim()):this.groupBy:[],this.data={},this.rawData=[],this.divisionCount=0,this.maxTime,this.minTime,this.refreshFunction=params.refreshFunction,this.wrapper=document.createElement("div"),this.wrapper.classList.add("gfb-gantt-wrapper"),document.getElementById(this.staticID).appendChild(this.wrapper),this.refreshData()}refreshData(){this.rawData=this.refreshFunction(),this.empty(),this.rawData.length<1?this.noDataFound():(this.processData(),this.render())}processData(){this.divisionCount=0;let sortedData=this.rawData.sort((a,b)=>new Date(a[this.startTimeAlias])-new Date(b[this.startTimeAlias])),groupedData={};for(let dataRow of sortedData)this.groupArray(groupedData,dataRow);this.data=groupedData;let maxTime=this.rawData.reduce((max,curr)=>curr[this.endTimeAlias]?max>new Date(curr[this.endTimeAlias])?max:new Date(curr[this.endTimeAlias]):max,new Date(0)),minTime=this.rawData.reduce((min,curr)=>curr[this.startTimeAlias]&&min>new Date(curr[this.startTimeAlias])?new Date(curr[this.startTimeAlias]):min,new Date(maxTime.getTime()));if(this.maxTime=roundHourUp(maxTime),this.minTime=roundHourDown(minTime),this.minTime!==this.maxTime)for(let i=new Date(this.minTime.getTime());i<=this.maxTime;i.setTime(i.getTime()+36e5))this.divisionCount++}groupArray(result,entry,iter=0){let nextResult;if(iter===this.groupBy.length+1)return void result.push(entry);let groupingProperty=result[entry[this.groupBy[iter]]],chartEntry=result[entry[this.idAlias]];!result[entry[this.groupBy[iter]]]&&iter<this.groupBy.length?(result[entry[this.groupBy[iter]]]={groupName:entry[this.groupByAlias[iter]]},nextResult=result[entry[this.groupBy[iter]]]):result[entry[this.idAlias]]||iter!==this.groupBy.length?result[entry[this.groupBy[iter]]]?nextResult=result[entry[this.groupBy[iter]]]:result[entry[this.idAlias]]&&(nextResult=result[entry[this.idAlias]]):(result[entry[this.idAlias]]=[],nextResult=result[entry[this.idAlias]]),iter++,this.groupArray(nextResult,entry,iter)}buildHeader(){let headerDivs='<div class="gfb-gantt-header-spacer"></div>';if(this.divisionCount>1)for(let i=0;i<this.divisionCount;i++){let date=new Date(this.minTime.getTime()+36e5*i),hour=date.getHours()>12?date.getHours()-12:date.getHours(),amPm=date.getHours()>12?"PM":"AM",minutes;headerDivs+=`<div class="gfb-gantt-header">${hour}:${1===date.getMinutes().toString().length?"0"+date.getMinutes():date.getMinutes()} ${amPm}</div>`}return`<div class="gfb-gantt-headers" style="grid-template-columns: 100px repeat(${this.divisionCount}, 1fr)">${headerDivs}</div>`}buildLines(){let lines='<div class="gfb-gantt-sidebar-template"></div>';for(let i=0;i<this.divisionCount;i++)lines+='<div class="gfb-gantt-line"></div>';return`<div class="gfb-gantt-lines-container" style="grid-template-columns: 100px repeat(${this.divisionCount}, 1fr)">${lines}</div>`}buildRow(rowArr,dataIndex){let totalTime=this.maxTime-this.minTime,compositeRows=`<div style="grid-column: 2/${this.divisionCount+1};grid-row:1;display:flex;align-items:center"><div class="gfb-gantt-sub-row-wrapper">`;for(let i=0;i<rowArr.length&&(rowArr[i][this.startTimeAlias]&&rowArr[i][this.startTimeAlias]);i++){let currElStart=new Date(rowArr[i][this.startTimeAlias]),currElEnd,currElRunPercent=(new Date(rowArr[i][this.endTimeAlias])-currElStart)/totalTime*100;if(0===i||rowArr[i-1]&&new Date(rowArr[i-1][this.endTimeAlias])!==currElStart){let baseTime,difference;compositeRows+=`<div style="width:${(currElStart-(0===i?this.minTime:new Date(rowArr[i-1][this.endTimeAlias])))/totalTime*100}%;"></div>`}this.linkAlias?compositeRows+=`<a class="gfb-gantt-row-entry" style="width:${currElRunPercent}%;" href="${rowArr[i][this.linkAlias]}" data-index="${dataIndex.join("-")}-${i}"></a>`:compositeRows+=`<div class="gfb-gantt-row-entry" style="width:${currElRunPercent}%;" data-index="${dataIndex.join("-")}-${i}"></div>`}return compositeRows+"</div></div>"}buildContent(){let body=['<div class="gfb-gantt-row-container">'],header=this.buildHeader(),this1=this;return buildContent(this.data,body),body.push("</div>"),`<div class="gfb-gantt-content">${header}${body.join("")}</div>`;function buildContent(data,result,depth=0,dataIndex=[]){for(let prop in data)data[prop]&&("groupName"!==prop&&dataIndex.push(prop),"object"!=typeof data[prop]||Array.isArray(data[prop])?Array.isArray(data[prop])&&result.push(`<div class="gfb-gantt-row" style="grid-template-columns: 100px repeat(${this1.divisionCount}, 1fr)"><div class="gfb-gantt-sidebar-header">${data[prop][0][this1.rowAlias]}</div>${this1.buildRow(data[prop],dataIndex)}</div>`):(result.push(`<div class="gfb-gantt-grouping-header" style="padding-left: ${5+20*depth}px">${data[prop].groupName}</div><div>`),depth+=1,buildContent(data[prop],result,depth,dataIndex),depth-=1),dataIndex.pop());result.push("</div>")}}buildChart(){let content=this.buildContent(),lines;return`${this.buildLines()}${content}`}bindHover(){let bindElements;document.querySelectorAll(`#${this.staticID} .gfb-gantt-row-entry`).forEach(bindElement=>{let toolTipElement,timeout;bindElement.addEventListener("mouseover",e=>{let target=e.target,indexArray=target.getAttribute("data-index").split("-"),position=getElOffset(target),targetHeight=getBoundingRect(target).height,this1=this;function getToolTipData(){let dataArray=this1.data;for(let i=0;i<indexArray.length;i++){if(i===indexArray.length-1)return dataArray[indexArray[i]];dataArray=dataArray[indexArray[i]]}}indexArray.push(this.tooltipAlias),position.top+=targetHeight+5,target.classList.add("hovering"),timeout=setTimeout(()=>{target.classList.contains("hovering")&&(toolTipElement=document.createElement("div"),toolTipElement.classList.add("gfb-gantt-row-entry-tooltip"),toolTipElement.innerHTML=getToolTipData(),toolTipElement.style.top=`${position.top}px`,toolTipElement.style.left=`${position.left}px`,document.body.appendChild(toolTipElement),fadeIn(toolTipElement,300))},300)}),bindElement.addEventListener("mouseout",e=>{let target=e.target;clearTimeout(timeout),target.classList.remove("hovering"),toolTipElement&&fadeOut(toolTipElement,300,()=>toolTipElement.remove())})})}bindCollapse(){let bindElements;document.querySelectorAll(`#${this.staticID} .gfb-gantt-grouping-header`).forEach(bindElement=>{bindElement.addEventListener("click",e=>{let group=e.target.nextSibling,duration=300;group.classList.contains("gfb-gantt-group--collapsed")?(slideDown(group,300),group.classList.remove("gfb-gantt-group--collapsed")):(slideUp(group,300),group.classList.add("gfb-gantt-group--collapsed"))})})}render(){this.wrapper.innerHTML=this.buildChart(),this.bindCollapse(),this.tooltipAlias&&this.bindHover()}noDataFound(){this.wrapper.innerHTML=this.noDataFoundMessage}empty(){this.wrapper.innerHTML=""}}function roundHourUp(date){let m=36e5;return new Date(Math.ceil(date.getTime()/m)*m)}function roundHourDown(date){let m=36e5;return new Date(Math.floor(date.getTime()/m)*m)}function fadeIn(element,ms,callback=null){let duration,interval=50,gap=50/ms,opacity=0;element.style.display="block",element.style.opacity=opacity;let fading=window.setInterval(fade,50);function fade(){opacity+=gap,element.style.opacity=opacity,opacity<=0&&(element.style.display="none"),opacity>=1&&(window.clearInterval(fading),callback&&callback())}}function fadeOut(element,ms,callback=null){let duration,interval=50,gap=50/ms,opacity=1,fading=window.setInterval(fade,50);function fade(){opacity-=gap,element.style.opacity=opacity,opacity<=0&&(element.style.display="none"),opacity<=0&&(window.clearInterval(fading),callback&&callback())}}function getElOffset(element){let boundingRect=getBoundingRect(element);return{top:boundingRect.top+window.scrollY,left:boundingRect.left+window.scrollX}}function getBoundingRect(element){return element.getBoundingClientRect()}function slideUp(element,ms){setProperty(element,"height",`${element.offsetHeight}px`),setProperty(element,"transition-property","height, margin, padding"),setProperty(element,"transition-duration",`${ms}ms`),setProperty(element,"box-sizing","border-box"),zeroMultiProperty(element,["margin-bottom","margin-top","padding-top","padding-bottom"]),setProperty(element,"overflow","hidden"),setTimeout(()=>setProperty(element,"height"),0),setTimeout(()=>{setProperty(element,"display","none"),removeMultiProperty(element,["height","padding-top","padding-bottom","margin-top","margin-bottom","overflow","transition-duration","transition-property"])},ms)}function slideDown(element,ms){element.style.display="block";let height=element.offsetHeight;setProperty(element,"height"),setProperty(element,"transition-property","height, margin, padding"),setProperty(element,"transition-duration",`${ms}ms`),setProperty(element,"box-sizing","border-box"),setProperty(element,"overflow","hidden"),setTimeout(()=>setProperty(element,"height",`${height}px`),0),setTimeout(()=>{removeMultiProperty(element,["height","overflow","transition-duration","transition-property"])},ms)}function setProperty(element,property,value=0){element.style.setProperty(property,value)}function zeroMultiProperty(element,properties){for(property of properties)setProperty(element,property)}function removeMultiProperty(element,properties){for(property of properties)removeProperty(element,property)}function removeProperty(element,property){element.style.removeProperty(property)} |