added project detail report (#2651)

- avatars via css only
- random colors for entities
- improves print view
- added colors for teams
- added color to tag
This commit is contained in:
Kevin Papst
2021-07-06 22:01:20 +02:00
committed by GitHub
parent cc6031bfde
commit 9ddb87a6fd
122 changed files with 2736 additions and 1549 deletions

View File

@@ -41,7 +41,9 @@
{% for entry in entries %}
<tr {{ widgets.activity_row_attr(entry, now) }}>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('activity', true, entry.name, null, entry.color) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
{{ widgets.label_name(entry.name, entry.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">
{# only none-global activities have a project and customer assigned #}
{% if entry.project %}

View File

@@ -67,16 +67,18 @@
{% block page_subtitle %}{% endblock %}
{% block logo_mini %}
{% if not kimai_context.branding.mini is empty %}
{{ kimai_context.branding.mini|raw }}
{% set mini = config('theme.branding.mini') %}
{% if mini is not empty %}
{{ mini|raw }}
{% else %}
<b>K</b>TT
{% endif %}
{% endblock %}
{% block logo_large %}
{% if not kimai_context.branding.company is empty %}
{{ kimai_context.branding.company|raw }}
{% set company = config('theme.branding.company') %}
{% if company is not empty %}
{{ company|raw }}
{% else %}
<b>Kimai</b> - Time Tracking
{% endif %}
@@ -230,7 +232,7 @@
</a>
</li>
{% endif %}
{% if kimai_context.show_about %}
{% if config('theme.show_about') %}
<li class="divider"></li>
<li>
<a href="{{ path('about') }}">

View File

@@ -323,7 +323,7 @@
callback(apiEvents);
});
},
color: '{{ kimai_context.calendar.background_color }}',
color: '{{ config('theme.calendar.background_color') }}',
name: 'kimaiUserTimeSource'
}
{% if google is not null %}

View File

@@ -52,7 +52,9 @@
{% for entry in entries %}
<tr {{ widgets.customer_row_attr(entry, now) }}>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
{{ widgets.label_name(entry.name, entry.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'company') }}">{{ entry.company }}</td>

View File

@@ -88,8 +88,9 @@
{% endif %}
</td>
<td align="right">
{% if kimai_context.branding.company is not empty %}
{{ kimai_context.branding.company|raw }} &ndash; {{ now|date_full }}
{% set company = config('theme.branding.company') %}
{% if company is not empty %}
{{ company|raw }} &ndash; {{ now|date_full }}
{% else %}
{{ 'export.date_copyright'|trans({'%date%': now|date_full, '%kimai%': '<a href="' ~ constant('App\\Constants::HOMEPAGE') ~ '">' ~ constant('App\\Constants::SOFTWARE') ~ '</a>'})|raw }}
{% endif %}

View File

@@ -138,7 +138,8 @@
{% import "macros/datatables.html.twig" as tables %}
{% block box_title %}
<span id="invoice_preview_details_{{ customer.id }}">{{ widgets.label_customer(customer) }}</span>
<small>{{ 'label.duration'|trans }}: {{ model.calculator.timeWorked|duration() }}, {{ 'label.total_rate'|trans }}: {{ model.calculator.total|money(currency) }}</small>
{{ widgets.label(model.calculator.timeWorked|duration, 'gray') }}
{{ widgets.label(model.calculator.total|money(currency), 'gray') }}
{% endblock %}
{% block box_body_class %}invoice-preview-box no-padding{% endblock %}
{% block box_body %}

View File

@@ -0,0 +1,224 @@
{# ------------------------------------------------------------------------------ #}
{# DOUNGHNUT CHART - @see https://www.chartjs.org/docs/2.9.4/charts/doughnut.html #}
{# ------------------------------------------------------------------------------ #}
{#
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': false}
#}
{% macro doughnut_javascript(options) %}
<script type="text/javascript">
function doughnutChartOptions() {
return {
legend: {% if options.legend is defined %}{{ options.legend }}{% else %}false{% endif %},
maintainAspectRatio: true,
responsive: true,
cutoutPercentage: 40,
animation: {
animateRotate: true
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return ' ' + tooltipData['{{ options.label|default('label') }}'];
},
afterTitle: function(tooltipItems, data) {
return ' ';
},
{% if options.title is defined and options.title is not empty %}
title: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return ' ' + tooltipData['{{ options.title }}'];
},
{% endif %}
{% if options.footer is defined and options.footer is not empty %}
beforeFooter: function(tooltipItems, data) {
return ' ';
},
footer: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return tooltipData['{{ options.footer }}'];
},
{% endif %}
}
}
};
}
</script>
{% endmacro %}
{#
id = 'name'
labels = ['Foo', 'Bar']
dataset = [{name: '', color: '', duration: 0, rate: 0}]
options = {'height: '100px', 'renderEvent': 'kimai.initialized'}
#}
{% macro doughnut_chart(id, labels, dataset, options) %}
{% set height = options.height|default(config('theme.chart.height') ~ 'px') %}
{% set bgColor = config('theme.chart.background_color') %}
<div class="chart" style="position: relative;">
<canvas id="{{ id }}" style="height: {{ height }};"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('{{ options.renderEvent|default('kimai.initialized') }}', function() {
new Chart(
$("#{{ id }}").get(0).getContext("2d"), {
type: 'doughnut',
data: {
{% if labels is not empty %}
labels: {{ labels|json_encode|raw }},
{% endif %}
datasets: [
{
backgroundColor: [
{% for entry in dataset %}
'{{ entry.color|default(bgColor) }}',
{% endfor %}
],
data: [
{% for entry in dataset %}
{{ entry.value }}
{% if not loop.last %},{% endif %}
{% endfor %}
],
realData: {{ dataset|json_encode|raw }},
}
]
},
options: Object.assign(doughnutChartOptions(),{{ options|json_encode|raw }})
}
);
});
</script>
{% endmacro %}
{# ------------------------------------------------------------------- #}
{# BAR CHART - @see https://www.chartjs.org/docs/2.9.4/charts/bar.html #}
{# ------------------------------------------------------------------- #}
{#
options = {'label': 'label', 'title': string|null, 'footer': string|null, 'legend': false, 'onclick': {'url': string, 'replacer': {'name': 'value'}}}
#}
{% macro bar_javascript(options) %}
<script type="text/javascript">
function barChartOptions() {
return {
legend: {% if options.legend is defined %}{{ options.legend }}{% else %}false{% endif %},
maintainAspectRatio: true,
responsive: true,
barPercentage: 0.5,
categoryPercentage: 0.9,
scales: {
xAxes: [{
gridLines: {
display: false
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
},
gridLines: {
display: true,
color: '{{ config('theme.chart.grid_color') }}',
lineWidth: 1
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return ' ' + tooltipData['{{ options.label|default('label') }}'];
},
afterTitle: function(tooltipItems, data) {
return ' ';
},
{% if options.title is defined and options.title is not empty %}
title: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return ' ' + tooltipData['{{ options.title }}'];
},
{% endif %}
{% if options.footer is defined and options.footer is not empty %}
beforeFooter: function(tooltipItems, data) {
return ' ';
},
footer: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return tooltipData['{{ options.footer }}'];
},
{% endif %}
}
},
{% if options.onclick is defined and options.onclick.url is defined %}
onClick: function(event, elements) {
if (elements.length !== 1) {
return;
}
var element = elements[0];
var data = this.data.datasets[element._datasetIndex].realData[element._index];
var url = '{{ options.onclick.url|raw }}';
{% if options.onclick.replacer is defined %}
{% for replacer, field in options.onclick.replacer %}
url = url.replace(/{{ replacer }}/, data['{{ field }}']);
{% endfor %}
document.location = url;
{% endif %}
},
{% endif %}
};
}
</script>
{% endmacro %}
{#
id = 'name'
labels = ['Foo', 'Bar']
datasets = [[{'value': 1234, 'tooltip': '1234€', 'color': ''}]]
options = {'height: '100px', 'renderEvent': 'kimai.initialized'}
#}
{% macro bar_chart(id, labels, datasets, options) %}
{% set height = options.height|default(config('theme.chart.height') ~ 'px') %}
{% set bgColor = config('theme.chart.background_color') %}
<div class="chart" style="position: relative;">
<canvas id="{{ id }}" style="height: {{ height }};"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('{{ options.renderEvent|default('kimai.initialized') }}', function() {
new Chart(
document.getElementById('{{ id }}').getContext("2d"), {
type: '{{ options.type|default('bar') }}',
data: {
{% if labels is not empty %}
labels: {{ labels|json_encode|raw }},
{% endif %}
datasets: [
{% for dataset in datasets %}
{
backgroundColor: [
{% for entry in dataset %}
'{{ entry.color|default(bgColor) }}',
{% endfor %}
],
data: [
{% for entry in dataset %}
{{ entry.value }},
{% endfor %}
],
realData: {{ dataset|json_encode|raw }},
},
{% endfor %}
]
},
options: Object.assign(barChartOptions(),{{ options|json_encode|raw }})
}
);
});
</script>
{% endmacro %}

View File

@@ -63,49 +63,96 @@
{% endmacro %}
{% macro label_user(user) %}
{{ _self.label(user.displayName, 'primary') }}
{{ _self.label_color(user.displayName, user.color|colorize(user.displayName)) }}
{% endmacro %}
{% macro label_team(team, class) %}
{{ _self.label(team.name, class|default('primary')) }}
{% macro label_team(team) %}
{{ _self.label_color(team.name, team.color|colorize(team.name)) }}
{% endmacro %}
{% macro user_avatar(user, tooltip, class) %}
{% set avatar = avatar(user, admin_lte_context.default_avatar) %}
{% if tooltip is same as (false) %}
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" alt="{{ user.displayName }}" />
{% if user.avatar is not empty and kimai_config.themeAllowAvatarUrls %}
{% set avatar = asset(user.avatar, 'avatars') %}
{% if tooltip is same as (false) %}
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" alt="{{ user.displayName }}" />
{% else %}
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" data-toggle="tooltip" data-placement="top" alt="{{ user.displayName }}" title="{{ tooltip|default(user.displayName) }}" />
{% endif %}
{% else %}
<img src="{{ avatar }}" class="img-circle{% if class is not empty %} {{ class }}{% endif %}" data-toggle="tooltip" data-placement="top" alt="{{ user.displayName }}" title="{{ tooltip|default(user.displayName) }}" />
{% set color = user.color|colorize(user.displayName) %}
<span class="avatar {{ class }}" style="background-color: {{ color }}; color: {{ color|font_contrast }}"
{%- if tooltip is not same as (false) %} data-toggle="tooltip" data-placement="top" title="{{ tooltip|default(user.displayName) }}"{% endif -%}>
<span class="initials">{{ user.initials }}</span>
</span>
{% endif %}
{% endmacro %}
{% macro label_activity(activity, url) %}
{#
options = {'inherit': true, 'random': false}
#}
{% macro label_activity(activity, options) %}
{% set inherit = options.inherit ?? true %}
{% set random = options.random ?? false %}
{% set isVisible = activity.visible %}
{% set color = activity.color %}
{% if color is empty and activity.project is not null %}
{% if color is empty and inherit and activity.project is not null %}
{% set color = activity.project.color ?? activity.project.customer.color %}
{% endif %}
{% if color is empty and random %}
{% set color = color|colorize(activity.name) %}
{% endif %}
{% if isVisible and not activity.project is null %}
{% set isVisible = activity.project.visible %}
{% if isVisible and not activity.project.customer is null %}
{% set isVisible = activity.project.customer.visible %}
{% endif %}
{% endif %}
{{ _self.label_color_dot('activity', isVisible, activity.name, url, color) }}
{{ _self.label_color_dot('activity', isVisible, activity.name, null, color) }}
{% endmacro %}
{% macro label_project(project, url) %}
{#
options = {'inherit': true, 'random': false}
#}
{% macro label_project(project, options) %}
{% set inherit = options.inherit ?? true %}
{% set random = options.random ?? false %}
{% set isVisible = false %}
{% if project.visible and project.customer.visible %}
{% set isVisible = true %}
{% endif %}
{{ _self.label_color_dot('project', isVisible, project.name, url, (project.color ?? project.customer.color)) }}
{% set color = project.color %}
{% if color is empty and inherit %}
{% set color = project.customer.color %}
{% endif %}
{% if color is empty and random %}
{% set color = color|colorize(project.name) %}
{% endif %}
{{ _self.label_color_dot('project', isVisible, project.name, null, color) }}
{% endmacro %}
{% macro label_customer(customer, url) %}
{{ _self.label_color_dot('customer', customer.visible, customer.name, url, customer.color) }}
{#
options = {'random': false}
#}
{% macro label_customer(customer, options) %}
{% set random = options.random ?? false %}
{% set color = customer.color %}
{% if color is empty and random %}
{% set color = color|colorize(customer.name) %}
{% endif %}
{{ _self.label_color_dot('customer', customer.visible, customer.name, null, color) }}
{% endmacro %}
{# Use for list views and the main column (if the entity has something like a name) #}
{% macro label_name(label, color, isVisible) %}
{{ _self.label_color_dot('color', isVisible, label, null, color) }}
{% endmacro %}
{# Use in datatables to show linked entity names #}
{% macro label_dot(label, color, isVisible) %}
{{ _self.label_color_dot('color', isVisible, label, null, color|colorize(label)) }}
{% endmacro %}
{# for @internal use only #}
{% macro label_color_dot(type, isVisible, name, url, color) %}
<span class="label-{{ type }}{{ isVisible ? '' : ' label-invisible' }}">
<span class="name">
@@ -142,12 +189,15 @@
{% endmacro %}
{% macro label(title, type, tooltip) %}
{# success, warning, danger, primary #}
<span {% if tooltip %}data-toggle="tooltip" data-placement="top" title="{{ tooltip }}" {% endif %}class="label label-{{ type|default('success') }}">{{ title }}</span>
{% endmacro %}
{% macro label_color(title, color) %}
<span class="label" style="background-color: {{ color|default_color }};color: {{ color|default_color|font_contrast }}">{{ title }}</span>
{% endmacro %}
{% macro badge(title, color) %}
<span class="badge" style="background-color:{{ color }}; color:{{ color|font_contrast }}">{{ title }}</span>
<span class="badge"{% if color is not null %} style="background-color:{{ color }}; color:{{ color|font_contrast }}"{% endif %}>{{ title }}</span>
{% endmacro %}
{% macro alert(type, description, title, icon) %}
@@ -393,25 +443,23 @@
{% macro button_action(icon, url) %}
<a href="{{ url }}" class="btn btn-default btn-{{ icon }}">
{{ macro.icon(icon) }}
{{ _self.icon(icon) }}
</a>
{% endmacro %}
{% macro tag_list(taglist) %}
{% import _self as macro %}
{% for tag in taglist %}
{{ macro.badge(tag.name, tag.color|default('#00a65a')) }}
{{ _self.badge(tag.name, tag.color|colorize(tag.name)) }}
{% endfor %}
{% endmacro %}
{% macro form_type_value(type, value, entity) %}
{% import _self as widgets %}
{% if '\\ColorPickerType' in type %}
{{ widgets.color_dot(value, value) }}
{{ _self.color_dot(value, value) }}
{% elseif '\\DurationType' in type %}
{{ value|duration }}
{% elseif '\\YesNoType' in type or '\\CheckBoxType' in type %}
{{ widgets.label_boolean(value) }}
{{ _self.label_boolean(value) }}
{% elseif '\\DatePickerType' in type %}
{{ value|date_short }}
{% elseif '\\DateTimePickerType' in type %}
@@ -448,11 +496,10 @@
{% endmacro %}
{% macro team_list(teams, showTitle, collapseAt) %}
{% set collapseAt = collapseAt ?? 5 %}
{% set collapseAt = collapseAt ?? 12 %}
{% if showTitle is null %}
{% set showTitle = true %}
{% endif %}
{% import _self as macro %}
<table class="table table-hover dataTable" role="grid">
{% if showTitle %}
<thead>
@@ -471,15 +518,15 @@
{{ team.name }}
</td>
<td class="avatars">
{{ macro.user_avatar(team.teamlead, ('label.teamlead'|trans ~ ': ' ~ team.teamlead.displayName), 'teamlead') }}
{{ _self.user_avatar(team.teamlead, ('label.teamlead'|trans ~ ': ' ~ team.teamlead.displayName), 'teamlead') }}
{% set teamHiddenId = 'team_' ~ team.id ~ '_hiddenUser' ~ random() %}
{% set counter = 0 %}
{% for user in users %}
{% if user != team.teamlead %}
{{ macro.user_avatar(user) }}
{{ _self.user_avatar(user) }}
{% set counter = counter + 1 %}
{% endif %}
{% if userTeamCount > collapseAt and counter == (collapseAt - 1) and loop.index != userTeamCount %}
{% if userTeamCount > (collapseAt + 1) and counter == (collapseAt - 1) and loop.index != userTeamCount %}
<a href="#" onclick="$('#{{ teamHiddenId }}').toggleClass('hidden');$(this).hide();return false;" class="badge">{{ 'label.plus_more'|trans({'%count%': (userTeamCount - collapseAt)}) }}</a>
<span class="hidden" id="{{ teamHiddenId }}">
{% set counter = counter + 1 %}

View File

@@ -1,7 +1,9 @@
{% if not kimai_context.branding.logo is empty %}
<img src="{{ kimai_context.branding.logo }}" style="max-width: 300px;">
{% elseif not kimai_context.branding.company is empty %}
<b>{{ kimai_context.branding.company|raw }}</b>
{% set logo = config('theme.branding.logo') %}
{% set company = config('theme.branding.company') %}
{% if logo is not empty %}
<img src="{{ logo }}" style="max-width: 300px;">
{% elseif company is not empty %}
<b>{{ company|raw }}</b>
{% else %}
{{- get_title('<b>', '</b><br>')|raw -}}
{% endif %}

View File

@@ -45,7 +45,9 @@
{% for entry in entries %}
<tr {{ widgets.project_row_attr(entry, now) }}>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('project', true, entry.name, null, entry.color) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
{{ widgets.label_name(entry.name, entry.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>

View File

@@ -0,0 +1,488 @@
{% 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_revenue_tab = project_details is not null and is_granted('budget', project_details.project) %}
{% set view_revenue_user = project_details is not null and is_granted('view_rate_other_timesheet') %}
{% 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_user and view_revenue_tab %}
{% 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_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_view, project_details, view_revenue_tab, view_revenue_user) }}
{% set currency = project_view.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_tab) }}
{% endfor %}
{% endif %}
{% endblock %}
{% macro duration_stat(id, title, year, labels, yearStat, activities, users, currency, view_revenue_tab) %}
{% 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">
<li><a data-chart="{{ chartPrefix }}User" href="#user-chart-{{ id}}" data-toggle="tab">{{ 'label.user'|trans }}</a></li>
<li><a data-chart="{{ chartPrefix }}Activity" href="#activity-chart-{{ id}}" data-toggle="tab">{{ 'label.activity'|trans }}</a></li>
{% if view_revenue_tab %}
<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') }} {{ widgets.label(yearStat.rate|money(currency), 'gray') }}</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_tab %}
<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_tab) }}
</div>
<div class="chart tab-pane" id="user-chart-{{ id }}">
{{ _self.user_tab(year, users) }}
</div>
</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) => a.duration <= b.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_tab = boolean
#}
{% macro activity_tab(activities, totalDuration, currency, chartPrefix, view_revenue_tab) %}
{% 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) => a.duration <= b.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_tab %}
<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 %}
{#
entry = ProjectViewModel
project_details = ProjectDetailsModel
#}
{% macro project_details(entry, project_details, view_revenue_tab, view_revenue_user) %}
{% 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 project = entry.project %}
{% 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">
<li><a data-chart="{{ chartPrefix }}User" href="#user-chart" data-toggle="tab">{{ 'label.user'|trans }}</a></li>
<li><a data-chart="{{ chartPrefix }}Activity" href="#activity-chart" data-toggle="tab">{{ 'label.activity'|trans }}</a></li>
{% if view_revenue_tab %}
<li><a data-chart="{{ chartPrefix }}Rate" href="#revenue-chart" data-toggle="tab">{{ 'stats.revenue'|trans }}</a></li>
{% endif %}
<li><a data-chart="{{ chartPrefix }}Duration" href="#time-chart" data-toggle="tab">{{ 'stats.workingTime'|trans }}</a></li>
<li class="active"><a href="#details-chart" data-toggle="tab">{{ 'report_project_details'|trans({}, 'reporting') }}</a></li>
<li class="pull-left header">
{{ 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-6">
<table class="table table-hover dataTable">
<tr {{ widgets.customer_row_attr(entry.project.customer) }}>
<th class="w-min">
{{ 'label.customer'|trans }}
</th>
<td>
{{ widgets.label_customer(entry.project.customer) }}
</td>
</tr>
<tr>
<th class="w-min">
{{ 'stats.durationToday'|trans }}
</th>
<td>{{ entry.durationDay|duration }}</td>
</tr>
<tr>
<th class="w-min">
{{ 'stats.durationWeek'|trans }}
</th>
<td>{{ entry.durationWeek|duration }}</td>
</tr>
<tr>
<th class="w-min">
{{ 'stats.durationMonth'|trans }}
</th>
<td>{{ entry.durationMonth|duration }}</td>
</tr>
<tr>
<th class="w-min">
{{ 'stats.durationTotal'|trans }}
</th>
<td>{{ entry.durationTotal|duration }}</td>
</tr>
</table>
</div>
<div class="col-xs-6">
<table class="table table-hover dataTable">
<tr>
<th class="w-min">
{{ 'label.last_record'|trans }}
</th>
<td>
{% if entry.lastRecord is not null %}
{{ entry.lastRecord|date_short }}
{% else %}
&ndash;
{% endif %}
</td>
</tr>
<tr>
<th class="w-min">
{{ 'label.not_exported'|trans }}
</th>
<td>
{% 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>
</tr>
<tr>
<th class="w-min">
{{ 'label.not_invoiced'|trans }}
</th>
<td>
{% 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>
</tr>
{% if project.start is not null %}
<tr>
<th class="w-min">
{{ 'label.project_start'|trans }}
</th>
<td>
{{ project.start|date_short }}
</td>
</tr>
{% endif %}
{% if project.end is not null %}
<tr>
<th class="w-min">
{{ 'label.project_end'|trans }}
</th>
<td>
{{ project.end|date_short }}
</td>
</tr>
{% endif %}
</table>
</div>
</div>
{% if is_granted('budget', project) and (project.timeBudget > 0 or project.budget > 0) %}
<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 }}
</th>
<td>
{% set timeBudgetLeft = project.timeBudget - entry.billableDuration %}
{% set options = {
'max': project.timeBudget|default(0),
'current': entry.billableDuration|default(0),
'open': timeBudgetLeft,
'format': 'duration'
} %}
{{ progress.progressbar_full(options) }}
</td>
</tr>
{% endif %}
{% if project.budget > 0 %}
<tr>
<th class="w-min">
{{ 'label.budget'|trans }}
</th>
<td>
{% set budgetLeft = project.budget - entry.billableRate %}
{% set options = {
'max': project.budget|default(0),
'current': entry.billableRate|default(0),
'open': budgetLeft,
'format': 'money',
'currency': currency
} %}
{{ progress.progressbar_full(options) }}
</td>
</tr>
{% endif %}
</table>
</div>
</div>
{% endif %}
</div>
{% if view_revenue_tab %}
<div class="chart tab-pane" id="revenue-chart">
{{ charts.bar_chart(chartPrefix ~ 'Rate', labels, [rates], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Rate'}) }}
</div>
{% endif %}
<div class="chart tab-pane" id="time-chart">
{{ charts.bar_chart(chartPrefix ~ 'Duration', labels, [durations], {'height': '300px', 'renderEvent': 'render.' ~ chartPrefix ~ 'Duration'}) }}
</div>
<div class="chart tab-pane" id="activity-chart">
{{ _self.activity_tab(activities, entry.durationTotal, project.customer.currency, chartPrefix, view_revenue_tab) }}
</div>
<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) => a.duration <= b.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 entry.durationTotal > 0 and userStat.duration > 0 %}
{% set percentage = (100 / (entry.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_user %}
<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_user %}
<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>
</div>
</div>
{% endmacro %}

View File

@@ -61,7 +61,7 @@
{% set project = entry.project %}
{% set currency = project.customer.currency %}
{% if is_granted('budget', project) %}
<tr {{ widgets.project_row_attr(project, now) }}>
<tr{% if is_granted('details', project) %} class="alternative-link open-edit" data-href="{{ path('report_project_details', {'project': project.id}) }}"{% endif %}>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_project(project) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'lastRecord') }}">
{% if entry.lastRecord is not null %}

View File

@@ -33,7 +33,7 @@
{% set usersTotalDuration = 0 %}
<tr class="user">
<td class="text-nowrap">
<strong>{{ widgets.username(userDay.user) }}</strong>
{{ widgets.label_dot(userDay.user.displayName, userDay.user.color) }}
</td>
{% for day in userDay.days %}
{% if day.totalDuration > 0 %}

View File

@@ -34,7 +34,7 @@
{% set usersTotalDuration = 0 %}
<tr class="user">
<td class="text-nowrap">
<strong>{{ widgets.username(userYear.user) }}</strong>
{{ widgets.label_dot(userYear.user.displayName, userYear.user.color) }}
</td>
{% for yid, year in userYear.years %}
{% for mid, month in year.months %}

View File

@@ -6,9 +6,22 @@
{% block page_actions %}{{ actions.tag(tag, 'edit') }}{% endblock %}
{% block main %}
{{ include(app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig', {
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
{% set formOptions = {
'title': tag.name|default('create'|trans),
'form': form,
'back': path('tags')
}) }}
} %}
{% embed formEditTemplate with formOptions %}
{% block form_body %}
<div class="row">
<div class="col-md-10">
{{ form_row(form.name) }}
</div>
<div class="col-md-2">
{{ form_row(form.color) }}
</div>
</div>
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -45,7 +45,7 @@
</td>
{% endif %}
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
{{ widgets.label_color_dot('tag', true, tag.name, null, tag.color|default('#00a65a')) }}
{{ widgets.label_name(tag.name, tag.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'amount') }}">{{ widgets.label(tag.amount, type) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'actions') }}">

View File

@@ -26,8 +26,12 @@
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.teamUpdate'}) }}
{% for team in teams %}
<tr{% if is_granted('edit', team) %} class="open-edit alternative-link" data-href="{{ path('admin_team_edit', {'id': team.id}) }}"{% endif %}>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ team.name }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'teamlead') }}">{{ widgets.label_user(team.teamlead) }}</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">
{{ widgets.label_name(team.name, team.color) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'teamlead') }}">
{{ widgets.label_user(team.teamlead) }}
</td>
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">
{% for user in team.users %}
{{ widgets.label_user(user) }}&nbsp;

View File

@@ -2,17 +2,15 @@
{% block profile_content %}
{{ form_start(form) }}
{% for section in sections %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_body %}
{% for pref in form.children.preferences|filter(pref => pref.vars.data.section == section)|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
{{ form_row(pref) }}
{% endfor %}
{% endblock %}
{% endembed %}
{% endfor %}
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
{{ form_end(form) }}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ ('profile.' ~ tab)|trans }}{% endblock %}
{% block box_body %}
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
{{ form_end(form) }}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends 'user/layout.html.twig' %}
{% block profile_content %}
{{ form_start(form) }}
{% for section in sections %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_body %}
{% for pref in form.children.preferences|filter(pref => pref.vars.data.section == section)|sort((a, b) => a.vars.data.order <=> b.vars.data.order) %}
{{ form_row(pref) }}
{% endfor %}
{% endblock %}
{% endembed %}
{% endfor %}
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
{{ form_end(form) }}
{% endblock %}

View File

@@ -7,6 +7,14 @@
{% block box_title %}{{ ('profile.' ~ tab)|trans }}{% endblock %}
{% block box_body %}
{{ form_start(form) }}
<div class="row">
<div class="col-md-10">
{{ form_row(form.alias) }}
</div>
<div class="col-md-2">
{{ form_row(form.color) }}
</div>
</div>
{{ form_widget(form) }}
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
{{ form_end(form) }}

View File

@@ -1,5 +1,6 @@
{% extends 'user/layout.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/charts.html.twig" as charts %}
{% block stylesheets %}
{{ parent() }}
@@ -25,127 +26,26 @@
{{ widgets.nothing_found() }}
{% endif %}
<script type="text/javascript">
function userProfileChartOptions() {
return {
maintainAspectRatio: true,
responsive: true,
legend: false,
barPercentage: 0.5,
categoryPercentage: 0.9,
scales: {
xAxes: [{
gridLines: {
display: false
}
}],
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true,
color: '{{ kimai_context.chart.grid_color }}',
lineWidth: 1
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return tooltipData['duration'];
},
afterTitle: function(tooltipItems, data) {
return ' ';
},
beforeFooter: function(tooltipItems, data) {
return ' ';
},
footer: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return '{{ 'label.billable'|trans }}: ' + tooltipData['billable'];
},
}
}
{%- if user.enabled and is_granted('view_reporting') and (app.user.id == user.id or is_granted('view_other_timesheet')) -%}
,
onClick: function(event, elements) {
var element = elements[0];
var month = this.data.datasets[0].monthData[element._index];
var formattedMonth = moment(month).format('{{ stat_date_format }}');
document.location = '{{ path('report_user_month', {'user': user.id, 'date': 'XXXXX'})|raw }}'.replace('XXXXX', formattedMonth);
}
{% endif %}
};
}
</script>
{% set monthRoute = null %}
{%- if user.enabled and is_granted('view_reporting') and (app.user.id == user.id or is_granted('view_other_timesheet')) -%}
{% set monthRoute = path('report_user_month', {'user': user.id, 'date': '__MONTH__'}) %}
{% endif %}
{% for year, yearStat in years %}
{{ charts.bar_javascript({'footer': 'footer', 'label': 'tooltip', 'onclick': {'url': monthRoute, 'replacer': {'__MONTH__': 'month'}}}) }}
{% for yearStat in years %}
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_title %}{{ year }}{% endblock %}
{% import "macros/charts.html.twig" as charts %}
{% block box_title %}{{ yearStat.year }}{% endblock %}
{% block box_body %}
<div class="chart">
<canvas id="userProfileChart{{ year }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
var userProfileChartData{{ year }} = {
labels: moment.months(),
datasets: [
{
label: '{{ year }}',
backgroundColor: '{{ kimai_context.chart.background_color }}',
borderColor: '{{ kimai_context.chart.border_color }}',
data: [
{% for monthNumber in 1..12 %}
{% set duration = 0 %}
{% set month = yearStat.month(monthNumber) %}
{% if month is not null %}
{% set duration = month.totalDuration %}
{% endif %}
{{ (duration / 3600)|number_format(2, '.', '') }}
{% if not loop.last %},{% endif %}
{% endfor %}
],
realData: [
{% for monthNumber in 1..12 %}
{% set duration = 0 %}
{% set billable = 0 %}
{% set month = yearStat.month(monthNumber) %}
{% if month is not null %}
{% set duration = month.totalDuration %}
{% set billable = month.billableDuration %}
{% endif %}
{% set realDayData = {'duration': duration|duration, 'billable': billable|duration} %}
{{ realDayData|json_encode|raw }}
{% if not loop.last %},{% endif %}
{% endfor %}
],
monthData: [
{% for monthNumber in 1..12 %}
'{{ yearStat.year }}-{{ monthNumber < 10 ? '0' ~ monthNumber : monthNumber }}-01'
{% if not loop.last %},{% endif %}
{% endfor %}
],
}
]
};
var userProfileChart{{ year }} = new Chart(
$("#userProfileChart{{ year }}").get(0).getContext("2d"), {
type: 'bar',
data: userProfileChartData{{ year }},
options: userProfileChartOptions()
}
);
});
</script>
{% set dataset = [] %}
{% for month in yearStat.months %}
{% set monthDate = create_date(yearStat.year ~ '-' ~ month.month ~ '-01') %}
{% set dataset = dataset|merge([{'value': month.duration|chart_duration, 'tooltip': month.duration|duration, 'footer': ('label.billable'|trans ~ ': ' ~ month.billableDuration|duration), 'month': monthDate|date_short}]) %}
{% endfor %}
{{ charts.bar_chart('userProfileChart'~yearStat.year, month_names(), [dataset], {}) }}
{% endblock %}
{% endembed %}
{% endfor %}
{% if user.teams is not empty and (is_granted('teams', user) or (app.user == user and is_granted('view_team_member'))) %}
@@ -244,7 +144,7 @@
{% import "@AdminLTE/Macros/default.html.twig" as macro %}
{% import "macros/widgets.html.twig" as widgets %}
{% set color = kimai_context.chart.background_color %}
{% set color = config('theme.chart.background_color') %}
{% set event = trigger('user.profile_background', {'color': color, 'font': color|font_contrast, 'user': user, 'image': null}) %}
{% set config = event.payload %}

