handle hidden activities #33 (#58)

* refactored toolbars forms for re-usage #33
This commit is contained in:
Kevin Papst
2018-01-09 12:05:45 +01:00
committed by GitHub
parent a3e7a484d1
commit 964eaea2d0
20 changed files with 351 additions and 168 deletions

View File

@@ -14,22 +14,47 @@
{% macro label_role(role) %}
{% import _self as macro %}
{{ macro.label(role, 'primary') }}
{% if role == 'ROLE_SUPER_ADMIN' %}
{{ macro.label(role, 'danger') }}
{% else %}
{{ macro.label(role, 'primary') }}
{% endif %}
{% endmacro %}
{% macro label_activity(role) %}
{% macro username(user) %}
{{ user.alias|default(user.username) }}
{% endmacro %}
{% macro label_user(user) %}
{% import _self as macro %}
{{ macro.label(role, 'primary') }}
{{ macro.label(macro.username(user), 'primary') }}
{% endmacro %}
{% macro label_activity(activity) %}
{% import _self as macro %}
{% if activity.visible and activity.project.visible and activity.project.customer.visible %}
{{ macro.label(activity.name, 'primary', activity.project.customer.name ~ ': ' ~ activity.project.name) }}
{% else %}
{{ macro.label(activity.name, 'warning', activity.project.customer.name ~ ': ' ~ activity.project.name) }}
{% endif %}
{% endmacro %}
{% macro label_project(project) %}
{% import _self as macro %}
{{ macro.label(project.name, 'primary') }}
{% if project.visible and project.customer.visible %}
{{ macro.label(project.name, 'primary', project.customer.name) }}
{% else %}
{{ macro.label(project.name, 'warning', project.customer.name) }}
{% endif %}
{% endmacro %}
{% macro label_customer(customer) %}
{% import _self as macro %}
{{ macro.label(customer.name, 'primary') }}
{% if customer.visible %}
{{ macro.label(customer.name, 'primary') }}
{% else %}
{{ macro.label(customer.name, 'warning') }}
{% endif %}
{% endmacro %}
{% macro badge_counter(count, url) %}
@@ -40,9 +65,9 @@
{% endif %}
{% endmacro %}
{% macro label(title, type) %}
{% macro label(title, type, tooltip) %}
{# success, warning, danger, primary #}
<span class="label label-{{ type|default('success') }}">{{ title|trans }}</span>
<span {% if tooltip %}title="{{ tooltip }}" {% endif %}class="label label-{{ type|default('success') }}">{{ title|trans }}</span>
{% endmacro %}
{% macro badge(title, color) %}

View File

@@ -28,6 +28,10 @@ class BaseQuery
const RESULT_TYPE_PAGER = 'PagerFanta';
const RESULT_TYPE_QUERYBUILDER = 'QueryBuilder';
/**
* @var \stdClass
*/
protected $hiddenEntity;
/**
* @var int
*/
@@ -146,4 +150,23 @@ class BaseQuery
}
return $this;
}
/**
* @return \stdClass
*/
public function getHiddenEntity()
{
return $this->hiddenEntity;
}
/**
* @param \stdClass $hiddenEntity
* @return BaseQuery
*/
public function setHiddenEntity($hiddenEntity)
{
$this->hiddenEntity = $hiddenEntity;
return $this;
}
}

View File

@@ -18,7 +18,14 @@ namespace AppBundle\Repository\Query;
*/
trait VisibilityTrait
{
/**
* @var integer
*/
protected $visibility = self::SHOW_VISIBLE;
/**
* @var bool
*/
protected $exclusiveVisibility = false;
/**
* @return int
@@ -39,4 +46,25 @@ trait VisibilityTrait
}
return $this;
}
/**
* @return bool
*/
public function isExclusiveVisibility()
{
return $this->exclusiveVisibility;
}
/**
* If set to true, this will ONLY filter the visibility on the main queried object.
*
* @param bool $exclusiveVisibility
* @return VisibilityTrait
*/
public function setExclusiveVisibility($exclusiveVisibility)
{
$this->exclusiveVisibility = (bool) $exclusiveVisibility;
return $this;
}
}

View File

