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

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());
}