add possibility to generate filter/order elements, with only search box

for now
This commit is contained in:
Julien Fastré 2021-10-08 16:50:31 +02:00
parent e286acf9fe
commit 4d71a1c630
15 changed files with 325 additions and 89 deletions

View File

@ -7,6 +7,7 @@ namespace Chill\AsideActivityBundle\Controller;
use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Entity\AsideActivity;
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
@ -22,9 +23,9 @@ final class AsideActivityController extends CRUDController
$this->categoryRepository = $categoryRepository; $this->categoryRepository = $categoryRepository;
} }
protected function buildQueryEntities(string $action, Request $request) protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null)
{ {
$qb = parent::buildQueryEntities($action, $request); $qb = parent::buildQueryEntities($action, $request, $filterOrder);
if ('index' === $action) { if ('index' === $action) {
$qb->where($qb->expr()->eq('e.agent', ':user')); $qb->where($qb->expr()->eq('e.agent', ':user'));

View File

@ -20,6 +20,8 @@
namespace Chill\MainBundle\CRUD\Controller; namespace Chill\MainBundle\CRUD\Controller;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -251,7 +253,8 @@ class CRUDController extends AbstractController
return $response; return $response;
} }
$totalItems = $this->countEntities($action, $request); $filterOrder = $this->buildFilterOrderHelper($action, $request);
$totalItems = $this->countEntities($action, $request, $filterOrder);
$paginator = $this->getPaginatorFactory()->create($totalItems); $paginator = $this->getPaginatorFactory()->create($totalItems);
$response = $this->onPreIndexBuildQuery($action, $request, $totalItems, $response = $this->onPreIndexBuildQuery($action, $request, $totalItems,
@ -261,7 +264,7 @@ class CRUDController extends AbstractController
return $response; return $response;
} }
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator); $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
$response = $this->onPostIndexFetchQuery($action, $request, $totalItems, $response = $this->onPostIndexFetchQuery($action, $request, $totalItems,
$paginator, $entities); $paginator, $entities);
@ -273,7 +276,8 @@ class CRUDController extends AbstractController
$defaultTemplateParameters = [ $defaultTemplateParameters = [
'entities' => $entities, 'entities' => $entities,
'crud_name' => $this->getCrudName(), 'crud_name' => $this->getCrudName(),
'paginator' => $paginator 'paginator' => $paginator,
'filter_order' => $filterOrder
]; ];
return $this->render( return $this->render(
@ -282,6 +286,11 @@ class CRUDController extends AbstractController
); );
} }
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
{
return null;
}
/** /**
* @param string $action * @param string $action
* @param Request $request * @param Request $request
@ -354,9 +363,9 @@ class CRUDController extends AbstractController
* @param PaginatorInterface $paginator * @param PaginatorInterface $paginator
* @return type * @return type
*/ */
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator) protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
{ {
$query = $this->buildQueryEntities($action, $request) $query = $this->buildQueryEntities($action, $request, $filterOrder)
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
->setMaxResults($paginator->getItemsPerPage()); ->setMaxResults($paginator->getItemsPerPage());
@ -388,9 +397,10 @@ class CRUDController extends AbstractController
* @param PaginatorInterface $paginator * @param PaginatorInterface $paginator
* @return mixed * @return mixed
*/ */
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator,
?FilterOrderHelper $filterOrder = null)
{ {
$query = $this->queryEntities($action, $request, $paginator); $query = $this->queryEntities($action, $request, $paginator, $filterOrder);
return $query->getQuery()->getResult(); return $query->getQuery()->getResult();
} }
@ -402,9 +412,9 @@ class CRUDController extends AbstractController
* @param Request $request * @param Request $request
* @return int * @return int
*/ */
protected function countEntities(string $action, Request $request): int protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
{ {
return $this->buildQueryEntities($action, $request) return $this->buildQueryEntities($action, $request, $filterOrder)
->select('COUNT(e)') ->select('COUNT(e)')
->getQuery() ->getQuery()
->getSingleScalarResult() ->getSingleScalarResult()
@ -1177,6 +1187,11 @@ class CRUDController extends AbstractController
return $this->get(Resolver::class); return $this->get(Resolver::class);
} }
protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
{
return $this->get(FilterOrderHelperFactoryInterface::class);
}
/** /**
* @return array * @return array
*/ */
@ -1191,6 +1206,7 @@ class CRUDController extends AbstractController
EventDispatcherInterface::class => EventDispatcherInterface::class, EventDispatcherInterface::class => EventDispatcherInterface::class,
Resolver::class => Resolver::class, Resolver::class => Resolver::class,
SerializerInterface::class => SerializerInterface::class, SerializerInterface::class => SerializerInterface::class,
FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
] ]
); );
} }