@@ -22,7 +22,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use TimesheetBundle\Entity\Customer;
use TimesheetBundle\Entity\Project;
use TimesheetBundle\Form\ActivityEditForm;
use TimesheetBundle\Form\ActivityToolbarForm;
use TimesheetBundle\Form\Toolbar\ActivityToolbarForm;
use TimesheetBundle\Repository\Query\ActivityQuery;
/**
@@ -41,7 +41,7 @@ class ActivityController extends AbstractController
*/
protected function getQueryForRequest(Request $request)
{
$visibility = $request->get('visibility');
$visibility = $request->get('visibility', ActivityQuery::SHOW_VISIBLE);
if (strlen($visibility) == 0 || (int)$visibility != $visibility) {
$visibility = ActivityQuery::SHOW_BOTH;
}
@@ -70,6 +70,7 @@ class ActivityController extends AbstractController
->setVisibility($visibility)
->setCustomer($customer)
->setProject($project)
->setExclusiveVisibility(true)
;
return $query ;

View File

@@ -16,7 +16,7 @@ use TimesheetBundle\Entity\Customer;
use TimesheetBundle\Entity\Project;
use TimesheetBundle\Entity\Timesheet;
use Symfony\Component\HttpFoundation\Request;
use TimesheetBundle\Form\TimesheetToolbarForm;
use TimesheetBundle\Form\Toolbar\TimesheetToolbarForm;
use TimesheetBundle\Repository\Query\TimesheetQuery;
use TimesheetBundle\Repository\TimesheetRepository;

View File

@@ -179,17 +179,18 @@ class LoadFixtures extends AppBundleLoadFixtures
$allCustomer = $this->getCustomers();
shuffle($allCustomer);
$i = 0;
$i = 1;
foreach ($allCustomer as $customerName) {
$visible = $i++ % 6 != 0;
$entry = new Customer();
$entry
->setCurrency($this->getRandomCurrency())
->setVat(rand(0, 30))
->setName($customerName)
->setName($customerName . ($visible ? '' : '.'))
->setAddress($this->getRandomLocation())
->setComment($this->getRandomPhrase())
->setVisible($i++ % 3 != 0)
->setVisible($visible)
->setTimezone($allTimezones[rand(1, $amountTimezone)]);
$manager->persist($entry);
@@ -203,15 +204,16 @@ class LoadFixtures extends AppBundleLoadFixtures
foreach ($allCustomer as $id => $customer) {
$projectForCustomer = rand(0, 7);
for ($i = 0; $i < $projectForCustomer; $i++) {
for ($i = 1; $i <= $projectForCustomer; $i++) {
$visible = $i % 5 != 0;
$entry = new Project();
$entry
->setName($this->getRandomProject())
->setName($this->getRandomProject() . ($visible ? '' : '.'))
->setBudget(rand(500, 100000))
->setComment($this->getRandomPhrase())
->setCustomer($customer)
->setVisible($i % 3 != 0);
->setVisible($visible);
$manager->persist($entry);
}
@@ -225,13 +227,14 @@ class LoadFixtures extends AppBundleLoadFixtures
foreach ($allProject as $projectId => $project) {
$activityCount = rand(0, 10);
for ($i = 0; $i < $activityCount; $i++) {
for ($i = 1; $i <= $activityCount; $i++) {
$visible = $i % 4 != 0;
$entry = new Activity();
$entry
->setName($this->getRandomActivity() . ($visible ? '' : '.'))
->setProject($project)
->setName($this->getRandomActivity())
->setComment($this->getRandomPhrase())
->setVisible($i % 3 != 0);
->setVisible($visible);
$manager->persist($entry);
}
@@ -266,7 +269,11 @@ class LoadFixtures extends AppBundleLoadFixtures
'Eating',
'Watching TV',
'Talking',
'Cooking'
'Cooking',
'Writing',
'Reading',
'Brainstroming',
'Post Processing',
];
}
@@ -297,6 +304,14 @@ class LoadFixtures extends AppBundleLoadFixtures
'Software Upgrade',
'Office Management',
'Project X',
'Customer Excellence',
'Crazy Monkey',
'Interface Design',
'Human Ressources',
'Book Release',
'Studio Photography',
'Professional Art',
'Video Production',
];
}

View File

@@ -1,67 +0,0 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form;
use AppBundle\Form\Type\PageSizeType;
use AppBundle\Form\Type\VisibilityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Repository\Query\CustomerQuery;
/**
* Defines the form used for filtering the customer.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class CustomerToolbarForm extends AbstractType
{
/**
* Dirty hack to enable easy handling of GET form in controller and javascript.
*Cleans up the name of all form elents (and unfortunately of the form itself).
*
* @return null|string
*/
public function getBlockPrefix()
{
return '';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var CustomerQuery $query */
$query = $options['data'];
$builder
->add('pageSize', PageSizeType::class, [
'required' => false,
])
->add('visibility', VisibilityType::class, [
'required' => false,
])
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => CustomerQuery::class,
'csrf_protection' => false,
]);
}
}

