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

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

View File

@@ -1,39 +0,0 @@
<div class="row">
<div class="col-md-12">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_body %}
<div class="row">
<div class="col-md-12">
{{ render_widget(widgets.0) }}
</div>
</div>
{% endblock %}
{% block box_footer %}
{% if widgets|length > 1 %}
<div class="row">
{% set width = (widgets|length) - 1 %}
{% set rawWidth = 12 / width %}
{% set columnWidth = rawWidth|round(0, 'floor') %}
{% for widget in widgets|slice(1, width) %}
{% set data = widget.data %}
{% if widget.option('dataType') == 'duration' %}
{% set data = widget.data|duration %}
{% elseif widget.option('dataType') == 'money' %}
{% set data = widget.data|money %}
{% endif %}
<div class="col-sm-{{ columnWidth }} col-xs-{{ columnWidth * 2 }}">
<div class="description-block border-right">
<h5 class="description-header">{{ data }}</h5>
<span class="description-text">{{ widget.title|trans }}</span>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% endembed %}
</div>
</div>

View File

@@ -1,22 +0,0 @@
{% import "macros/widgets.html.twig" as widgets %}
{% set width = widgets|length %}
{% if width > 0 %}
{% if not title is empty %}
{{ widgets.page_header(title) }}
{% endif %}
{% 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>
{% endif %}

View File

@@ -0,0 +1,4 @@
{% extends 'widget/widget-counter.html.twig' %}
{% block widget_data %}
{{ data|duration }}
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends 'widget/widget-counter.html.twig' %}
{% from 'macros/widgets.html.twig' import label %}
{% block widget_data %}
{% if data is empty %}
{% set data = {(null): 0.00} %}
{% endif %}
{% if data|length > 1 %}
{% for currency, revenue in data %}
{{ label(revenue|money(currency|default(options.currency|default(null))), options.color) }}
{% endfor %}
{% else %}
{% for currency, revenue in data %}
{{ revenue|money(currency|default(options.currency|default(null))) }}
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -3,26 +3,26 @@
{% 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 class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<span class="bg-{{ options.color|default('green') }} text-white avatar">
<i class="{{ options.icon|icon(true, options.icon) }}"></i>
</span>
</div>
<div class="col">
{% if not url is empty %}<a href="{{ url }}">{% endif %}
<div class="font-weight-medium">
{{ title|trans }}
</div>
<div class="text-muted">
{% block widget_data %}
{{ data }}
{% endblock %}
</div>
{% if not url is empty %}</a>{% endif %}
</div>
</div>
</div>
</div>

View File

@@ -6,12 +6,12 @@
{% set data = data.data %}
<div class="chart">
<canvas id="{{ chart_id }}" style="height: {{ config('theme.chart.height') }}px;"></canvas>
<canvas id="{{ chart_id }}" style="height: {{ config('theme.chart.height') }}px; width: 100%;"></canvas>
</div>
<script type="text/javascript">
var myChart = null;
var paintChart = function() {
myChart = new Chart(
document.getElementById('{{ chart_id }}').getContext('2d'), {
@@ -19,7 +19,7 @@
data: {
labels: [
{% for day in data -%}
moment('{{ day.day|date_format('Y-m-d') }}').format('ll')
'{{ day.day|date_format('d.') }} {{ day.day|month_name(true) }}'
{% if not loop.last %},{% endif -%}
{%- endfor %}
],
@@ -72,18 +72,17 @@
},
options: {
maintainAspectRatio: true,
responsive: true,
legend: false,
responsive: false,
barPercentage: 0.5,
categoryPercentage: 0.9,
categoryPercentage: 1.0,
scales: {
xAxes: [{
x: {
stacked: true,
gridLines: {
display: false
},
}],
yAxes: [{
},
y: {
stacked: true,
ticks: {
beginAtZero: true
@@ -93,37 +92,42 @@
color: '{{ gridColor }}',
lineWidth: 1
}
}],
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return ' ' + tooltipData.duration + ': ' + tooltipData.activity;
},
beforeTitle: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return tooltipData.customer;
},
title: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return tooltipData.project;
},
afterTitle: function(tooltipItems, data) {
return ' ';
},
footer: function(tooltipItems, data) {
var tooltipItem = tooltipItems[0];
var tooltipData = data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
return '{{ 'stats.durationTotal'|trans }}: ' + tooltipData.total + '\n{{ 'label.billable'|trans }}: ' + tooltipData.billable;
},
beforeFooter: function(tooltipItems, data) {
return ' ';
},
},
}
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function(tooltipItem) {
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
return ' ' + tooltipData.duration + ': ' + tooltipData.activity;
},
beforeTitle: function(tooltipItems) {
const tooltipItem = tooltipItems[0];
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
return tooltipData.customer;
},
title: function(tooltipItems) {
const tooltipItem = tooltipItems[0];
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
return tooltipData.project;
},
afterTitle: function(tooltipItems) {
return ' ';
},
footer: function(tooltipItems) {
const tooltipItem = tooltipItems[0];
const tooltipData = tooltipItem.dataset.realData[tooltipItem.dataIndex];
return '{{ 'stats.durationTotal'|trans }}: ' + tooltipData.total + '\n{{ 'billable'|trans }}: ' + tooltipData.billable;
},
beforeFooter: function(tooltipItems) {
return ' ';
},
},
}
},
}
}
);
@@ -132,10 +136,14 @@
var destroyChart = function () {
myChart.destroy();
};
{% if app.request.xmlHttpRequest %}
{% if kimai_context.javascriptRequest %}
paintChart();
{% else %}
document.addEventListener('kimai.initialized', paintChart);
if (document.getElementById('dashboard-grid-container') !== null) {
document.addEventListener('dashboard.initialized', paintChart);
} else {
document.addEventListener('kimai.initialized', paintChart);
}
{% endif %}
</script>

