Form and theme improvements (#513)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
templates/invoice/renderer/.~lock*
|
||||
|
||||
/var/data/*.sqlite
|
||||
/var/data/*.sqlite-journal
|
||||
!/var/data/kimai_test.sqlite
|
||||
/var/coverage/
|
||||
/var/cache/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -35,6 +35,12 @@
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.control-label {
|
||||
width: 100%;
|
||||
}
|
||||
.input-group {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
composer.lock
generated
14
composer.lock
generated
@@ -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",
|
||||
|
||||
@@ -2,3 +2,5 @@ twig:
|
||||
#paths: ['%kernel.project_dir%/templates']
|
||||
debug: '%kernel.debug%'
|
||||
strict_variables: '%kernel.debug%'
|
||||
form_themes:
|
||||
- 'form/kimai-theme.html.twig'
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -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',
|
||||
|
||||
85
src/Form/Extension/SelectWithApiDataExtension.php
Normal file
85
src/Form/Extension/SelectWithApiDataExtension.php
Normal 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', []);
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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-']
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,8 @@ class InvoiceToolbarForm extends AbstractToolbarForm
|
||||
protected function addTemplateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('template', InvoiceTemplateType::class, [
|
||||
'required' => true
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
])
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class ProfileControllerTest extends ControllerBaseTest
|
||||
'title' => 'Code Monkey',
|
||||
'avatar' => '/fake/image.jpg',
|
||||
'email' => 'updated@example.com',
|
||||
'enabled' => 0,
|
||||
'enabled' => false,
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user