View File

@ -0,0 +1,55 @@
<?php
namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
final class FilterOrderType extends \Symfony\Component\Form\AbstractType
{
private RequestStack $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var FilterOrderHelper $helper */
$helper = $options['helper'];
if ($helper->hasSearchBox()) {
$builder->add('q', SearchType::class, [
'label' => false,
'required' => false
]);
}
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
switch($key) {
case 'q':
continue;
case 'page':
$builder->add($key, HiddenType::class, [
'data' => 1
]);
break;
default:
$builder->add($key, HiddenType::class, [
'data' => $value
]);
break;
}
}
}
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
{
$resolver->setRequired('helper')
->setAllowedTypes('helper', FilterOrderHelper::class);
}
}

View File

@ -4,6 +4,12 @@
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1> <h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
{% endblock index_header %} {% endblock index_header %}
{% block filter_order %}
{% if filter_order is not null %}
{{ filter_order|chill_render_filter_order_helper }}
{% endif %}
{% endblock %}
{% if entities|length == 0 %} {% if entities|length == 0 %}
{% block no_existing_entities %} {% block no_existing_entities %}
<p>{{ no_existing_entities_sentences|default('No entities')|trans }}</p> <p>{{ no_existing_entities_sentences|default('No entities')|trans }}</p>

View File

@ -0,0 +1,12 @@
{{ form_start(form) }}
<div class="chill_filter_order container">
<div class="row">
<div class="col-md-12">
<div class="input-group mb-3">
{{ form_widget(form.q)}}
<button type="submit" class="btn btn-chill-l-gray"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
{{ form_end(form) }}

View File