View File

@@ -0,0 +1,4 @@
{% extends 'widget/widget-more.html.twig' %}
{% block widget_data %}
{{ data|duration }}
{% endblock %}

View File

@@ -0,0 +1,4 @@
{% extends 'widget/widget-more.html.twig' %}
{% block widget_data %}
{{ data|money(options.currency|default(null)) }}
{% endblock %}

View File

@@ -3,31 +3,31 @@
{% 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 %}
<div class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<span class="bg-{{ options.color|default('green') }} text-white avatar">
<i class="{{ options.icon|icon(true, options.icon) }}"></i>
</span>
</div>
<div class="col">
<div class="font-weight-medium">
<a href="{{ url }}" class="link-dark stretched-link text-reset">
{{ title|trans }}
</a>
</div>
<div class="text-muted">
{% if data is iterable %}
{{ 'Invalid data' }}
{% else %}
{% block widget_data %}
{{ data }}
{% endblock %}
{% endif %}
{{ data }}
{% endblock %}
{% endif %}
<sup style="font-size: 20px">{{ unit|default('') }}</sup>
</h3>
<p>{{ title|trans }}</p>
<sup style="font-size: 20px">{{ unit|default('') }}</sup>
</div>
</div>
</div>
</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

@@ -1,83 +1,76 @@
<div class="row">
<div class="col-md-12">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_attributes %}
id="PaginatedWorkingTimeChart" data-href="#" data-reload=""
{% endblock %}
{% block box_tools %}
<ul class="pagination pagination-sm inline">
{% set prevYear = options.year %}
{% set prevWeek = options.week %}
{% set nextYear = options.year %}
{% set nextWeek = options.week %}
{% if options.week == 1 %}
{% set prevYear = prevYear - 1 %}
{% set prevWeek = data.lastWeekInLastYear %}
{% else %}
{% set prevWeek = prevWeek - 1 %}
{% endif %}
{% if options.week == data.lastWeekInYear %}
{% set nextYear = nextYear + 1 %}
{% set nextWeek = 1 %}
{% else %}
{% set nextWeek = nextWeek + 1 %}
{% endif %}
<li class="prev"><a onclick="destroyChart()" href="{{ path('widgets_working_time_chart', {'year': prevYear, 'week': prevWeek}) }}" rel="prev"><i class="{{ 'left'|icon }}"></i></a></li>
<li class="next"><a onclick="destroyChart()" href="{{ path('widgets_working_time_chart', {'year': nextYear, 'week': nextWeek}) }}" rel="next"><i class="{{ 'right'|icon }}"></i></a></li>
</ul>
{% endblock %}
{% block box_body %}
<div class="row">
<div class="col-md-12">
{{ render_widget('DailyWorkingTimeChart', options|merge({'begin': data.begin, 'end': data.end})) }}
</div>
{% embed '@theme/embeds/card.html.twig' with {'margin_bottom': 0} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_attributes %}
id="PaginatedWorkingTimeChartBox" data-href="#" data-reload=""
{% endblock %}
{% block box_tools %}
{% set prevYear = options.year %}
{% set prevWeek = options.week %}
{% set nextYear = options.year %}
{% set nextWeek = options.week %}
{% if options.week == 1 %}
{% set prevYear = prevYear - 1 %}
{% set prevWeek = data.lastWeekInLastYear %}
{% else %}
{% set prevWeek = prevWeek - 1 %}
{% endif %}
{% if options.week == data.lastWeekInYear %}
{% set nextYear = nextYear + 1 %}
{% set nextWeek = 1 %}
{% else %}
{% set nextWeek = nextWeek + 1 %}
{% endif %}
{{ widgets.card_tool_button('left', {'class': 'pagination-link', 'onclick': 'myChart.destroy()', 'url': path('widgets_working_time_chart', {'year': prevYear, 'week': prevWeek})}) }}
{{ widgets.card_tool_button('right', {'class': 'pagination-link', 'onclick': 'myChart.destroy()', 'url': path('widgets_working_time_chart', {'year': nextYear, 'week': nextWeek})}) }}
{% endblock %}
{% block box_body %}
{{ render_widget('DailyWorkingTimeChart', options|merge({'begin': data.begin, 'end': data.end})) }}
{% endblock %}
{% block box_footer %}
<div class="row">
<div class="col-sm-3 col-xs-6">
<div class="text-center">
<h5>{{ data.day|duration }}</h5>
<span>{{ 'stats.workingTimeToday'|trans({'%day%': 'now'|date_short}) }}</span>
</div>
{% endblock %}
{% block box_footer %}
<div class="row">
<div class="col-sm-3 col-xs-6">
<div class="description-block border-right">
<h5 class="description-header">{{ data.day|duration }}</h5>
<span class="description-text">{{ 'stats.workingTimeToday'|trans({'%day%': 'now'|date_short}) }}</span>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="description-block border-right">
<h5 class="description-header">{{ data.week|duration }}</h5>
<span class="description-text">{{ 'stats.workingTimeWeek'|trans({'%week%': data.begin|date_format('W')}) }}</span>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="description-block border-right">
<h5 class="description-header">{{ data.month|duration }}</h5>
<span class="description-text">{{ 'stats.workingTimeMonth'|trans({'%month%': data.thisMonth|month_name, '%year%': data.thisMonth|date_format('Y')}) }}</span>
</div>
</div>
{% if data.financial is not null %}
<div class="col-sm-3 col-xs-6">
<div class="description-block border-right">
<h5 class="description-header">{{ data.financial|duration }}</h5>
<span class="description-text">{{ 'stats.workingTimeFinancialYear'|trans }}</span>
</div>
</div>
{% else %}
<div class="col-sm-3 col-xs-6">
<div class="description-block border-right">
<h5 class="description-header">{{ data.year|duration }}</h5>
<span class="description-text">{{ 'stats.workingTimeYear'|trans({'%year%': data.thisMonth|date_format('Y')}) }}</span>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-3 col-xs-6">
<div class="text-center">
<h5>{{ data.week|duration }}</h5>
<span>{{ 'stats.workingTimeWeek'|trans({'%week%': data.begin|date_format('W')}) }}</span>
</div>
{% endblock %}
{% endembed %}
</div>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="text-center">
<h5>{{ data.month|duration }}</h5>
<span>{{ 'stats.workingTimeMonth'|trans({'%month%': data.thisMonth|month_name, '%year%': data.thisMonth|date_format('Y')}) }}</span>
</div>
</div>
{% if data.financial is not null %}
<div class="col-sm-3 col-xs-6">
<div class="text-center">
<h5>{{ data.financial|duration }}</h5>
<span>{{ 'stats.workingTimeFinancialYear'|trans }}</span>
</div>
</div>
{% else %}
<div class="col-sm-3 col-xs-6">
<div class="text-center">
<h5>{{ data.year|duration }}</h5>
<span>{{ 'stats.workingTimeYear'|trans({'%year%': data.thisMonth|date_format('Y')}) }}</span>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% endembed %}
{% if not kimai_context.javascriptRequest %}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function() {
KimaiPaginatedBoxWidget.create('#PaginatedWorkingTimeChart');
KimaiPaginatedBoxWidget.create('#PaginatedWorkingTimeChartBox');
});
</script>
{% endif %}

