mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-01-10 19:47:35 +00:00
540 lines
28 KiB
Twig
540 lines
28 KiB
Twig
{% extends 'reporting/layout.html.twig' %}
|
|
{% import "macros/charts.html.twig" as charts %}
|
|
{% import "macros/widgets.html.twig" as widgets %}
|
|
|
|
{% set tableName = tableName|default('project_details_reporting') %}
|
|
{% set tableId = 'project-details-form' %}
|
|
{% set showMoneyBudget = project_details is not null and is_granted('budget', project_details.project) %}
|
|
{% set showTimeBudget = project_details is not null and is_granted('time', project_details.project) %}
|
|
{% set view_revenue = project_details is not null and is_granted('view_rate_other_timesheet') %}
|
|
{% set see_users = is_granted('view_other_timesheet') or is_granted('view_other_reporting') %}
|
|
|
|
{% block stylesheets %}
|
|
{{ parent() }}
|
|
{{ encore_entry_link_tags('chart') }}
|
|
{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ parent() }}
|
|
{{ encore_entry_script_tags('chart') }}
|
|
{% endblock %}
|
|
|
|
{% block javascripts %}
|
|
{{ parent() }}
|
|
{% set options = {'label': 'duration', 'title': 'name', 'legend': {'display': false}} %}
|
|
{% if view_revenue %}
|
|
{% set options = options|merge({'footer': 'rate'}) %}
|
|
{% endif %}
|
|
{{ charts.doughnut_javascript(options) }}
|
|
{{ charts.bar_javascript({'legend': {'display': false}}) }}
|
|
<script type="text/javascript">
|
|
[].slice.call(document.querySelectorAll('a[data-bs-toggle="tab"]')).forEach(function (triggerEl) {
|
|
triggerEl.addEventListener('shown.bs.tab', function (event) {
|
|
renderChart(event.target.dataset.chart);
|
|
});
|
|
{% if project is not null %}
|
|
renderChart('project{{ project.id }}Budget');
|
|
{% endif %}
|
|
});
|
|
|
|
function renderChart(chartName)
|
|
{
|
|
let found = false;
|
|
|
|
for (const instance of Object.values(Chart.instances)) {
|
|
if (instance.canvas.id === chartName) {
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
document.dispatchEvent(new Event('render.' + chartName))
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block report_form_layout %}
|
|
{{ form_widget(form.project, {'label': false, 'placeholder': 'project'}) }}
|
|
{% endblock %}
|
|
|
|
{% block report %}
|
|
{% set hasData = project is not null and project_view is not null %}
|
|
|
|
{% if not hasData %}
|
|
{{ widgets.nothing_found() }}
|
|
{% else %}
|
|
{{ _self.project_details(project, project_view, project_details, showMoneyBudget, showTimeBudget, view_revenue, see_users) }}
|
|
{% set currency = project.customer.currency %}
|
|
|
|
{%- for yearStat in project_details.years|reverse %}
|
|
{% set year = yearStat.year %}
|
|
{{ _self.duration_stat(year, year, year, month_names(), yearStat, project_details.getYearActivities(year), project_details.userYears(year), currency, view_revenue, see_users) }}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% macro duration_stat(id, title, year, labels, yearStat, activities, users, currency, view_revenue, see_users) %}
|
|
{% set rates = [] %}
|
|
{% set durations = [] %}
|
|
{% set chartPrefix = 'chart' ~ id %}
|
|
{% for monthNumber in 1..12 %}
|
|
{% set rate = 0 %}
|
|
{% set duration = 0 %}
|
|
{% set month = yearStat.month(monthNumber) %}
|
|
{% if month is not null %}
|
|
{% set rate = month.totalRate %}
|
|
{% set duration = month.totalDuration %}
|
|
{% endif %}
|
|
{% set durations = durations|merge([{'label': duration|duration, 'value': duration|chart_duration}]) %}
|
|
{% set rates = rates|merge([{'label': rate|money(currency), 'value': rate}]) %}
|
|
{% endfor -%}
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h3 class="card-title me-3">
|
|
{{ title }}
|
|
</h3>
|
|
<ul class="nav nav-pills" data-bs-toggle="tabs">
|
|
<li class="nav-item"><a class="nav-link active" data-chart="{{ chartPrefix }}Duration" href="#time-chart-{{ id }}" data-bs-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
|
|
{% if view_revenue %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart-{{ id }}" data-bs-toggle="tab">{{ 'revenue'|trans }}</a></li>
|
|
{% endif %}
|
|
{% if see_users %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}User" href="#user-chart-{{ id}}" data-bs-toggle="tab">{{ 'user'|trans }}</a></li>
|
|
{% endif %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Activity" href="#activity-chart-{{ id}}" data-bs-toggle="tab">{{ 'activity'|trans }}</a></li>
|
|
</ul>
|
|
<div class="card-actions">
|
|
{{ widgets.badge_counter(yearStat.duration|duration) }}
|
|
{% if view_revenue %}
|
|
{{ widgets.badge_counter(yearStat.rate|money(currency)) }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="tab-content mt-2 p-2">
|
|
<div class="chart tab-pane active" id="time-chart-{{ id }}">
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
{{ charts.bar_chart(chartPrefix ~ 'Duration', labels, [durations], {'height': '300px'}) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% if view_revenue %}
|
|
<div class="chart tab-pane" id="revenue-chart-{{ id }}">
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
{{ charts.bar_chart(chartPrefix ~ 'Rate', labels, [rates], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Rate'}) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="chart tab-pane" id="activity-chart-{{ id }}">
|
|
{{ _self.activity_tab(activities, yearStat.duration, currency, chartPrefix, view_revenue) }}
|
|
</div>
|
|
{% if see_users %}
|
|
<div class="chart tab-pane" id="user-chart-{{ id }}">
|
|
{{ _self.user_tab(year, users) }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
{% macro user_tab(year, userYearStats) %}
|
|
<div class="row">
|
|
<div class="col-xs-12 table-responsive">
|
|
<table class="table table-hover dataTable">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ 'username'|trans }}</th>
|
|
<th></th>
|
|
{% for monthName in month_names() %}
|
|
<th class="text-center">{{ monthName }}</th>
|
|
{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% set yearTotal = 0 %}
|
|
{% for userYearStat in userYearStats|sort((a, b) => b.duration <=> a.duration) %}
|
|
{% set user = userYearStat.user %}
|
|
{% set userYear = userYearStat.year %}
|
|
{% set userTotal = userYearStat.duration %}
|
|
{% set yearTotal = yearTotal + userTotal %}
|
|
<tr>
|
|
<td class="text-nowrap">
|
|
{{ widgets.label_dot(user.displayName, user.color) }}
|
|
</td>
|
|
<th class="text-nowrap text-center total">
|
|
{{ userTotal|duration }}
|
|
</th>
|
|
{% for monthNumber in 1..12 %}
|
|
{% set month = userYear.getMonth(monthNumber) %}
|
|
<td class="text-nowrap text-center">
|
|
{% if month is not null %}
|
|
{{ month.totalDuration|duration }}
|
|
{% endif %}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="summary">
|
|
<td></td>
|
|
<td class="text-nowrap text-center total">
|
|
{{ yearTotal|duration }}
|
|
</td>
|
|
{% for monthNumber in 1..12 %}
|
|
{% set total = 0 %}
|
|
{% for userYearStat in userYearStats %}
|
|
{% set month = userYearStat.year.getMonth(monthNumber) %}
|
|
{% if month is not null %}
|
|
{% set total = total + month.duration %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
<td class="text-nowrap text-center total">
|
|
{{ total|duration }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
{#
|
|
activities = array<ActivityStatistic>
|
|
totalDuration = int
|
|
currency = string
|
|
chartPrefix = string
|
|
view_revenue = boolean
|
|
#}
|
|
{% macro activity_tab(activities, totalDuration, currency, chartPrefix, view_revenue) %}
|
|
{% set dataset = [] %}
|
|
{% set labels = [] %}
|
|
<div class="row">
|
|
<div class="col-xs-12 col-sm-9 col-md-8 col-lg-6">
|
|
<table class="table table-hover dataTable">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th class="text-nowrap text-end">{{ 'duration'|trans }}</th>
|
|
{% if view_revenue %}
|
|
<th class="text-nowrap text-end">{{ 'billable'|trans }}</th>
|
|
<th class="text-nowrap text-end">{{ 'stats.amountTotal'|trans }}</th>
|
|
{% endif %}
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% set totalRate = 0.0 %}
|
|
{% set billable = 0 %}
|
|
{% for stat in activities|sort((a, b) => b.duration <=> a.duration) %}
|
|
{% set rate = stat.rate %}
|
|
{% set totalRate = totalRate + rate %}
|
|
{% set billable = billable + stat.rateBillable %}
|
|
{% set dataset = dataset|merge([{'value': stat.duration, 'name': stat.name, 'color': stat.activity.color|colorize(stat.activity.name), 'duration': stat.duration|duration, 'rate': rate|money(currency)}]) %}
|
|
{% set percentage = 0 %}
|
|
{% if totalDuration > 0 and stat.duration > 0 %}
|
|
{% set percentage = (100 / (totalDuration / stat.duration)) %}
|
|
{% endif %}
|
|
<tr>
|
|
<td>{{ widgets.label_activity(stat.activity, {'inherit': false, 'random': true}) }}</td>
|
|
<td class="text-nowrap text-end">{{ stat.duration|duration }}</td>
|
|
{% if view_revenue %}
|
|
<td class="text-nowrap text-end">{{ stat.rateBillable|money(currency) }}</td>
|
|
<td class="text-nowrap text-end">{{ stat.rate|money(currency) }}</td>
|
|
{% endif %}
|
|
<td class="text-nowrap text-end">{{ percentage|number_format(1) }} %</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="summary">
|
|
<td></td>
|
|
<td class="text-nowrap text-end">{{ totalDuration|duration }}</td>
|
|
{% if view_revenue %}
|
|
<td class="text-nowrap text-end">{{ billable|money(currency) }}</td>
|
|
<td class="text-nowrap text-end">{{ totalRate|money(currency) }}</td>
|
|
{% endif %}
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
<div class="col-xs-12 col-sm-3 col-md-4 col-lg-6">
|
|
{% set chartOptions = {'height': (dataset|length > 12 ? '600px' : '300px'), 'renderEvent': 'render.' ~ chartPrefix ~ 'Activity'} %}
|
|
{{ charts.doughnut_chart(chartPrefix ~ 'Activity', labels, dataset, chartOptions) }}
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
{#
|
|
project = Project
|
|
project_view = ProjectViewModel
|
|
project_details = ProjectDetailsModel
|
|
#}
|
|
{% macro project_details(project, project_view, project_details, showMoneyBudget, showTimeBudget, view_revenue, see_users) %}
|
|
{% set activities = project_details.activities %}
|
|
{% set years = project_details.years %}
|
|
{% import "macros/progressbar.html.twig" as progress %}
|
|
{% import "macros/widgets.html.twig" as widgets %}
|
|
{% import "macros/charts.html.twig" as charts %}
|
|
{% set currency = project.customer.currency %}
|
|
{% set chartPrefix = 'project' ~ project.id %}
|
|
|
|
{%- set labels = [] %}
|
|
{% set rates = [] %}
|
|
{% set durations = [] %}
|
|
{% for year in years %}
|
|
{% set labels = labels|merge([year.year]) %}
|
|
{% set rates = rates|merge([{'label': year.rate|money(currency), 'value': year.rate}]) %}
|
|
{% set durations = durations|merge([{'label': year.duration|duration, 'value': year.duration|chart_duration}]) %}
|
|
{% endfor -%}
|
|
{% set showTotalDurationChart = project_view.durationTotal > 0 and years|length > 1 %}
|
|
{% set showTotalRevenueChart = view_revenue and project_view.rateTotal > 0 and years|length > 1 %}
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h3 class="card-title me-3">
|
|
{{ widgets.label_project(project, {'inherit': false}) }}
|
|
</h3>
|
|
<ul class="nav nav-pills" data-bs-toggle="tabs">
|
|
<li class="nav-item"><a class="nav-link active" href="#details-chart" data-bs-toggle="tab">{{ 'report_project_details'|trans({}, 'reporting') }}</a></li>
|
|
{% if showTotalDurationChart %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Duration" href="#time-chart" data-bs-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
|
|
{% endif %}
|
|
{% if showTotalRevenueChart %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-bs-toggle="tab">{{ 'revenue'|trans }}</a></li>
|
|
{% endif %}
|
|
{% if see_users and project_details.userStats|length > 0 %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}User" href="#user-chart" data-bs-toggle="tab">{{ 'user'|trans }}</a></li>
|
|
{% endif %}
|
|
{% if activities is not empty %}
|
|
<li class="nav-item"><a class="nav-link" data-chart="{{ chartPrefix }}Activity" href="#activity-chart" data-bs-toggle="tab">{{ 'activity'|trans }}</a></li>
|
|
{% endif %}
|
|
</ul>
|
|
<div class="card-actions">
|
|
{{ widgets.badge_counter(project_view.durationTotal|duration) }}
|
|
{% if view_revenue %}
|
|
{{ widgets.badge_counter(project_view.rateTotal|money(currency)) }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="tab-content mt-2 p-2 ps-3">
|
|
<div class="chart tab-pane active" id="details-chart">
|
|
<div class="row">
|
|
<div class="col-xs-12 col-sm-6 col-md-7 col-lg-7 col-xl-6 col-xxl-5">
|
|
<div class="datagrid" style="--tblr-datagrid-item-width: 12rem">
|
|
<div class="datagrid-item" {{ widgets.customer_row_attr(project.customer) }}>
|
|
<div class="datagrid-title">
|
|
{{ 'customer'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">
|
|
{{ widgets.label_customer(project.customer) }}
|
|
</div>
|
|
</div>
|
|
<div class="datagrid-item" {{ widgets.project_row_attr(project) }}>
|
|
<div class="datagrid-title">
|
|
{{ 'project'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">
|
|
{{ widgets.label_project(project) }}
|
|
</div>
|
|
</div>
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'stats.durationTotal'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">{{ project_view.durationTotal|duration }}</div>
|
|
</div>
|
|
{% if view_revenue %}
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'stats.amountTotal'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">{{ project_view.rateTotal|money(currency) }}</div>
|
|
</div>
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'billable'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">{{ project_view.billableRate|money(currency) }}</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'last_record'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">
|
|
{% if project_view.lastRecord is not null %}
|
|
{{ project_view.lastRecord|date_short }}
|
|
{% else %}
|
|
–
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if is_granted('create_export') %}
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'not_exported'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">
|
|
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
|
|
{{ project_view.notExportedDuration|duration }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if is_granted('view_invoice') %}
|
|
<div class="datagrid-item">
|
|
<div class="datagrid-title">
|
|
{{ 'not_invoiced'|trans }}
|
|
</div>
|
|
<div class="datagrid-content">
|
|
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
|
|
{{ project_view.notExportedRate|money(currency) }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-xs-12 col-sm-6 col-md-5 col-lg-5 col-xl-6 col-xxl-7">
|
|
{% if (showMoneyBudget and project.hasBudget()) or (showTimeBudget and project.hasTimeBudget()) %}
|
|
{% from "project/charts.html.twig" import project_budget %}
|
|
{{ project_budget(project, project_details, chartPrefix) }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if (showMoneyBudget and project.hasBudget()) or (showTimeBudget and project.hasTimeBudget()) %}
|
|
{% set budgetStats = project_details.budgetStatisticModel %}
|
|
<div class="row">
|
|
<div class="col-xs-12">
|
|
<table class="table table-borderless">
|
|
{% if showTimeBudget and project.hasTimeBudget() %}
|
|
<tr>
|
|
<th class="w-min">
|
|
{{ 'timeBudget'|trans }}
|
|
{% if budgetStats.isMonthlyBudget() %}
|
|
({{ 'budgetType_month'|trans }})
|
|
{% endif %}
|
|
</th>
|
|
<td>
|
|
{{ progress.progressbar_timebudget(budgetStats) }}
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
{% if showMoneyBudget and project.hasBudget() %}
|
|
<tr>
|
|
<th class="w-min">
|
|
{{ 'budget'|trans }}
|
|
{% if budgetStats.isMonthlyBudget() %}
|
|
({{ 'budgetType_month'|trans }})
|
|
{% endif %}
|
|
</th>
|
|
<td>
|
|
{{ progress.progressbar_budget(budgetStats, project.customer.currency) }}
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% if showTotalDurationChart %}
|
|
<div class="chart tab-pane" id="time-chart">
|
|
{{ charts.bar_chart(chartPrefix ~ 'Duration', labels, [durations], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Duration'}) }}
|
|
</div>
|
|
{% if showTotalRevenueChart %}
|
|
<div class="chart tab-pane" id="revenue-chart">
|
|
{{ charts.bar_chart(chartPrefix ~ 'Rate', labels, [rates], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Rate'}) }}
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if activities is not empty %}
|
|
<div class="chart tab-pane" id="activity-chart">
|
|
{{ _self.activity_tab(activities, project_view.durationTotal, project.customer.currency, chartPrefix, view_revenue) }}
|
|
</div>
|
|
{% endif %}
|
|
{% if see_users and project_details.userStats|length > 0 %}
|
|
<div class="chart tab-pane" id="user-chart">
|
|
<div class="row">
|
|
<div class="col-xs-12 col-sm-9 col-md-8 col-lg-6">
|
|
<table class="table table-hover dataTable">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th class="text-nowrap text-end">{{ 'duration'|trans }}</th>
|
|
{% if view_revenue %}
|
|
<th class="text-nowrap text-end">{{ 'billable'|trans }}</th>
|
|
<th class="text-nowrap text-end">{{ 'stats.amountTotal'|trans }}</th>
|
|
{% endif %}
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% set rateTotal = 0 %}
|
|
{% set rateTotalBillable = 0 %}
|
|
{% set totalDuration = 0 %}
|
|
{% set datasets = [] %}
|
|
{% set labels = [] %}
|
|
{% for userStat in project_details.userStats|sort((a, b) => b.duration <=> a.duration) %}
|
|
{% set user = userStat.user %}
|
|
{% set color = user.color|colorize(user.displayName) %}
|
|
{% set rateTotal = rateTotal + userStat.rate %}
|
|
{% set rateTotalBillable = rateTotalBillable + userStat.rateBillable %}
|
|
{% set totalDuration = totalDuration + userStat.duration %}
|
|
{% set datasets = datasets|merge([{'name': user.displayName, 'duration': userStat.duration|duration, 'value': userStat.duration, 'color': color, 'rate': userStat.rate|money(currency)}]) %}
|
|
{% set percentage = 0 %}
|
|
{% if project_view.durationTotal > 0 and userStat.duration > 0 %}
|
|
{% set percentage = (100 / (project_view.durationTotal / userStat.duration)) %}
|
|
{% endif %}
|
|
<tr>
|
|
<td>
|
|
{{ widgets.label_dot(user.displayName, user.color) }}
|
|
</th>
|
|
<td class="text-nowrap text-end">
|
|
{{ userStat.duration|duration }}
|
|
</td>
|
|
{% if view_revenue %}
|
|
<td class="text-nowrap text-end">
|
|
{{ userStat.rateBillable|money(currency) }}
|
|
</td>
|
|
<td class="text-nowrap text-end">
|
|
{{ userStat.rate|money(currency) }}
|
|
</td>
|
|
{% endif %}
|
|
<td class="text-nowrap text-end">
|
|
{{ percentage|number_format(1) }} %
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="summary">
|
|
<td></td>
|
|
<td class="text-nowrap text-end total">{{ totalDuration|duration }}</td>
|
|
{% if view_revenue %}
|
|
<td class="text-nowrap text-end total">{{ rateTotalBillable|money(currency) }}</td>
|
|
<td class="text-nowrap text-end total">{{ rateTotal|money(currency) }}</td>
|
|
{% endif %}
|
|
<td></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
<div class="col-xs-12 col-sm-3 col-md-4 col-lg-6">
|
|
{% set chartOptions = {'height': (datasets|length > 12 ? '600px' : '300px'), 'renderEvent': 'render.' ~ chartPrefix ~ 'User'} %}
|
|
{{ charts.doughnut_chart(chartPrefix ~ 'User', labels, datasets, chartOptions) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|