Release 2.0.21 (#4030)

* increase padding in wizard
* fix theme chooser in wizard
* improve dynamic export columns in PDFs
* added label for total column
* bump theme bundle
* improve avatar list spacing
* be more flexible parsing times - fixes #4031
This commit is contained in:
Kevin Papst
2023-05-17 15:02:10 +02:00
committed by GitHub
parent 29624354a2
commit 9bd5965ecd
16 changed files with 148 additions and 113 deletions

View File

@@ -147,18 +147,49 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
return null; return null;
} }
const date = this.getDateUtils().fromFormat( let date = this._parseBegin(this._beginTime.dataset['format']);
this._beginDate.value + ' ' + this._beginTime.value,
this._beginDate.dataset['format'] + ' ' + this._beginTime.dataset['format'], if (date.invalid) {
); date = this._parseBegin(this._fixTimeFormat(this._beginTime.dataset['format']));
if (date.invalid) { if (date.invalid) {
return null; return null;
} }
}
return date; return date;
} }
_parseBegin(timeFormat)
{
return this.getDateUtils().fromFormat(
this._beginDate.value + ' ' + this._beginTime.value,
this._beginDate.dataset['format'] + ' ' + timeFormat,
);
}
_parseEnd(endDate, timeFormat)
{
let date = this.getDateUtils().fromFormat(
endDate.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
'yyyy-LL-dd ' + timeFormat,
);
if (date.invalid) {
date = this.getDateUtils().fromFormat(
endDate.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
'yyyy-LL-dd ' + this._fixTimeFormat(timeFormat),
);
}
return date;
}
_fixTimeFormat(format)
{
return format.replace('HH', 'H').replace('hh', 'h');
}
/** /**
* @returns {DateTime|null} * @returns {DateTime|null}
* @private * @private
@@ -169,17 +200,11 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
return null; return null;
} }
let date = this.getDateUtils().fromFormat( let date = this._parseEnd(DateTime.now(), this._endTime.dataset['format']);
DateTime.now().toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
'yyyy-LL-dd ' + this._endTime.dataset['format'],
);
const begin = this._getBegin(); const begin = this._getBegin();
if (begin !== null) { if (begin !== null) {
date = this.getDateUtils().fromFormat( date = this._parseEnd(begin, this._endTime.dataset['format']);
begin.toFormat('yyyy-LL-dd') + ' ' + this._endTime.value,
'yyyy-LL-dd ' + this._endTime.dataset['format'],
);
if (date < begin) { if (date < begin) {
date = date.plus({days: 1}); date = date.plus({days: 1});

View File

@@ -42,5 +42,5 @@ table.dataTable.table > tbody > tr > td {
/* decreases the margin between avatars, see https://github.com/tabler/tabler/issues/1567 */ /* decreases the margin between avatars, see https://github.com/tabler/tabler/issues/1567 */
.avatar-list-stacked .avatar { .avatar-list-stacked .avatar {
margin-right: calc(var(--tblr-avatar-size)*-.1)!important; margin-right: calc(var(--tblr-avatar-size)*-.2)!important;
} }

View File

@@ -34,7 +34,7 @@
"friendsofsymfony/rest-bundle": "^3.0", "friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.6", "gedmo/doctrine-extensions": "^3.6",
"jms/serializer-bundle": "^5.0", "jms/serializer-bundle": "^5.0",
"kevinpapst/tabler-bundle": "^0.17", "kevinpapst/tabler-bundle": "^0.18",
"league/csv": "^9.4", "league/csv": "^9.4",
"mpdf/mpdf": "^8.0", "mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^4.0", "nelmio/api-doc-bundle": "^4.0",