View File

@@ -1,42 +1,34 @@
{% set projectStats = data %}
{% set title = options.title|default('label.my_team_projects') %}
{% set widgetId = options.id %}
{% set title = options.title|default('my_team_projects') %}
{% if projectStats|length > 0 %}
<div class="row">
<div class="col-md-12">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/progressbar.html.twig" as progress %}
{% block box_attributes %}id="{{ widgetId }}"{% endblock %}
{% block box_body_class %}no-padding box-body-scrollable{% endblock %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable" role="grid">
<tbody>
{% for row in projectStats|sort((a, b) => a.entity.name <=> b.entity.name) %}
{% set project = row.entity %}
<tr{% if is_granted('details', project) %} class="alternative-link open-edit" data-href="{{ path('project_details', {'id': project.id}) }}"{% endif %}>
<td>
{{ widgets.label_project(project) }}
<br>
<small>{{ widgets.label_customer(project.customer) }}</small>
</td>
<td style="width:50%">
{% if row.hasBudget() and is_granted('budget', row.entity) %}
{{ progress.progressbar_budget(row, row.entity.customer.currency) }}
{% elseif row.hasTimeBudget() and is_granted('time', row.entity) %}
{{ progress.progressbar_timebudget(row) }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% endembed %}
</div>
</div>
{% endif %}
{% embed '@theme/embeds/card.html.twig' with {'margin_bottom': 0} %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/progressbar.html.twig" as progress %}
{% block box_body_class %}p-0 box-body-scrollable{% endblock %}
{% block box_title %}
{% if not title is empty %}{{ title|trans }}{% endif %}
{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable" role="grid">
<tbody>
{% for row in projectStats|sort((a, b) => a.entity.name <=> b.entity.name) %}
{% set project = row.entity %}
<tr{% if is_granted('details', project) %} class="alternative-link open-edit" data-href="{{ path('project_details', {'id': project.id}) }}"{% endif %}>
<td>
{{ widgets.label_project(project) }}
<br>
<small>{{ widgets.label_customer(project.customer) }}</small>
</td>
<td style="width:50%">
{% if row.hasBudget() and is_granted('budget', row.entity) %}
{{ progress.progressbar_budget(row, row.entity.customer.currency) }}
{% elseif row.hasTimeBudget() and is_granted('time', row.entity) %}
{{ progress.progressbar_timebudget(row) }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% endembed %}

View File

@@ -1,19 +1,11 @@
{% set teams = data %}
{% set title = options.title|default(title|default('label.teams')) %}
{% set widgetId = options.id %}
{% set title = options.title|default(title|default('teams')) %}
{% if teams|length > 0 %}
<div class="row">
<div class="col-md-12">
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'border': false} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ title|trans }}{% endblock %}
{% block box_attributes %}id="{{ widgetId }}"{% endblock %}
{% block box_body_class %}no-padding box-body-scrollable{% endblock %}
{% block box_body %}
{{ widgets.team_list(teams, false) }}
{% endblock %}
{% endembed %}
</div>
</div>
{% endif %}
{% embed '@theme/embeds/card.html.twig' with {'border': false, 'margin_bottom': 0} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ title|trans }}{% endblock %}
{% block box_body_class %}p-0 box-body-scrollable{% endblock %}
{% block box_body %}
{{ widgets.team_list(teams, {title: false}) }}
{% endblock %}
{% endembed %}

View File

@@ -25,7 +25,7 @@ document.addEventListener('kimai.initialized', function() {
document.getElementById('timeChart').getContext('2d'), {
type: '{{ type }}',
data: {
labels: moment.months(),
labels: {{ month_names()|json_encode|raw }},
datasets: [
{% for yearName, year in data %}
{% set yearBackgroundColor = backgroundColor %}
@@ -71,26 +71,28 @@ document.addEventListener('kimai.initialized', function() {
maintainAspectRatio: true,
responsive: true,
scales: {
xAxes: [{
x: {
gridLines: {
display: false
}
}],
yAxes: [{
},
y: {
gridLines: {
display: true,
color: '{{ gridColor }}',
lineWidth: 1
}
}]
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].realData[tooltipItem.index];
plugins: {
tooltip: {
callbacks: {
label: function(tooltipItem) {
return tooltipItem.dataset.realData[tooltipItem.dataIndex];
}
}
}
}
},
}
}
);