Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)

This commit is contained in:
Kevin Papst
2022-12-31 21:19:55 +01:00
committed by GitHub
parent 95e06746bd
commit 90a0fd8a22
2164 changed files with 83700 additions and 86426 deletions

View File

@@ -1,34 +1,17 @@
{% extends 'reporting/layout.html.twig' %}
{% block report_title %}{{ report_title|trans({}, 'reporting') }}{% endblock %}
{% block report_form_layout %}
{{ form_widget(form.date) }}
{{ form_widget(form.customer, {'label': false, 'placeholder': 'please_choose'}) }}
{{ form_widget(form.sumType) }}
{% from '@theme/components/buttons.html.twig' import submit_button %}
{{ submit_button('download', {'attr': {'formaction': path(export_route)}, 'icon': 'download', 'combined': false}, 'primary') }}
{% endblock %}
{% block report %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% embed '@theme/embeds/card.html.twig' %}
{% from "macros/widgets.html.twig" import nothing_found %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'report-toolbar-form'}}) }}
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% block box_title %}
{{ form_widget(form.date) }}
{% if form.sumType.vars.choices|length > 1 %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'display'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.sumType) }}
</li>
</ul>
</div>
{% endif %}
<button class="btn btn-primary" formaction="{{ path(export_route) }}" type="submit"><i class="{{ 'download'|icon }}"></i></button>
{% endblock %}
{% block box_body_class %} table-responsive {% if stats is not empty %}no-padding{% endif %}{% endblock %}
{% block box_body_class %} table-responsive {% if stats is not empty %}p-0{% endif %}{% endblock %}
{% block box_body %}
{% if stats is empty %}
{{ nothing_found() }}
@@ -39,14 +22,4 @@
{% endif %}
{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
jQuery('#report-toolbar-form').on('change', function(ev) {
jQuery(this).submit();
});
</script>
{% endblock %}

View File

@@ -7,14 +7,14 @@
{% if dataType == 'rate' %}
{% set dataTypeTitle = 'stats.amountTotal' %}
{% elseif dataType == 'internalRate' %}
{% set dataTypeTitle = 'label.rate_internal' %}
{% set dataTypeTitle = 'internalRate' %}
{% else %}
{% set dataTypeTitle = 'stats.durationTotal' %}
{% endif %}
<table class="table table-bordered dataTable">
<thead>
<tr>
<th rowspan="2" style="{{ rowspanStyle }}">{{ 'label.project'|trans }}</th>
<th rowspan="2" style="{{ rowspanStyle }}">{{ 'project'|trans }}</th>
{% for activity in stats.activities %}
<th colspan="3" class="text-center">{{ activity.name }}</th>
{% endfor %}
@@ -22,7 +22,7 @@
</tr>
<tr>
{% for activity in stats.activities %}
<th class="text-center">{{ 'label.user'|trans }}</th>
<th class="text-center">{{ 'user'|trans }}</th>
<th class="text-center">{{ dataTypeTitle|trans }}</th>
<th class="text-center">{{ 'sum.total'|trans }}</th>
{% endfor %}

View File

@@ -0,0 +1,26 @@
{% extends 'base.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block page_actions %}
{% set event = actions(app.user, 'reporting', 'report') %}
{{ widgets.page_actions(event.actions) }}
{% endblock %}
{% block main %}
<div class="row row-cards">
{% for report in reports %}
<div class="col-md-6 col-lg-3">
<div class="card">
<div class="card-body p-4 text-center">
<span class="avatar avatar-xl mb-3 avatar-rounded"><i class="{{ report.reportIcon|icon(false, report.reportIcon) }}" style="color: {{ null|colorize(report.reportIcon) }}"></i></span>
</div>
<div class="d-flex">
<a href="{{ path(report.route) }}" class="card-btn">{{ report.label|trans({}, 'reporting') }}</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -2,16 +2,45 @@
{% import "macros/widgets.html.twig" as widgets %}
{% block page_title %}{{ 'menu.reporting'|trans }}{% endblock %}
{% block page_subtitle %}{% block report_title %}{% endblock %}{% endblock %}
{% block page_actions %}
{% set event = actions(app.user, 'reporting', 'index') %}
{{ widgets.page_actions(event.actions) }}
{% block breadcrumb %}
{% from "macros/breadcrumb.html.twig" import breadcrumb %}
{{ breadcrumb({
('menu.reporting'|trans): path('reporting'),
(report_title|trans({}, 'reporting')): '#'
}) }}
{% endblock %}
{% block main %}
{% if form is defined %}
<div class="card mb-3">
<div class="card-body">
{% block report_form %}
{{ form_start(form, {'attr': {'class': 'form-reporting', 'id': 'report-form'}}) }}
<div class="{% block report_form_container_class %}btn-list w-100{% endblock %}">
{% block report_form_layout %}{{ form_rest(form) }}{% endblock %}
</div>
{{ form_end(form) }}
{% endblock %}
</div>
</div>
{% endif %}
<div class="row">
<div class="col-md-12" id="reporting-content">
{% block report %}{% endblock %}
</div>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
const reportForm = document.getElementById('report-form');
if (reportForm !== null) {
reportForm.addEventListener('change', (event) => {
reportForm.submit();
});
}
</script>
{% endblock %}

View File

@@ -1,8 +1,6 @@
{% extends 'reporting/layout.html.twig' %}
{% import "macros/datatables.html.twig" as tables %}
{% block report_title %}{{ 'report_project_daterange'|trans({}, 'reporting') }}{% endblock %}
{% set showMoneyBudget = is_granted('budget_money', 'project') %}
{% set showTimeBudget = is_granted('budget_time', 'project') %}
{% set viewRevenue = is_granted('view_rate_other_timesheet') %}
@@ -12,12 +10,20 @@
} %}
{% if showTimeBudget %}
{% set columns = columns|merge({
'timeBudget': {'class': 'hidden-xs', 'title': 'label.timeBudget'|trans},
'timeBudget': {'class': 'd-none d-md-table-cell', 'title': 'timeBudget'|trans},
}) %}
{% endif %}
{% if showMoneyBudget %}
{% set columns = columns|merge({
'budget': {'class': 'hidden-xs', 'title': 'label.budget'|trans},
'budget': {'class': 'd-none d-md-table-cell', 'title': 'budget'|trans},
}) %}
{% endif %}
{% set columns = columns|merge({
'duration': {'class': 'text-end hw-min w-min', 'title': 'stats.durationMonth'|trans, 'columnClass': 'w-min'},
}) %}
{% if showMoneyBudget %}
{% set columns = columns|merge({
'rate': {'class': 'text-end hw-min w-min', 'title': 'stats.amountMonth'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% set columns = columns|merge({
@@ -29,7 +35,7 @@
}) %}
{% endif %}
{% set columns = columns|merge({
'billable': {'class': 'text-center hw-min', 'columnClass': 'w-min'},
'billable': {'class': 'd-none d-sm-table-cell text-center hw-min w-min', 'columnClass': 'w-min'},
'actions': {'class': 'actions alwaysVisible'},
}) %}
{% set tableName = 'project_daterange_reporting' %}
@@ -38,52 +44,46 @@
{{ tables.data_table_column_modal(tableName, columns) }}
{% endblock %}
{% block report_form_layout %}
{{ form_start(form, {'attr': {'class': 'form-reporting', 'id': 'report-form'}}) }}
<div class="btn-list w-100">
{{ form_widget(form.month, {'label': false}) }}
{{ form_widget(form.customer, {'label': false, 'placeholder': 'please_choose'}) }}
<div class="dropdown">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ icon('filter', true) }}
</button>
<ul class="dropdown-menu checkbox-menu">
{% for option in form.budgetType.children %}
<li class="dropdown-item">
{{ form_widget(option) }}
</li>
{% endfor %}
<li class="dropdown-divider"></li>
<li class="dropdown-item">
{{ form_widget(form.includeNoWork) }}
</li>
</ul>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% block report %}
{% set hasData = entries|length > 0 %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% embed '@theme/embeds/card.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% import "project/actions.html.twig" as projectActions %}
{% block box_body_class %}{{ tableName }}-box {% if hasData %}no-padding{% endif %}{% endblock %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'project-daterange-form'}}) }}
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% block box_tools %}
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
{% endblock %}
{% block box_title %}
{% if form.customer is defined %}
{{ form_widget(form.customer) }}
{% endif %}
{% if form.month is defined %}
{{ form_widget(form.month) }}
{% endif %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'filter'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.includeNoWork) }}
</li>
<li>
{{ form_widget(form.budgetType) }}
</li>
</ul>
</div>
{{ form_rest(form) }}
{% endblock %}
{% block box_body_class %}{{ tableName }}-box {% if hasData %}p-0{% endif %}{% endblock %}
{% block box_body %}
{% if not hasData %}
{{ widgets.nothing_found() }}
{% else %}
{{ tables.datatable_header(tableName, columns, null, {'bordered': true, 'boxClass': ''}) }}
{{ tables.datatable_header(tableName, columns, null, {'boxClass': ''}) }}
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
<tr class="summary">
@@ -125,14 +125,3 @@
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#project-daterange-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% endblock %}