@ -0,0 +1,60 @@
<?php
namespace Chill\MainBundle\Templating\Listing;
use Chill\MainBundle\Form\Type\Listing\FilterOrderType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class FilterOrderHelper
{
private FormFactoryInterface $formFactory;
private RequestStack $requestStack;
private ?array $searchBoxFields = null;
public function __construct(
FormFactoryInterface $formFactory,
RequestStack $requestStack
) {
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
}
public function setSearchBox($searchBoxFields = null): self
{
$this->searchBoxFields = $searchBoxFields;
return $this;
}
public function hasSearchBox(): bool
{
return $this->searchBoxFields !== null;
}
private function getFormData(): array
{
return [
'q' => $this->getQueryString()
];
}
public function getQueryString(): ?string
{
$q = $this->requestStack->getCurrentRequest()
->query->get('q', null);
return empty($q) ? NULL : $q;
}
public function buildForm($name = null, string $type = FilterOrderType::class, array $options = []): FormInterface
{
return $this->formFactory
->createNamed($name, $type, $this->getFormData(), \array_merge([
'helper' => $this,
'method' => 'GET',
'csrf_protection' => false,
], $options));
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Chill\MainBundle\Templating\Listing;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class FilterOrderHelperBuilder
{
private ?array $searchBoxFields = null;
private FormFactoryInterface $formFactory;
private RequestStack $requestStack;
public function __construct(
FormFactoryInterface $formFactory,
RequestStack $requestStack
) {
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
}
public function addSearchBox(array $fields, ?array $options = []): self
{
$this->searchBoxFields = $fields;
return $this;
}
public function build(): FilterOrderHelper
{
$helper = new FilterOrderHelper(
$this->formFactory,
$this->requestStack
);
$helper->setSearchBox($this->searchBoxFields);
return $helper;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Chill\MainBundle\Templating\Listing;
use Symfony\Component\Form\FormFactoryBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class FilterOrderHelperFactory implements FilterOrderHelperFactoryInterface
{
private FormFactoryInterface $formFactory;
private RequestStack $requestStack;
public function __construct(
FormFactoryInterface $formFactory,
RequestStack $requestStack
) {
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
}
public function create(string $context, ?array $options = []): FilterOrderHelperBuilder
{
return new FilterOrderHelperBuilder($this->formFactory, $this->requestStack);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Chill\MainBundle\Templating\Listing;
interface FilterOrderHelperFactoryInterface
{
public function create(string $context, ?array $options = []): FilterOrderHelperBuilder;
}

View File

@ -0,0 +1,34 @@
<?php
namespace Chill\MainBundle\Templating\Listing;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class Templating extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('chill_render_filter_order_helper', [$this, 'renderFilterOrderHelper'], [
'needs_environment' => true, 'is_safe' => ['html'],
])
];
}
public function renderFilterOrderHelper(
Environment $environment,
FilterOrderHelper $helper,
?string $template = '@ChillMain/FilterOrder/base.html.twig',
?array $options = []
) {
return $environment->render($template, [
'helper' => $helper,
'form' => $helper->buildForm()->createView(),
'options' => $options
]);
}
}

View File

@ -47,3 +47,9 @@ services:
tags: tags:
- { name: 'chill.render_entity' } - { name: 'chill.render_entity' }
Chill\MainBundle\Templating\Listing\:
resource: './../../Templating/Listing'
autoconfigure: true
autowire: true
Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface: '@Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory'

View File

@ -5,6 +5,7 @@ namespace Chill\ThirdPartyBundle\Controller;
use Chill\MainBundle\CRUD\Controller\AbstractCRUDController; use Chill\MainBundle\CRUD\Controller\AbstractCRUDController;
use Chill\MainBundle\CRUD\Controller\CRUDController; use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface; use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
@ -56,15 +57,20 @@ final class ThirdPartyController extends CRUDController
$this->thirdPartyACLAwareRepository = $thirdPartyACLAwareRepository; $this->thirdPartyACLAwareRepository = $thirdPartyACLAwareRepository;
} }
protected function countEntities(string $action, Request $request): int protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
{ {
return $this->thirdPartyACLAwareRepository->countThirdParties(ThirdPartyVoter::SHOW); if (NULL === $filterOrder){
throw new \LogicException('filterOrder should not be null');
} }
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) return $this->thirdPartyACLAwareRepository->countThirdParties(ThirdPartyVoter::SHOW,
$filterOrder->getQueryString());
}
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
{ {
return $this->thirdPartyACLAwareRepository return $this->thirdPartyACLAwareRepository
->listThirdParties(ThirdPartyVoter::class, ['name' => 'ASC'], $paginator->getItemsPerPage(), ->listThirdParties(ThirdPartyVoter::SHOW, $filterOrder->getQueryString(), ['name' => 'ASC'], $paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()); $paginator->getCurrentPageFirstItemNumber());
} }
@ -78,4 +84,12 @@ final class ThirdPartyController extends CRUDController
return null; return null;
} }
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
{
return $this->getFilterOrderHelperFactory()
->create(self::class)
->addSearchBox(['name', 'company_name', 'acronym'])
->build();
}
} }

View File

@ -22,11 +22,12 @@ final class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepository
public function listThirdParties( public function listThirdParties(
string $role, string $role,
?string $filterString,
?array $orderBy = [], ?array $orderBy = [],
?int $limit = null, ?int $limit = null,
?int $offset = null ?int $offset = null
): array { ): array {
$qb = $this->buildQuery($role); $qb = $this->buildQuery($filterString);
foreach ($orderBy as $sort => $direction) { foreach ($orderBy as $sort => $direction) {
$qb->addOrderBy('tp.'.$sort, $direction); $qb->addOrderBy('tp.'.$sort, $direction);
@ -39,15 +40,24 @@ final class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepository
} }
public function countThirdParties( public function countThirdParties(
string $role string $role,
?string $filterString
): int { ): int {
$qb = $this->buildQuery($role); $qb = $this->buildQuery($filterString);
$qb->select('count(tp)'); $qb->select('count(tp)');
return $qb->getQuery()->getSingleScalarResult(); return $qb->getQuery()->getSingleScalarResult();
} }
public function buildQuery(): QueryBuilder { public function buildQuery(?string $filterString = null): QueryBuilder
return $this->thirdPartyRepository->createQueryBuilder('tp'); {
$qb = $this->thirdPartyRepository->createQueryBuilder('tp');
if (NULL !== $filterString) {
$qb->andWhere($qb->expr()->like('tp.canonicalized', 'LOWER(UNACCENT(:filterString))'))
->setParameter('filterString', '%'.$filterString.'%');
}
return $qb;
} }
} }

