Edit file File name : project_details.html.twig Content :{% extends 'reporting/layout.html.twig' %} {% import "macros/charts.html.twig" as charts %} {% import "macros/widgets.html.twig" as widgets %} {% block report_title %}{{ 'report_project_details'|trans({}, 'reporting') }}{% endblock %} {% set tableName = tableName|default('project_details_reporting') %} {% set tableId = 'project-details-form' %} {% set view_budget = project_details is not null and is_granted('budget', 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': 'false'} %} {% if view_revenue %} {% set options = options|merge({'footer': 'rate'}) %} {% endif %} {{ charts.doughnut_javascript(options) }} {{ charts.bar_javascript({'legend': 'false'}) }} <script type="text/javascript"> document.addEventListener('kimai.initialized', function() { jQuery('#{{ tableId }}').on('change', function(ev) { jQuery(this).submit(); }); }); jQuery('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { renderChart(e.target.dataset.chart); }); function renderChart(chartName) { var found = false; Chart.helpers.each(Chart.instances, function(instance){ if (instance.chart.canvas.id === chartName) { found = true; } }); if (!found) { document.dispatchEvent(new Event('render.' + chartName)) } } </script> {% endblock %} {% block report %} {% set hasData = project is not null and project_view is not null %} {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/widgets.html.twig" as widgets %} {% block box_before %} {{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': tableId}}) }} {% endblock %} {% block box_after %} {{ form_end(form) }} {% endblock %} {% block box_title %} {{ form_widget(form) }} {% endblock %} {% block box_body %}{% endblock %} {% block box_body_class %}hidden{% endblock %} {% endembed %} {% if not hasData %} {{ widgets.nothing_found() }} {% endif %} {% if hasData %} {{ _self.project_details(project, project_view, project_details, view_budget, 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="nav-tabs-custom"> <ul class="nav nav-tabs pull-right"> {% if see_users %} <li><a data-chart="{{ chartPrefix }}User" href="#user-chart-{{ id}}" data-toggle="tab">{{ 'label.user'|trans }}</a></li> {% endif %} <li><a data-chart="{{ chartPrefix }}Activity" href="#activity-chart-{{ id}}" data-toggle="tab">{{ 'label.activity'|trans }}</a></li> {% if view_revenue %} <li><a data-chart="{{ chartPrefix }}Rate" href="#revenue-chart-{{ id }}" data-toggle="tab">{{ 'stats.revenue'|trans }}</a></li> {% endif %} <li class="active"><a data-chart="{{ chartPrefix }}Duration" href="#time-chart-{{ id }}" data-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li> <li class="pull-left header">{{ title }} {{ widgets.label(yearStat.duration|duration, 'gray') }}{% if view_revenue %} {{ widgets.label(yearStat.rate|money(currency), 'gray') }}{% endif %}</small></li> </ul> <div class="tab-content no-padding"> <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"> <tr> <th>{{ 'label.username'|trans }}</th> <th></th> {% for monthName in month_names() %} <th class="text-center">{{ monthName }}</th> {% endfor %} </tr> {% 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 %} <tr> <th></th> <th class="text-nowrap text-center total"> {{ yearTotal|duration }} </th> {% 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 %} <th class="text-nowrap text-center total"> {{ total|duration }} </th> {% endfor %} </tr> </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"> {% for stat in activities|sort((a, b) => b.duration <=> a.duration) %} {% set dataset = dataset|merge([{'value': stat.duration, 'name': stat.name, 'color': stat.activity.color|colorize(stat.activity.name), 'duration': stat.duration|duration, 'rate': stat.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-right">{{ stat.duration|duration }}</td> {% if view_revenue %} <td class="text-nowrap text-right">{{ stat.rate|money(currency) }}</td> {% endif %} <td class="text-nowrap text-right">{{ percentage|number_format(1) }} %</td> </tr> {% endfor %} </table> </div> <div class="col-xs-12 col-sm-3 col-md-4 col-lg-6"> {% set chartOptions = {'height': (dataset|length > 12 ? '600px' : '300px'), 'legend': {'position': (dataset|length > 12 ? 'top' : 'right')}, '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, view_budget, 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 -%} <div class="nav-tabs-custom"> <ul class="nav nav-tabs pull-right"> {% if see_users and project_details.userStats|length > 0 %} <li><a data-chart="{{ chartPrefix }}User" href="#user-chart" data-toggle="tab">{{ 'label.user'|trans }}</a></li> {% endif %} {% if activities is not empty %} <li><a data-chart="{{ chartPrefix }}Activity" href="#activity-chart" data-toggle="tab">{{ 'label.activity'|trans }}</a></li> {% endif %} {% if view_revenue and project_view.rateTotal > 0 %} <li><a data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-toggle="tab">{{ 'stats.revenue'|trans }}</a></li> {% endif %} {% if project_view.durationTotal > 0 %} <li><a data-chart="{{ chartPrefix }}Duration" href="#time-chart" data-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li> {% endif %} <li class="active"><a href="#details-chart" data-toggle="tab">{{ 'report_project_details'|trans({}, 'reporting') }}</a></li> <li class="pull-left header hidden-xs"> {{ widgets.label_project(project, {'inherit': false}) }} </li> </ul> <div class="tab-content no-padding"> <div class="chart tab-pane active" id="details-chart"> <div class="row"> <div class="col-xs-12 col-md-6"> <table class="table table-hover dataTable"> <tr {{ widgets.customer_row_attr(project.customer) }}> <th class="w-min"> {{ 'label.customer'|trans }} </th> <td> {{ widgets.label_customer(project.customer) }} </td> </tr> <tr> <th class="w-min"> {{ 'stats.durationTotal'|trans }} </th> <td>{{ project_view.durationTotal|duration }}</td> </tr> {% if view_revenue %} <tr> <th class="w-min"> {{ 'stats.amountTotal'|trans }} </th> <td>{{ project_view.rateTotal|money(currency) }}</td> </tr> {% endif %} </table> </div> <div class="col-xs-12 col-md-6"> <table class="table table-hover dataTable"> <tr> <th class="w-min"> {{ 'label.last_record'|trans }} </th> <td> {% if project_view.lastRecord is not null %} {{ project_view.lastRecord|date_short }} {% else %} – {% endif %} </td> </tr> {% if is_granted('create_export') %} <tr> <th class="w-min"> {{ 'label.not_exported'|trans }} </th> <td> <a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}"> {{ project_view.notExportedDuration|duration }} </a> </td> </tr> {% endif %} {% if is_granted('view_invoice') %} <tr> <th class="w-min"> {{ 'label.not_invoiced'|trans }} </th> <td> <a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}"> {{ project_view.notBilledRate|money(currency) }} </a> </td> </tr> {% endif %} </table> </div> </div> {% if view_budget and (project.timeBudget > 0 or project.budget > 0) %} {% set budgetStats = project_details.budgetStatisticModel %} <div class="row"> <div class="col-xs-12"> <table class="table table-hover dataTable"> {% if project.timeBudget > 0 %} <tr> <th class="w-min"> {{ 'label.timeBudget'|trans }} {% if budgetStats.isMonthlyBudget() %} ({{ 'label.budgetType_month'|trans }}) {% endif %} </th> <td> {{ progress.progressbar_timebudget(budgetStats) }} </td> </tr> {% endif %} {% if project.budget > 0 %} <tr> <th class="w-min"> {{ 'label.budget'|trans }} {% if budgetStats.isMonthlyBudget() %} ({{ 'label.budgetType_month'|trans }}) {% endif %} </th> <td> {{ progress.progressbar_budget(budgetStats, project.customer.currency) }} </td> </tr> {% endif %} </table> </div> </div> {% endif %} </div> {% if view_revenue and project_view.rateTotal > 0 %} <div class="chart tab-pane" id="revenue-chart"> {{ charts.bar_chart(chartPrefix ~ 'Rate', labels, [rates], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Rate'}) }} </div> {% endif %} {% if project_view.durationTotal > 0 %} <div class="chart tab-pane" id="time-chart"> {{ charts.bar_chart(chartPrefix ~ 'Duration', labels, [durations], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Duration'}) }} </div> {% 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"> {% set rateTotal = 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 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-right"> {{ userStat.duration|duration }} </td> {% if view_revenue %} <td class="text-nowrap text-right"> {{ userStat.rate|money(currency) }} </td> {% endif %} <td class="text-nowrap text-right"> {{ percentage|number_format(1) }} % </td> </tr> {% endfor %} <tr> <td></td> <th class="text-nowrap text-right total">{{ totalDuration|duration }}</th> {% if view_revenue %} <th class="text-nowrap text-right total">{{ rateTotal|money(currency) }}</th> {% endif %} <td></td> </tr> </table> </div> <div class="col-xs-12 col-sm-3 col-md-4 col-lg-6"> {% set chartOptions = {'height': (datasets|length > 12 ? '600px' : '300px'), 'legend': {'position': (datasets|length > 12 ? 'top' : 'right')}, 'renderEvent': 'render.' ~ chartPrefix ~ 'User'} %} {{ charts.doughnut_chart(chartPrefix ~ 'User', labels, datasets, chartOptions) }} </div> </div> </div> {% endif %} </div> </div> {% endmacro %} Save