View File

@@ -2,8 +2,6 @@
{% 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 showMoneyBudget = project_details is not null and is_granted('budget', project_details.project) %}
@@ -23,35 +21,31 @@
{% block javascripts %}
{{ parent() }}
{% set options = {'label': 'duration', 'title': 'name', 'legend': 'false'} %}
{% 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': 'false'}) }}
{{ charts.bar_javascript({'legend': {'display': false}}) }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#{{ tableId }}').on('change', function(ev) {
jQuery(this).submit();
[].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 %}
});
jQuery('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
renderChart(e.target.dataset.chart);
});
function renderChart(chartName)
{
var found = false;
let found = false;
Chart.helpers.each(Chart.instances, function(instance){
if (instance.chart.canvas.id === chartName) {
for (const instance of Object.values(Chart.instances)) {
if (instance.canvas.id === chartName) {
found = true;
}
});
}
if (!found) {
document.dispatchEvent(new Event('render.' + chartName))
@@ -60,29 +54,16 @@
</script>
{% endblock %}
{% block report_form_layout %}
{{ form_widget(form.project, {'label': false, 'placeholder': 'please_choose'}) }}
{% 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 %}
{% else %}
{{ _self.project_details(project, project_view, project_details, showMoneyBudget, showTimeBudget, view_revenue, see_users) }}
{% set currency = project.customer.currency %}
@@ -109,19 +90,29 @@
{% 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="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">{{ 'stats.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">
@@ -155,7 +146,7 @@
<div class="col-xs-12 table-responsive">
<table class="table table-hover dataTable">
<tr>
<th>{{ 'label.username'|trans }}</th>
<th>{{ 'username'|trans }}</th>
<th></th>
{% for monthName in month_names() %}
<th class="text-center">{{ monthName }}</th>
@@ -228,17 +219,17 @@
{% endif %}
<tr>
<td>{{ widgets.label_activity(stat.activity, {'inherit': false, 'random': true}) }}</td>
<td class="text-nowrap text-right">{{ stat.duration|duration }}</td>
<td class="text-nowrap text-end">{{ stat.duration|duration }}</td>
{% if view_revenue %}
<td class="text-nowrap text-right">{{ stat.rate|money(currency) }}</td>
<td class="text-nowrap text-end">{{ stat.rate|money(currency) }}</td>
{% endif %}
<td class="text-nowrap text-right">{{ percentage|number_format(1) }} %</td>
<td class="text-nowrap text-end">{{ 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'} %}
{% set chartOptions = {'height': (dataset|length > 12 ? '600px' : '300px'), 'renderEvent': 'render.' ~ chartPrefix ~ 'Activity'} %}
{{ charts.doughnut_chart(chartPrefix ~ 'Activity', labels, dataset, chartOptions) }}
</div>
</div>
@@ -266,106 +257,116 @@
{% 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="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">
<div class="card mb-3">
<div class="card-header">
<h3 class="card-title me-3">
{{ widgets.label_project(project, {'inherit': false}) }}
</li>
</ul>
<div class="tab-content no-padding">
</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">{{ 'stats.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-md-6">
<table class="table table-hover dataTable">
<tr {{ widgets.customer_row_attr(project.customer) }}>
<th class="w-min">
{{ 'label.customer'|trans }}
</th>
<td>
<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) }}
</td>
</tr>
<tr {{ widgets.project_row_attr(project) }}>
<th class="w-min">
{{ 'label.project'|trans }}
</th>
<td>
</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) }}
</td>
</tr>
<tr>
<th class="w-min">
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">
{{ 'stats.durationTotal'|trans }}
</th>
<td>{{ project_view.durationTotal|duration }}</td>
</tr>
</div>
<div class="datagrid-content">{{ project_view.durationTotal|duration }}</div>
</div>
{% if view_revenue %}
<tr>
<th class="w-min">
<div class="datagrid-item">
<div class="datagrid-title">
{{ 'stats.amountTotal'|trans }}
</th>
<td>{{ project_view.rateTotal|money(currency) }}</td>
</tr>
<tr>
<th class="w-min">
{{ 'label.billable'|trans }}
</th>
<td>{{ project_view.billableRate|money(currency) }}</td>
</tr>
</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 %}
<tr>
<th class="w-min">
{{ 'label.last_record'|trans }}
</th>
<td>
<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 %}
&ndash;
{% endif %}
</td>
</tr>
</div>
</div>
{% if is_granted('create_export') %}
<tr>
<th class="w-min">
{{ 'label.not_exported'|trans }}
</th>
<td>
<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>
</td>
</tr>
</div>
</div>
{% endif %}
{% if is_granted('view_invoice') %}
<tr>
<th class="w-min">
{{ 'label.not_invoiced'|trans }}
</th>
<td>
<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.notBilledRate|money(currency) }}
</a>
</td>
</tr>
</div>
</div>
{% endif %}
</table>
</div>
</div>
<div class="col-xs-12 col-md-6">
<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) }}
@@ -376,13 +377,13 @@
{% set budgetStats = project_details.budgetStatisticModel %}
<div class="row">
<div class="col-xs-12">
<table class="table table-hover dataTable">
<table class="table table-borderless">
{% if showTimeBudget and project.hasTimeBudget() %}
<tr>
<th class="w-min">
{{ 'label.timeBudget'|trans }}
{{ 'timeBudget'|trans }}
{% if budgetStats.isMonthlyBudget() %}
({{ 'label.budgetType_month'|trans }})
({{ 'budgetType_month'|trans }})
{% endif %}
</th>
<td>
@@ -393,9 +394,9 @@
{% if showMoneyBudget and project.hasBudget() %}
<tr>
<th class="w-min">
{{ 'label.budget'|trans }}
{{ 'budget'|trans }}
{% if budgetStats.isMonthlyBudget() %}
({{ 'label.budgetType_month'|trans }})
({{ 'budgetType_month'|trans }})
{% endif %}
</th>
<td>
@@ -409,15 +410,15 @@
{% endif %}
</div>
{% if view_revenue and project_view.rateTotal > 0 %}
{% 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 %}
{% 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">
@@ -447,31 +448,31 @@
<td>
{{ widgets.label_dot(user.displayName, user.color) }}
</th>
<td class="text-nowrap text-right">
<td class="text-nowrap text-end">
{{ userStat.duration|duration }}
</td>
{% if view_revenue %}
<td class="text-nowrap text-right">
<td class="text-nowrap text-end">
{{ userStat.rate|money(currency) }}
</td>
{% endif %}
<td class="text-nowrap text-right">
<td class="text-nowrap text-end">
{{ percentage|number_format(1) }} %
</td>
</tr>
{% endfor %}
<tr>
<td></td>
<th class="text-nowrap text-right total">{{ totalDuration|duration }}</th>
<th class="text-nowrap text-end total">{{ totalDuration|duration }}</th>
{% if view_revenue %}
<th class="text-nowrap text-right total">{{ rateTotal|money(currency) }}</th>
<th class="text-nowrap text-end 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'} %}
{% set chartOptions = {'height': (datasets|length > 12 ? '600px' : '300px'), 'renderEvent': 'render.' ~ chartPrefix ~ 'User'} %}
{{ charts.doughnut_chart(chartPrefix ~ 'User', labels, datasets, chartOptions) }}
</div>
</div>

View File

@@ -0,0 +1,8 @@
{% extends 'reporting/project_list_data.html.twig' %}
{% block report_form_layout %}
{% form_theme form 'form/horizontal.html.twig' %}
{{ form_row(form.lastChange, {'row_attr': {'class': 'row'}}) }}
{% endblock %}
{% block report_form_container_class %}w-100{% endblock %}

View File

@@ -0,0 +1,134 @@
{% extends 'reporting/layout.html.twig' %}
{% import "macros/datatables.html.twig" as tables %}
{% set showMoneyBudget = is_granted('budget_money', 'project') %}
{% set showTimeBudget = is_granted('budget_time', 'project') %}
{% set availableColumns = {
'name': {'class': 'alwaysVisible'},
} %}
{% if showTimeBudget %}
{% set availableColumns = availableColumns|merge({
'timeBudget': {'class': 'd-none d-md-table-cell', 'title': 'timeBudget'|trans},
}) %}
{% endif %}
{% if showMoneyBudget %}
{% set availableColumns = availableColumns|merge({
'budget': {'class': 'd-none d-md-table-cell', 'title': 'budget'|trans},
}) %}
{% endif %}
{% set availableColumns = availableColumns|merge({
'lastRecord': {'class': 'd-none d-sm-table-cell text-start hw-min w-min', 'title': 'last_record'|trans, 'columnClass': 'w-min'},
'today': {'class': 'd-none text-end hw-min w-min', 'title': 'stats.durationToday'|trans},
'week': {'class': 'd-none text-end hw-min w-min', 'title': 'stats.durationWeek'|trans},
'month': {'class': 'd-none d-lg-table-cell text-end hw-min w-min', 'title': 'stats.durationMonth'|trans},
'durationTotal': {'class': 'text-end hw-min w-min', 'title': 'stats.durationTotal'|trans, 'columnClass': 'w-min'},
}) %}
{% if showTimeBudget and is_granted('create_export') %}
{% set availableColumns = availableColumns|merge({
'exported': {'class': 'd-none d-xl-table-cell text-end hw-min w-min', 'title': 'not_exported'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% if showMoneyBudget and is_granted('view_invoice') %}
{% set availableColumns = availableColumns|merge({
'invoiced': {'class': 'd-none d-xl-table-cell text-end hw-min w-min', 'title': 'not_invoiced'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% set availableColumns = availableColumns|merge({
'projectStart': {'class': 'd-none text-start hw-min w-min', 'title': 'project_start'|trans},
'projectEnd': {'class': 'd-none text-start hw-min w-min', 'title': 'project_end'|trans},
'comment': {'class': 'd-none', 'title': 'comment'|trans},
'actions': {'class': 'actions alwaysVisible'},
}) %}
{% set tableName = tableName|default('project_view_reporting') %}
{% set skipColumns = skipColumns is defined ? skipColumns : {} %}
{% set columns = {} %}
{% for name, config in availableColumns %}
{% if name not in skipColumns %}
{% set columns = columns|merge({(name): config}) %}
{% endif %}
{% endfor %}
{% block main_before %}
{{ tables.data_table_column_modal(tableName, columns) }}
{% endblock %}
{% block report %}
{% set hasData = entries|length > 0 %}
{% embed '@theme/embeds/card.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% import "project/actions.html.twig" as projectActions %}
{% block box_body_class %}{{ tableName }}-box {% if hasData %}p-0{% endif %}{% endblock %}
{% block box_body %}
{% if not hasData %}
{{ widgets.nothing_found() }}
{% else %}
{{ tables.datatable_header(tableName, columns, null, {boxClass: ''}) }}
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
<tr class="summary">
<td colspan="{{ columns|length }}">{{ widgets.label_customer(mapping.customer) }}</td>
</tr>
{% for entry in mapping.projects|sort((a, b) => a.project.name <=> b.project.name) %}
{% set project = entry.project %}
{% set budgetStats = entry.getBudgetStatisticModel() %}
{% set currency = project.customer.currency %}
<tr {{ widgets.project_row_attr(project, now) }}>
{% for name, column_config in columns %}
<td class="{{ tables.data_table_column_class(tableName, columns, name) }}">
{% if name == 'name' %}
{{ widgets.label_project(project) }}
{% elseif name == 'lastRecord' %}
{% if entry.lastRecord is not null %}
{{ entry.lastRecord|date_short }}
{% else %}
&ndash;
{% endif %}
{% elseif name == 'today' %}
{{ entry.durationDay|duration }}
{% elseif name == 'week' %}
{{ entry.durationWeek|duration }}
{% elseif name == 'month' %}
{{ entry.durationMonth|duration }}
{% elseif name == 'durationTotal' %}
{{ entry.durationTotal|duration }}
{% elseif name == 'timeBudget' %}
{% if budgetStats.hasTimeBudget() and is_granted('time', project) %}
{{ progress.progressbar_timebudget(budgetStats) }}
{% endif %}
{% elseif name == 'budget' %}
{% if project.hasBudget() and is_granted('budget', project) %}
{{ progress.progressbar_budget(budgetStats, project.customer.currency) }}
{% endif %}
{% elseif name == 'exported' %}
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
{{ entry.notExportedDuration|duration }}
</a>
{% elseif name == 'invoiced' %}
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
{{ entry.notBilledRate|money(currency) }}
</a>
{% elseif name == 'projectStart' %}
{% if project.start is not null %}{{ project.start|date_short }}{% endif %}
{% elseif name == 'projectEnd' %}
{% if project.end is not null %}{{ project.end|date_short }}{% endif %}
{% elseif name == 'comment' %}
{{ project.comment }}
{% elseif name == 'actions' %}
{{ projectActions.project(project, 'custom') }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
{{ tables.data_table_footer(entries) }}
{% endif %}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -1,193 +1,21 @@
{% extends 'reporting/layout.html.twig' %}
{% import "macros/datatables.html.twig" as tables %}
{% extends 'reporting/project_list_data.html.twig' %}
{% block report_title %}{{ (title|default('report_project_view'))|trans({}, 'reporting') }}{% endblock %}
{% set showMoneyBudget = is_granted('budget_money', 'project') %}
{% set showTimeBudget = is_granted('budget_time', 'project') %}
{% set showBudgets = showMoneyBudget or showTimeBudget %}
{% set availableColumns = {
'name': {'class': 'alwaysVisible'},
} %}
{% if showMoneyBudget %}
{% set availableColumns = availableColumns|merge({
'budget': {'class': 'hidden-xs', 'title': 'label.budget'|trans},
'invoiced': {'class': 'hidden-sm hidden-xs hidden text-center hw-min', 'title': 'label.not_invoiced'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% if showTimeBudget %}
{% set availableColumns = availableColumns|merge({
'timeBudget': {'class': 'hidden-xs', 'title': 'label.timeBudget'|trans},
'exported': {'class': 'hidden-sm hidden-xs hidden text-center hw-min', 'title': 'label.not_exported'|trans, 'columnClass': 'w-min'},
}) %}
{% endif %}
{% set availableColumns = availableColumns|merge({
'lastRecord': {'class': 'text-center hw-min', 'title': 'label.last_record'|trans, 'columnClass': 'w-min'},
'today': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans},
'week': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans},
'month': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans},
'durationTotal': {'class': 'text-center hw-min', 'title': 'stats.durationTotal'|trans, 'columnClass': 'w-min'},
'projectStart': {'class': 'hidden-md hidden-sm hidden-xs hidden text-center w-min', 'title': 'label.project_start'|trans},
'projectEnd': {'class': 'hidden-md hidden-sm hidden-xs hidden text-center w-min', 'title': 'label.project_end'|trans},
'comment': {'class': 'hidden-md hidden-sm hidden-xs hidden', 'title': 'label.comment'|trans},
'actions': {'class': 'actions alwaysVisible'},
}) %}
{% set tableName = tableName|default('project_view_reporting') %}
{% set skipColumns = skipColumns is defined ? skipColumns : {} %}
{% set columns = {} %}
{% for name, config in availableColumns %}
{% if name not in skipColumns %}
{% set columns = columns|merge({(name): config}) %}
{% endif %}
{% endfor %}
{% block main_before %}
{{ tables.data_table_column_modal(tableName, columns) }}
{% endblock %}
{% block report %}
{% set hasData = entries|length > 0 %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/progressbar.html.twig" as progress %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% import "project/actions.html.twig" as projectActions %}
{% block box_body_class %}{{ tableName }}-box {% if hasData %}no-padding{% endif %}{% endblock %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'project-view-form'}}) }}
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% block box_tools %}
{{ widgets.action_button('visibility', {'modal': ('#modal_' ~ tableName), 'class': 'btn-sm'}) }}
{% endblock %}
{% block box_title %}
{% if form.customer is defined %}
{{ form_widget(form.customer) }}
{% endif %}
{% if form.month is defined %}
{{ form_widget(form.month) }}
{% endif %}
{% if form.includeNoBudget is defined or form.includeNoWork is defined %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'filter'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
{% if form.includeNoBudget is defined %}
<li>
{{ form_widget(form.includeNoBudget) }}
</li>
{% endif %}
{% if form.includeNoWork is defined %}
<li>
{{ form_widget(form.includeNoWork) }}
</li>
{% endif %}
</ul>
</div>
{% endif %}
{{ form_rest(form) }}
{% endblock %}
{% block box_body %}
{% if not hasData %}
{{ widgets.nothing_found() }}
{% else %}
{{ tables.datatable_header(tableName, columns, null, {'bordered': true, 'boxClass': ''}) }}
{% for id, mapping in entries|sort((a, b) => a.customer.name <=> b.customer.name) %}
<tr class="summary">
<td colspan="{{ columns|length }}">{{ widgets.label_customer(mapping.customer) }}</td>
</tr>
{% for entry in mapping.projects|sort((a, b) => a.project.name <=> b.project.name) %}
{% set project = entry.project %}
{% set budgetStats = entry.getBudgetStatisticModel() %}
{% set currency = project.customer.currency %}
<tr {{ widgets.project_row_attr(project, now) }}>
{% for name, column_config in columns %}
{% if name == 'name' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_project(project) }}</td>
{% elseif name == 'lastRecord' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'lastRecord') }}">
{% if entry.lastRecord is not null %}
{{ entry.lastRecord|date_short }}
{% else %}
&ndash;
{% endif %}
</td>
{% elseif name == 'today' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'today') }}">{{ entry.durationDay|duration }}</td>
{% elseif name == 'week' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'week') }}">{{ entry.durationWeek|duration }}</td>
{% elseif name == 'month' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'month') }}">{{ entry.durationMonth|duration }}</td>
{% elseif name == 'durationTotal' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'durationTotal') }} w-min">{{ entry.durationTotal|duration }}</td>
{% elseif name == 'timeBudget' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'timeBudget') }}">
{% if budgetStats.hasTimeBudget() and is_granted('time', project) %}
{{ progress.progressbar_timebudget(budgetStats) }}
{% endif %}
</td>
{% elseif name == 'budget' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'budget') }}">
{% if project.hasBudget() and is_granted('budget', project) %}
{{ progress.progressbar_budget(budgetStats, project.customer.currency) }}
{% endif %}
</td>
{% elseif name == 'exported' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}">
{% if is_granted('create_export') %}
<a href="{{ path('export', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': '', 'preview': true}) }}">
{{ entry.notExportedDuration|duration }}
</a>
{% else %}
{{ entry.notExportedDuration|duration }}
{% endif %}
</td>
{% elseif name == 'invoiced' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'invoiced') }}">
{% if is_granted('view_invoice') %}
<a href="{{ path('invoice', {'customers[]': project.customer.id, 'projects[]': project.id, 'daterange': ''}) }}">
{{ entry.notBilledRate|money(currency) }}
</a>
{% else %}
{{ entry.notBilledRate|money(currency) }}
{% endif %}
</td>
{% elseif name == 'projectStart' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectStart') }}">{% if project.start is not null %}{{ project.start|date_short }}{% endif %}</td>
{% elseif name == 'projectEnd' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'projectEnd') }}">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
{% elseif name == 'comment' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ project.comment }}</td>
{% elseif name == 'actions' %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">
{{ projectActions.project(project, 'custom') }}
</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
{{ tables.data_table_footer(entries) }}
{% endif %}
{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#project-view-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% block report_form_layout %}
{{ form_widget(form.customer, {'label': false, 'placeholder': 'please_choose'}) }}
<div class="dropdown">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ icon('filter', true) }}
</button>
<ul class="dropdown-menu checkbox-menu">
{% for option in form.budgetType.children %}
<li class="dropdown-item">
{{ form_widget(option) }}
</li>
{% endfor %}
<li class="dropdown-divider"></li>
<li class="dropdown-item">
{{ form_widget(form.includeNoWork) }}
</li>
</ul>
</div>
{% endblock %}