View File

@ -6,7 +6,7 @@ use Chill\ThirdPartyBundle\Entity\ThirdParty;
interface ThirdPartyACLAwareRepositoryInterface interface ThirdPartyACLAwareRepositoryInterface
{ {
public function countThirdParties(string $role): int; public function countThirdParties(string $role, ?string $filterString): int;
/** /**
* @param string $role * @param string $role
@ -17,8 +17,9 @@ interface ThirdPartyACLAwareRepositoryInterface
*/ */
public function listThirdParties( public function listThirdParties(
string $role, string $role,
?string $filterString,
?array $orderBy = [], ?array $orderBy = [],
int $limit = null, ?int $limit = 0,
int $offset = null ?int $offset = 50
): array; ): array;
} }

View File

@ -12,21 +12,6 @@
{% block table_entities %} {% block table_entities %}
<div class="thirdparty-list my-5"> <div class="thirdparty-list my-5">
{#
<div class="row justify-content-center">
<div class="col-md-10 col-xxl">
{% if third_parties|length == 0 %}
<p class="chill-no-data-statement">{{ 'No third parties'|trans }}</p>
{% else %}
<nav class="filter-actions border border-secondary my-4 p-3">
<i>outils de filtrage</i>
</nav>
</div>
</div>
#}
<div class="row justify-content-center"> <div class="row justify-content-center">
<div> <div>
@ -34,49 +19,11 @@
<span>{{ paginator.totalItems }}</span> {{ 'third parties'|trans }} <span>{{ paginator.totalItems }}</span> {{ 'third parties'|trans }}
</label> </label>
<table class="table table-bordered border-dark table-striped align-middle"> <div class="flex-table">
<thead>
<tr>
<th class="chill-pink" style="width: 35px;"></th>
<th class="chill-pink">{{ 'Name'|trans }}
<i class="fa fa-fw fa-sort"></i>
</th>
<th class="chill-pink">{{ 'Category'|trans }}
<i class="fa fa-fw fa-sort"></i>
</th>
<th class="chill-pink">{{ 'Address'|trans }}
<i class="fa fa-fw fa-sort"></i>
</th>
<th class="chill-pink">{{ 'thirdparty.UpdatedAt.short'|trans }}
<i class="fa fa-fw fa-sort"></i>
</th>
<th class="chill-pink"></th>
</tr>
</thead>
<tbody>
{% for tp in third_parties %} {% for tp in third_parties %}
<tr> <div class="item-bloc">
<th>{{ (tp.active ? '<i class="fa fa-check chill-green">' : '<i class="fa fa-times chill-red">')|raw }}</th> {{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false}) }}
<td> <div class="item-row separator">
{{ tp.name }}
{% if tp.isChild %}<span class="badge bg-info">{{ 'Contact'|trans }}</span>{% endif %}
</td>
{% set types = [] %}
{% for t in tp.types %}
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
{% endfor %}
<td>{{ types|join(', ') }}</td>
<td>
{{ tp.address|chill_entity_render_box({'multiline': false, 'with_valid_from': false}) }}
</td>
<td>
{% if tp.updatedAt != null %}
{{ tp.updatedAt|format_date('short') }}
{% else %}
{{ tp.createdAt|format_date('short') }}
{% endif %}
</td>
<td>
<ul class="record_actions"> <ul class="record_actions">
{% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %} {% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %}
<li> <li>
@ -91,11 +38,11 @@
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</td>
</tr> </div>
</div>
{% endfor %} {% endfor %}
</tbody> </div>
</table>
</div> </div>
</div> </div>
</div> </div>