users weekly stats as bar-chart in dashboard (#847)

This commit is contained in:
Kevin Papst
2019-06-13 18:38:49 +02:00
committed by GitHub
parent e1f862507e
commit 3311f17bbb
96 changed files with 2754 additions and 846 deletions

View File

@@ -35,13 +35,8 @@
{% set currency = activity.project.customer.currency %}
{% endif %}
{% if activity.budget > 0 %}
{{ progress.progressbar(activity.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(currency) ~ ' / ' ~ activity.budget|money(currency) ) }}
{% endif %}
{% if activity.timeBudget > 0 %}
{{ progress.progressbar(activity.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ activity.timeBudget|duration ) }}
{% endif %}
{{ progress.progressbar(activity.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(currency) ~ ' / ' ~ activity.budget|money(currency) ) }}
{{ progress.progressbar(activity.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ activity.timeBudget|duration ) }}
{% endblock %}
{% endembed %}

View File

@@ -24,13 +24,8 @@
{{ 'admin_customer.short_stats'|trans(params)|raw }}
</p>
{% if customer.budget > 0 %}
{{ progress.progressbar(customer.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(customer.currency) ~ ' / ' ~ customer.budget|money(customer.currency) ) }}
{% endif %}
{% if customer.timeBudget > 0 %}
{{ progress.progressbar(customer.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ customer.timeBudget|duration ) }}
{% endif %}
{{ progress.progressbar(customer.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(customer.currency) ~ ' / ' ~ customer.budget|money(customer.currency) ) }}
{{ progress.progressbar(customer.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ customer.timeBudget|duration ) }}
{% endblock %}
{% endembed %}

View File

@@ -18,12 +18,8 @@
{% block main %}
{% for row in widget_rows %}
{% if row.type == constant('App\\Model\\DashboardSection::TYPE_CHART') %}
{% embed 'dashboard/section-chart.html.twig' with { section: row } %}{% endembed %}
{% else %}
{% embed 'dashboard/section-simple.html.twig' with { section: row } %}{% endembed %}
{% endif %}
{% for row in widgets %}
{{ render_widget(row) }}
{% endfor %}
{% endblock %}

View File

@@ -1,34 +0,0 @@
{% import "macros/widgets.html.twig" as widgets %}
{% if section.title %}
{{ widgets.page_header(section.title) }}
{% endif %}
{% set width = section.widgets|length %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
<div class="row">
{% for widget in section.widgets %}
{% set columnSize = columnWidth %}
{% if width == 5 and (loop.first or loop.last) %}
{% set columnSize = columnWidth + 1 %}
{% endif %}
<div class="col-md-{{ columnSize }} col-sm-{{ columnSize * 2 }} col-xs-{{ columnSize * 4 }}">
{% set data = widget.data %}
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
{% set data = widget.data|duration %}
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
{% set data = widget.data|money %}
{% endif %}
{% set url = null %}
{% if widget.route %}
{% set url = path(widget.route, widget.routeOptions) %}
{% endif %}
{% set embedOptions = {data: data, title: widget.title, icon: widget.icon, color: widget.color, url: url} %}
{% embed 'embeds/widget-'~widget.type~'.html.twig' with embedOptions %}{% endembed %}
</div>
{% endfor %}
</div>

View File

@@ -1,10 +0,0 @@
<div class="info-box">
<span class="info-box-icon bg-{{ color|default('green') }}"><i class="{{ icon|icon(icon) }}"></i></span>
<div class="info-box-content">
{% if url %}<a href="{{ url }}" class="small-box-footer">{% endif %}
<span class="info-box-text">{{ title|trans }}</span>
<span class="info-box-number">{{ data }}</span>
{% if url %}</a>{% endif %}
</div>
</div>

View File

@@ -1,14 +0,0 @@
<div class="small-box bg-{{ color|default('green') }}">
<div class="inner">
<h3>{{ data }}<sup style="font-size: 20px">{{ unit|default('') }}</sup></h3>
<p>{{ title|trans }}</p>
</div>
<div class="icon">
<i class="{{ icon|icon(icon) }}"></i>
</div>
{% if url %}
<a href="{{ url }}" class="small-box-footer">
{{ 'more.info.link'|trans }}
</a>
{% endif %}
</div>

View File

@@ -1,5 +1,8 @@
{% macro progressbar(max, current, title, subTitle) %}
{% set percentReached = (current / (max / 100)) %}
{% set percentReached = 0 %}
{% if max > 0 %}
{% set percentReached = (current / (max / 100)) %}
{% endif %}
{% set class = "progress-bar-info" %}
{% set width = percentReached|number_format(1, '.', '') %}
@@ -18,7 +21,7 @@
{% endif %}
<div class="progress-group">
<span class="progress-text">{{ title }} &ndash; {{ percentReached|number_format(2) }}%</span>
<span class="progress-text">{{ title }}{% if percentReached > 0 %} &ndash; {{ percentReached|number_format(2) }}%{% endif %}</span>
<span class="progress-number">{{ subTitle }}</span>
<div class="progress">

View File

@@ -254,29 +254,17 @@
{% endif %}
<a class="{{ class | trim }}" href="{{ url }}"
{%- if disabled is same as (true) -%}
disabled="disabled"
{%- endif -%}
{%- if id is not empty -%}
id="{{ id }}"
{%- endif -%}
{%- if toggle is not empty -%}
data-toggle="{{ toggle }}"
{%- endif -%}
{%- if modal is not empty -%}
data-toggle="modal" data-target="{{ modal }}"
{%- endif -%}
{%- if onclick is not empty -%}
onclick="{{ onclick }}"
{%- endif -%}
{%- if target is not empty -%}
target="{{ target }}"
{%- endif -%}
{%- if attr is not empty -%}
{%- for name, value in attr %}
{{- ' ' ~ name }}={% if '"' in value %}'{{ value|raw }}'{% else %}"{{ value|raw }}"{% endif %}
{%- endfor -%}
{%- endif -%}
{%- if disabled is same as (true) %} disabled="disabled"{% endif %}
{%- if id is not empty %} id="{{ id }}"{% endif %}
{%- if toggle is not empty %} data-toggle="{{ toggle }}"{% endif %}
{%- if modal is not empty %} data-toggle="modal" data-target="{{ modal }}"{% endif %}
{%- if onclick is not empty %} onclick="{{ onclick }}"{% endif %}
{%- if target is not empty %} target="{{ target }}"{% endif %}
{%- if attr is not empty %}
{% for name, value in attr %}
{{ ' ' ~ name }}={% if '"' in value %}'{{ value|raw }}'{% else %}"{{ value|raw }}"{% endif %}
{% endfor %}
{% endif %}
>{% if title is not null %}{{ title }}{% else %}{{ macro.icon(icon) }}{% endif %}</a>
{% endapply %}
{% endmacro %}

View File

@@ -23,13 +23,8 @@
{{ 'admin_project.short_stats'|trans(params)|raw }}
</p>
{% if project.budget > 0 %}
{{ progress.progressbar(project.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(project.customer.currency) ~ ' / ' ~ project.budget|money(project.customer.currency) ) }}
{% endif %}
{% if project.timeBudget > 0 %}
{{ progress.progressbar(project.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ project.timeBudget|duration ) }}
{% endif %}
{{ progress.progressbar(project.budget, stats.recordRate, 'label.budget'|trans, stats.recordRate|money(project.customer.currency) ~ ' / ' ~ project.budget|money(project.customer.currency) ) }}
{{ progress.progressbar(project.timeBudget, stats.recordDuration, 'label.timeBudget'|trans, stats.recordDuration|duration ~ ' / ' ~ project.timeBudget|duration ) }}
{% endblock %}
{% endembed %}

View File

@@ -33,12 +33,22 @@
}
}],
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true,
color: 'rgba(0,0,0,.05)',
color: '{{ kimai_context.chart.grid_color }}',
lineWidth: 1
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
}
}
}
};
}
@@ -51,7 +61,7 @@
{% block box_body %}
<div class="chart">
<canvas id="userProfileChart{{ year }}" style="height: 200px;"></canvas>
<canvas id="userProfileChart{{ year }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
@@ -60,11 +70,17 @@
datasets: [
{
label: '{{ year }}',
backgroundColor : '#00a65a',
borderColor : '#00a65a',
backgroundColor: '{{ kimai_context.chart.background_color }}',
borderColor: '{{ kimai_context.chart.border_color }}',
data: [
{% for month in yearStat.months %}
{{ (month.totalDuration / 3600)|round }}
{{ (month.totalDuration / 3600)|number_format(2, '.', '') }}
{% if not loop.last %},{% endif %}
{% endfor %}
],
realData : [
{% for month in yearStat.months %}
'{{ month.totalDuration|duration }}'
{% if not loop.last %},{% endif %}
{% endfor %}
]

View File

@@ -1,11 +1,9 @@
{% set chartWidget = section.widgets.0 %}
{% set colors = chartWidget.color|split(';') %}
<div class="row">
<div class="col-md-12">
<div class="box">
{#
<div class="box-header with-border">
<h3 class="box-title">{{ section.title|trans }}</h3>
<h3 class="box-title">{{ title|trans }}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
@@ -27,25 +25,7 @@
<div class="box-body">
<div class="row">
<div class="col-md-12">
{#
<p class="text-center">
<strong>
{% for yearName, year in chartWidget.data|sort %}
{% if loop.first or loop.last %}
{% for id, month in year.months %}
{% if loop.first %}
moment.months({{ loop.index }}) {{ yearName }}
{% endif %}
{% endfor %}
{% endif %}
{% if loop.first %} - {% endif %}
{% endfor %}
</strong>
</p>
#}
<div class="chart">
<canvas id="timeChart" style="height: 180px;"></canvas>
</div>
{{ render_widget(widgets.0) }}
</div>
{#
<div class="col-md-4">
@@ -85,17 +65,17 @@
#}
</div>
</div>
{% if section.widgets|length > 1 %}
{% if widgets|length > 1 %}
<div class="box-footer">
<div class="row">
{% set width = (section.widgets|length) - 1 %}
{% set width = (widgets|length) - 1 %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
{% for widget in section.widgets|slice(1, width) %}
{% for widget in widgets|slice(1, width) %}
{% set data = widget.data %}
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
{% if widget.option('dataType') == 'duration' %}
{% set data = widget.data|duration %}
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
{% elseif widget.option('dataType') == 'money' %}
{% set data = widget.data|money %}
{% endif %}
<div class="col-sm-{{ columnWidth }} col-xs-{{ columnWidth * 2 }}">
@@ -113,63 +93,5 @@
</div>
{% endif %}
</div>
<script>
$(function () {
'use strict';
var timeChartData = {
labels: moment.months(),
datasets: [
{% for yearName, year in chartWidget.data %}
{% set yearColors = colors[loop.index-1]|split('|') %}
{
label : '{{ yearName }}',
backgroundColor : '{{ yearColors.1 }}',
borderColor : '{{ yearColors.0 }}',
pointRadius : 2,
borderWidth : 1,
pointHitRadius : 10,
lineTension : 0.3,
data : [
{% for month in year.months %}
{{ (month.totalDuration / 3600)|round }}
{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
};
var timeChartOptions = {
maintainAspectRatio : true,
responsive : true,
scales: {
xAxes: [{
gridLines: {
display: false
}
}],
yAxes: [{
gridLines: {
display: true,
color: 'rgba(0,0,0,.05)',
lineWidth: 1
}
}]
}
};
var timeChartCanvas = $('#timeChart').get(0).getContext('2d');
var timeChart = new Chart(
timeChartCanvas, {
type: 'line',
data: timeChartData,
options: timeChartOptions
}
);
});
</script>
</div>
</div>

View File

@@ -0,0 +1,20 @@
{% import "macros/widgets.html.twig" as widgets %}
{% if not title is empty %}
{{ widgets.page_header(title) }}
{% endif %}
{% set width = widgets|length %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
<div class="row">
{% for widget in widgets %}
{% set columnSize = columnWidth %}
{% if width == 5 and (loop.first or loop.last) %}
{% set columnSize = columnWidth + 1 %}
{% endif %}
<div class="col-md-{{ columnSize }} col-sm-{{ columnSize * 2 }} col-xs-{{ columnSize * 4 }}">
{{ render_widget(widget) }}
</div>
{% endfor %}
</div>

View File

@@ -0,0 +1,28 @@
{% set url = null %}
{% if options.route is defined %}
{% set url = path(options.route, options.routeOptions|default([])) %}
{% endif %}
<div class="info-box">
<span class="info-box-icon bg-{{ options.color|default('green') }}"><i class="{{ options.icon|icon(options.icon) }}"></i></span>
<div class="info-box-content">
{% if not url is empty %}<a href="{{ url }}" class="small-box-footer">{% endif %}
<span class="info-box-text">{{ title|trans }}</span>
<span class="info-box-number">
{% if data is iterable %}
{{ 'Invalid data' }}
{% else %}
{% block widget_data %}
{% if options.dataType == 'duration' %}
{% set data = data|duration %}
{% elseif options.dataType == 'money' %}
{% set data = data|money %}
{% endif %}
{{ data }}
{% endblock %}
{% endif %}
</span>
{% if not url is empty %}</a>{% endif %}
</div>
</div>

View File

@@ -0,0 +1,95 @@
{% set type = options.type|default('bar') %}
{% set chart_id = options.id %}
{% set backgroundColor = kimai_context.chart.background_color %}
{% set borderColor = kimai_context.chart.border_color %}
{% set gridColor = kimai_context.chart.grid_color %}
{% set colors = options.color|default('')|split(';') %}
{% if colors.0 is defined and not colors.0 is empty %}
{% set backgroundColor = colors.0 %}
{% set borderColor = colors.0 %}
{% if colors.1 is defined and not colors.1 is empty %}
{% set borderColor = colors.1 %}
{% endif %}
{% endif %}
{{ encore_entry_link_tags('chart') }}
{{ encore_entry_script_tags('chart') }}
{% if not title is empty %}
<p class="text-center">
<strong>
{{ title|trans }}
</strong>
</p>
{% endif %}
<div class="chart">
<canvas id="{{ chart_id }}" style="height: {{ kimai_context.chart.height }}px;"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
new Chart(
document.getElementById('{{ chart_id }}').getContext('2d'), {
type: '{{ type }}',
data: {
labels: [
{% for day in data -%}
moment('{{ day.day|date_format(constant('\DateTime::ISO8601')) }}').format('ll')
{% if not loop.last %},{% endif -%}
{%- endfor %}
],
datasets: [
{
backgroundColor: '{{ backgroundColor }}',
borderColor: '{{ borderColor }}',
data: [
{% for day in data -%}
{{ (day.totalDuration / 3600)|number_format(2, '.', '') }}
{%- if not loop.last %},{% endif -%}
{%- endfor %}
],
realData: [
{% for day in data -%}
'{{ day.totalDuration|duration }}'
{%- if not loop.last %},{% endif -%}
{%- endfor %}
]
}
]
},
options: {
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: '{{ gridColor }}',
lineWidth: 1
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
}
}
}
}
}
);
});
</script>

View File

@@ -0,0 +1,33 @@
{% set url = null %}
{% if options.route is defined %}
{% set url = path(options.route, options.routeOptions|default([])) %}
{% endif %}
<div class="small-box bg-{{ options.color|default('green') }}">
<div class="inner">
<h3>
{% if data is iterable %}
{{ 'Invalid data' }}
{% else %}
{% block widget_data %}
{% if options.dataType == 'duration' %}
{% set data = data|duration %}
{% elseif options.dataType == 'money' %}
{% set data = data|money %}
{% endif %}
{{ data }}
{% endblock %}
{% endif %}
<sup style="font-size: 20px">{{ unit|default('') }}</sup>
</h3>
<p>{{ title|trans }}</p>
</div>
<div class="icon">
<i class="{{ options.icon|icon(options.icon) }}"></i>
</div>
{% if not url is empty %}
<a href="{{ url }}" class="small-box-footer">
{{ 'more.info.link'|trans }}
</a>
{% endif %}
</div>

View File

@@ -0,0 +1,98 @@
{% set backgroundColor = kimai_context.chart.background_color %}
{% set borderColor = kimai_context.chart.border_color %}
{% set gridColor = kimai_context.chart.grid_color %}
{% set colors = options.color|default('')|split(';') %}
{% set type = options.type|default('line') %}
{% if type not in ['line', 'bar'] %}
{% set type = 'line' %}
{% endif %}
{% if not title is empty %}
<p class="text-center">
<strong>
{{ title|trans }}
</strong>
</p>
{% endif %}
<div class="chart">
<canvas id="timeChart" style="height: {{ kimai_context.chart.height }}px;"></canvas>
</div>
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
new Chart(
document.getElementById('timeChart').getContext('2d'), {
type: '{{ type }}',
data: {
labels: moment.months(),
datasets: [
{% for yearName, year in data %}
{% set yearBackgroundColor = backgroundColor %}
{% set yearBorderColor = borderColor %}
{% if colors[loop.index-1] is defined %}
{% set yearColors = colors[loop.index-1]|split('|') %}
{% if yearColors.0 is defined and not yearColors.0 is empty %}
{% set yearBackgroundColor = yearColors.0 %}
{% set yearBorderColor = yearColors.0 %}
{% if yearColors.1 is defined and not yearColors.1 is empty %}
{% set yearBorderColor = yearColors.1 %}
{% endif %}
{% endif %}
{% endif %}
{
label : '{{ yearName }}',
backgroundColor : '{{ yearBackgroundColor }}',
borderColor : '{{ yearBorderColor }}',
pointRadius : 2,
borderWidth : 1,
pointHitRadius : 10,
lineTension : 0.3,
data : [
{% for month in year.months %}
{{ (month.totalDuration / 3600)|number_format(2, '.', '') }}
{% if not loop.last %},{% endif %}
{% endfor %}
],
realData : [
{% for month in year.months %}
'{{ month.totalDuration|duration }}'
{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
},
options: {
maintainAspectRatio: true,
responsive: true,
scales: {
xAxes: [{
gridLines: {
display: false
}
}],
yAxes: [{
gridLines: {
display: true,
color: '{{ gridColor }}',
lineWidth: 1
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
}
}
}
}
}
);
});
</script>