Release 2.47 (#5784)

This commit is contained in:
Kevin Papst
2026-01-25 09:51:22 +01:00
committed by GitHub
parent 6a86afb5fd
commit d429c56687
81 changed files with 2487 additions and 3801 deletions

View File

@@ -26,19 +26,21 @@
{% endblock %}
{% block page_content_start %}
{% if app.session and app.session.started and app.session.flashbag.peekAll|length > 0 %}
{% set flashes = app.flashes %}
{% if flashes|length > 0 %}
{% set domain = 'flashmessages' %}
<script type="text/javascript">
document.addEventListener('kimai.initialized', function(options) {
const ALERT = options.detail.kimai.getPlugin('alert');
{% for type, messages in app.session.flashbag.all %}
{% for type, messages in flashes %}
{% for message in messages %}
{% if type == 'error' %}
ALERT.error('{{ message|trans({}, domain)|e('js') }}');
{% elseif type == 'warning' %}
ALERT.warning('{{ message|trans({}, domain)|e('js') }}');
{% elseif type == 'success' %}
ALERT.success('{{ message|trans({}, domain)|e('js') }}');
// success is expected so we do not need to show messages like "saved successfully"
// ALERT.success('{{ message|trans({}, domain)|e('js') }}');
{% else %}
ALERT.info('{{ message|trans({}, domain)|e('js') }}');
{% endif %}

View File

@@ -56,7 +56,7 @@
<td class="actions">
<div class="btn-group">
<a href="{{ path(edit_route, {'id': entity.id, 'rate': rate.id}) }}" class="modal-ajax-form btn btn-icon">{{ icon('edit', true) }}</a>
<a href="{{ path(delete_route, {'id': entity.id, 'rateId': rate.id}) }}" class="btn btn-icon api-link" data-question="confirm.delete" data-event="kimai.rateUpdate kimai.rateDelete" data-method="DELETE" data-msg-error="action.delete.error" data-msg-success="action.delete.success">{{ icon('delete', true) }}</a>
<a href="{{ path(delete_route, {'id': entity.id, 'rateId': rate.id}) }}" class="btn btn-icon api-link" data-question="confirm.delete" data-event="kimai.rateUpdate kimai.rateDelete" data-method="DELETE" data-msg-error="action.delete.error">{{ icon('delete', true) }}</a>
</div>
</td>
</tr>

View File

@@ -158,7 +158,7 @@
{{ btnTitle }}
{% if allowCreate and cfg.internal %}
<span class="dropdown-action modal-ajax-form open-edit ps-2 ms-auto" data-href="{{ path('export_template_edit', {exportTemplate: id}) }}">{{ icon('edit') }}</span>
<span class="dropdown-action api-link text-red dd-ts-trash text-danger ms-2" href="{{ path('delete_export_template', {id: id}) }}" data-event="kimai.exportTemplate" data-method="DELETE" data-question="confirm.delete" data-msg-error="action.delete.error" data-msg-success="action.delete.success">{{ icon('delete') }}</span>
<span class="dropdown-action api-link text-red dd-ts-trash text-danger ms-2" href="{{ path('delete_export_template', {id: id}) }}" data-event="kimai.exportTemplate" data-method="DELETE" data-question="confirm.delete" data-msg-error="action.delete.error">{{ icon('delete') }}</span>
{% endif %}
</a>
{% endfor %}

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link rel="stylesheet" href="{{ asset('bundles/tabler/tabler.css') }}">
{{ encore_entry_link_tags('app') }}
{{ encore_entry_script_tags('app') }}
{% block styles %}{% endblock %}
{% if block('document_title') is defined %}
<title>{{ block('document_title') }}</title>
{% endif %}
</head>
<body class="pad export_print">
<div class="wrapper">
<section class="export">
{% block export %}{% endblock %}
</section>
</div>
{% block javascripts %}
<script src="{{ asset('bundles/tabler/tabler.js') }}"></script>
{% endblock %}
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
{% macro meta_field_value(entity, field) %}
{% set metaField = entity.metaField(field.name) %}
{% if not metaField is null %}
{% set metaField = metaField.merge(field) %}
{% set type = metaField.type %}
{% set value = metaField.value %}
{% if 'DurationType' in type %}
{{ value|duration }}
{% elseif 'YesNoType' in type or 'CheckboxType' in type %}
{{ value ? ('yes'|trans) : ('no'|trans) }}
{% elseif 'DatePickerType' in type %}
{{ value|date_short }}
{% elseif 'DateTimePickerType' in type %}
{{ value|date_time }}
{% elseif 'CountryType' in type %}
{{ value|country_name }}
{% elseif 'CurrencyType' in type %}
{{ value|currency_name }}
{% elseif 'LanguageType' in type %}
{{ value|locale_name }}
{% elseif 'MoneyType' in type %}
{% set classname = class_name(entity) %}
{% if classname == 'App\\Entity\\Timesheet' %}
{{ value|money(entity.project.customer.currency) }}
{% elseif classname == 'App\\Entity\\Customer' %}
{{ value|money(entity.currency) }}
{% elseif classname == 'App\\Entity\\Project' %}
{{ value|money(entity.customer.currency) }}
{% elseif classname == 'App\\Entity\\Activity' and entity.project is not null %}
{{ value|money(entity.project.customer.currency) }}
{% else %}
{{ value }}
{% endif %}
{% elseif 'TextareaType' in type %}
{{ value|nl2br }}
{% else %}
{# EmailType, UrlType, TagsType, ColorPickerType, ColorChoiceType #}
{{ value }}
{% endif %}
{% endif %}
{% endmacro %}

View File

@@ -314,9 +314,7 @@
{% macro form_type_value(type, value, entity) %}
{% if 'ColorPickerType' in type or 'ColorChoiceType' in type %}
{% if value is not empty %}
<span class="label-color" data-toggle="tooltip" data-placement="top" title="{{ value }}">
<i class="dot {{ 'dot'|icon(true) }} fa-fw" style="color:{{ value }}"></i>
</span>
{{ _self.label_color_dot('color', true, value, null, value) }}
{% endif %}
{% elseif 'DurationType' in type %}
{{ value|duration }}
@@ -340,7 +338,7 @@
{% endfor %}
{% elseif 'MoneyType' in type %}
{% set classname = class_name(entity) %}
{% if entity is null %}
{% if classname is null %}
{{ value }}
{% elseif classname == 'App\\Entity\\Timesheet' %}
{{ value|money(entity.project.customer.currency) }}

View File

@@ -20,16 +20,14 @@
{% block main %}
{% if form is defined %}
<div class="card mb-3">
<div class="card-body">
{% block report_form %}
{{ form_start(form, {'attr': {'class': 'form-reporting', 'id': 'report-form'}}) }}
<div class="{% block report_form_container_class %}btn-list w-100{% endblock %}">
{% block report_form_layout %}{{ form_rest(form) }}{% endblock %}
</div>
{{ form_end(form) }}
{% endblock %}
</div>
<div class="mb-3">
{% block report_form %}
{{ form_start(form, {'attr': {'class': 'form-reporting', 'id': 'report-form'}}) }}
<div class="{% block report_form_container_class %}btn-list w-100{% endblock %}">
{% block report_form_layout %}{{ form_rest(form) }}{% endblock %}
</div>
{{ form_end(form) }}
{% endblock %}
</div>
{% endif %}

View File

@@ -42,27 +42,34 @@
document.addEventListener('kimai.initialized', (event) => {
/** @type {KimaiAPI} API */
const API = event.detail.kimai.getPlugin('api');
/** @type {KimaiAlert} ALERT */
const ALERT = event.detail.kimai.getPlugin('alert');
const successHandle = (json) => {
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
};
const errorHandle = (error) => {
document.dispatchEvent(new CustomEvent('kimai.reloadedContent'));
API.handleError('action.update.error', error);
};
{% if customerForm is defined and customerForm is not null %}
document.getElementById('team_customer_form').addEventListener('change',
(event) => {
if (event.target.matches('input[type=checkbox]')) {
document.dispatchEvent(new CustomEvent('kimai.reloadContent'));
const checkbox = event.target;
const customerId = checkbox.value;
if (checkbox.checked) {
API.post(
'{{ path('post_team_customer', {'id': team.id, 'customerId': '000'}) }}'.replace(/000/, customerId),
{},
(json) => {ALERT.success('action.update.success');},
(error) => {ALERT.error('action.update.error', error);}, // TODO test me
successHandle,
errorHandle,
);
} else {
API.delete(
'{{ path('delete_team_customer', {'id': team.id, 'customerId': '000'}) }}'.replace(/000/, customerId),
(json) => {ALERT.success('action.delete.success');},
(error) => {ALERT.error('action.delete.error', error);}, // TODO test me
successHandle,
errorHandle,
);
}
}
@@ -74,20 +81,21 @@
document.getElementById('team_project_form').addEventListener('change',
(event) => {
if (event.target.matches('input[type=checkbox]')) {
document.dispatchEvent(new CustomEvent('kimai.reloadContent'));
const checkbox = event.target;
const projectId = checkbox.value;
if (checkbox.checked) {
API.post(
'{{ path('post_team_project', {'id': team.id, 'projectId': '000'}) }}'.replace(/000/, projectId),
{},
(json) => {ALERT.success('action.update.success');},
(error) => {ALERT.error('action.update.error', error);}, // TODO test me
successHandle,
errorHandle,
);
} else {
API.delete(
'{{ path('delete_team_project', {'id': team.id, 'projectId': '000'}) }}'.replace(/000/, projectId),
(json) => {ALERT.success('action.delete.success');},
(error) => {ALERT.error('action.delete.error', error);}, // TODO test me
successHandle,
errorHandle,
);
}
}

View File

@@ -1,12 +1,9 @@
<!DOCTYPE html>
{% import "macros/datatables.html.twig" as tables %}
{% set language = app.request is not null ? app.request.locale : 'en' %}
{% set decimal = false %}
{% set showUserColumn = true %}
{% if query.user %}
{% set showUserColumn = false %}
{% endif %}
<html lang="{{ language }}">
<html lang="{{ app.locale }}">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
@@ -79,10 +76,11 @@
{{ 'customer'|trans }}: {{ entry.project.customer.name }}
</span>
</td>
{% from "macros/meta_fields.html.twig" import meta_field_value %}
{% for field in timesheetMetaFields %}
<td>{{ tables.datatable_meta_column(entry, field) }}</td>
<td>{{ meta_field_value(entry, field) }}</td>
{% endfor %}
<td class="text-nowrap">{{ entry.duration|duration(decimal) }}</td>
<td class="text-nowrap">{{ entry.duration|duration }}</td>
</tr>
{% endfor %}
</tbody>
@@ -96,7 +94,7 @@
<th></th>
{% endfor %}
<th>{{ 'invoice.total_working_time'|trans }}</th>
<th class="text-nowrap">{{ timeWorked|duration(decimal) }}</th>
<th class="text-nowrap">{{ timeWorked|duration }}</th>
</tr>
</tfoot>
</table>

View File

@@ -69,7 +69,6 @@
'data-method': 'DELETE',
'data-question': 'confirm.delete',
'data-msg-error': 'action.delete.error',
'data-msg-success': 'action.delete.success'
}}) }}
</td>
</tr>