View File

@@ -20,6 +20,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Entity\Customer;
use TimesheetBundle\Entity\Timesheet;
use TimesheetBundle\Form\Type\ActivityGroupedWithCustomerNameType;
use TimesheetBundle\Repository\ActivityRepository;
/**
* Defines the form used to manipulate Timesheet entries.
@@ -34,6 +35,14 @@ class TimesheetEditForm extends AbstractType
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var Timesheet $entry */
$entry = $options['data'];
$activity = null;
if ($entry->getId() !== null) {
$activity = $entry->getActivity();
}
$builder
// datetime
->add('begin', DateTimeType::class, [
@@ -46,16 +55,12 @@ class TimesheetEditForm extends AbstractType
'date_widget' => 'single_text',
'required' => false,
])
// integer
/*
// User
->add('user', UserType::class, [
'label' => 'label.user',
])
*/
// Activity
->add('activity', ActivityGroupedWithCustomerNameType::class, [
'label' => 'label.activity',
'query_builder' => function (ActivityRepository $repo) use ($activity) {
return $repo->builderForEntityType($activity);
},
])
// customer
->add('description', TextareaType::class, [

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form\Toolbar;
use Symfony\Component\Form\AbstractType;
/**
* Defines the base form used for all toolbars.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
abstract class AbstractToolbarForm extends AbstractType
{
/**
* Dirty hack to enable easy handling of GET form in controller and javascript.
*Cleans up the name of all form elents (and unfortunately of the form itself).
*
* @return null|string
*/
public function getBlockPrefix()
{
return '';
}
}

View File

@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form;
namespace TimesheetBundle\Form\Toolbar;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -29,11 +29,11 @@ class ActivityToolbarForm extends ProjectToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
/** @var ActivityQuery $query */
$query = $options['data'];
parent::buildForm($builder, $options);
if ($query->getCustomer() === null) {
return;
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form\Toolbar;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Repository\Query\CustomerQuery;
/**
* Defines the form used for filtering the customer.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class CustomerToolbarForm extends VisibilityToolbarForm
{
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => CustomerQuery::class,
'csrf_protection' => false,
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form\Toolbar;
use AppBundle\Form\Type\PageSizeType;
use Symfony\Component\Form\FormBuilderInterface;
use TimesheetBundle\Repository\Query\CustomerQuery;
/**
* Defines the base form used for all toolbars with pageSizes.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class PagedToolbarForm extends AbstractToolbarForm
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var CustomerQuery $query */
$query = $options['data'];
$builder
->add('pageSize', PageSizeType::class, [
'required' => false,
])
;
}
}

View File

@@ -9,11 +9,13 @@
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form;
namespace TimesheetBundle\Form\Toolbar;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Form\Type\CustomerType;
use TimesheetBundle\Repository\CustomerRepository;
use TimesheetBundle\Repository\Query\CustomerQuery;
use TimesheetBundle\Repository\Query\ProjectQuery;
/**
@@ -21,7 +23,7 @@ use TimesheetBundle\Repository\Query\ProjectQuery;
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ProjectToolbarForm extends CustomerToolbarForm
class ProjectToolbarForm extends VisibilityToolbarForm
{
/**
@@ -29,13 +31,17 @@ class ProjectToolbarForm extends CustomerToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var ProjectQuery $query */
$query = $options['data'];
parent::buildForm($builder, $options);
$builder
->add('customer', CustomerType::class, [
'required' => false,
'query_builder' => function (CustomerRepository $repo) {
$query = new CustomerQuery();
$query->setVisibility(CustomerQuery::SHOW_BOTH); // this field is the reason for the query here
$query->setResultType(CustomerQuery::RESULT_TYPE_QUERYBUILDER);
return $repo->findByQuery($query);
},
])
;
}

