Form and theme improvements (#513)
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user