Form and theme improvements (#513)

This commit is contained in:
Kevin Papst
2019-01-21 13:10:53 +01:00
committed by GitHub
parent 47ac50b4d0
commit a9ece209ae
35 changed files with 234 additions and 292 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
templates/invoice/renderer/.~lock*
/var/data/*.sqlite
/var/data/*.sqlite-journal
!/var/data/kimai_test.sqlite
/var/coverage/
/var/cache/*

View File

@@ -1,165 +0,0 @@
/*
* 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.
*/
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
.error-page {
margin-bottom: 50px;
}
/* ================================ CONTENT ================================ */
.content {
padding: 15px 0;
}
.content-header {
h1 {
small {
display: none;
}
}
}
@media (min-width: $screen-sm-min) {
.content-header {
h1 {
small {
display: inline-block;
}
}
}
.content {
padding: 15px;
}
}
/* ================================ NAVBAR ================================ */
.navbar-nav > li > a.ddt-large {
padding-top: 12px;
padding-bottom: 9px;
}
li.messages-menu ul.menu li .pull-left i {
color: #dd4b39;
}
.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p {
overflow: hidden;
text-overflow: ellipsis;
}
.navbar-nav .start_record {
text-align: center;
}
/*
.ticktac:hover i.running{
color: #4ff131;
}
li.open .ticktac i.running{
color: #4ff131;
}
.ticktac i.stopped{
color: #fff;
}
.ticktac:hover i.stopped{
color: #4ff131;
}
.user-panel>.info {
left: 40px;
padding-top: 0px;
}
*/
/* ================================ TOOLBAR ================================ */
/* Page based action buttons in the upper right corner of the content area */
.content-header > .breadcrumb {
position: absolute;
float: right;
background: transparent;
}
/* Right sidebar with additional tabs for personal settings and "About Kimai" section */
.control-sidebar {
select {
color: $input-color;
background-color: $input-bg;
}
div.image {
img.img-circle {
max-width: 34px;
}
}
}
/* The filter form is available on most pages above the datatable */
@media (min-width: 768px) {
.toolbar {
form.navbar-form {
font-size: $font-size-base;
.form-control {
display: inline-block;
width: 100%;
vertical-align: middle;
}
}
}
}
/* ================================ SIDEBAR ================================ */
/* Used for the Sidebar UserPanel (which is deactivated right now) */
.user-panel>.info {
left: 80px;
}
/* ================================ FOOTER ================================ */
footer.main-footer {
padding: 5px;
font-size: 80%;
}
/* ================================ PRINT ================================ */
@media print{
.sf-toolbar, .control-sidebar {display: none !important;}
}
/* ================================ INVOICES ================================ */
.invoice {
margin: 0;
}
@media (min-width: $screen-sm-min) {
.invoice {
margin: 10px 25px;
}
}
.page-header-small{
font-size: 14px;
}
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-records {}
table.invoice-meta th {
padding-right: 40px;
}
table.invoice-sum th {
width: 70%;
}
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}

View File

@@ -35,6 +35,12 @@
width: 100%;
vertical-align: middle;
}
.control-label {
width: 100%;
}
.input-group {
width: 100%;
}
}
}
}

14
composer.lock generated
View File