View File

@@ -1,65 +1,20 @@
{% extends 'reporting/layout.html.twig' %}
{% extends 'reporting/report_by_user_layout.html.twig' %}
{% block report_title %}{{ report_title|trans({}, 'reporting') }}{% endblock %}
{% block report %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline', 'id': 'user-filter-form'}}) }}
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% block box_title %}
{% if form.user is defined %}
{{ form_row(form.user, {'label': false}) }}
{% else %}
{{ widgets.username(user) }}
{% endif %}
{{ form_widget(form.date) }}
<div class="btn-group"{% if form.sumType.vars.choices|length <= 1 %} style="display: none"{% endif %}>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'display'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.sumType) }}
</li>
</ul>
</div>
{% endblock %}
{% block box_body_class %}{{ box_id }} table-responsive no-padding{% endblock %}
{% block box_body %}
{% embed 'reporting/report_by_user_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap{% if column is weekend %} weekend{% endif %}{% if column is today %} today{% endif %}">
{{ column|date_weekday }}
</th>
{% endblock %}
{% block column_classes_project -%}
{% if column.date is weekend %} weekend{% endif %}{% if column.date is today %} today{% endif %}
{%- endblock %}
{% block column_classes_activity -%}
{% if column.date is weekend %} weekend{% endif %}{% if column.date is today %} today{% endif %}
{%- endblock %}
{% block column_classes_total -%}
{% if column is weekend %} weekend{% endif %}
{%- endblock %}
{% endembed %}
{% block report_content %}
{% embed 'reporting/report_by_user_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap{% if column is weekend %} weekend{% endif %}{% if column is today %} today{% endif %}">
{{ column|date_weekday }}
</th>
{% endblock %}
{% block column_classes_project -%}
{% if column.date is weekend %} weekend{% endif %}{% if column.date is today %} today{% endif %}
{%- endblock %}
{% block column_classes_activity -%}
{% if column.date is weekend %} weekend{% endif %}{% if column.date is today %} today{% endif %}
{%- endblock %}
{% block column_classes_total -%}
{% if column is weekend %} weekend{% endif %}
{%- endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#user-filter-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% endblock %}

View File

@@ -8,13 +8,13 @@
{% if dataType == 'rate' %}
{% set dataTypeTitle = 'stats.amountTotal' %}
{% elseif dataType == 'internalRate' %}
{% set dataTypeTitle = 'label.rate_internal' %}
{% set dataTypeTitle = 'internalRate' %}
{% else %}
{% set dataTypeTitle = 'stats.durationTotal' %}
{% endif %}
{% set columns = 2 %}
{% set totalCurrency = false %}
<table class="table table-bordered table-hover dataTable">
<table class="table table-hover dataTable">
<thead>
<tr>
<th>&nbsp;</th>

View File

@@ -0,0 +1,22 @@
{% extends 'reporting/layout.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block report_form_layout %}
{{ form_widget(form.date) }}
{% if form.user is defined %}
{{ form_widget(form.user, {'label': false}) }}
{% elseif app.user != user %}
{{ widgets.username(user) }}
{% endif %}
{{ form_widget(form.sumType) }}
{% endblock %}
{% block report %}
{% embed '@theme/embeds/card.html.twig' with {report_content: block('report_content'), box_id: box_id, fullsize: true} only %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_body_class %}{{ box_id }} table-responsive m-0{% endblock %}
{% block box_body %}
{{ report_content|raw }}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -1,62 +1,17 @@
{% extends 'reporting/layout.html.twig' %}
{% extends 'reporting/report_by_user_layout.html.twig' %}
{% block report_title %}{{ report_title|trans({}, 'reporting') }}{% endblock %}
{% block report %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline', 'id': 'user-filter-form'}}) }}
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% endblock %}
{% block box_title %}
{% if form.user is defined %}
{{ form_row(form.user, {'label': false}) }}
{% else %}
{{ widgets.username(user) }}
{% endif %}
{{ form_widget(form.date) }}
<div class="btn-group"{% if form.sumType.vars.choices|length <= 1 %} style="display: none"{% endif %}>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'display'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.sumType) }}
</li>
</ul>
</div>
{% endblock %}
{% block box_body_class %}{{ box_id }} table-responsive no-padding{% endblock %}
{% block box_body %}
{% embed 'reporting/report_by_user_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap">
<a href="{{ path('report_user_month', {'date': column|report_date, 'sumType': dataType, 'user': user.id}) }}">
{{ column|month_name }}<br>
{{ column|date_format('Y') }}
</a>
</th>
{% endblock %}
{% block column_classes_project %}{% endblock %}
{% block column_classes_activity %}{% endblock %}
{% block column_classes_total %}{% endblock %}
{% endembed %}
{% block report_content %}
{% embed 'reporting/report_by_user_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap">
<a href="{{ path('report_user_month', {'date': column|report_date, 'sumType': dataType, 'user': user.id}) }}">
{{ column|month_name }}<br>
{{ column|date_format('Y') }}
</a>
</th>
{% endblock %}
{% block column_classes_project %}{% endblock %}
{% block column_classes_activity %}{% endblock %}
{% block column_classes_total %}{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#user-filter-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% endblock %}

View File

@@ -1,80 +1,30 @@
{% extends 'reporting/layout.html.twig' %}
{% extends 'reporting/report_user_list_layout.html.twig' %}
{% block report_title %}{{ report_title|trans({}, 'reporting') }}{% endblock %}
{% block report %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% from "macros/widgets.html.twig" import nothing_found %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline', 'id': 'user-list-filter-form'}}) }}
{% block report_content %}
{% embed 'reporting/user_list_period_data.html.twig' with {stats: stats, dataType: dataType, period_attribute: period_attribute, subReportRoute: subReportRoute, subReportDate: subReportDate, decimal: decimal} only %}
{% block period_name %}
<th class="text-center text-nowrap{% if column is weekend %} weekend{% endif %}{% if column is today %} today{% endif %}">
{{ column|date_weekday }}
</th>
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% block total_rate_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalRate|money }}</a>
{% endblock %}
{% block box_title %}
{{ form_widget(form.date) }}
{% if form.team is defined %}
{{ form_widget(form.team) }}
{% endif %}
{% if form.sumType.vars.choices|length > 1 %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'display'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.sumType) }}
</li>
</ul>
</div>
{% endif %}
<button class="btn btn-primary" formaction="{{ path(export_route) }}" type="submit"><i class="{{ 'download'|icon }}"></i></button>
{% block total_internal_rate_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalInternalRate|money }}</a>
{% endblock %}
{% block box_body_class %}{{ box_id }} table-responsive {% if hasData %}no-padding{% endif %}{% endblock %}
{% block box_body %}
{% if not hasData %}
{{ nothing_found() }}
{% else %}
{% embed 'reporting/user_list_period_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap{% if column is weekend %} weekend{% endif %}{% if column is today %} today{% endif %}">
{{ column|date_weekday }}
</th>
{% endblock %}
{% block total_rate_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalRate|money }}</a>
{% endblock %}
{% block total_internal_rate_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalInternalRate|money }}</a>
{% endblock %}
{% block total_duration_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalDuration|duration(decimal) }}</a>
{% endblock %}
{% block rate %}
{{ period.totalRate|money }}
{% endblock %}
{% block internal_rate %}
{{ period.totalInternalRate|money }}
{% endblock %}
{% block duration %}
{{ period.totalDuration|duration(decimal) }}
{% endblock %}
{% block period_cell_class %}{% if period.date is weekend %} weekend{% endif %}{% if period.date is today %} today{% endif %}{% endblock %}
{% endembed %}
{% endif %}
{% block total_duration_user %}
<a href="{{ path(subReportRoute, {'sumType': dataType, 'date': subReportDate|report_date, 'user': userPeriod.user.id}) }}">{{ usersTotalDuration|duration(decimal) }}</a>
{% endblock %}
{% block rate %}
{{ period.totalRate|money }}
{% endblock %}
{% block internal_rate %}
{{ period.totalInternalRate|money }}
{% endblock %}
{% block duration %}
{{ period.totalDuration|duration(decimal) }}
{% endblock %}
{% block period_cell_class %}{% if period.date is weekend %} weekend{% endif %}{% if period.date is today %} today{% endif %}{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#user-list-filter-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% embed 'reporting/user_list_period_data.html.twig' with {'showAccountNumber': true, 'stats': stats, 'dataType': dataType, 'period_attribute': period_attribute} only %}
{% embed 'reporting/user_list_period_data.html.twig' with {'avatar': false, 'showAccountNumber': true, 'stats': stats, 'dataType': dataType, 'period_attribute': period_attribute} only %}
{% set dataTypeFormat = '' %}
{% if dataType == 'rate' or dataType == 'internalRate' %}
{% set dataTypeFormat = ' data-format="' ~ constant('App\\Export\\Base\\AbstractSpreadsheetRenderer::RATE_FORMAT_NO_CURRENCY') ~ '"' %}

View File

@@ -0,0 +1,25 @@
{% extends 'reporting/layout.html.twig' %}
{% block report_form_layout %}
{{ form_widget(form.date) }}
{% if form.team is defined %}
{{ form_widget(form.team, {'label': false, 'placeholder': 'please_choose'}) }}
{% endif %}
{{ form_widget(form.sumType) }}
{% from '@theme/components/buttons.html.twig' import submit_button %}
{{ submit_button('download', {'attr': {'formaction': path(export_route)}, 'icon': 'download', 'combined': false}, 'primary') }}
{% endblock %}
{% block report %}
{% embed '@theme/embeds/card.html.twig' with {report_content: block('report_content'), box_id: box_id, hasData: hasData, fullsize: hasData} only %}
{% block box_body_class %}{{ box_id }} table-responsive m-0{% endblock %}
{% block box_body %}
{% if not hasData %}
{% from "macros/widgets.html.twig" import nothing_found %}
{{ nothing_found() }}
{% else %}
{{ report_content|raw }}
{% endif %}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -1,94 +1,44 @@
{% extends 'reporting/layout.html.twig' %}
{% extends 'reporting/report_user_list_layout.html.twig' %}
{% block report_title %}{{ report_title|trans({}, 'reporting') }}{% endblock %}
{% block report %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% from "macros/widgets.html.twig" import nothing_found %}
{% block box_before %}
{{ form_start(form, {'attr': {'class': 'form-inline form-reporting', 'id': 'user-list-filter-form'}}) }}
{% block report_content %}
{% embed 'reporting/user_list_period_data.html.twig' with {stats: stats, dataType: dataType, period_attribute: period_attribute, subReportDate: subReportDate, decimal: decimal} only %}
{% block period_name %}
<th class="text-center text-nowrap">
<a href="{{ path('report_monthly_users', {'date': column|report_date, 'sumType': dataType}) }}">
{{ column|month_name }}<br>
{{ column|date_format('Y') }}
</a>
</th>
{% endblock %}
{% block box_after %}
{{ form_end(form) }}
{% block total_rate_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(subReportDate.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalRate|money }}
</a>
{% endblock %}
{% block box_title %}
{{ form_widget(form.date) }}
{% if form.team is defined %}
{{ form_widget(form.team) }}
{% endif %}
{% if form.sumType.vars.choices|length > 1 %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="{{ 'display'|icon }}"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu checkbox-menu">
<li>
{{ form_widget(form.sumType) }}
</li>
</ul>
</div>
{% endif %}
<button class="btn btn-primary" formaction="{{ path(export_route) }}" type="submit"><i class="{{ 'download'|icon }}"></i></button>
{% block total_internal_rate_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(subReportDate.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalInternalRate|money }}
</a>
{% endblock %}
{% block box_body_class %}{{ box_id }} table-responsive {% if hasData %}no-padding{% endif %}{% endblock %}
{% block box_body %}
{% if not hasData %}
{{ nothing_found() }}
{% else %}
{% embed 'reporting/user_list_period_data.html.twig' %}
{% block period_name %}
<th class="text-center text-nowrap">
<a href="{{ path('report_monthly_users', {'date': column|report_date, 'sumType': dataType}) }}">
{{ column|month_name }}<br>
{{ column|date_format('Y') }}
</a>
</th>
{% endblock %}
{% block total_rate_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(query.date.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalRate|money }}
</a>
{% endblock %}
{% block total_internal_rate_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(query.date.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalInternalRate|money }}
</a>
{% endblock %}
{% block total_duration_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(query.date.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalDuration|duration(decimal) }}
</a>
{% endblock %}
{% block rate %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'label.billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalRate|money }}
</a>
{% endblock %}
{% block internal_rate %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'label.billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalInternalRate|money }}
</a>
{% endblock %}
{% block duration %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'label.billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalDuration|duration(decimal) }}
</a>
{% endblock %}
{% endembed %}
{% endif %}
{% block total_duration_user %}
<a href="{{ path('report_user_year', {'sumType': dataType, 'date': create_date(subReportDate.format('Y') ~'-01-01')|report_date, 'user': userPeriod.user.id}) }}">
{{ usersTotalDuration|duration(decimal) }}
</a>
{% endblock %}
{% block rate %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalRate|money }}
</a>
{% endblock %}
{% block internal_rate %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalInternalRate|money }}
</a>
{% endblock %}
{% block duration %}
<a href="{{ path('report_user_month', {'sumType': dataType, 'date': create_date(period.date.format('Y') ~'-'~period.date.format('m')~'-01')|report_date, 'user': userPeriod.user.id}) }}" data-toggle="tooltip" title="{{ 'billable'|trans }}: {{ period.billableDuration|duration(decimal) }}">
{{ period.totalDuration|duration(decimal) }}
</a>
{% endblock %}
{% endembed %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
jQuery('#user-list-filter-form').on('change', function(ev) {
jQuery(this).submit();
});
});
</script>
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% embed 'reporting/user_list_period_data.html.twig' with {'showAccountNumber': true, 'stats': stats, 'dataType': dataType, 'period_attribute': period_attribute} only %}
{% embed 'reporting/user_list_period_data.html.twig' with {'avatar': false, 'showAccountNumber': true, 'stats': stats, 'dataType': dataType, 'period_attribute': period_attribute} only %}
{% set dataTypeFormat = '' %}
{% if dataType == 'rate' or dataType == 'internalRate' %}
{% set dataTypeFormat = ' data-format="' ~ constant('App\\Export\\Base\\AbstractSpreadsheetRenderer::RATE_FORMAT_NO_CURRENCY') ~ '"' %}

View File

@@ -1,3 +1,4 @@
{%- set with_avatar = avatar is defined ? avatar : true -%}
{%- set absoluteDuration = 0 -%}
{%- set absoluteInternalRate = 0 -%}
{%- set absoluteRate = 0 -%}
@@ -8,16 +9,19 @@
{% if dataType == 'rate' %}
{% set dataTypeTitle = 'stats.amountTotal' %}
{% elseif dataType == 'internalRate' %}
{% set dataTypeTitle = 'label.rate_internal' %}
{% set dataTypeTitle = 'internalRate' %}
{% else %}
{% set dataTypeTitle = 'stats.durationTotal' %}
{% endif %}
<table class="table table-bordered table-hover dataTable">
<table class="table table-hover dataTable">
<thead>
<tr>
{% if with_avatar %}
<th class="w-avatar"></th>
{% endif %}
<th>&nbsp;</th>
{% if showAccountNumber %}
<th>{{ 'label.account_number'|trans }}</th>
<th>{{ 'account_number'|trans }}</th>
{% endif %}
<th class="text-center reportDataTypeTitle">{{ dataTypeTitle|trans }}</th>
{% for column in stats.0.getDateTimes() %}
@@ -35,11 +39,14 @@
{% set usersTotalInternalRate = 0 %}
{% set usersTotalRate = 0 %}
<tr class="user">
{% if with_avatar %}
<td class="w-avatar">
{% from "macros/widgets.html.twig" import user_avatar %}
{{ user_avatar(userPeriod.user) }}
</td>
{% endif %}
<td class="text-nowrap"{% block user_column_cell_attribute %}{% endblock %}>
{% block user_column %}
{% from "macros/widgets.html.twig" import label_dot %}
{{ label_dot(userPeriod.user.displayName, userPeriod.user.color) }}
{% endblock %}
{{ userPeriod.user.displayName }}
</td>
{% if showAccountNumber %}
<td>{{ userPeriod.user.accountNumber }}</td>
@@ -89,7 +96,7 @@
</tbody>
<tfoot>
<tr class="summary">
<td>{{ dataTypeTitle|trans }}</td>
<td{% if with_avatar %} colspan="2"{% endif %}>{{ dataTypeTitle|trans }}</td>
{% if showAccountNumber %}
<td></td>
{% endif %}