14
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "63b69c5c8020a78b308210425547d46c", "content-hash": "aedc077bff6e5890b17415527e991e17",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@@ -2416,16 +2416,16 @@
}, },
{ {
"name": "kevinpapst/tabler-bundle", "name": "kevinpapst/tabler-bundle",
"version": "0.17", "version": "0.18",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/kevinpapst/TablerBundle.git", "url": "https://github.com/kevinpapst/TablerBundle.git",
"reference": "0061d82a28059cf876cf819f1780607aac131d32" "reference": "f438b55cd393331385700a30515d3966747ecd7a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/0061d82a28059cf876cf819f1780607aac131d32", "url": "https://api.github.com/repos/kevinpapst/TablerBundle/zipball/f438b55cd393331385700a30515d3966747ecd7a",
"reference": "0061d82a28059cf876cf819f1780607aac131d32", "reference": "f438b55cd393331385700a30515d3966747ecd7a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2474,7 +2474,7 @@
"description": "Admin/Backend theme bundle for Symfony based on Tabler.io", "description": "Admin/Backend theme bundle for Symfony based on Tabler.io",
"support": { "support": {
"issues": "https://github.com/kevinpapst/TablerBundle/issues", "issues": "https://github.com/kevinpapst/TablerBundle/issues",
"source": "https://github.com/kevinpapst/TablerBundle/tree/0.17" "source": "https://github.com/kevinpapst/TablerBundle/tree/0.18"
}, },
"funding": [ "funding": [
{ {
@@ -2486,7 +2486,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-05-15T18:25:59+00:00" "time": "2023-05-16T16:30:10+00:00"
}, },
{ {
"name": "laminas/laminas-escaper", "name": "laminas/laminas-escaper",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,10 +3,10 @@
"app": { "app": {
"js": [ "js": [
"/build/runtime.f0079159.js", "/build/runtime.f0079159.js",
"/build/app.45d541e0.js" "/build/app.70a820f6.js"
], ],
"css": [ "css": [
"/build/app.66162a27.css" "/build/app.f43008a1.css"
] ]
}, },
"export-pdf": { "export-pdf": {
@@ -63,8 +63,8 @@
}, },
"integrity": { "integrity": {
"/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV", "/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV",
"/build/app.45d541e0.js": "sha384-KhCwP7jLZJ8+g02Lr+oITVZ8woE+EU01t/VQNuDyHmpVPCVggaaoI9EU9lJeB62l", "/build/app.70a820f6.js": "sha384-WKz8wOwY8EYT1ncPkMRnEE3q5LnP76keOZxTD410dXpsowUktUg+vWUqDhVXwLat",
"/build/app.66162a27.css": "sha384-TeR8dC4yDJMVshAZ+sa3WotB4RqTHOiTtHROHg7jXEE29WCabeBlXB6MKiPdnsu3", "/build/app.f43008a1.css": "sha384-pZxN+QyK3/ggIPic7tC9sHJImYoGGtVX2CRA2JqH/BK5RYd2UGOsAhb5VXzjLX0l",
"/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u", "/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u",
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg", "/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
"/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ", "/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ",

View File

@@ -1,6 +1,6 @@
{ {
"build/app.css": "/build/app.66162a27.css", "build/app.css": "/build/app.f43008a1.css",
"build/app.js": "/build/app.45d541e0.js", "build/app.js": "/build/app.70a820f6.js",
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css", "build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
"build/export-pdf.js": "/build/export-pdf.d367a32e.js", "build/export-pdf.js": "/build/export-pdf.d367a32e.js",
"build/invoice.css": "/build/invoice.5bea118e.css", "build/invoice.css": "/build/invoice.5bea118e.css",

View File

@@ -17,11 +17,11 @@ class Constants
/** /**
* The current release version * The current release version
*/ */
public const VERSION = '2.0.20'; public const VERSION = '2.0.21';
/** /**
* The current release: major * 10000 + minor * 100 + patch * The current release: major * 10000 + minor * 100 + patch
*/ */
public const VERSION_ID = 20020; public const VERSION_ID = 20021;
/** /**
* The software name * The software name
*/ */

View File

@@ -80,7 +80,7 @@
</tbody> </tbody>
<tfoot> <tfoot>
<tr class="summary"> <tr class="summary">
<td></td> <td>{{ 'sum.total'|trans }}</td>
<td class="text-end total"> <td class="text-end total">
{{ work_times_result(summaries.expectedTime, summaries.actualTime, decimal) }} {{ work_times_result(summaries.expectedTime, summaries.actualTime, decimal) }}
</td> </td>

View File

@@ -6,6 +6,7 @@
{% set showCustomerSummary = showCustomerSummary ?? true %} {% set showCustomerSummary = showCustomerSummary ?? true %}
{% set showTotalSummary = showTotalSummary ?? true %} {% set showTotalSummary = showTotalSummary ?? true %}
{% set showDateTimeShort = showDateTimeShort ?? false %} {% set showDateTimeShort = showDateTimeShort ?? false %}
{% set showSummaryHourlyRate = showSummaryHourlyRate ?? false %}
{% set now = create_date('now', app.user) %} {% set now = create_date('now', app.user) %}
{% set decimal = decimal ?? false %} {% set decimal = decimal ?? false %}
{# this is only triggered, if a user exports from his personal timesheet screen #} {# this is only triggered, if a user exports from his personal timesheet screen #}
@@ -14,8 +15,26 @@
{# if exporting via the admin screen, users without view_rate_own_timesheet might still see their own rates #} {# if exporting via the admin screen, users without view_rate_own_timesheet might still see their own rates #}
{% set showRateColumn = is_granted('view_rate_own_timesheet') %} {% set showRateColumn = is_granted('view_rate_own_timesheet') %}
{% endif %} {% endif %}
{% set summaryColumns = ['customer', 'project'] %}
{% if showTimeBudget %}
{% set summaryColumns = summaryColumns|merge(['timeBudget']) %}
{% endif %}
{% if showRateBudget %}
{% set summaryColumns = summaryColumns|merge(['budget']) %}
{% endif %}
{% if showSummaryHourlyRate %}
{% set summaryColumns = summaryColumns|merge(['hourlyRate']) %}
{% endif %}
{% set summaryColumns = summaryColumns|merge(['duration']) %}
{% if showRateColumn %}
{% if showInternalRate %}
{% set summaryColumns = summaryColumns|merge(['internalRate']) %}
{% endif %}
{% set summaryColumns = summaryColumns|merge(['rate']) %}
{% endif %}
<html{% if app.request is defined and app.request is not null %} lang="{{ app.request.locale }}"{% endif %}> <html{% if app.request is defined and app.request is not null %} lang="{{ app.request.locale }}"{% endif %}>
<head> <head>
<title>{% block document_title %}{{ 'export'|trans }}{% endblock %}</title>
{% block styles %} {% block styles %}
<style> <style>
{{ encore_entry_css_source('export-pdf')|raw }} {{ encore_entry_css_source('export-pdf')|raw }}
@@ -64,21 +83,15 @@ mpdf-->
<table class="items"> <table class="items">
<thead> <thead>
<tr> <tr>
{% for summaryColumn in summaryColumns %}
{% if summaryColumn == 'customer' %}
<th>{{ 'customer'|trans }}</th> <th>{{ 'customer'|trans }}</th>
{% elseif summaryColumn == 'project' %}
<th>{{ 'project'|trans }}</th> <th>{{ 'project'|trans }}</th>
{% if showTimeBudget %} {% else %}
<th class="center">{{ 'timeBudget'|trans }}</th> <th class="center">{{ summaryColumn|trans }}</th>
{% endif %}
{% if showRateBudget %}
<th class="center">{{ 'budget'|trans }}</th>
{% endif %}
<th class="center">{{ 'duration'|trans }}</th>
{% if showRateColumn %}
{% if showInternalRate %}
<th class="center">{{ 'internalRate'|trans }}</th>
{% endif %}
<th class="center">{{ 'rate'|trans }}</th>
{% endif %} {% endif %}
{% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -106,21 +119,17 @@ mpdf-->
{% endif %} {% endif %}
{% if customer is not same as(summary.customer) %} {% if customer is not same as(summary.customer) %}
<tr class="summary"> <tr class="summary">
<td colspan="2"> {% for summaryColumn in summaryColumns %}
</td> {% if summaryColumn == 'duration' %}
{% if showTimeBudget %}
<td></td>
{% endif %}
{% if showRateBudget %}
<td></td>
{% endif %}
<td class="totals duration">{{ customerDuration|duration(decimal) }}</td> <td class="totals duration">{{ customerDuration|duration(decimal) }}</td>
{% if showRateColumn %} {% elseif summaryColumn == 'internalRate' %}
{% if showInternalRate %}
<td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td> <td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td>
{% endif %} {% elseif summaryColumn == 'rate' %}
<td class="totals cost">{{ customerRate|money(customerCurrency) }}</td> <td class="totals cost">{{ customerRate|money(customerCurrency) }}</td>
{% else %}
<td></td>
{% endif %} {% endif %}
{% endfor %}
</tr> </tr>
{% set customerCurrency = summary.currency %} {% set customerCurrency = summary.currency %}
{% set customer = summary.customer %} {% set customer = summary.customer %}
@@ -130,29 +139,34 @@ mpdf-->
{% set customerCount = 0 %} {% set customerCount = 0 %}
{% endif %} {% endif %}
<tr class="{{ cycle(['odd', 'even'], customerCount) }}"> <tr class="{{ cycle(['odd', 'even'], customerCount) }}">
{% for summaryColumn in summaryColumns %}
{% if summaryColumn == 'customer' %}
<td>{{ summary.customer }}</td> <td>{{ summary.customer }}</td>
{% elseif summaryColumn == 'project' %}
<td>{{ summary.project }}</td> <td>{{ summary.project }}</td>
{% if showTimeBudget %} {% elseif summaryColumn == 'timeBudget' %}
<td class="center"> <td class="center">
{% if budgets[id] is defined and budgets[id].time_left > 0 %} {% if budgets[id] is defined and budgets[id].time_left > 0 %}
{{ budgets[id].time_left|duration(decimal) }} {{ budgets[id].time_left|duration(decimal) }}
{% endif %} {% endif %}
</td> </td>
{% endif %} {% elseif summaryColumn == 'budget' %}
{% if showRateBudget %}
<td class="center"> <td class="center">
{% if budgets[id] is defined and budgets[id].money_left > 0 %} {% if budgets[id] is defined and budgets[id].money_left > 0 %}
{{ budgets[id].money_left|money(summary.currency) }} {{ budgets[id].money_left|money(summary.currency) }}
{% endif %} {% endif %}
</td> </td>
{% endif %} {% elseif summaryColumn == 'hourlyRate' %}
{% set summaryHourlyRate = summary.duration/3600 %}
<td class="cost">{{ summaryHourlyRate > 0 ? (summary.rate/summaryHourlyRate)|money(summary.currency) : '' }}</td>
{% elseif summaryColumn == 'duration' %}
<td class="duration">{{ summary.duration|duration(decimal) }}</td> <td class="duration">{{ summary.duration|duration(decimal) }}</td>
{% if showRateColumn %} {% elseif summaryColumn == 'internalRate' %}
{% if showInternalRate %}
<td class="cost">{{ summary.rate_internal|money(summary.currency) }}</td> <td class="cost">{{ summary.rate_internal|money(summary.currency) }}</td>
{% endif %} {% elseif summaryColumn == 'rate' %}
<td class="cost">{{ summary.rate|money(summary.currency) }}</td> <td class="cost">{{ summary.rate|money(summary.currency) }}</td>
{% endif %} {% endif %}
{% endfor %}
</tr> </tr>
{% set customerDuration = customerDuration + summary.duration %} {% set customerDuration = customerDuration + summary.duration %}
{% set customerRate = customerRate + summary.rate %} {% set customerRate = customerRate + summary.rate %}
@@ -161,20 +175,17 @@ mpdf-->
{% endfor %} {% endfor %}
{% if showCustomerSummary and customer is not same as(null) %} {% if showCustomerSummary and customer is not same as(null) %}
<tr class="summary"> <tr class="summary">
<td colspan="2"></td> {% for summaryColumn in summaryColumns %}
{% if showTimeBudget %} {% if summaryColumn == 'duration' %}
<td></td>
{% endif %}
{% if showRateBudget %}
<td></td>
{% endif %}
<td class="totals duration">{{ customerDuration|duration(decimal) }}</td> <td class="totals duration">{{ customerDuration|duration(decimal) }}</td>
{% if showRateColumn %} {% elseif summaryColumn == 'internalRate' %}
{% if showInternalRate %}
<td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td> <td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td>
{% endif %} {% elseif summaryColumn == 'rate' %}
<td class="totals cost">{{ customerRate|money(customerCurrency) }}</td> <td class="totals cost">{{ customerRate|money(customerCurrency) }}</td>
{% else %}
<td></td>
{% endif %} {% endif %}
{% endfor %}
</tr> </tr>
{% endif %} {% endif %}
{% if showTotalSummary and not multiCurrency %} {% if showTotalSummary and not multiCurrency %}
@@ -182,19 +193,17 @@ mpdf-->
<td class="totals" colspan="2"> <td class="totals" colspan="2">
{{ 'sum.total'|trans }} {{ 'sum.total'|trans }}
</td> </td>
{% if showTimeBudget %} {% for summaryColumn in summaryColumns %}
<td></td> {% if summaryColumn == 'duration' %}
{% endif %}
{% if showRateBudget %}
<td></td>
{% endif %}
<td class="totals duration">{{ totalDuration|duration(decimal) }}</td> <td class="totals duration">{{ totalDuration|duration(decimal) }}</td>
{% if showRateColumn %} {% elseif summaryColumn == 'internalRate' %}
{% if showInternalRate %}
<td class="totals cost">{{ totalInternalRate|money(customerCurrency) }}</td> <td class="totals cost">{{ totalInternalRate|money(customerCurrency) }}</td>
{% endif %} {% elseif summaryColumn == 'rate' %}
<td class="totals cost">{{ totalRate|money(customerCurrency) }}</td> <td class="totals cost">{{ totalRate|money(customerCurrency) }}</td>
{% elseif summaryColumn not in ['customer', 'project'] %}
<td></td>
{% endif %} {% endif %}
{% endfor %}
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>

View File

@@ -2,7 +2,7 @@
{% from '@Tabler/components/button.html.twig' import button %} {% from '@Tabler/components/button.html.twig' import button %}
{% block wizard_content %} {% block wizard_content %}
<div class="card-body text-center py-4 p-sm-5"> <div class="card-body text-center py-4 p-sm-7">
<img src="{{ asset('wizard/done.png') }}" height="120" class="mb-n2" alt="Illustration by Katerina Limpitsouni from https://undraw.co/"> <img src="{{ asset('wizard/done.png') }}" height="120" class="mb-n2" alt="Illustration by Katerina Limpitsouni from https://undraw.co/">
<h1 class="mt-5">{{ 'wizard.done.title'|trans({}, 'wizard') }}</h1> <h1 class="mt-5">{{ 'wizard.done.title'|trans({}, 'wizard') }}</h1>
<p class="text-body-secondary">{{ 'wizard.done.description'|trans({}, 'wizard') }}</p> <p class="text-body-secondary">{{ 'wizard.done.description'|trans({}, 'wizard') }}</p>

View File

@@ -1,7 +1,7 @@
{% extends 'wizard/layout.html.twig' %} {% extends 'wizard/layout.html.twig' %}
{% block wizard_content %} {% block wizard_content %}
<div class="card-body text-center py-4 p-sm-5"> <div class="card-body text-center py-4 p-sm-7">
<img src="{{ asset('wizard/time-management.png') }}" height="120" class="mb-n2" alt="Illustration by Katerina Limpitsouni from https://undraw.co/"> <img src="{{ asset('wizard/time-management.png') }}" height="120" class="mb-n2" alt="Illustration by Katerina Limpitsouni from https://undraw.co/">
<h1 class="mt-5">{{ 'wizard.intro.title'|trans({}, 'wizard') }}</h1> <h1 class="mt-5">{{ 'wizard.intro.title'|trans({}, 'wizard') }}</h1>
<p class="text-body-secondary">{{ 'wizard.intro.description'|trans({}, 'wizard') }}</p> <p class="text-body-secondary">{{ 'wizard.intro.description'|trans({}, 'wizard') }}</p>

View File

@@ -1,7 +1,7 @@
{% extends 'wizard/layout.html.twig' %} {% extends 'wizard/layout.html.twig' %}
{% block wizard_content %} {% block wizard_content %}
<div class="card-body text-center py-4 p-sm-5"> <div class="card-body text-center py-4 p-sm-6">
<h1 class="mt-2">{{ 'wizard.profile.title'|trans({}, 'wizard') }}</h1> <h1 class="mt-2">{{ 'wizard.profile.title'|trans({}, 'wizard') }}</h1>
<p class="text-body-secondary">{{ 'wizard.profile.description'|trans({}, 'wizard') }}</p> <p class="text-body-secondary">{{ 'wizard.profile.description'|trans({}, 'wizard') }}</p>
</div> </div>
@@ -19,10 +19,11 @@
const skinChooser = document.getElementById('form_skin'); const skinChooser = document.getElementById('form_skin');
if (skinChooser !== null) { if (skinChooser !== null) {
skinChooser.addEventListener('change', () => { skinChooser.addEventListener('change', () => {
document.body.classList.remove('theme-light'); document.querySelectorAll('[data-bs-theme]').forEach(
document.body.classList.remove('theme-default'); element => {
document.body.classList.remove('theme-dark'); element.dataset['bsTheme'] = (skinChooser.value === 'default' ? 'light' : 'dark');
document.body.classList.add('theme-' + skinChooser.value); }
);
}); });
} }