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:
224
templates/macros/charts.html.twig
Normal file
224
templates/macros/charts.html.twig
Normal 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 %}
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user