diff --git a/.changes/unreleased/Feature-20230705-140336.yaml b/.changes/unreleased/Feature-20230705-140336.yaml
new file mode 100644
index 000000000..3ce7f3c0f
--- /dev/null
+++ b/.changes/unreleased/Feature-20230705-140336.yaml
@@ -0,0 +1,5 @@
+kind: Feature
+body: Allow filtering on the basis of a user within general tasks list
+time: 2023-07-05T14:03:36.664880092+02:00
+custom:
+ Issue: ""
diff --git a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php
index f22b6bfba..51d1b3974 100644
--- a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php
+++ b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php
@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Form\Type\ChillDateType;
+use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -114,6 +115,28 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
$builder->add($singleCheckBoxBuilder);
}
+
+ if ([] !== $helper->getUserPickers()) {
+ $userPickersBuilder = $builder->create('user_pickers', null, ['compound' => true]);
+
+ foreach ($helper->getUserPickers() as $name => [
+ 'label' => $label, 'options' => $opts
+ ]) {
+
+ $userPickersBuilder->add(
+ $name,
+ PickUserDynamicType::class,
+ [
+ 'multiple' => true,
+ 'label' => $label,
+ ...$opts,
+ ]
+ );
+ }
+
+ $builder->add($userPickersBuilder);
+ }
+
}
public static function buildCheckboxChoices(array $choices, array $trans = []): array
diff --git a/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig
index b517eb154..adf67b81b 100644
--- a/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig
+++ b/src/Bundle/ChillMainBundle/Resources/views/FilterOrder/base.html.twig
@@ -18,6 +18,7 @@
{% endif %}
+
{% if form.dateRanges is defined %}
{% set btnSubmit = 1 %}
{% if form.dateRanges|length > 0 %}
@@ -40,6 +41,7 @@
{% endfor %}
{% endif %}
{% endif %}
+
{% if form.checkboxes is defined %}
{% set btnSubmit = 1 %}
{% if form.checkboxes|length > 0 %}
@@ -56,6 +58,7 @@
{% endfor %}
{% endif %}
{% endif %}
+
{% if form.entity_choices is defined %}
{% set btnSubmit = 1 %}
{% if form.entity_choices |length > 0 %}
@@ -74,6 +77,25 @@
{% endfor %}
{% endif %}
{% endif %}
+
+ {% if form.user_pickers is defined %}
+ {% set btnSubmit = 1 %}
+ {% if form.user_pickers.children|length > 0 %}
+ {% for name, options in form.user_pickers %}
+
+ {% if form.user_pickers[name].vars.label is not same as(false) %}
+ {{ form_label(form.user_pickers[name]) }}
+ {% else %}
+ {{ form_label(form.user_pickers[name].vars.label) }}
+ {% endif %}
+
+ {{ form_widget(form.user_pickers[name]) }}
+
+
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+
{% if form.single_checkboxes is defined %}
{% set btnSubmit = 1 %}
{% for name, _o in form.single_checkboxes %}
@@ -91,8 +113,10 @@
{% endif %}
+
+
{% if active|length > 0 %}
{% for f in active %}
diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderGetActiveFilterHelper.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderGetActiveFilterHelper.php
index 6b204e552..5b36e52b3 100644
--- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderGetActiveFilterHelper.php
+++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderGetActiveFilterHelper.php
@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
+use Chill\MainBundle\Templating\Entity\UserRender;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPathInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -20,6 +21,7 @@ final readonly class FilterOrderGetActiveFilterHelper
public function __construct(
private TranslatorInterface $translator,
private PropertyAccessorInterface $propertyAccessor,
+ private UserRender $userRender,
) {
}
@@ -73,6 +75,12 @@ final readonly class FilterOrderGetActiveFilterHelper
}
}
+ foreach ($filterOrderHelper->getUserPickers() as $name => ['label' => $label, 'options' => $options]) {
+ foreach ($filterOrderHelper->getUserPickerData($name) as $user) {
+ $result[] = ['value' => $this->userRender->renderString($user, []), 'label' => (string) $label, 'position' => FilterOrderPositionEnum::UserPicker->value, 'name' => $name];
+ }
+ }
+
foreach ($filterOrderHelper->getSingleCheckbox() as $name => ['label' => $label]) {
if (true === $filterOrderHelper->getSingleCheckboxData($name)) {
$result[] = ['label' => '', 'value' => $this->translator->trans($label), 'position' => FilterOrderPositionEnum::SingleCheckbox->value, 'name' => $name];
diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php
index 84939a052..74d9a7a22 100644
--- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php
+++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php
@@ -11,8 +11,11 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
+use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\Listing\FilterOrderType;
use DateTimeImmutable;
+use Symfony\Component\Form\Extension\Core\Type\FormType;
+use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -52,6 +55,11 @@ final class FilterOrderHelper
private array $entityChoices = [];
+ /**
+ * @var array
+ */
+ private array $userPickers = [];
+
public function __construct(
private readonly FormFactoryInterface $formFactory,
private readonly RequestStack $requestStack,
@@ -80,6 +88,14 @@ final class FilterOrderHelper
return $this->entityChoices;
}
+ public function addUserPicker(string $name, ?string $label = null, array $options = []): self
+ {
+ $this->userPickers[$name] = ['label' => $label, 'options' => $options];
+
+ return $this;
+ }
+
+
public function addCheckbox(string $name, array $choices, ?array $default = [], ?array $trans = [], array $options = []): self
{
if ([] === $trans) {
@@ -114,6 +130,19 @@ final class FilterOrderHelper
->handleRequest($this->requestStack->getCurrentRequest());
}
+ public function getUserPickers(): array
+ {
+ return $this->userPickers;
+ }
+
+ /**
+ * @return list
+ */
+ public function getUserPickerData(string $name): array
+ {
+ return $this->getFormData()['user_pickers'][$name];
+ }
+
public function hasCheckboxData(string $name): bool
{
return array_key_exists($name, $this->checkboxes);
@@ -203,7 +232,8 @@ final class FilterOrderHelper
'checkboxes' => [],
'dateRanges' => [],
'single_checkboxes' => [],
- 'entity_choices' => []
+ 'entity_choices' => [],
+ 'user_pickers' => []
];
if ($this->hasSearchBox()) {
@@ -227,6 +257,10 @@ final class FilterOrderHelper
$r['entity_choices'][$name] = ($c['options']['multiple'] ?? true) ? [] : null;
}
+ foreach ($this->userPickers as $name => $u) {
+ $r['user_pickers'][$name] = ($u['options']['multiple'] ?? true) ? [] : null;
+ }
+
return $r;
}
diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php
index f2bded220..56c73871c 100644
--- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php
+++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php
@@ -39,6 +39,11 @@ class FilterOrderHelperBuilder
*/
private array $entityChoices = [];
+ /**
+ * @var array
+ */
+ private array $userPickers = [];
+
public function __construct(
FormFactoryInterface $formFactory,
RequestStack $requestStack,
@@ -85,6 +90,13 @@ class FilterOrderHelperBuilder
return $this;
}
+ public function addUserPicker(string $name, ?string $label = null, ?array $options = []): self
+ {
+ $this->userPickers[$name] = ['label' => $label, 'options' => $options];
+
+ return $this;
+ }
+
public function build(): FilterOrderHelper
{
$helper = new FilterOrderHelper(
@@ -126,6 +138,17 @@ class FilterOrderHelperBuilder
$helper->addDateRange($name, $label, $from, $to);
}
+
+ foreach (
+ $this->userPickers as $name => [
+ 'label' => $label,
+ 'options' => $options
+ ]
+ ) {
+ $helper->addUserPicker($name, $label, $options);
+ }
+
+
return $helper;
}
}
diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderPositionEnum.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderPositionEnum.php
index 09e8d39aa..ed2337f1d 100644
--- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderPositionEnum.php
+++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderPositionEnum.php
@@ -18,4 +18,5 @@ enum FilterOrderPositionEnum: string
case DateRange = 'date_range';
case EntityChoice = 'entity_choice';
case SingleCheckbox = 'single_checkbox';
+ case UserPicker = 'user_picker';
}
diff --git a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
index 2ba9488b6..3523c7a58 100644
--- a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
+++ b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
@@ -26,6 +26,7 @@ use Chill\TaskBundle\Event\TaskEvent;
use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\TaskBundle\Form\SingleTaskType;
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
+use Chill\TaskBundle\Repository\SingleTaskStateRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use LogicException;
use Psr\Log\LoggerInterface;
@@ -71,7 +72,8 @@ final class SingleTaskController extends AbstractController
EventDispatcherInterface $eventDispatcher,
TimelineBuilder $timelineBuilder,
LoggerInterface $logger,
- FilterOrderHelperFactoryInterface $filterOrderHelperFactory
+ FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
+ private SingleTaskStateRepository $singleTaskStateRepository
) {
$this->eventDispatcher = $eventDispatcher;
$this->timelineBuilder = $timelineBuilder;
@@ -299,13 +301,17 @@ final class SingleTaskController extends AbstractController
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
$filterOrder = $this->buildFilterOrder();
+
+ $filteredUsers = $filterOrder->getUserPickerData('userPicker');
+
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
);
$nb = $this->singleTaskAclAwareRepository->countByAllViewable(
$filterOrder->getQueryString(),
- $flags
+ $flags,
+ $filteredUsers
);
$paginator = $this->paginatorFactory->create($nb);
@@ -313,6 +319,7 @@ final class SingleTaskController extends AbstractController
$tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
$filterOrder->getQueryString(),
$flags,
+ $filteredUsers,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
[
@@ -447,7 +454,7 @@ final class SingleTaskController extends AbstractController
{
$this->denyAccessUnlessGranted('ROLE_USER');
- $filterOrder = $this->buildFilterOrder();
+ $filterOrder = $this->buildFilterOrder(false);
$flags = array_merge(
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->getCheckboxData('states'))
@@ -662,7 +669,7 @@ final class SingleTaskController extends AbstractController
return $form;
}
- private function buildFilterOrder(): FilterOrderHelper
+ private function buildFilterOrder($includeFilterByUser = true): FilterOrderHelper
{
$statuses = ['no-alert', 'warning', 'alert'];
$statusTrans = [
@@ -670,17 +677,22 @@ final class SingleTaskController extends AbstractController
'Tasks near deadline',
'Tasks over deadline',
];
- $states = [
- // todo: get a list of possible states dynamically
- 'new', 'in_progress', 'closed', 'canceled',
- ];
+ $states = $this->singleTaskStateRepository->findAllExistingStates();
+ $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['closed', 'canceled', 'validated'], true)));
- return $this->filterOrderHelperFactory
+ $filterBuilder = $this->filterOrderHelperFactory
->create(self::class)
->addSearchBox()
->addCheckbox('status', $statuses, $statuses, $statusTrans)
- ->addCheckbox('states', $states, ['new', 'in_progress'])
- ->build();
+ ->addCheckbox('states', $states, $checked)
+ ;
+
+ if ($includeFilterByUser) {
+ $filterBuilder
+ ->addUserPicker('userPicker', 'Filter by user', ['multiple' => true, 'required' => false]);
+ }
+
+ return $filterBuilder->build();
}
/**
diff --git a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
index 0efc16085..d1652cc89 100644
--- a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
+++ b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
@@ -51,7 +51,8 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function buildBaseQuery(
?string $pattern = null,
- ?array $flags = []
+ ?array $flags = [],
+ ?array $users = []
): QueryBuilder {
$qb = $this->em->createQueryBuilder();
$qb
@@ -62,6 +63,24 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
->setParameter('pattern', '%' . $pattern . '%');
}
+ if (count($users) > 0) {
+ $orXUser = $qb->expr()->orX();
+
+ foreach ($users as $key => $user) {
+ $orXUser->add(
+ $qb->expr()->eq('t.assignee', ':user_' . $key)
+ );
+
+ $qb->setParameter('user_' . $key, $user);
+ }
+
+ if ($orXUser->count() > 0) {
+ $qb->andWhere($orXUser);
+ }
+
+ return $qb;
+ }
+
if (count($flags) > 0) {
$orXDate = $qb->expr()->orX();
$orXState = $qb->expr()->orX();
@@ -183,9 +202,10 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function countByAllViewable(
?string $pattern = null,
- ?array $flags = []
+ ?array $flags = [],
+ ?array $users = []
): int {
- $qb = $this->buildBaseQuery($pattern, $flags);
+ $qb = $this->buildBaseQuery($pattern, $flags, $users);
return $this
->addACLGlobal($qb)
@@ -231,11 +251,12 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
+ ?array $users = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []
): array {
- $qb = $this->buildBaseQuery($pattern, $flags);
+ $qb = $this->buildBaseQuery($pattern, $flags, $users);
$qb = $this->addACLGlobal($qb);
return $this->getResult($qb, $start, $limit, $orderBy);
diff --git a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php
index 57c57e592..7d2870c67 100644
--- a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php
+++ b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php
@@ -18,7 +18,8 @@ interface SingleTaskAclAwareRepositoryInterface
{
public function countByAllViewable(
?string $pattern = null,
- ?array $flags = []
+ ?array $flags = [],
+ ?array $users = []
): int;
public function countByCourse(
@@ -38,6 +39,7 @@ interface SingleTaskAclAwareRepositoryInterface
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
+ ?array $users = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []
diff --git a/src/Bundle/ChillTaskBundle/Repository/SingleTaskStateRepository.php b/src/Bundle/ChillTaskBundle/Repository/SingleTaskStateRepository.php
new file mode 100644
index 000000000..2d64c69a0
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Repository/SingleTaskStateRepository.php
@@ -0,0 +1,47 @@
+
+ * @throws Exception
+ */
+ public function findAllExistingStates(): array
+ {
+ $states = [];
+
+ foreach ($this->connection->fetchAllNumeric(self::FIND_ALL_STATES) as $row) {
+ if ('' !== $row[0] && null !== $row[0]) {
+ $states[] = $row[0];
+ }
+ }
+
+ return $states;
+ }
+
+}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig
index 8fe45959e..b479eb948 100644
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig
@@ -27,8 +27,10 @@
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('page_task_list') }}
+ {{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_task_list') }}
+ {{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/config/services/repositories.yaml b/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
index 7bee5abd0..9681e5d5c 100644
--- a/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
@@ -1,4 +1,8 @@
services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+
chill_task.single_task_repository:
class: Chill\TaskBundle\Repository\SingleTaskRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
@@ -10,8 +14,8 @@ services:
- "@chill.main.security.authorization.helper"
Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository'
- Chill\TaskBundle\Repository\SingleTaskAclAwareRepository:
- autowire: true
- autoconfigure: true
+ Chill\TaskBundle\Repository\SingleTaskAclAwareRepository: ~
Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface: '@Chill\TaskBundle\Repository\SingleTaskAclAwareRepository'
+
+ Chill\TaskBundle\Repository\SingleTaskStateRepository: ~
diff --git a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
index d437baac2..599ca8640 100644
--- a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
@@ -65,6 +65,7 @@ Not assigned: Aucun utilisateur assigné
For person: Pour
By: Par
Any tasks: Aucune tâche
+Filter by user: Filtrer par utilisateur(s)
# transitions - default task definition
"new": "nouvelle"