@@ -1172,12 +1172,12 @@
"version": "v2.6.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/doctrine2.git",
"url": "https://github.com/doctrine/orm.git",
"reference": "d2b4dd71d2a276edd65d0c170375b445f8a4a4a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/doctrine2/zipball/d2b4dd71d2a276edd65d0c170375b445f8a4a4a8",
"url": "https://api.github.com/repos/doctrine/orm/zipball/d2b4dd71d2a276edd65d0c170375b445f8a4a4a8",
"reference": "d2b4dd71d2a276edd65d0c170375b445f8a4a4a8",
"shasum": ""
},
@@ -2118,16 +2118,16 @@
},
{
"name": "kevinpapst/adminlte-bundle",
"version": "2.1.2",
"version": "2.2",
"source": {
"type": "git",
"url": "https://github.com/kevinpapst/AdminLTEBundle.git",
"reference": "6a085dbfbf46a24cf21b6a1d54fa0086e6ab4cc9"
"reference": "f30230e476eec0b22d1f850b6902488d14e0b95e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/6a085dbfbf46a24cf21b6a1d54fa0086e6ab4cc9",
"reference": "6a085dbfbf46a24cf21b6a1d54fa0086e6ab4cc9",
"url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/f30230e476eec0b22d1f850b6902488d14e0b95e",
"reference": "f30230e476eec0b22d1f850b6902488d14e0b95e",
"shasum": ""
},
"require": {
@@ -2175,7 +2175,7 @@
}
],
"description": "Admin theme bundle for Symfony 4 based on AdminLTE 2.4.8 with FOSUserBundle support",
"time": "2018-10-10T11:38:54+00:00"
"time": "2019-01-20T16:36:22+00:00"
},
{
"name": "markbaker/complex",

View File

@@ -2,3 +2,5 @@ twig:
#paths: ['%kernel.project_dir%/templates']
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
form_themes:
- 'form/kimai-theme.html.twig'

View File

@@ -97,6 +97,10 @@ services:
arguments:
$durationOnly: "%kimai.timesheet.duration_only%"
App\Form\Extension\SelectWithApiDataExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Bridge\Doctrine\Form\Type\EntityType }
# ================================================================================
# THEME
# ================================================================================

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"build/app.js": "/build/app.js?d823754f6c3293656052",
"build/app.css": "/build/app.css?979d88ed847886068fcf3eb0bc3de545",
"build/app.css": "/build/app.css?c7fd4b9cec5de89467ba89547d2857aa",
"build/images/blue@2x.png": "/build/images/blue@2x.png?2694acfd",
"build/images/blue.png": "/build/images/blue.png?96f8a905",
"build/fonts/fa-solid-900.woff2": "/build/fonts/fa-solid-900.woff2?e8a92a29",

View File

