list calendar by period: date range and query acl aware

This commit is contained in:
Julien Fastré 2022-06-01 23:04:59 +02:00
parent 089c92d0ee
commit c804462f15
11 changed files with 233 additions and 43 deletions

View File

@ -14,11 +14,14 @@ namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Form\CalendarType;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
@ -37,11 +40,11 @@ use Symfony\Component\Serializer\SerializerInterface;
class CalendarController extends AbstractController
{
private AuthorizationHelper $authorizationHelper;
private CalendarRepository $calendarRepository;
private EventDispatcherInterface $eventDispatcher;
private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository;
private FilterOrderHelperFactoryInterface $filterOrderHelperFactory;
private LoggerInterface $logger;
@ -54,18 +57,18 @@ class CalendarController extends AbstractController
private UserRepository $userRepository;
public function __construct(
AuthorizationHelper $authorizationHelper,
CalendarRepository $calendarRepository,
EventDispatcherInterface $eventDispatcher,
CalendarACLAwareRepositoryInterface $calendarACLAwareRepository,
FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
LoggerInterface $logger,
PaginatorFactory $paginator,
RemoteCalendarConnectorInterface $remoteCalendarConnector,
SerializerInterface $serializer,
UserRepository $userRepository
) {
$this->authorizationHelper = $authorizationHelper;
$this->calendarRepository = $calendarRepository;
$this->eventDispatcher = $eventDispatcher;
$this->calendarACLAwareRepository = $calendarACLAwareRepository;
$this->filterOrderHelperFactory = $filterOrderHelperFactory;
$this->logger = $logger;
$this->paginator = $paginator;
$this->remoteCalendarConnector = $remoteCalendarConnector;
@ -193,48 +196,39 @@ class CalendarController extends AbstractController
/**
* Lists all Calendar entities.
*
* @Route("/{_locale}/calendar/calendar/", name="chill_calendar_calendar_list")
* @Route("/{_locale}/calendar/calendar/by-period/{id}", name="chill_calendar_calendar_list_by_period")
*/
public function listAction(Request $request): Response
public function listActionByCourse(AccompanyingPeriod $accompanyingPeriod): Response
{
$view = null;
$filterOrder = $this->buildListFilterOrder();
['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate');
[$user, $accompanyingPeriod] = $this->getEntity($request);
/*
dead code ?
if ($user instanceof User) {
$calendarItems = $this->calendarRepository->findByUser($user);
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
return $this->render($view, [
'calendarItems' => $calendarItems,
'user' => $user,
]);
}
*/
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod);
$total = $this->calendarACLAwareRepository
->countByAccompanyingPeriod($accompanyingPeriod, $from, $to);
$paginator = $this->paginator->create($total);
$calendarItems = $this->calendarRepository->findBy(
['accompanyingPeriod' => $accompanyingPeriod],
$calendarItems = $this->calendarACLAwareRepository->findByAccompanyingPeriod(
$accompanyingPeriod,
$from,
$to,
['startDate' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
$view = '@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig';
return $this->render($view, [
return $this->render('@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig', [
'calendarItems' => $calendarItems,
'accompanyingCourse' => $accompanyingPeriod,
'paginator' => $paginator,
'filterOrder' => $filterOrder,
]);
}
throw new Exception('Unable to list actions.');
private function buildListFilterOrder(): FilterOrderHelper
{
$filterOrder = $this->filterOrderHelperFactory->create(self::class);
$filterOrder->addDateRange('startDate', 'chill_calendar.start date filter', new \DateTimeImmutable('3 days ago'), null);
return $filterOrder->build();
}
/**

View File

@ -37,9 +37,9 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
if ($this->security->isGranted(CalendarVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Calendar'), [
'route' => 'chill_calendar_calendar_list',
'route' => 'chill_calendar_calendar_list_by_period',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
'id' => $period->getId(),
], ])
->setExtras(['order' => 35]);
}

View File

@ -0,0 +1,75 @@
<?php
namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface
{
private EntityManagerInterface $em;
/**
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* @return array|Calendar[]
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array
{
$qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('c');
foreach ($orderBy as $sort => $order) {
$qb->addOrderBy('c.'.$sort, $order);
}
if (null !== $offset) {
$qb->setFirstResult($offset);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $qb->getQuery()->getResult();
}
public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int
{
$qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('count(c)');
return $qb->getQuery()->getSingleScalarResult();
}
public function buildQueryByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): QueryBuilder
{
$qb = $this->em->createQueryBuilder();
$qb->from(Calendar::class, 'c');
$andX = $qb->expr()->andX($qb->expr()->eq('c.accompanyingPeriod', ':period'));
$qb->setParameter('period', $period);
if (null !== $startDate) {
$andX->add($qb->expr()->gte('c.startDate', ':startDate'));
$qb->setParameter('startDate', $startDate);
}
if (null !== $endDate) {
$andX->add($qb->expr()->lte('c.endDate', ':endDate'));
$qb->setParameter('endDate', $endDate);
}
$qb->where($andX);
return $qb;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\QueryBuilder;
interface CalendarACLAwareRepositoryInterface
{
/**
* @return array|Calendar[]
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array;
public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate): int;
}

View File

@ -3,9 +3,8 @@ services:
Chill\CalendarBundle\Repository\:
autowire: true
autoconfigure: true
resource: '../../Repository/'
tags:
- { name: 'doctrine.repository_service' }
Chill\CalendarBundle\Menu\:
autowire: true

View File

@ -8,18 +8,21 @@
{% set accompanying_course_id = accompanyingCourse.id %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_answer') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_answer') }}
{% endblock %}
{% block content %}
<h1>{{ 'Calendar list' |trans }}</h1>
{{ filterOrder|chill_render_filter_order_helper }}
{% if calendarItems|length == 0 %}
{% if calendarItems|length == 0 %}
<p class="chill-no-data-statement">
{{ "There is no calendar items."|trans }}
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create button-small"></a>

View File

@ -43,6 +43,9 @@ chill_calendar:
form:
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
Create for referrer: Créer pour le référent
start date filter: Début du rendez-vous
From: Du
To: Au
remote_ms_graph:
freebusy_statuses:

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
@ -70,10 +71,38 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType
$builder->add($checkboxesBuilder);
}
if (0 < count($helper->getDateRanges())) {
$dateRangesBuilder = $builder->create('dateRanges', null, ['compound' => true]);
foreach ($helper->getDateRanges() as $name => $opts) {
$rangeBuilder = $dateRangesBuilder->create($name, null, [
'compound' => true,
'label' => $opts['label'] ?? $name,
]);
$rangeBuilder->add(
'from',
ChillDateType::class,
['input' => 'datetime_immutable', 'required' => false]
);
$rangeBuilder->add(
'to',
ChillDateType::class,
['input' => 'datetime_immutable', 'required' => false]
);
$dateRangesBuilder->add($rangeBuilder);
}
$builder->add($dateRangesBuilder);
}
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
switch ($key) {
case 'q':
case 'checkboxes' . $key:
case $key . '_from':
case $key . '_to':
break;
case 'page':

View File

@ -10,6 +10,28 @@
</div>
{% endif %}
</div>
{% if form.dateRanges is defined %}
{% if form.dateRanges|length > 0 %}
{% for dateRangeName, _o in form.dateRanges %}
<div class="row gx-2">
<div class="col-md-5">
{{ form_label(form.dateRanges[dateRangeName])}}
</div>
<div class="col-md-6">
<div class="input-group mb-3">
<span class="input-group-text">{{ 'chill_calendar.From'|trans }}</span>
{{ form_widget(form.dateRanges[dateRangeName]['from']) }}
<span class="input-group-text">{{ 'chill_calendar.To'|trans }}</span>
{{ form_widget(form.dateRanges[dateRangeName]['to']) }}
</div>
</div>
<div class="col-md-1">
<button type="submit" class="btn btn-misc"><i class="fa fa-filter"></i></button>
</div>
</div>
{% endfor %}
{% endif %}
{% endif %}
{% if form.checkboxes is defined %}
{% if form.checkboxes|length > 0 %}
{% for checkbox_name, options in form.checkboxes %}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
use Chill\MainBundle\Form\Type\Listing\FilterOrderType;
use \DateTimeImmutable;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
@ -23,6 +24,8 @@ class FilterOrderHelper
{
private array $checkboxes = [];
private array $dateRanges = [];
private FormFactoryInterface $formFactory;
private ?string $formName = 'f';
@ -60,6 +63,13 @@ class FilterOrderHelper
return $this;
}
public function addDateRange(string $name, string $label, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null): self
{
$this->dateRanges[$name] = ['from' => $from, 'to' => $to, 'label' => $label];
return $this;
}
public function buildForm(): FormInterface
{
return $this->formFactory
@ -76,6 +86,19 @@ class FilterOrderHelper
return $this->getFormData()['checkboxes'][$name];
}
public function getDateRanges(): array
{
return $this->dateRanges;
}
/**
* @return array<'to': DateTimeImmutable, 'from': DateTimeImmutable>
*/
public function getDateRangeData(string $name): array
{
return $this->getFormData()['dateRanges'][$name];
}
public function getCheckboxes(): array
{
return $this->checkboxes;
@ -110,6 +133,11 @@ class FilterOrderHelper
$r['checkboxes'][$name] = $c['default'];
}
foreach ($this->dateRanges as $name => $defaults) {
$r['dateRanges'][$name]['from'] = $defaults['from'];
$r['dateRanges'][$name]['to'] = $defaults['to'];
}
return $r;
}

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Listing;
use \DateTimeImmutable;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
@ -18,6 +19,8 @@ class FilterOrderHelperBuilder
{
private array $checkboxes = [];
private array $dateRanges = [];
private FormFactoryInterface $formFactory;
private RequestStack $requestStack;
@ -39,6 +42,13 @@ class FilterOrderHelperBuilder
return $this;
}
public function addDateRange(string $name, string $label, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null): self
{
$this->dateRanges[$name] = ['from' => $from, 'to' => $to, 'label' => $label];
return $this;
}
public function addSearchBox(?array $fields = [], ?array $options = []): self
{
$this->searchBoxFields = $fields;
@ -65,6 +75,16 @@ class FilterOrderHelperBuilder
$helper->addCheckbox($name, $choices, $default, $trans);
}
foreach (
$this->dateRanges as $name => [
'from' => $from,
'to' => $to,
'label' => $label,
]
) {
$helper->addDateRange($name, $label, $from, $to);
}
return $helper;
}
}