View File

@@ -9,16 +9,12 @@
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form;
namespace TimesheetBundle\Form\Toolbar;
use AppBundle\Form\Type\PageSizeType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Form\Type\ActivityType;
use TimesheetBundle\Form\Type\CustomerType;
use TimesheetBundle\Form\Type\ProjectType;
use TimesheetBundle\Repository\Query\TimesheetQuery;
/**
@@ -26,31 +22,15 @@ use TimesheetBundle\Repository\Query\TimesheetQuery;
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class TimesheetToolbarForm extends AbstractType
class TimesheetToolbarForm extends ActivityToolbarForm
{
/**
* Dirty hack to enable easy handling of GET form in controller and javascript.
*Cleans up the name of all form elents (and unfortunately of the form itself).
*
* @return null|string
*/
public function getBlockPrefix()
{
return '';
}
/**
* {@inheritdoc}
* @inheritdoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var TimesheetQuery $query */
$query = $options['data'];
$builder
->add('pageSize', PageSizeType::class, [
'required' => false,
])
->add('state', ChoiceType::class, [
'label' => 'label.entryState',
'choices' => [
@@ -59,35 +39,11 @@ class TimesheetToolbarForm extends AbstractType
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
],
])
->add('customer', CustomerType::class, [
'required' => false,
])
;
parent::buildForm($builder, $options);
$this->addActivityChoice($builder, $options['data']);
$this->addProjectChoice($builder, $query);
$this->addActivityChoice($builder, $query);
}
/**
* @param FormBuilderInterface $builder
* @param TimesheetQuery $query
*/
protected function addProjectChoice(FormBuilderInterface $builder, TimesheetQuery $query)
{
if ($query->getCustomer() === null) {
return;
}
$choices = [];
foreach ($query->getCustomer()->getProjects() as $project) {
$choices[] = $project;
}
$builder
->add('project', ProjectType::class, [
'required' => false,
'choices' => $choices,
]);
$builder->remove('visibility');
}
/**
@@ -103,7 +59,6 @@ class TimesheetToolbarForm extends AbstractType
$choices = [];
foreach ($query->getProject()->getActivities() as $activity) {
$choices[] = $activity;
//$choices[$activity->getName()] = $activity->getId();
}
$builder

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace TimesheetBundle\Form\Toolbar;
use AppBundle\Form\Type\VisibilityType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Defines the form used for filtering entities with a "visibility" field.
*
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class VisibilityToolbarForm extends PagedToolbarForm
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('visibility', VisibilityType::class, [
'required' => false,
])
;
}
}

View File

@@ -29,6 +29,15 @@ class ActivityGroupedWithCustomerNameType extends ActivityType
*/
public function groupBy(Activity $activity, $key, $index)
{
return $activity->getProject()->getCustomer()->getName() . ': ' . $activity->getProject()->getName();
return $activity->getProject()->getCustomer()->getName();
}
/**
* @param Activity $activity
* @return string
*/
public function choiceLabel(Activity $activity)
{
return $activity->getProject()->getName() . ': ' . $activity->getName();
}
}

View File