View File

@@ -1,12 +1,12 @@
{% set type = options.type|default('bar') %}
{% set chart_id = options.id %}
{% set backgroundColor = kimai_context.chart.background_color %}
{% set gridColor = kimai_context.chart.grid_color %}
{% set backgroundColor = config('theme.chart.background_color') %}
{% set gridColor = config('theme.chart.grid_color') %}
{% set activities = data.activities %}
{% set data = data.data %}
<div class="chart">
<canvas id="{{ chart_id }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
<canvas id="{{ chart_id }}" style="height: {{ config('theme.chart.height') }}px;"></canvas>
</div>
<script type="text/javascript">
@@ -38,7 +38,7 @@
{%- for entry in day.details -%}
{% set loopId = (entry.project.id ~ '_' ~ entry.activity.id) %}
{%- if loopId == activityId -%}
{% set realDayData = (entry.duration / 3600)|number_format(2, '.', '') %}
{% set realDayData = entry.duration|chart_duration %}
{%- endif -%}
{%- endfor -%}
{%- if realDayData is not null -%}

View File

@@ -1,6 +1,6 @@
{% set backgroundColor = kimai_context.chart.background_color %}
{% set borderColor = kimai_context.chart.border_color %}
{% set gridColor = kimai_context.chart.grid_color %}
{% set backgroundColor = config('theme.chart.background_color') %}
{% set borderColor = config('theme.chart.border_color') %}
{% set gridColor = config('theme.chart.grid_color') %}
{% set colors = options.color|default('')|split(';') %}
{% set type = options.type|default('line') %}
{% if type not in ['line', 'bar'] %}
@@ -16,7 +16,7 @@
{% endif %}
<div class="chart">
<canvas id="timeChart" style="height: {{ kimai_context.chart.height }}px;"></canvas>
<canvas id="timeChart" style="height: {{ config('theme.chart.height') }}px;"></canvas>
</div>
<script type="text/javascript">
@@ -52,7 +52,7 @@ document.addEventListener('kimai.initialized', function() {
lineTension : 0.3,
data : [
{% for month in year.months %}
{{ (month.totalDuration / 3600)|number_format(2, '.', '') }}
{{ month.totalDuration|chart_duration }}
{% if not loop.last %},{% endif %}
{% endfor %}
],