Release 2.5.0 (#4454)

- added command to list users
- corrected wrong german translation
- work contract validations
- prevent error with missing params
- use collapsible, minor UI improvement
- added classes to target menu buttons in custom rules
- disable webpack notifier, incompatible with mac arm
- use explicit menu service to generate menu
- bump to fontawesome 6 and replace restart icon
- change repeat icon for recent activities
- moved user bookmarks (favorites) to top nav
- fix totp seconds window (leeway)
- new migration to fix remaining user preferences with dots in name
- remove duplicate named column in user screen
- unify and added translations
- added missing filter and tags to InvoiceSecurity
This commit is contained in:
Kevin Papst
2023-12-01 11:38:17 +01:00
committed by GitHub
parent 16d4a691f8
commit 02bcd45116
72 changed files with 922 additions and 562 deletions

View File

@@ -170,6 +170,7 @@ $fixer
->setFinder(
PhpCsFixer\Finder::create()
->in([
__DIR__ . '/migrations/',
__DIR__ . '/src/',
__DIR__ . '/tests/',
])

View File

@@ -43,3 +43,21 @@ h1.navbar-brand a {
.navbar {
--tblr-navbar-brand-font-size: 1rem;
}
/* user shortcuts dropdown */
.user-shortcuts {
.dropdown-menu-card {
min-width: 260px;
}
.user-shortcuts-icon {
background-color: var(--tblr-bg-surface-secondary);
color: var(--tblr-gray-500);
height: 3.125rem;
width: 3.125rem;
margin-left: auto;
margin-right: auto;
display: flex;
align-items: center;
justify-content: center;
}
}

View File

@@ -26,7 +26,7 @@ table.dataTable {
thead .sorting_asc:after,
thead .sorting_desc:after {
padding-left: 5px;
font-family: 'Font Awesome 5 Free';
font-family: 'Font Awesome 6 Free';
opacity: 0.5;
font-size: 12px;
}

View File

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

490
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,9 +8,10 @@ scheb_two_factor:
totp:
enabled: true
template: security/2fa.html.twig # Overwritten template
window: 1 # How many codes before/after the current one would be accepted as valid
leeway: 29 # How many seconds the code is valid
issuer: Kimai # Issuer name used in QR code
two_factor_condition: App\Security\TwoFactorCondition
# FIXME add backup codes - https://symfony.com/bundles/SchebTwoFactorBundle/current/backup_codes.html
# TODO add: backup codes - https://symfony.com/bundles/SchebTwoFactorBundle/current/backup_codes.html
# TODO add: Trusted device feature - https://github.com/scheb/2fa/blob/6.x/doc/configuration.rst

View File

@@ -110,7 +110,7 @@ tabler:
profile: fas fa-user-edit
profile-stats: far fa-chart-bar
project: fas fa-briefcase
repeat: fas fa-redo-alt
repeat: fas fa-repeat
reporting: far fa-chart-bar
report: far fa-chart-bar
review: fas fa-user-check

View File

@@ -85,7 +85,7 @@ final class Version20230126002049 extends AbstractMigration
$this->addSql("UPDATE kimai2_configuration SET `value` = '15' WHERE `name` = 'timesheet.time_increment' and `value` = '0'");
$this->addSql("UPDATE kimai2_configuration SET `value` = '5' WHERE `name` = 'timesheet.time_increment' and `value` IN ('1', '2', '3', '4')");
$this->addSql("UPDATE kimai2_configuration SET `value` = '0' WHERE `name` = 'user.registration'");
$this->addSql("UPDATE kimai2_roles SET `name` = UPPER(`name`)");
$this->addSql('UPDATE kimai2_roles SET `name` = UPPER(`name`)');
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'service-date' WHERE `renderer` = 'freelancer'");
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'invoice' WHERE `renderer` = 'default'");
$this->addSql("UPDATE kimai2_invoice_templates SET `renderer` = 'default' WHERE `renderer` = 'default-pdf'");

View File

@@ -2,6 +2,13 @@
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;

View File

@@ -2,6 +2,13 @@
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use App\Doctrine\AbstractMigration;

View File

@@ -2,6 +2,13 @@
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* @version 2.5.0
*/
final class Version20231130000719 extends AbstractMigration
{
public function getDescription(): string
{
return 'Fix user preferences with dots in the name';
}
public function up(Schema $schema): void
{
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from kimai2_user_preferences u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id);
// select u1.* from kimai2_user_preferences u1 where u1.name like '%.%';
$this->addSql("delete u1 from kimai2_user_preferences u1 where u1.name like '%.%' and exists(select 1 from (SELECT name, user_id FROM kimai2_user_preferences WHERE name LIKE '%_%') u2 where u2.name = replace(u1.name, '.', '_') and u1.user_id = u2.user_id)");
$this->addSql("update kimai2_user_preferences set `name` = replace(`name`, '.', '_')");
}
public function down(Schema $schema): void
{
}
public function isTransactional(): bool
{
return false;
}
}

View File

@@ -27,7 +27,7 @@
"@babel/core": "^7.19.6",
"@babel/eslint-parser": "^7.18.2",
"@babel/preset-env": "^7.19.4",
"@fortawesome/fontawesome-free": "^6.2.0",
"@fortawesome/fontawesome-free": "^6.5.0",
"@fullcalendar/bootstrap5": "^5.11",
"@fullcalendar/core": "^5.11",
"@fullcalendar/daygrid": "^5.11",
@@ -48,7 +48,6 @@
"sass-loader": "^13.2",
"tom-select": "^2.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-notifier": "^1.13"
"webpack-cli": "^4.10.0"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
"/build/app.63b27bee.js"
],
"css": [
"/build/app.fc41168c.css"
"/build/app.0ecb28c1.css"
]
},
"export-pdf": {
@@ -64,7 +64,7 @@
"integrity": {
"/build/runtime.f0079159.js": "sha384-H22sAW1aTvyIPqvHOvGXWSWTxf0y6mptp+MsVmyXCfjx/WJjBbhX9gbUZ+qIuihV",
"/build/app.63b27bee.js": "sha384-1CV+he7m+KfetJpq/DmRwKfUhKH4L7MImv7rRUz/mr/KNR3VkT0fQJHF7ViPTVYE",
"/build/app.fc41168c.css": "sha384-3Td9W7KogJoBiywzWOgpkPPNHEIT8/a/SkMZM5NgIRmuChj/3PeOKkOHVJQlhP2E",
"/build/app.0ecb28c1.css": "sha384-iCao48T4VAR0rv39D1+kCk8GCRtve2QDTH5gGq1I1TMXmy1xA/OPxjeBFgGKkRk+",
"/build/export-pdf.d367a32e.js": "sha384-Z5baqnzjI636nYFs4g63ViIKBZKRW4Jhv/7PQmTEQlqhfA7eK0vUMUtiyy0R5A9u",
"/build/export-pdf.d8a6c23b.css": "sha384-ztepocHE4rnGE9eKZ4kL6jTKaePUyiwiB9TjJjstjpf/ckcKg1HedrEOOk/8ElJg",
"/build/invoice.2604495e.js": "sha384-D6JvhGSqlx7z72b/qD3nF3QDXPy+XsCSRGtWfs1icjDKOcd2UzuXwuSa/E1Fg2TJ",

View File

@@ -1,5 +1,5 @@
{
"build/app.css": "/build/app.fc41168c.css",
"build/app.css": "/build/app.0ecb28c1.css",
"build/app.js": "/build/app.63b27bee.js",
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
"build/export-pdf.js": "/build/export-pdf.d367a32e.js",

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Command;
use App\Repository\UserRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'kimai:user:list', description: 'List all users')]
final class ListUserCommand extends Command
{
public function __construct(private UserRepository $repository)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output = new SymfonyStyle($input, $output);
$users = $this->repository->findAll();
$data = [];
foreach ($users as $user) {
$data[] = [
$user->getUserIdentifier(),
$user->getEmail(),
implode(', ', $user->getRoles()),
$user->isEnabled() ? 'X' : '',
$user->getPasswordRequestedAt()?->format('Y-m-d H:i:s')
];
}
$header = ['Username', 'Email', 'Roles', 'Active', 'PW Reset'];
$output->table($header, $data);
return Command::SUCCESS;
}
}

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.4.1';
public const VERSION = '2.5.0';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20401;
public const VERSION_ID = 20500;
/**
* The software name
*/

View File

@@ -70,8 +70,8 @@ final class UserController extends AbstractController
$table->setReloadEvents('kimai.userUpdate');
$table->addColumn('avatar', ['class' => 'alwaysVisible w-avatar', 'title' => null, 'orderBy' => false]);
$table->addColumn('user', ['class' => 'alwaysVisible', 'orderBy' => 'user']);
$table->addColumn('username', ['class' => 'd-none']);
//$table->addColumn('user', ['class' => 'alwaysVisible', 'orderBy' => 'user']);
$table->addColumn('username', ['class' => 'alwaysVisible']);
$table->addColumn('alias', ['class' => 'd-none']);
$table->addColumn('account_number', ['class' => 'd-none']);
$table->addColumn('title', ['class' => 'd-none']);

View File

@@ -9,13 +9,10 @@
namespace App\EventSubscriber;
use App\Entity\User;
use App\Event\ConfigureMainMenuEvent;
use App\Utils\MenuItemModel;
use App\Utils\MenuService;
use KevinPapst\TablerBundle\Event\MenuEvent;
use KevinPapst\TablerBundle\Model\MenuItemInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
@@ -24,7 +21,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
final class MenuBuilderSubscriber implements EventSubscriberInterface
{
public function __construct(private EventDispatcherInterface $eventDispatcher, private Security $security)
public function __construct(private MenuService $menuService)
{
}
@@ -37,15 +34,7 @@ final class MenuBuilderSubscriber implements EventSubscriberInterface
public function onSetupNavbar(MenuEvent $event): void
{
$menuEvent = new ConfigureMainMenuEvent();
/** @var User $user */
$user = $this->security->getUser();
// error pages don't have a user and will fail when is_granted() is called
if (null !== $user) {
$this->eventDispatcher->dispatch($menuEvent);
}
$menuEvent = $this->menuService->getKimaiMenu();
foreach ($menuEvent->getMenu()->getChildren() as $child) {
if ($child->getRoute() === null && !$child->hasChildren()) {

View File

@@ -1,61 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\EventSubscriber;
use App\Entity\User;
use App\Event\ConfigureMainMenuEvent;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class MenuFavoritesSubscriber implements EventSubscriberInterface
{
public function __construct(private Security $security)
{
}
public static function getSubscribedEvents(): array
{
return [
ConfigureMainMenuEvent::class => ['onMainMenuConfigure', 90], // see MenuSubscriber
];
}
public function onMainMenuConfigure(ConfigureMainMenuEvent $menuEvent): void
{
/** @var User|null $user */
$user = $this->security->getUser();
if (null === $user) {
return;
}
$userFavorites = $user->getPreferenceValue('favorite_routes');
if (!\is_string($userFavorites) || trim($userFavorites) === '') {
return;
}
$favMenu = $menuEvent->findById('favorites');
if ($favMenu === null) {
return;
}
$userFavorites = explode(',', $userFavorites);
foreach ($userFavorites as $fav) {
$tmp = $menuEvent->findById($fav);
if ($tmp !== null && !$tmp->hasChildren()) {
$favMenu->addChild(clone $tmp);
}
}
if ($favMenu->hasChildren()) {
$favMenu->setExpanded(true);
$menuEvent->getTimesheetMenu()?->setExpanded(false);
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Twig\Runtime;
use App\Entity\User;
use App\Utils\MenuItemModel;
use App\Utils\MenuService;
use Twig\Extension\RuntimeExtensionInterface;
final class MenuExtension implements RuntimeExtensionInterface
{
public function __construct(private MenuService $menuService)
{
}
/**
* @return array<MenuItemModel>
*/
public function getUserShortcuts(User $user): array
{
$shortcuts = $user->getPreferenceValue('favorite_routes');
if (!\is_string($shortcuts) || trim($shortcuts) === '') {
return [];
}
$favMenu = [];
$shortcuts = explode(',', $shortcuts);
$menu = $this->menuService->getKimaiMenu();
foreach ($shortcuts as $fav) {
$tmp = $menu->findById($fav);
if ($tmp !== null && !$tmp->hasChildren()) {
$favMenu[] = clone $tmp;
}
}
return $favMenu;
}
}

View File

@@ -11,6 +11,7 @@ namespace App\Twig;
use App\Twig\Runtime\EncoreExtension;
use App\Twig\Runtime\MarkdownExtension;
use App\Twig\Runtime\MenuExtension;
use App\Twig\Runtime\QrCodeExtension;
use App\Twig\Runtime\ThemeExtension;
use App\Twig\Runtime\TimesheetExtension;
@@ -37,6 +38,7 @@ final class RuntimeExtensions extends AbstractExtension
new TwigFunction('render_widget', [WidgetExtension::class, 'renderWidget'], ['is_safe' => ['html'], 'needs_environment' => true]),
new TwigFunction('icon', [RuntimeExtension::class, 'createIcon'], ['is_safe' => ['html']]),
new TwigFunction('qr_code_data_uri', [QrCodeExtension::class, 'qrCodeDataUriFunction']),
new TwigFunction('user_shortcuts', [MenuExtension::class, 'getUserShortcuts']),
];
}

View File

@@ -29,7 +29,7 @@ final class InvoicePolicy implements SecurityPolicyInterface
$this->policy = new ChainPolicy();
$this->policy->addPolicy(new DefaultPolicy());
$this->policy->addPolicy(new SecurityPolicy(
['block', 'if', 'for', 'set', 'extends'],
['block', 'if', 'for', 'set', 'extends', 'import'],
[
// =================================================================
// vendor/twig/twig/src/Extension/CoreExtension.php
@@ -102,9 +102,16 @@ final class InvoicePolicy implements SecurityPolicyInterface
'currency_symbol',
'language_name',
'locale_name',
'timezone_name',
'format_currency',
'format_number',
'format_*_number',
'format_decimal_number',
'format_currency_number',
'format_percent_number',
'format_scientific_number',
'format_spellout_number',
'format_ordinal_number',
'format_duration_number',
'format_datetime',
'format_date',
'format_time',

40
src/Utils/MenuService.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils;
use App\Entity\User;
use App\Event\ConfigureMainMenuEvent;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Bundle\SecurityBundle\Security;
final class MenuService
{
private ?ConfigureMainMenuEvent $menuEvent = null;
public function __construct(private EventDispatcherInterface $eventDispatcher, private Security $security)
{
}
public function getKimaiMenu(): ConfigureMainMenuEvent
{
if (null === $this->menuEvent) {
$this->menuEvent = new ConfigureMainMenuEvent();
/** @var User $user */
$user = $this->security->getUser();
// error pages don't have a user and will fail when is_granted() is called
if (null !== $user) {
$this->eventDispatcher->dispatch($this->menuEvent);
}
}
return $this->menuEvent;
}
}

View File

@@ -100,6 +100,7 @@
{% if is_granted('view_own_timesheet') %}
{% include 'partials/recent-activities.html.twig' %}
{% endif %}
{% include 'partials/user-shortcuts.html.twig' %}
{% block navbar_extensions_after %}
{% set event = trigger(constant('App\\Event\\ThemeEvent::TOOLBAR')) %}
{{ event.content|raw }}

View File

@@ -87,56 +87,8 @@
{% endembed %}
{% endif %}
{% if withWorkHourConfiguration %}
{% set expectedWeekTimes = user.workHoursMonday + user.workHoursTuesday + user.workHoursWednesday + user.workHoursThursday + user.workHoursFriday + user.workHoursSaturday + user.workHoursSunday %}
{% embed '@theme/embeds/card.html.twig' with {'collapsed': box_configuration.collapsed} %}
{% from "macros/status.html.twig" import status_duration %}
{% block box_title %}
{{ 'work_times_should'|trans }}
{{ status_duration(expectedWeekTimes|duration) }}
{% endblock %}
{% block box_body %}
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title">{{ 'agendaWeek'|trans }}</div>
<div class="datagrid-content">{{ expectedWeekTimes|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Monday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursMonday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Tuesday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursTuesday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Wednesday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursWednesday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Thursday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursThursday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Friday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursFriday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Saturday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursSaturday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Sunday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursSunday|duration }}</div>
</div>
</div>
{% endblock %}
{% endembed %}
{% endif %}
{% if year is defined %}
{% embed '@theme/embeds/card.html.twig' with {'collapsed': box_configuration.collapsed, 'id': 'working_time_detail_box', 'fullsize': true} %}
{% embed '@theme/embeds/collapsible.html.twig' with {open: not box_configuration.collapsed, id: 'working_time_detail_box', border: false, item: {options: {bodyExtraClass: 'border-top'}, bodyClasses: 'p-0 table-responsive'}} %}
{% set yearShould = 0 %}
{% set yearIs = 0 %}
{% set approved = 0 %}
@@ -154,17 +106,14 @@
{% endfor %}
{% from "macros/widgets.html.twig" import work_times_result, table_actions, card_tool_button, username %}
{% from "macros/status.html.twig" import status_count %}
{% block box_title %}
{% block title %}
{{ 'work_times'|trans }}
{% if withWorkHourConfiguration %}
&nbsp;
{{ status_count(approved ~ '/' ~ year.months|length) }}
{% endif %}
{% endblock %}
{% block box_body_classes %}card-table table-responsive{% endblock %}
{% block box_tools %}
{# {{ card_tool_button('visibility', {'onclick': "toggleDays(); return false;", 'id': 'toggleDaysIcon'}) }} #}
{% endblock %}
{% block box_body %}
{% block body %}
<table id="working_times_details" class="table table-vcenter table-hover dataTable">
<thead>
<tr>
@@ -276,6 +225,51 @@
{% endembed %}
{% endif %}
{% if withWorkHourConfiguration %}
{% set expectedWeekTimes = user.workHoursMonday + user.workHoursTuesday + user.workHoursWednesday + user.workHoursThursday + user.workHoursFriday + user.workHoursSaturday + user.workHoursSunday %}
{% embed '@theme/embeds/collapsible.html.twig' with {open: not box_configuration.collapsed, id: 'work_contract_should_preview', border: false, item: {options: {bodyExtraClass: 'border-top'}}} %}
{% from "macros/status.html.twig" import status_duration %}
{% block title %}
{{ 'work_times_should'|trans }}
&nbsp;
{{ status_duration(expectedWeekTimes|duration) }}
{% endblock %}
{% block body %}
<div class="datagrid">
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Monday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursMonday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Tuesday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursTuesday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Wednesday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursWednesday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Thursday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursThursday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Friday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursFriday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Saturday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursSaturday|duration }}</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">{{ 'Sunday'|trans({}, 'system-configuration') }}</div>
<div class="datagrid-content">{{ user.workHoursSunday|duration }}</div>
</div>
</div>
{% endblock %}
{% endembed %}
{% endif %}
{% for controller in boxes %}
{{ render(controller(controller, {'year': year, 'boxConfiguration': box_configuration})) }}
{% endfor %}

View File

@@ -277,7 +277,7 @@
{% macro action_button(icon, values, type) %}
{% import '@theme/components/button.html.twig' as macro %}
{% set values = values|merge({'tooltip_attr': 'data-toggle'}) %}
{% set values = (values ?? {})|merge({'tooltip_attr': 'data-toggle'}) %}
{{ macro.button(icon, values, type) }}
{% endmacro %}

View File

@@ -1,11 +1,7 @@
{% if is_granted('start_own_timesheet') %}
{% set class = "btn-outline-white" %}
{% if tabler_bundle.isDarkMode() or tabler_bundle.isNavbarOverlapping() %}
{% set class = "btn-dark" %}
{% endif %}
<div class="nav-item d-flex me-sm-3 me-1">
<a href="{{ path('favorites_timesheets') }}" class="btn btn-icon {{ class}} px-0 remote-modal-load" data-modal-title="{{ 'recent.activities'|trans }}" tabindex="-1" aria-label="{{ 'recent.activities'|trans }}">
{{ icon('repeat') }}
<div class="nav-item d-flex me-1 recent-activities">
<a href="{{ path('favorites_timesheets') }}" class="nav-link px-0 remote-modal-load recent-activities-btn" data-modal-title="{{ 'recent.activities'|trans }}" tabindex="-1" aria-label="{{ 'recent.activities'|trans }}">
{{ icon('repeat', true) }}
</a>
</div>
{% endif %}

View File

@@ -4,7 +4,7 @@
{# fake entry, because at least one html template node is needed #}
{% set active_timesheets = [{'id': '000', 'begin': null, 'activity': {'name': ''}, 'project': {'name': '', 'customer': {'name': ''}}}] %}
{% endif %}
<div class="nav-item d-flex me-sm-3 me-1">
<div class="nav-item d-flex me-1 ticktac">
<div class="btn-list">
{% set class = "btn-outline-white" %}
{% if tabler_bundle.isDarkMode() or tabler_bundle.isNavbarOverlapping() %}

View File

@@ -0,0 +1,36 @@
{% set user_shortcuts = user_shortcuts(app.user) %}
{% if user_shortcuts|length > 0 %}
<div class="nav-item dropdown d-flex me-1 user-shortcuts">
<a class="nav-link px-0 user-shortcuts-toggle" href="javascript:void(0);" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="true">
{{ icon('far fa-bookmark', true) }}
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow dropdown-menu-card" data-bs-popper="static">
<div class="card">
<div class="card-header text-nowrap">{{ 'favorite_routes'|trans }}</div>
<div class="card-body user-shortcuts-list scrollable-container p-0 pb-2">
<div class="row overflow-visible g-0">
{% for menu in user_shortcuts %}
{% if not loop.first and loop.index0 % 2 == 0 %}
</div>
<div class="row overflow-visible g-0">
{% endif %}
<div class="text-center pt-3 px-3 col">
<a href="{{ path(menu.route) }}" class="text-decoration-none">
<span class="user-shortcuts-icon rounded-circle mb-2">
<i class="{{ menu.icon|icon }} fs-2"></i>
</span>
</a>
<a href="{{ path(menu.route) }}" class="nav-link">
{{- menu.label|trans -}}
</a>
</div>
{% endfor %}
{% if user_shortcuts|length > 1 and user_shortcuts|length % 2 == 1 %}
<div class="pt-3 px-3 col"></div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endif %}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Command;
use App\Command\ListUserCommand;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
/**
* @covers \App\Command\ListUserCommand
* @group integration
*/
class ListUserCommandTest extends KernelTestCase
{
public function testWithPlugins(): void
{
$commandTester = $this->getCommandTester();
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Username Email Roles Active PW Reset', $output);
$this->assertStringContainsString('---------- ------- ------- -------- ----------', $output);
}
protected function getCommandTester(): CommandTester
{
$repository = $this->createMock(UserRepository::class);
$repository->method('findAll')->willReturn([]);
$kernel = self::bootKernel();
$application = new Application($kernel);
$application->add(new ListUserCommand($repository));
$command = $application->find('kimai:user:list');
$commandTester = new CommandTester($command);
$inputs = array_merge(['command' => $command->getName()], []);
$commandTester->execute($inputs);
return $commandTester;
}
}

View File

@@ -1,30 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\EventSubscriber;
use App\Event\ConfigureMainMenuEvent;
use App\EventSubscriber\MenuFavoritesSubscriber;
use App\EventSubscriber\MenuSubscriber;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\EventSubscriber\MenuFavoritesSubscriber
*/
class MenuFavoritesSubscriberTest extends TestCase
{
public function testGetSubscribedEvents(): void
{
$events = MenuFavoritesSubscriber::getSubscribedEvents();
$this->assertArrayHasKey(ConfigureMainMenuEvent::class, $events);
$methodName = $events[ConfigureMainMenuEvent::class][0];
self::assertIsString($methodName);
$this->assertTrue(method_exists(MenuSubscriber::class, $methodName));
}
}

View File

@@ -49,7 +49,6 @@ abstract class BaseFormTypeQueryTest extends TestCase
$team = new Team('foo');
self::assertInstanceOf(BaseFormTypeQuery::class, $sut->addTeam($team));
self::assertCount(1, $sut->getTeams());
/* @phpstan-ignore-next-line */
self::assertSame($team, $sut->getTeams()[0]);
$sut->setTeams([]);

View File

@@ -108,8 +108,7 @@ class BaseQueryTest extends TestCase
$team = new Team('foo');
self::assertInstanceOf(BaseQuery::class, $sut->setTeams([$team]));
self::assertEquals(1, \count($sut->getTeams()));
/* @phpstan-ignore-next-line */
self::assertCount(1, $sut->getTeams());
self::assertSame($team, $sut->getTeams()[0]);
}

View File

@@ -20,7 +20,7 @@ use Twig\TwigFunction;
*/
class RuntimeExtensionsTest extends TestCase
{
public function testGetFilters()
public function testGetFilters(): void
{
$expected = ['md2html', 'desc2html', 'comment2html', 'comment1line', 'colorize', 'icon'];
$i = 0;
@@ -35,7 +35,7 @@ class RuntimeExtensionsTest extends TestCase
}
}
public function testGetFunctions()
public function testGetFunctions(): void
{
$expected = [
'trigger',
@@ -50,6 +50,7 @@ class RuntimeExtensionsTest extends TestCase
'render_widget',
'icon',
'qr_code_data_uri',
'user_shortcuts',
];
$i = 0;
@@ -65,7 +66,7 @@ class RuntimeExtensionsTest extends TestCase
}
}
public function testGetFilterDefinition()
public function testGetFilterDefinition(): void
{
$sut = new RuntimeExtensions();
$filters = $sut->getFilters();

View File

@@ -8852,21 +8852,6 @@ parameters:
count: 1
path: Twig/Runtime/WidgetExtensionTest.php
-
message: "#^Method App\\\\Tests\\\\Twig\\\\RuntimeExtensionsTest\\:\\:testGetFilterDefinition\\(\\) has no return type specified\\.$#"
count: 1
path: Twig/RuntimeExtensionsTest.php
-
message: "#^Method App\\\\Tests\\\\Twig\\\\RuntimeExtensionsTest\\:\\:testGetFilters\\(\\) has no return type specified\\.$#"
count: 1
path: Twig/RuntimeExtensionsTest.php
-
message: "#^Method App\\\\Tests\\\\Twig\\\\RuntimeExtensionsTest\\:\\:testGetFunctions\\(\\) has no return type specified\\.$#"
count: 1
path: Twig/RuntimeExtensionsTest.php
-
message: "#^Method App\\\\Tests\\\\Utils\\\\ColorTest\\:\\:assertIsValidColor\\(\\) has no return type specified\\.$#"
count: 1

View File

@@ -40,7 +40,7 @@
</trans-unit>
<trans-unit id="5G3QNHA" resname="activity.filter">
<source>Filter activities</source>
<target state="translated">Aktivitäten filtern</target>
<target state="translated">Tätigkeiten filtern</target>
</trans-unit>
<trans-unit id="b1pgNOd" resname="copy">
<source>copy</source>
@@ -48,7 +48,7 @@
</trans-unit>
<trans-unit id="UbouFOs" resname="create-activity">
<source>create-activity</source>
<target state="translated">Aktivität erstellen</target>
<target state="translated">Tätigkeit erstellen</target>
</trans-unit>
<trans-unit id="QR1nvcR" resname="create-timesheet">
<source>create-timesheet</source>

View File

@@ -24,11 +24,11 @@
</trans-unit>
<trans-unit id="BlGGO.X" resname="activity">
<source>activity</source>
<target>Aktivität</target>
<target>Tätigkeit</target>
</trans-unit>
<trans-unit id="oRa0mkz" resname="activity_user">
<source>activity_user</source>
<target>Aktivität und Benutzer</target>
<target>Tätigkeit und Benutzer</target>
</trans-unit>
<trans-unit id="JEIQ5IQ" resname="project">
<source>project</source>

View File

@@ -24,11 +24,11 @@
</trans-unit>
<trans-unit id="BlGGO.X" resname="activity">
<source>activity</source>
<target>Aktivität</target>
<target>Tätigkeit</target>
</trans-unit>
<trans-unit id="oRa0mkz" resname="activity_user">
<source>activity_user</source>
<target>Aktivität und Benutzer</target>
<target>Tätigkeit und Benutzer</target>
</trans-unit>
<trans-unit id="JEIQ5IQ" resname="project">
<source>project</source>

View File

@@ -886,11 +886,11 @@
<source>profile.registration_date</source>
<target>Registrován</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Připojit</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Nahradit</target>
</trans-unit>

View File

@@ -349,11 +349,11 @@
<source>color</source>
<target>Farve</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Erstat</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Tilføj</target>
</trans-unit>

View File

@@ -523,11 +523,11 @@
<source>color</source>
<target>Farbe</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Ersetzen</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Hinzufügen</target>
</trans-unit>
@@ -1573,6 +1573,10 @@
<source>absence</source>
<target>Abwesenheit</target>
</trans-unit>
<trans-unit id="YTzKpcP" resname="Absence calendar">
<source>Absence calendar</source>
<target>Abwesenheitskalender</target>
</trans-unit>
<trans-unit id="4PMHpIp" resname="time_off">
<source>time_off</source>
<target>Freizeitausgleich</target>
@@ -1633,6 +1637,42 @@
<source>force_password_change_help</source>
<target state="translated">Bei der nächsten Anmeldung muss der Benutzer ein neues Passwort hinterlegen.</target>
</trans-unit>
<trans-unit id="vURcRit" resname="overview">
<source>Overview</source>
<target>Übersicht</target>
</trans-unit>
<trans-unit id="0xFBb6O" resname="Expenses">
<source>Expenses</source>
<target>Ausgaben</target>
</trans-unit>
<trans-unit id="KSwG8AR" resname="Category">
<source>Category</source>
<target>Kategorie</target>
</trans-unit>
<trans-unit id="uLHYlMa" resname="Categories">
<source>Categories</source>
<target>Kategorien</target>
</trans-unit>
<trans-unit id="7Sycs4M" resname="Importer">
<source>Importer</source>
<target>Importer</target>
</trans-unit>
<trans-unit id="J6EJUj9" resname="Meta fields">
<source>Meta fields</source>
<target>Eigene Felder</target>
</trans-unit>
<trans-unit id="JmpBuQ1" resname="Translations">
<source>Translations</source>
<target>Übersetzungen</target>
</trans-unit>
<trans-unit id="b712axp" resname="Translation">
<source>Translation</source>
<target>Übersetzung</target>
</trans-unit>
<trans-unit id="Vp7xjKh" resname="Audit logs">
<source>Audit logs</source>
<target>Audit-Logs</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -573,11 +573,11 @@
<source>profile.title</source>
<target>Benutzerprofil</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Hinzufügen</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Ersetzen</target>
</trans-unit>

View File

@@ -441,11 +441,11 @@
<source>color</source>
<target>Χρώμα</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Αντικατάσταση</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Προσάρτηση</target>
</trans-unit>

View File

@@ -523,11 +523,11 @@
<source>color</source>
<target>Color</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Replace</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Append</target>
</trans-unit>
@@ -1573,6 +1573,10 @@
<source>absence</source>
<target>Absence</target>
</trans-unit>
<trans-unit id="YTzKpcP" resname="Absence calendar">
<source>Absence calendar</source>
<target>Absence calendar</target>
</trans-unit>
<trans-unit id="4PMHpIp" resname="time_off">
<source>time_off</source>
<target>Time off</target>
@@ -1633,6 +1637,42 @@
<source>force_password_change_help</source>
<target>The next time the user logs in, they must enter a new password.</target>
</trans-unit>
<trans-unit id="vURcRit" resname="overview">
<source>Overview</source>
<target>Overview</target>
</trans-unit>
<trans-unit id="0xFBb6O" resname="Expenses">
<source>Expenses</source>
<target>Expenses</target>
</trans-unit>
<trans-unit id="KSwG8AR" resname="Category">
<source>Category</source>
<target>Category</target>
</trans-unit>
<trans-unit id="uLHYlMa" resname="Categories">
<source>Categories</source>
<target>Categories</target>
</trans-unit>
<trans-unit id="7Sycs4M" resname="Importer">
<source>Importer</source>
<target>Importer</target>
</trans-unit>
<trans-unit id="J6EJUj9" resname="Meta fields">
<source>Meta fields</source>
<target>Custom fields</target>
</trans-unit>
<trans-unit id="JmpBuQ1" resname="Translations">
<source>Translations</source>
<target>Translations</target>
</trans-unit>
<trans-unit id="b712axp" resname="Translation">
<source>Translation</source>
<target>Translation</target>
</trans-unit>
<trans-unit id="Vp7xjKh" resname="Audit logs">
<source>Audit logs</source>
<target>Audit logs</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -373,11 +373,11 @@
<source>color</source>
<target>Koloro</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Anstataŭigi</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Postglui</target>
</trans-unit>

View File

@@ -910,7 +910,7 @@
<source>status</source>
<target>Situación</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Reemplazar</target>
</trans-unit>
@@ -1654,7 +1654,7 @@
<source>Account working times</source>
<target state="translated">Horario de atención al público</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Adjuntar</target>
</trans-unit>

View File

@@ -937,11 +937,11 @@
<source>profile.registration_date</source>
<target>Hemen erregistratuta</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Gehitu</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Ordeztu</target>
</trans-unit>

View File

@@ -405,11 +405,11 @@
<source>color</source>
<target state="translated">Väri</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">Vaihda</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Lisää</target>
</trans-unit>

View File

@@ -759,11 +759,11 @@
<source>profile.title</source>
<target>Brúkara vangi</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Legg aftrat</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Útskift</target>
</trans-unit>

View File

@@ -766,11 +766,11 @@
<source>globalsOnly</source>
<target>Uniquement global</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Ajouter</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Remplacer</target>
</trans-unit>
@@ -1550,6 +1550,10 @@
<source>confirmed_by_at</source>
<target state="needs-translation">Confirmed by %user% at %date%</target>
</trans-unit>
<trans-unit id="J6EJUj9" resname="Meta fields">
<source>Meta fields</source>
<target>Champs personnalisés</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -1590,7 +1590,7 @@
<source>supervisor</source>
<target state="translated">פיקוח</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">הוספה לסוף</target>
</trans-unit>
@@ -1598,7 +1598,7 @@
<source>force_password_change</source>
<target state="translated">בקשת סיסמה חדשה</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">החלפה</target>
</trans-unit>
@@ -1606,6 +1606,18 @@
<source>force_password_change_help</source>
<target state="translated">בכניסה הבאה של המשתמש, צריך למלא סיסמה חדש.</target>
</trans-unit>
<trans-unit id="0xFBb6O" resname="Expenses">
<source>Expenses</source>
<target>הוצאות</target>
</trans-unit>
<trans-unit id="uLHYlMa" resname="Categories">
<source>Categories</source>
<target>קטגוריות</target>
</trans-unit>
<trans-unit id="KSwG8AR" resname="Category">
<source>Category</source>
<target>קטגוריה</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -453,11 +453,11 @@
<source>color</source>
<target>Boja</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Zamijeni</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Dodaj</target>
</trans-unit>

View File

@@ -405,11 +405,11 @@
<source>color</source>
<target>Colore</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Sostituisci</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Appendi</target>
</trans-unit>

View File

@@ -1634,6 +1634,18 @@
<source>holiday</source>
<target state="translated">Vakantie</target>
</trans-unit>
<trans-unit id="0xFBb6O" resname="Expenses">
<source>Expenses</source>
<target>Kosten</target>
</trans-unit>
<trans-unit id="KSwG8AR" resname="Category">
<source>Category</source>
<target>Categorie</target>
</trans-unit>
<trans-unit id="uLHYlMa" resname="Categories">
<source>Categories</source>
<target>Categorieën</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -361,11 +361,11 @@
<source>color</source>
<target>Kolor</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Zamień</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Dodaj</target>
</trans-unit>
@@ -1542,6 +1542,22 @@
<source>supervisor</source>
<target state="translated">Kierowca</target>
</trans-unit>
<trans-unit id="0xFBb6O" resname="Expenses">
<source>Expenses</source>
<target>Wydatki</target>
</trans-unit>
<trans-unit id="KSwG8AR" resname="Category">
<source>Category</source>
<target>Kategorie</target>
</trans-unit>
<trans-unit id="uLHYlMa" resname="Categories">
<source>Categories</source>
<target>Categories</target>
</trans-unit>
<trans-unit id="Vp7xjKh" resname="Audit logs">
<source>Audit logs</source>
<target>Historia zmian</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -862,11 +862,11 @@
<source>lastLogin</source>
<target>Última sessão</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Anexar</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Substituir</target>
</trans-unit>

View File

@@ -1654,7 +1654,7 @@
<source>supervisor</source>
<target state="translated">Supervisor</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Acrescentar</target>
</trans-unit>
@@ -1662,7 +1662,7 @@
<source>force_password_change</source>
<target state="translated">Solicitar uma nova senha</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">Substituir</target>
</trans-unit>

View File

@@ -712,11 +712,11 @@
<source>profile.registration_date</source>
<target state="translated">Зарегистрирован(-а)</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Добавить</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">Заменить</target>
</trans-unit>

View File

@@ -357,11 +357,11 @@
<source>color</source>
<target>Farba</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Zmeniť</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Pridať</target>
</trans-unit>

View File

@@ -938,11 +938,11 @@
<source>export_decimal</source>
<target>Använd decimallängd vid export</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target>Fäst</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target>Ersätt</target>
</trans-unit>

View File

@@ -1654,7 +1654,7 @@
<source>supervisor</source>
<target state="translated">Süpervizör</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Sonuna ekle</target>
</trans-unit>
@@ -1662,7 +1662,7 @@
<source>force_password_change</source>
<target state="translated">Yeni parola iste</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">Değiştir</target>
</trans-unit>

View File

@@ -1590,11 +1590,11 @@
<source>supervisor</source>
<target state="translated">Керівник</target>
</trans-unit>
<trans-unit id="ICpl9sx" resname="append">
<trans-unit id="rlJLVNm" resname="append">
<source>append</source>
<target state="translated">Додати</target>
</trans-unit>
<trans-unit id="tl01EdS" resname="replace">
<trans-unit id="erN3h3b" resname="replace">
<source>replace</source>
<target state="translated">Замінити</target>
</trans-unit>

View File

@@ -44,7 +44,7 @@
</trans-unit>
<trans-unit id="8RaUHlW" resname="report_customer_monthly_projects">
<source>report_customer_monthly_projects</source>
<target state="translated">Projekte nach Monat, Aktivität und Benutzer</target>
<target state="translated">Projekte nach Monat, Tätigkeit und Benutzer</target>
</trans-unit>
</body>
</file>

View File

@@ -296,7 +296,7 @@
</trans-unit>
<trans-unit id="12sQGSo" resname="activity.allow_inline_create">
<source>activity.allow_inline_create</source>
<target state="translated">Erstellung von Aktivitäten im Zeiterfassungsformular zulassen</target>
<target state="translated">Erstellung von Tätigkeiten im Zeiterfassungsformular zulassen</target>
</trans-unit>
<trans-unit id="pBxkAF_" resname="copy_teams_on_create" approved="no">
<source>copy_teams_on_create</source>

View File

@@ -4,7 +4,7 @@
<body>
<trans-unit id="FC4DW27" resname="team.activity_visibility_inherited">
<source>team.activity_visibility_inherited</source>
<target state="translated">Die Aktivität hat eingeschränkten Zugriff, da Teamberechtigungen für das Projekt und/oder den Kunden festgelegt sind.</target>
<target state="translated">Die Tätigkeit hat eingeschränkten Zugriff, da Teamberechtigungen für das Projekt und/oder den Kunden festgelegt sind.</target>
</trans-unit>
<trans-unit id="6g81kYY" approved="no" resname="team.member">
<source>team.member</source>

View File

@@ -106,6 +106,50 @@
<source>This value is not a valid role name.</source>
<target>Dies ist kein gültiger Name für eine Benutzer-Rolle.</target>
</trans-unit>
<trans-unit id="ZObikpA" resname="This absence type is not known.">
<source>This absence type is not known.</source>
<target>Dieser Abwesenheitstyp ist nicht bekannt.</target>
</trans-unit>
<trans-unit id="0ULsHQA" resname="The chosen date is not a working day.">
<source>The chosen date is not a working day.</source>
<target>Das ausgewählte Datum ist kein Arbeitstag.</target>
</trans-unit>
<trans-unit id="N21br.P" resname="The chosen date is already locked.">
<source>The chosen date is already locked.</source>
<target>Das ausgewählte Datum ist bereits gesperrt.</target>
</trans-unit>
<trans-unit id="6H84TMs" resname="An absence must be assigned to a user.">
<source>An absence must be assigned to a user.</source>
<target>Eine Abwesenheit muss einem Benutzer zugewiesen werden.</target>
</trans-unit>
<trans-unit id="nLcihCc" resname="An absence must have a date.">
<source>An absence must have a date.</source>
<target>Eine Abwesenheit muss ein Datum haben.</target>
</trans-unit>
<trans-unit id="SZO4xjC" resname="An absence cannot be booked before your first working day.">
<source>An absence cannot be booked before your first working day.</source>
<target>Eine Abwesenheit kann nicht vor dem ersten Arbeitstag gebucht werden.</target>
</trans-unit>
<trans-unit id="IN37g5N" resname="Not enough holidays left.">
<source>Not enough holidays left.</source>
<target>Nicht mehr genügend Urlaubstage übrig.</target>
</trans-unit>
<trans-unit id="ByeEMxW" resname="You do not have enough overtime, remaining are {{ value }}.">
<source>You do not have enough overtime, remaining are {{ value }}.</source>
<target>Sie haben nicht genug Überstunden, es verbleiben {{ Wert }}.</target>
</trans-unit>
<trans-unit id="DiaaRa1" resname="You cannot have two absences of this type at one day.">
<source>You cannot have two absences of this type at one day.</source>
<target>Sie können nicht zwei Abwesenheiten dieser Art an einem Tag buchen.</target>
</trans-unit>
<trans-unit id="kSBiyaF" resname="This is a public holiday, booking not allowed.">
<source>This is a public holiday, booking not allowed.</source>
<target>Dies ist ein Feiertag, Buchung ist nicht erlaubt.</target>
</trans-unit>
<trans-unit id="c2B6.lW" resname="You can book a maximum of {{ value }} days at once.">
<source>You can book a maximum of {{ value }} days at once.</source>
<target>Sie können maximal {{ Wert }} Tage auf einmal buchen.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -90,7 +90,7 @@
<source>Validation Failed</source>
<target>There was a problem saving.</target>
</trans-unit>
<trans-unit resname="Cannot stop running timesheet" id="7HYTefs">
<trans-unit id="7HYTefs" resname="Cannot stop running timesheet">
<source>Cannot stop running timesheet</source>
<target>You have an active time record which cannot be stopped automatically.</target>
</trans-unit>
@@ -106,6 +106,50 @@
<source>This value is not a valid role name.</source>
<target>This is not a valid name for a user role.</target>
</trans-unit>
<trans-unit id="ZObikpA" resname="This absence type is not known.">
<source>This absence type is not known.</source>
<target>This absence type is not known.</target>
</trans-unit>
<trans-unit id="0ULsHQA" resname="The chosen date is not a working day.">
<source>The chosen date is not a working day.</source>
<target>The chosen date is not a working day.</target>
</trans-unit>
<trans-unit id="N21br.P" resname="The chosen date is already locked.">
<source>The chosen date is already locked.</source>
<target>The chosen date is already locked.</target>
</trans-unit>
<trans-unit id="6H84TMs" resname="An absence must be assigned to a user.">
<source>An absence must be assigned to a user.</source>
<target>An absence must be assigned to a user.</target>
</trans-unit>
<trans-unit id="nLcihCc" resname="An absence must have a date.">
<source>An absence must have a date.</source>
<target>An absence must have a date.</target>
</trans-unit>
<trans-unit id="SZO4xjC" resname="An absence cannot be booked before your first working day.">
<source>An absence cannot be booked before your first working day.</source>
<target>An absence cannot be booked before your first working day.</target>
</trans-unit>
<trans-unit id="IN37g5N" resname="Not enough holidays left.">
<source>Not enough holidays left.</source>
<target>Not enough holidays left.</target>
</trans-unit>
<trans-unit id="ByeEMxW" resname="You do not have enough overtime, remaining are {{ value }}.">
<source>You do not have enough overtime, remaining are {{ value }}.</source>
<target>You do not have enough overtime, remaining are {{ value }}.</target>
</trans-unit>
<trans-unit id="DiaaRa1" resname="You cannot have two absences of this type at one day.">
<source>You cannot have two absences of this type at one day.</source>
<target>You cannot have two absences of this type at one day.</target>
</trans-unit>
<trans-unit id="kSBiyaF" resname="This is a public holiday, booking not allowed.">
<source>This is a public holiday, booking not allowed.</source>
<target>This is a public holiday, booking not allowed.</target>
</trans-unit>
<trans-unit id="c2B6.lW" resname="You can book a maximum of {{ value }} days at once.">
<source>You can book a maximum of {{ value }} days at once.</source>
<target>You can book a maximum of {{ value }} days at once.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -26,7 +26,9 @@ Encore
.enableSingleRuntimeChunk()
.enableVersioning(Encore.isProduction())
.enableSourceMaps(!Encore.isProduction())
.enableBuildNotifications()
// disabled as ""webpack-notifier": "^1.13"" id currently only compatible with rosetta
//.enableBuildNotifications()
.enableSassLoader(function(sassOptions) {}, {
resolveUrlLoader: false

View File

@@ -1397,10 +1397,10 @@ __metadata:
languageName: node
linkType: hard
"@fortawesome/fontawesome-free@npm:^6.2.0":
version: 6.2.0
resolution: "@fortawesome/fontawesome-free@npm:6.2.0"
checksum: fd3fcc0a963b907110922b5ac0ca67633aea20b5034102a1f5d285830f7f7a27f9c9de0c4a30228ccd2579bd8f95581a1bbef9519c7b74632e7012d60dd57bb7
"@fortawesome/fontawesome-free@npm:^6.5.0":
version: 6.5.0
resolution: "@fortawesome/fontawesome-free@npm:6.5.0"
checksum: 5bbb2beeb885136f97afcd41444f52502f0db990b5839ee7c795870db8a4aa64e6786b3c7c6913515c16b511ccd406d2d51c5a07474294dd737d51301cf69b0d
languageName: node
linkType: hard
@@ -2892,9 +2892,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001359, caniuse-lite@npm:^1.0.30001400":
version: 1.0.30001520
resolution: "caniuse-lite@npm:1.0.30001520"
checksum: 59991ad8f36cf282f81abbcc6074c3097c21914cdd54bd2b3f73ac9462f57fc74e90371cd22bcdff4d085d09da42a07dcea384cb81e4ac260496e1bd79e1fe7c
version: 1.0.30001565
resolution: "caniuse-lite@npm:1.0.30001565"
checksum: 7621f358d0e1158557430a111ca5506008ae0b2c796039ef53aeebf4e2ba15e5241cb89def21ea3a633b6a609273085835b44a522165d871fa44067cdf29cccd
languageName: node
linkType: hard
@@ -4274,13 +4274,6 @@ __metadata:
languageName: node
linkType: hard
"growly@npm:^1.3.0":
version: 1.3.0
resolution: "growly@npm:1.3.0"
checksum: 53cdecd4c16d7d9154a9061a9ccb87d602e957502ca69b529d7d1b2436c2c0b700ec544fc6b3e4cd115d59b81e62e44ce86bd0521403b579d3a2a97d7ce72a44
languageName: node
linkType: hard
"handle-thing@npm:^2.0.0":
version: 2.0.1
resolution: "handle-thing@npm:2.0.1"
@@ -4895,7 +4888,7 @@ __metadata:
"@babel/core": ^7.19.6
"@babel/eslint-parser": ^7.18.2
"@babel/preset-env": ^7.19.4
"@fortawesome/fontawesome-free": ^6.2.0
"@fortawesome/fontawesome-free": ^6.5.0
"@fullcalendar/bootstrap5": ^5.11
"@fullcalendar/core": ^5.11
"@fullcalendar/daygrid": ^5.11
@@ -4917,7 +4910,6 @@ __metadata:
tom-select: ^2.2
webpack: ^5.74.0
webpack-cli: ^4.10.0
webpack-notifier: ^1.13
languageName: unknown
linkType: soft
@@ -5387,20 +5379,6 @@ __metadata:
languageName: node
linkType: hard
"node-notifier@npm:^9.0.0":
version: 9.0.1
resolution: "node-notifier@npm:9.0.1"
dependencies:
growly: ^1.3.0
is-wsl: ^2.2.0
semver: ^7.3.2
shellwords: ^0.1.1
uuid: ^8.3.0
which: ^2.0.2
checksum: a75623912ec6c3fd7c4e6f1c04586adf716a3610268bdbd5adfeb95dc75a2126c10368d98051530aa0dc6fe8d72791d5033ad6dd2a5f63d009fea30f7292f07e
languageName: node
linkType: hard
"node-releases@npm:^2.0.5":
version: 2.0.5
resolution: "node-releases@npm:2.0.5"
@@ -6747,13 +6725,6 @@ __metadata:
languageName: node
linkType: hard
"shellwords@npm:^0.1.1":
version: 0.1.1
resolution: "shellwords@npm:0.1.1"
checksum: 8d73a5e9861f5e5f1068e2cfc39bc0002400fe58558ab5e5fa75630d2c3adf44ca1fac81957609c8320d5533e093802fcafc72904bf1a32b95de3c19a0b1c0d4
languageName: node
linkType: hard
"side-channel@npm:^1.0.4":
version: 1.0.4
resolution: "side-channel@npm:1.0.4"
@@ -6928,7 +6899,7 @@ __metadata:
languageName: node
linkType: hard
"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1":
"strip-ansi@npm:^6.0.1":
version: 6.0.1
resolution: "strip-ansi@npm:6.0.1"
dependencies:
@@ -7328,7 +7299,7 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:^8.3.0, uuid@npm:^8.3.2":
"uuid@npm:^8.3.2":
version: 8.3.2
resolution: "uuid@npm:8.3.2"
bin:
@@ -7465,21 +7436,6 @@ __metadata:
languageName: node
linkType: hard
"webpack-notifier@npm:^1.13":
version: 1.15.0
resolution: "webpack-notifier@npm:1.15.0"
dependencies:
node-notifier: ^9.0.0
strip-ansi: ^6.0.0
peerDependencies:
"@types/webpack": ">4.41.31"
peerDependenciesMeta:
"@types/webpack":
optional: true
checksum: d6dd514b36000b6fb21feb928b0efa8f004f03d28d94bfc3513295f306bc8e5f132984e60e7995af32685f6b544d1deaad7c421527d8f48db6356bdf40190973
languageName: node
linkType: hard
"webpack-sources@npm:^3.2.3":
version: 3.2.3
resolution: "webpack-sources@npm:3.2.3"