@@ -16,7 +16,6 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use TimesheetBundle\Entity\Activity;
use TimesheetBundle\Repository\ActivityRepository;
use TimesheetBundle\Repository\Query\ActivityQuery;
/**
* Custom form field type to select an activity.
@@ -26,6 +25,22 @@ use TimesheetBundle\Repository\Query\ActivityQuery;
class ActivityType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function groupBy(Activity $activity, $key, $index)
{
return '[' . $activity->getProject()->getId() . '] ' . $activity->getProject()->getName();
}
/**
* {@inheritdoc}
*/
public function choiceLabel(Activity $activity)
{
return $activity->getName();
}
/**
* {@inheritdoc}
*/
@@ -34,15 +49,10 @@ class ActivityType extends AbstractType
$resolver->setDefaults([
'label' => 'label.activity',
'class' => 'TimesheetBundle:Activity',
'choice_label' => 'name',
'group_by' => function (Activity $activity, $key, $index) {
return '[' . $activity->getProject()->getId() . '] ' . $activity->getProject()->getName();
},
'choice_label' => [$this, 'choiceLabel'],
'group_by' => [$this, 'groupBy'],
'query_builder' => function (ActivityRepository $repo) {
$query = new ActivityQuery();
$query->setVisibility(ActivityQuery::SHOW_BOTH);
$query->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER);
return $repo->findByQuery($query);
return $repo->builderForEntityType(null);
},
]);
}

View File

@@ -50,6 +50,9 @@ class ActivityRepository extends AbstractRepository
->join('a.project', 'p')
->join('p.customer', 'c')
->where($qb->expr()->isNotNull('t.end'))
->andWhere('a.visible = 1')
->andWhere('p.visible = 1')
->andWhere('c.visible = 1')
->groupBy('a.id')
->orderBy('t.end', 'DESC')
->setMaxResults(10)
@@ -93,6 +96,20 @@ class ActivityRepository extends AbstractRepository
return $stats;
}
/**
* Returns a query builder that is used for ActivityType and your own 'query_builder' option.
*
* @param Activity|null $entity
* @return \Doctrine\ORM\QueryBuilder
*/
public function builderForEntityType(Activity $entity = null)
{
$query = new ActivityQuery();
$query->setHiddenEntity($entity);
$query->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER);
return $this->findByQuery($query);
}
/**
* @param ActivityQuery $query
* @return \Doctrine\ORM\QueryBuilder|\Pagerfanta\Pagerfanta
@@ -108,11 +125,19 @@ class ActivityRepository extends AbstractRepository
->orderBy('a.' . $query->getOrderBy(), $query->getOrder());
if ($query->getVisibility() == ActivityQuery::SHOW_VISIBLE) {
if (!$query->isExclusiveVisibility()) {
$qb->andWhere('c.visible = 1');
$qb->andWhere('p.visible = 1');
}
$qb->andWhere('a.visible = 1');
// TODO check for visibility of customer and project
/** @var Activity $entity */
$entity = $query->getHiddenEntity();
if ($entity !== null) {
$qb->orWhere('a.id = :activity')->setParameter('activity', $entity);
}
} elseif ($query->getVisibility() == ActivityQuery::SHOW_HIDDEN) {
$qb->andWhere('a.visible = 0');
// TODO check for visibility of customer and project
}
if ($query->getProject() !== null) {

View File

@@ -36,8 +36,10 @@
<td>&dash;</td>
<td>&dash;</td>
{% endif %}
<td class="hidden-xs hidden-sm">{{ widgets.label_activity(entry.activity.name) }}</td>
<td class="hidden-xs"><a href="{{ path('profile'|route_alias, {'username' : entry.user.username}) }}">{{ entry.user.username }}</a></td>
<td class="hidden-xs hidden-sm">
<a href="{{ path('admin_activity_edit', {'id': entry.activity.id}) }}">{{ widgets.label_activity(entry.activity) }}</a>
</td>
<td class="hidden-xs"><a href="{{ path('profile'|route_alias, {'username' : entry.user.username}) }}">{{ widgets.label_user(entry.user) }}</a></td>
<td class="hidden-xs hidden-sm">{{ entry.description }}</td>
<td>
{% if entry.end %}

View File

@@ -35,7 +35,7 @@
<td class="hidden-xs"><i>{{ entry.duration|duration }}</i></td>
<td class="hidden-xs">&dash;</td>
{% endif %}
<td class="hidden-xs hidden-sm">{{ widgets.label_activity(entry.activity.name) }}</td>
<td class="hidden-xs hidden-sm">{{ widgets.label_activity(entry.activity) }}</td>
<td class="hidden-xs hidden-sm">{{ entry.description }}</td>
<td>
{% if entry.end %}