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

@@ -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 %}