@@ -92,6 +92,7 @@ class UserController extends AbstractController
public function createAction(Request $request)
{
$user = new User();
$user->setEnabled(true);
$editForm = $this->createEditForm($user);
$editForm->handleRequest($request);

View File

@@ -19,7 +19,7 @@ use App\Form\Type\SkinType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Validator\Constraints\Range;
@@ -86,13 +86,13 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
(new UserPreference())
->setName(UserPreference::HOURLY_RATE)
->setValue(0)
->setType(NumberType::class)
->setType(MoneyType::class)
->setEnabled($enableHourlyRate)
->addConstraint(new Range(['min' => 0])),
(new UserPreference())
->setName('language')
->setValue('en') // TODO fetch from services.yaml
->setValue('en')
->setType(LanguageType::class),
(new UserPreference())

View File

@@ -17,7 +17,7 @@ use App\Repository\CustomerRepository;
use App\Repository\ProjectRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -38,10 +38,12 @@ class ActivityEditForm extends AbstractType
$project = null;
$customer = null;
$currency = false;
if (null !== $entry->getProject()) {
$project = $entry->getProject();
$customer = $project->getCustomer();
$currency = $customer->getCurrency();
}
$builder
@@ -62,9 +64,10 @@ class ActivityEditForm extends AbstractType
'data' => $customer ? $customer : null,
'required' => false,
'mapped' => false,
'attr' => [
'data-related-select' => $this->getBlockPrefix() . '_project',
'data-api-url' => ['get_projects', ['customer' => '-s-']],
'api_data' => [
'select' => 'project',
'route' => 'get_projects',
'route_params' => ['customer' => '-s-']
],
])
->add('project', ProjectType::class, [
@@ -74,13 +77,15 @@ class ActivityEditForm extends AbstractType
return $repo->builderForEntityType($project, $customer);
},
])
->add('fixedRate', NumberType::class, [
->add('fixedRate', MoneyType::class, [
'label' => 'label.fixed_rate',
'required' => false,
'currency' => $currency,
])
->add('hourlyRate', NumberType::class, [
->add('hourlyRate', MoneyType::class, [
'label' => 'label.hourly_rate',
'required' => false,
'currency' => $currency,
])
// boolean
->add('visible', YesNoType::class, [

View File

@@ -15,7 +15,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
@@ -34,6 +34,9 @@ class CustomerEditForm extends AbstractType
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var Customer $customer */
$customer = $options['data'];
$builder
->add('name', TextType::class, [
'label' => 'label.name',
@@ -71,10 +74,12 @@ class CustomerEditForm extends AbstractType
->add('fax', TelType::class, [
'label' => 'label.fax',
'required' => false,
'attr' => ['icon' => 'fax'],
])
->add('mobile', TelType::class, [
'label' => 'label.mobile',
'required' => false,
'attr' => ['icon' => 'mobile'],
])
->add('mail', EmailType::class, [
'label' => 'label.email',
@@ -87,13 +92,15 @@ class CustomerEditForm extends AbstractType
->add('timezone', TimezoneType::class, [
'label' => 'label.timezone',
])
->add('fixedRate', NumberType::class, [
->add('fixedRate', MoneyType::class, [
'label' => 'label.fixed_rate',
'required' => false,
'currency' => $customer->getCurrency() ?? false,
])
->add('hourlyRate', NumberType::class, [
->add('hourlyRate', MoneyType::class, [
'label' => 'label.hourly_rate',
'required' => false,
'currency' => $customer->getCurrency() ?? false,
])
->add('visible', YesNoType::class, [
'label' => 'label.visible',

View File

@@ -0,0 +1,85 @@
<?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\Form\Extension;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Support Remote-API calls for Entity select-boxes.
*/
class SelectWithApiDataExtension extends AbstractTypeExtension
{
/**
* @var UrlGeneratorInterface
*/
private $router;
/**
* @param UrlGeneratorInterface $router
*/
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
/**
* @return string
*/
public function getExtendedType()
{
return EntityType::class;
}
/**
* @param FormView $view
* @param FormInterface $form
* @param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!isset($options['api_data'])) {
return;
}
$apiData = $options['api_data'];
if (!isset($apiData['select'])) {
throw new \InvalidArgumentException('Missing "select" option for "api_data" option for form "' . $form->getName() . '"');
}
if (!isset($apiData['route'])) {
throw new \InvalidArgumentException('Missing "route" option for "api_data" option for form "' . $form->getName() . '"');
}
if (!isset($apiData['route_params'])) {
$apiData['route_params'] = [];
}
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-related-select' => $form->getParent()->getName() . '_' . $apiData['select'],
'data-api-url' => $this->router->generate($apiData['route'], $apiData['route_params']),
]);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(['api_data']);
$resolver->setAllowedTypes('api_data', 'array');
//$resolver->setDefault('api_data', []);
}
}

View File

@@ -17,7 +17,6 @@ use App\Repository\CustomerRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -37,8 +36,11 @@ class ProjectEditForm extends AbstractType
$entry = $options['data'];
$customer = null;
$currency = false;
if ($entry->getId() !== null) {
$customer = $entry->getCustomer();
$currency = $customer->getCurrency();
}
$builder
@@ -59,18 +61,20 @@ class ProjectEditForm extends AbstractType
return $repo->builderForEntityType($customer);
},
])
->add('fixedRate', NumberType::class, [
->add('fixedRate', MoneyType::class, [
'label' => 'label.fixed_rate',
'required' => false,
'currency' => $currency,
])
->add('hourlyRate', NumberType::class, [
->add('hourlyRate', MoneyType::class, [
'label' => 'label.hourly_rate',
'required' => false,
'currency' => $currency,
])
->add('budget', MoneyType::class, [
'label' => 'label.budget',
'currency' => $customer ? $customer->getCurrency() : $builder->getOption('currency'),
'required' => false,
'currency' => $currency,
])
->add('visible', YesNoType::class, [
'label' => 'label.visible',

View File

@@ -20,7 +20,7 @@ use App\Repository\CustomerRepository;
use App\Repository\ProjectRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
@@ -67,6 +67,7 @@ class TimesheetEditForm extends AbstractType
$activity = null;
$project = null;
$customer = null;
$currency = false;
$end = null;
if (isset($options['data'])) {
@@ -75,12 +76,16 @@ class TimesheetEditForm extends AbstractType
$activity = $entry->getActivity();
$project = $entry->getProject();
$customer = null === $entry->getProject() ? null : $entry->getProject()->getCustomer();
$customer = null === $project ? null : $project->getCustomer();
if (null === $project && null !== $activity) {
$project = $activity->getProject();
}
if (null !== $customer) {
$currency = $customer->getCurrency();
}
$end = $entry->getEnd();
}
@@ -124,10 +129,12 @@ class TimesheetEditForm extends AbstractType
},
'data' => $customer ? $customer : '',
'required' => false,
'placeholder' => null === $customer ? '' : null,
'mapped' => false,
'attr' => [
'data-related-select' => $this->getBlockPrefix() . '_project',
'data-api-url' => ['get_projects', ['customer' => '-s-']],
'api_data' => [
'select' => 'project',
'route' => 'get_projects',
'route_params' => ['customer' => '-s-']
],
]);
} else {
@@ -148,12 +155,13 @@ class TimesheetEditForm extends AbstractType
'description' => 'Project ID',
],
'required' => true,
'query_builder' => function (ProjectRepository $repo) use ($project) {
return $repo->builderForEntityType($project);
'query_builder' => function (ProjectRepository $repo) use ($project, $customer) {
return $repo->builderForEntityType($project, $customer);
},
'attr' => [
'data-related-select' => $this->getBlockPrefix() . '_activity',
'data-api-url' => ['get_activities', ['project' => '-s-']],
'api_data' => [
'select' => 'activity',
'route' => 'get_activities',
'route_params' => ['project' => '-s-']
],
]));
@@ -164,8 +172,8 @@ class TimesheetEditForm extends AbstractType
'type' => 'integer',
'description' => 'Activity ID',
],
'query_builder' => function (ActivityRepository $repo) use ($activity) {
return $repo->builderForEntityType($activity);
'query_builder' => function (ActivityRepository $repo) use ($activity, $project) {
return $repo->builderForEntityType($activity, $project);
},
])
->add('description', TextareaType::class, [
@@ -176,13 +184,15 @@ class TimesheetEditForm extends AbstractType
if ($options['include_rate']) {
$builder
->add('fixedRate', NumberType::class, [
->add('fixedRate', MoneyType::class, [
'label' => 'label.fixed_rate',
'required' => false,
'currency' => $currency,
])
->add('hourlyRate', NumberType::class, [
->add('hourlyRate', MoneyType::class, [
'label' => 'label.hourly_rate',
'required' => false,
'currency' => $currency,
]);
}
@@ -198,9 +208,10 @@ class TimesheetEditForm extends AbstractType
'query_builder' => function (ProjectRepository $repo) use ($customer) {
return $repo->builderForEntityType(null, $customer);
},
'attr' => [
'data-related-select' => $this->getBlockPrefix() . '_activity',
'data-api-url' => ['get_activities', ['project' => '-s-']],
'api_data' => [
'select' => 'activity',
'route' => 'get_activities',
'route_params' => ['project' => '-s-']
],
]);
}

View File

@@ -78,10 +78,15 @@ abstract class AbstractToolbarForm extends AbstractType
/**
* @param FormBuilderInterface $builder
* @param null|string $label
*/
protected function addVisibilityChoice(FormBuilderInterface $builder)
protected function addVisibilityChoice(FormBuilderInterface $builder, ?string $label = null)
{
$builder->add('visibility', VisibilityType::class, []);
$builder->add('visibility', VisibilityType::class, [
'required' => false,
'placeholder' => null,
'label' => $label
]);
}
/**

View File

@@ -36,7 +36,8 @@ class InvoiceToolbarForm extends AbstractToolbarForm
protected function addTemplateChoice(FormBuilderInterface $builder)
{
$builder->add('template', InvoiceTemplateType::class, [
'required' => true
'required' => false,
'placeholder' => null,
]);
}

View File

@@ -40,6 +40,8 @@ class TimesheetToolbarForm extends AbstractToolbarForm
{
$builder->add('state', ChoiceType::class, [
'label' => 'label.entryState',
'required' => false,
'placeholder' => null,
'choices' => [
'entryState.all' => TimesheetQuery::STATE_ALL,
'entryState.running' => TimesheetQuery::STATE_RUNNING,

View File

@@ -24,7 +24,7 @@ class UserToolbarForm extends AbstractToolbarForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addPageSizeChoice($builder);
$this->addVisibilityChoice($builder);
$this->addVisibilityChoice($builder, 'label.active');
$this->addUserRoleChoice($builder);
}

View File

@@ -10,7 +10,7 @@
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
@@ -24,9 +24,9 @@ class YesNoType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => ['yes' => true, 'no' => false],
'multiple' => false,
'expanded' => true,
'value' => true,
'false_values' => [null, 0, false],
'required' => false,
]);
}
@@ -35,6 +35,6 @@ class YesNoType extends AbstractType
*/
public function getParent()
{
return ChoiceType::class;
return CheckboxType::class;
}
}

View File

@@ -12,6 +12,7 @@ namespace App\Form;
use App\Entity\User;
use App\Form\Type\YesNoType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -27,26 +28,21 @@ class UserEditType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// string - length 160
->add('alias', TextType::class, [
'label' => 'label.alias',
'required' => false,
])
// string - length 50
->add('title', TextType::class, [
'label' => 'label.title',
'required' => false,
])
// string - length 255
->add('avatar', TextType::class, [
'label' => 'label.avatar',
'required' => false,
])
// string - length 160
->add('email', TextType::class, [
->add('email', EmailType::class, [
'label' => 'label.email',
])
// boolean
->add('enabled', YesNoType::class, [
'label' => 'label.active',
])

View File

@@ -317,19 +317,24 @@ class TimesheetRepository extends AbstractRepository
->setParameter('user', $query->getUser());
}
if (TimesheetQuery::STATE_RUNNING == $query->getState()) {
$qb->andWhere($qb->expr()->isNull('t.end'));
} elseif (TimesheetQuery::STATE_STOPPED == $query->getState()) {
$qb->andWhere($qb->expr()->isNotNull('t.end'));
}
if (null !== $query->getBegin()) {
$qb->andWhere('t.begin >= :begin')
->setParameter('begin', $query->getBegin());
}
if (null !== $query->getEnd()) {
$qb->andWhere('t.end <= :end')
if (TimesheetQuery::STATE_RUNNING == $query->getState()) {
$qb->andWhere($qb->expr()->isNull('t.end'));
}
if (TimesheetQuery::STATE_STOPPED == $query->getState()) {
$qb->andWhere($qb->expr()->isNotNull('t.end'));
if (null !== $query->getEnd()) {
$qb->andWhere('t.end <= :end')
->setParameter('end', $query->getEnd());
}
} elseif (null !== $query->getBegin()) {
$qb->andWhere('t.begin <= :end')
->setParameter('end', $query->getEnd());
}

View File

@@ -7,7 +7,6 @@
{% endif %}
</h3>
</div>
{% form_theme form 'form/kimai-theme.html.twig' %}
{{ form_start(form) }}
<div class="box-body">
{{ form_widget(form) }}

View File

@@ -1,15 +1,9 @@
{% extends "@AdminLTE/layout/form-theme.html.twig" %}
{# Adds the help icon, including a link to the documentation #}
{% block form_label %}
{% if form.vars.docu_chapter is defined and form.vars.docu_chapter is not empty %}
<a href="{{ path('help_chapter', {'chapter': form.vars.docu_chapter}) }}"><i class="{{ 'help'|icon }}"></i></a>
{% endif %}
{{ parent() }}
{% endblock form_label %}
{% block choice_widget_collapsed %}
{% if 'data-api-url' in attr|keys %}
{% set attr = attr|merge({'data-api-url': path(attr['data-api-url']|first, attr['data-api-url']|last)}) %}
{% endif %}
{{ parent() }}
{% endblock %}

View File

@@ -1,25 +0,0 @@
{% extends "@AdminLTE/layout/form-theme.html.twig" %}
{% block date_widget %}
{% if widget == 'single_text' %}
{% if attr.class is defined %}
{% set class = attr.class ~ ' timepicker' %}
{% else %}
{% set class = ' timepicker' %}
{% endif %}
{% set class = class ~ ' datepicker' %}
{% set attr = attr|merge({'class' : class}) %}
{{ block('form_widget_simple') }}
{% else %}
{% set date_pattern = '<div class="row">' ~ date_pattern ~ '</div>'|raw %}
{{ date_pattern|replace({
'{{ year }}' : '<div class="col-xs-4">{{ year }}</div>',
'{{ month }}' : '<div class="col-xs-4">{{ month }}</div>',
'{{ day }}' : '<div class="col-xs-4">{{ day }}</div>',
})|raw|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw }}
{% endif %}
{% endblock %}

View File

@@ -1,11 +1,9 @@
{% macro toolbar(form, collapsible, displayInitial) %}
<div class="toolbar no-print">
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
{% form_theme form 'form/toolbar-theme.html.twig' %}
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% if collapsible %}</div>{% endif %}
</div>
<div class="toolbar no-print">
{% if collapsible %}<div class="collapse{% if displayInitial %} collapse.show in{% endif %}" id="{{ collapsible }}">{% endif %}
{{ form_start(form, { 'attr': {'class': 'navbar-form'}}) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% if collapsible %}</div>{% endif %}
</div>
{% endmacro %}

View File

@@ -240,13 +240,11 @@ class ActivityControllerTest extends ControllerBaseTest
'activity_edit_form' => [
'name' => '',
'project' => 0,
'visible' => 3,
]
],
[
'#activity_edit_form_name',
'#activity_edit_form_project',
'#activity_edit_form_visible',
]
],
];

View File

@@ -212,7 +212,6 @@ class CustomerControllerTest extends ControllerBaseTest
[
'customer_edit_form' => [
'name' => '',
'visible' => 3,
'country' => '00', // TODO why does it not fail?
'currency' => '00', // TODO why does it not fail?
'timezone' => 'XXX'
@@ -220,7 +219,6 @@ class CustomerControllerTest extends ControllerBaseTest
],
[
'#customer_edit_form_name',
'#customer_edit_form_visible',
//'#customer_edit_form_country',
//'#customer_edit_form_currency',
'#customer_edit_form_timezone',

View File

@@ -236,13 +236,11 @@ class ProjectControllerTest extends ControllerBaseTest
'project_edit_form' => [
'name' => '',
'customer' => 0,
'visible' => 3,
]
],
[
'#project_edit_form_name',
'#project_edit_form_customer',
'#project_edit_form_visible',
]
],
];

View File

@@ -132,14 +132,12 @@ class UserControllerTest extends ControllerBaseTest
'title' => '34rtwrtewrt',
'avatar' => 'asdfawer',
'email' => 'ydfbvsdfgs',
'enabled' => '3',
]
],
[
'#user_create_username',
'#user_create_plainPassword_first',
'#user_create_email',
'#user_create_enabled',
]
],
];

View File

@@ -210,8 +210,14 @@ abstract class ControllerBaseTest extends WebTestCase
$this->assertNotNull($field, 'Could not find form field: ' . $name);
$list = $field->nextAll();
$this->assertNotNull($list, 'Form field has no validation message: ' . $name);
$validation = $list->filter('li.text-danger');
$this->assertGreaterThanOrEqual(1, count($validation), 'Form field has no validation message: ' . $name);
if (count($validation) < 1) {
// decorated form fields with icon have a different html structure, see kimai-theme.html.twig
$classes = $field->parents()->getNode(1)->getAttribute('class');
$this->assertContains('has-feedback', $classes, 'Form field has no validation message: ' . $name);
$this->assertContains('has-error', $classes, 'Form field has no validation message: ' . $name);
}
}
}

View File

@@ -83,7 +83,7 @@ class ProfileControllerTest extends ControllerBaseTest
'title' => 'Code Monkey',
'avatar' => '/fake/image.jpg',
'email' => 'updated@example.com',
'enabled' => 0,
'enabled' => false,
]
]);

View File

@@ -10,6 +10,10 @@
<source>End date must not be earlier then start date.</source>
<target>Das Enddatum darf nicht vor dem Startdatum liegen.</target>
</trans-unit>
<trans-unit id="The begin date cannot be in the future.">
<source>The begin date cannot be in the future.</source>
<target>Das Startdatum darf nicht in der Zukunft liegen.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -10,6 +10,10 @@
<source>End date must not be earlier then start date.</source>
<target>End date must not be earlier then start date.</target>
</trans-unit>
<trans-unit id="The begin date cannot be in the future.">
<source>The begin date cannot be in the future.</source>
<target>The begin date cannot be in the future.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -1,16 +1,18 @@
# Kimai documentation
Welcome to the official Kimai v2 documentation.
This is the official Kimai v2 documentation.
We know there is a lot missing right now and would appreciate [any help](https://github.com/kevinpapst/kimai2/pulls) in writing.
If you need information which is not available right now, [ask us](https://github.com/kevinpapst/kimai2/issues) and we
try to add it as soon as possible.
Most parts of Kimai should be self-explanatory, but if you miss any information please [let us know](https://github.com/kevinpapst/kimai2/issues)
and we will add it as soon as possible.
## User manual
For the most parts Kimai usage should be self-explanatory, so we will only cover topics here which were
[requested](https://github.com/kevinpapst/kimai2/issues) by the community.
- [Invoices](invoices.md) - export your data to different formats with customizable templates
- [Timesheets](timesheet.md) - information about timesheets
- [User and Security](users.md) - docu for user and security topics, like authentication, registration and roles
## Admins and Developers
- [Configurations](configurations.md) - intro into the global application configs
- [Calendar](calendar.md) - the Timesheet calendar view
- [Dashboard & widgets](dashboard.md) - how to configure widgets and dashboard sections
@@ -19,11 +21,9 @@ For the most parts Kimai usage should be self-explanatory, so we will only cover
- [Developer](developers.md) - docs about extending Kimai's feature set
- [API](developers_api.md) - how to use the JSON API
- [Theme settings](theme.md) - theme related settings
- [FAQ](faq.md) - some answers to frequently asked questions
- [Installation](installation.md) - instructions about several installation methods
- [Update Kimai](updates.md) - updating an existing Kimai 2 installation to the latest version
- [Docker](docker.md) - using the built-in Dockerfile for development
- [Importing data](migration_v1.md) - how to import your timesheets from Kimai v1
- [Invoices](invoices.md) - export your data to different formats with customizable templates
- [Timesheets](timesheet.md) - information about timesheets
- [Translations](translations.md) - all about languages and translations
- [User and Security](users.md) - docu for user and security topics, like authentication, registration and roles
- [FAQ](faq.md) - some answers to frequently asked questions