Time statistics

[#if timereport.dateIntervalStart??] [#if timereport.dateIntervalEnd??] ${timereport.dateIntervalStart?date} - ${timereport.dateIntervalEnd?date} [#else] After ${timereport.dateIntervalStart?date} [/#if] [#else] [#if timereport.dateIntervalEnd??] Before ${timereport.dateIntervalEnd?date} [#else] All dates [/#if] [/#if]



[#if timereport.timeEntries?size == 0]

There is no data available for the selected report settings.

[#else]

[#assign clients = timereport.groupClients(timereport.timeEntries)] [#assign projects = timereport.groupProjects(timereport.timeEntries)] [#assign categories = timereport.groupTaskCategories(timereport.timeEntries)] [#assign dates=timereport.groupDatesByDate(timereport.timeEntries)?sort] [#assign months=timereport.groupDatesByMonth(timereport.timeEntries)] [#assign years=timereport.groupDatesByYear(timereport.timeEntries)] [#assign noCategoryTimeEntries = timereport.filterByTaskCategoryNone(timereport.timeEntries)] [#assign totalTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(timereport.timeEntries)] [#assign totalTimeAsHour = timereport.calculateElapsedTimeAsHour(timereport.timeEntries)] [#assign firstDate = (timereport.dateIntervalStart)!(dates?first.toDate())] [#assign lastDate = (timereport.dateIntervalEnd)!(dates?last.toDate())] [#assign reportDays = (lastDate?long - firstDate?long) / (1000 * 60 * 60 * 24)] [#assign clientsMap = []] [#list clients as group] [#assign groupTimeEntries = timereport.filterByClient(timereport.timeEntries, group)] [#assign groupTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(groupTimeEntries)] [#assign groupTimeAsHour = timereport.calculateElapsedTimeAsHour(groupTimeEntries)] [#assign clientsMap = clientsMap + [{"client" : group, "percentage": (groupTimeAsDecimal / totalTimeAsDecimal), "time": groupTimeAsHour, "hours": groupTimeAsDecimal}]] [/#list] [#assign projectsMap = []] [#list projects as group] [#assign groupTimeEntries = timereport.filterByProject(timereport.timeEntries, group)] [#assign groupTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(groupTimeEntries)] [#assign groupTimeAsHour = timereport.calculateElapsedTimeAsHour(groupTimeEntries)] [#assign projectsMap = projectsMap + [{"project" : group, "percentage": (groupTimeAsDecimal / totalTimeAsDecimal), "time": groupTimeAsHour, "hours": groupTimeAsDecimal}]] [/#list] [#assign categoriesMap = []] [#list categories as group] [#assign groupTimeEntries = timereport.filterByTaskCategory(timereport.timeEntries, group)] [#assign groupTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(groupTimeEntries)] [#assign groupTimeAsHour = timereport.calculateElapsedTimeAsHour(groupTimeEntries)] [#assign categoriesMap = categoriesMap + [{"category" : group, "percentage": (groupTimeAsDecimal / totalTimeAsDecimal), "time": groupTimeAsHour, "hours": groupTimeAsDecimal}]] [/#list] [#-- 1. Time stats --]
Hours Days Avg. Hours / Day
${totalTimeAsDecimal} ${dates?size?string("#")} ${totalTimeAsDecimal / dates?size}
[#assign billableTimeEntries = []] [#list timereport.timeEntries as te] [#if te.billable] [#assign billableTimeEntries = billableTimeEntries + [te]] [/#if] [/#list] [#assign totalBillableTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(billableTimeEntries)] [#assign invoicedTimeEntries = []] [#list billableTimeEntries as te] [#if te.invoiced] [#assign invoicedTimeEntries = invoicedTimeEntries + [te]] [/#if] [/#list] [#assign totalInvoicedTimeAsDecimal = timereport.calculateElapsedTimeAsDecimal(invoicedTimeEntries)] [#if billableTimeEntries?size != 0] [#assign billableHoursChartProperties = { 'legend.visible': true, 'legend.position': 'bottom', 'legend.verticalAlignment': 'top', 'legend.frame': {"insets": [0, 0, 0, 0]}, 'legend.margin': [0, 0, 0, 0], 'legend.itemLabelPadding': [05, 05, 05, 05], 'plot.legendLabelGenerator': '{0} ({1} h)', 'plot.backgroundPaint': '', 'plot.outlineVisible': false, 'plot.shadowPaint': '', 'plot.sectionOutlineStroke': [{'width': 2.0}, {'width': 2.0}], 'plot.sectionOutlinePaint': ['#3366cc', '#d7d7d7'], 'plot.sectionPaint':['#3366cc', '#d7d7d7'], 'plot.labelGenerator': '', 'plot.centerTextMode': 'fixed', 'plot.centerText': '${(totalBillableTimeAsDecimal/totalTimeAsDecimal)?string.percent}', 'plot.centerTextFont': {'size': 36}, 'plot.centerTextColor': '#000000', 'plot.sectionDepth': 0.03, 'plot.separatorsVisible': false } /] [#assign invoicedHoursChartProperties = billableHoursChartProperties + { 'plot.centerText': '${(totalInvoicedTimeAsDecimal/totalBillableTimeAsDecimal)?string.percent}', 'plot.sectionOutlinePaint': ['#3366cc', '#cacef0'], 'plot.sectionPaint':['#3366cc', '#cacef0'] } /] [#-- 2. Billable time stats --]
Billable Hours Invoiced Hours
[/#if] [#-- 3. The bar chart that summarize time by date, month or year depending on the report interval --] [#-- Don't show the chart if the report is for a single day --]

[#if reportDays > 1]

[#assign chartProperties = { 'padding': [10, 10, 10, 10], 'title.visible': false, 'legend.visible': false, 'plot.backgroundPaint': '', 'plot.outlineVisible': false, 'plot.renderer': 'org.jfree.chart.renderer.xy.XYBarRenderer', 'plot.renderer.margin': 0.40, 'plot.renderer.shadowVisible': false, 'plot.domainAxis.dateFormatOverride': "EEE, MMM dd", 'plot.domainAxis.minorTickMarksVisible': true, 'plot.domainAxis.minorTickMarkInsideLength': 1, 'plot.domainAxis.minorTickMarkOutsideLength': 0, 'plot.domainAxis.tickMarkPosition': 'middle', 'plot.domainAxis.tickMarkOutsideLength': 3, 'plot.domainAxis.tickMarkInsideLength': 3, 'plot.domainAxis.verticalTickLabels': true, 'plot.domainAxis.lowerMargin': 0.10, 'plot.domainAxis.upperMargin': 0.10, 'plot.domainAxis.minimumDate': firstDate, 'plot.domainAxis.maximumDate': lastDate, 'plot.rangeAxis.visible': true, 'plot.rangeAxis.axisLineVisible': false, 'plot.rangeAxis.tickMarksVisible': false, 'plot.rangeAxis.tickLabelFont': {'size': 12}, 'plot.rangeAxis.autoRangeIncludesZero': true, 'plot.rangeGridlinesVisible': true, 'plot.rangeGridlinePaint': '#aaaaaa' } /] [#assign title = ""] [#if reportDays < 32] [#-- if the report is for less than 32 days then show time by date --] [#assign title = "Hours by date"] [#-- show the names of the days horizontally when the're are less than 8 because they will fit and they are easier to read --] [#if reportDays < 8] [#assign chartProperties = chartProperties + { 'plot.domainAxis.verticalTickLabels': false } /] [/#if] [#-- make sure the domain axis shows only a single tick for each day if less than 15 days are displayed --] [#if reportDays < 15] [#assign chartProperties = chartProperties + { 'plot.domainAxis.tickUnit': {'unitType': 'day', 'multiple': 1} } /] [/#if] [#-- build a data set for the days that will be represented by the chart --] [#assign dataSet] [ ['Date', 'Hours'], [#list dates?sort as date] [#assign dateTimeEntries=timereport.filterByDate(timereport.timeEntries, date.toInterval())] [#assign dateHours=timereport.calculateElapsedTimeAsDecimal(dateTimeEntries)] ['com.fanurio.common.freemarker.SimpleDay'?new('${date.toDate()?date?iso_local}'?date.iso), ${dateHours?c}] [#if date?has_next],[/#if] [/#list] ] [/#assign] [#elseif reportDays < 367] [#-- if the report is for less than 367 days then show time by month --] [#assign title = "Hours by month"] [#-- show the names of the months horizontally because they will fit and they are easier to read --] [#assign chartProperties = chartProperties + { 'plot.domainAxis.dateFormatOverride': "MMM yy", 'plot.domainAxis.verticalTickLabels': false } /] [#-- make sure the domain axis shows only a single tick for each month if less than 7 months are displayed --] [#if months?size < 7] [#assign chartProperties = chartProperties + { 'plot.domainAxis.tickUnit': {'unitType': 'month', 'multiple': 1} } /] [/#if] [#assign dataSet] [ ['Month', 'Hours'], [#list months?sort as month] [#assign monthTimeEntries=timereport.filterByDate(timereport.timeEntries, month.toInterval())] [#assign monthHours=timereport.calculateElapsedTimeAsDecimal(monthTimeEntries)] ['com.fanurio.common.freemarker.SimpleMonth'?new('${month.toInterval().start.toDate()?date?iso_local}'?date.iso), ${monthHours?c}] [#if month?has_next],[/#if] [/#list] ] [/#assign] [#else] [#-- if the report is for 367 days or more then show time by year --] [#assign title = "Hours by year"] [#assign chartProperties = chartProperties + { 'plot.domainAxis.dateFormatOverride': "yyyy", 'plot.domainAxis.verticalTickLabels': false, 'plot.domainAxis.tickUnit': {'unitType': 'year', 'multiple': 1} } /] [#if years?size < 7] [#assign chartProperties = chartProperties + { 'plot.renderer.margin': 0.60 } /] [/#if] [#assign dataSet] [ ['Year', 'Hours'], [#list years?sort as year] [#assign yearTimeEntries=timereport.filterByDate(timereport.timeEntries, year.toInterval())] [#assign yearHours=timereport.calculateElapsedTimeAsDecimal(yearTimeEntries)] ['com.fanurio.common.freemarker.SimpleYear'?new('${year.toInterval().start.toDate()?date?iso_local}'?date.iso), ${yearHours?c}] [#if year?has_next],[/#if] [/#list] ] [/#assign] [/#if]
${title}

[/#if]

[#-- 4. The pie charts that summarize time by client, project and task category --] [#assign topPieChartProperties = { 'title.visible': false, 'legend.visible': false, 'plot.backgroundPaint': '', 'plot.outlineVisible': false, 'plot.shadowPaint': '', 'plot.startAngle': 90, 'plot.labelFont': {'size': 12}, 'plot.labelGenerator': '{0} ({2})', 'plot.labelBackgroundPaint': '', 'plot.labelShadowPaint': '', 'plot.labelOutlinePaint': '', 'plot.labelLinkStyle': 'standard', 'plot.maximumLabelWidth': 0.3, 'plot.sectionDepth': 0.25, 'plot.separatorsVisible': false } /] [#-- create datasets for the top elements --] [#assign topClientsDataSet] [ [#if clients?size > 5] [#assign others = totalTimeAsDecimal] [#list clientsMap?sort_by('hours')?reverse?chunk(4)?first as client] ['${client['client'].name}', ${client['hours']?c}] , [#assign others = others - client['hours']] [/#list] ['Others', ${others?c}] [#else] [#list clientsMap?sort_by('hours')?reverse as client] ['${client['client'].name}', ${client['hours']?c}] [#if client?has_next],[/#if] [/#list] [/#if] ] [/#assign] [#assign topProjectsDataSet] [ [#if projects?size > 5] [#assign others = totalTimeAsDecimal] [#list projectsMap?sort_by('hours')?reverse?chunk(4)?first as project] ['${project['project'].client.name} | ${project['project'].name}', ${project['hours']?c}] , [#assign others = others - project['hours']] [/#list] ['Others', ${others?c}] [#else] [#list projectsMap?sort_by('hours')?reverse as project] ['${project['project'].client.name} | ${project['project'].name}', ${project['hours']?c}] [#if project?has_next],[/#if] [/#list] [/#if] ] [/#assign] [#assign topCategoriesDataSet] [ [#if categories?size > 5] [#assign others = totalTimeAsDecimal] [#list categoriesMap?sort_by('hours')?reverse?chunk(4)?first as category] ['${category['category'].name}', ${category['hours']?c}] , [#assign others = others - category['hours']] [/#list] ['Others', ${others?c}] [#else] [#list categoriesMap?sort_by('hours')?reverse as category] ['${category['category'].name}', ${category['hours']?c}] [#if category?has_next],[/#if] [/#list] [#if noCategoryTimeEntries?size != 0] [#if categories?size != 0],[/#if] ['None', ${timereport.calculateElapsedTimeAsDecimal(noCategoryTimeEntries)?c}] [/#if] [/#if] ] [/#assign] [#-- make sure the label of the last pie section is on the right so that the chart is less crowded --] [#assign topClientsPieChartProperties = topPieChartProperties + { 'plot.startAngle': ((90 - ((topClientsDataSet?eval[topClientsDataSet?eval?size - 1][1] / totalTimeAsDecimal) * 360 / 2)) - 1)?int }/] [#assign topProjectsPieChartProperties = topPieChartProperties + { 'plot.startAngle': ((90 - ((topProjectsDataSet?eval[topProjectsDataSet?eval?size - 1][1] / totalTimeAsDecimal) * 360 / 2)) - 1)?int }/] [#assign topCategoriesPieChartProperties = topPieChartProperties + { 'plot.startAngle': ((90 - ((topCategoriesDataSet?eval[topCategoriesDataSet?eval?size - 1][1] / totalTimeAsDecimal) * 360 / 2)) - 1)?int }/]
Top Clients
Top Projects

[#if categories?size != 0]

Top Categories

[/#if]

[#-- 5. The table that summarizes time by client, project and task category --]

Time summary by client

[#list clientsMap?sort_by('percentage')?reverse as client]

${(client_index + 1)?string("#")}.

${client['client'].name}

${client['percentage']?string("0%")}

${client['time']}

[/#list]

Time summary by project

[#list projectsMap?sort_by('percentage')?reverse as project]

${(project_index + 1)?string("#")}.

${project['project'].client.name} | ${project['project'].name}

${project['percentage']?string("0%")}

${project['time']}

[/#list]
[#if categories?size != 0]

Time summary by task category

[#list categoriesMap?sort_by('percentage')?reverse as category]

${(category_index + 1)?string("#")}.

${category['category'].name}

${category['percentage']?string("0%")}

${category['time']}

[/#list]
[#assign noCategoryTimeEntries=timereport.filterByTaskCategoryNone(timereport.timeEntries)] [#if noCategoryTimeEntries?size != 0]

${(categories?size + 1)?string("#")}.

None

[#assign percentage = (timereport.calculateElapsedTimeAsDecimal(noCategoryTimeEntries) / totalTimeAsDecimal)?string("0%")]

${percentage}

${timereport.calculateElapsedTimeAsHour(noCategoryTimeEntries)}

[/#if]
[/#if]

[/#if]