[#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?date.iso)] [#assign lastDate = (timereport.dateIntervalEnd)!(interval(dates?last)?last)] [#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} |
Billable Hours | Invoiced Hours |
---|---|
[#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)] [#assign dateHours=timereport.calculateElapsedTimeAsDecimal(dateTimeEntries)] ['com.fanurio.common.freemarker.SimpleDay'?new('${date}'?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)] [#assign monthHours=timereport.calculateElapsedTimeAsDecimal(monthTimeEntries)] ['com.fanurio.common.freemarker.SimpleMonth'?new('${month}'?date("yyyy-MM")), ${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)] [#assign yearHours=timereport.calculateElapsedTimeAsDecimal(yearTimeEntries)] ['com.fanurio.common.freemarker.SimpleYear'?new('${year}'?date("yyyy")), ${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 --] [#outputformat "FTL"] [#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] [/#outputformat] [#outputformat "FTL"] [#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] [/#outputformat] [#outputformat "FTL"] [#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] [/#outputformat] [#-- 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?markup_string?eval[topClientsDataSet?markup_string?eval?size - 1][1] / totalTimeAsDecimal) * 360 / 2)) - 1)?int }/] [#assign topProjectsPieChartProperties = topPieChartProperties + { 'plot.startAngle': ((90 - ((topProjectsDataSet?markup_string?eval[topProjectsDataSet?markup_string?eval?size - 1][1] / totalTimeAsDecimal) * 360 / 2)) - 1)?int }/] [#assign topCategoriesPieChartProperties = topPieChartProperties + { 'plot.startAngle': ((90 - ((topCategoriesDataSet?markup_string?eval[topCategoriesDataSet?markup_string?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]