From eb53e2a843b489d7ea1712067b0335ca2b5ef8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 25 Oct 2023 09:37:40 +0200 Subject: [PATCH] Feat: Add teamer filter --- .../Admin/Teamer/IndexController.php | 7 +- .../Common/TeamerFilterController.php | 62 +++++++++++ src/Form/TeamerFilterType.php | 70 ++++++++++++ src/Model/AbstractFilterDto.php | 27 +++++ src/Model/AssignmentFilterDto.php | 38 ++----- src/Model/TeamerFilterDto.php | 100 ++++++++++++++++++ src/Repository/TeamerRepository.php | 65 +++++++++++- .../Common/AssignmentFilterHandler.php | 8 +- src/Service/Common/TeamerFilterHandler.php | 94 ++++++++++++++++ templates/admin/teamer/index.html.twig | 31 +++++- templates/common/teamer_filter.html.twig | 16 +++ 11 files changed, 475 insertions(+), 43 deletions(-) create mode 100644 src/Controller/Common/TeamerFilterController.php create mode 100644 src/Form/TeamerFilterType.php create mode 100644 src/Model/AbstractFilterDto.php create mode 100644 src/Model/TeamerFilterDto.php create mode 100644 src/Service/Common/TeamerFilterHandler.php create mode 100644 templates/common/teamer_filter.html.twig diff --git a/src/Controller/Admin/Teamer/IndexController.php b/src/Controller/Admin/Teamer/IndexController.php index 34c0670..041998a 100644 --- a/src/Controller/Admin/Teamer/IndexController.php +++ b/src/Controller/Admin/Teamer/IndexController.php @@ -3,6 +3,7 @@ namespace App\Controller\Admin\Teamer; use App\Repository\TeamerRepository; +use App\Service\Common\TeamerFilterHandler; use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -13,6 +14,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class IndexController extends AbstractController { public function __construct( + protected readonly TeamerFilterHandler $filterHandler, private readonly TeamerRepository $teamerRepository, private readonly PaginatorInterface $paginator ) { @@ -22,9 +24,11 @@ class IndexController extends AbstractController #[IsGranted('ROLE_ADMINISTRATIVE')] public function index(Request $request): Response { + $filterDto = $this->filterHandler->getFilterSettings(); + $query = $this ->teamerRepository - ->getListQuery() + ->getListQuery($filterDto) ; $pagination = $this->paginator->paginate( @@ -39,6 +43,7 @@ class IndexController extends AbstractController return $this->render('admin/teamer/index.html.twig', [ 'pagination' => $pagination, + 'filterDto' => $filterDto, ]); } } \ No newline at end of file diff --git a/src/Controller/Common/TeamerFilterController.php b/src/Controller/Common/TeamerFilterController.php new file mode 100644 index 0000000..bb4ccf4 --- /dev/null +++ b/src/Controller/Common/TeamerFilterController.php @@ -0,0 +1,62 @@ +generateUrl('app_common_teamer_filter', ['r' => $request->query->get('r')]); + $formData = $this->filterHandler->getFilterSettings(); + $form = $this->createForm(TeamerFilterType::class, $formData, [ + 'action' => $formAction, + 'ajax_submit' => true, + ]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->filterHandler->handleRequest($form); + $returnUrl = $this->getReturnUrl($request, 'app_admin_teamer_index'); + $response->setCloseAndRedirect($returnUrl); + } else { + $response->setContent($this->renderView('common/teamer_filter.html.twig', [ + 'filterForm' => $form, + 'filterDto' => $form->getData(), + ])); + } + + return $this->json($response); + } + + #[Route('/common/teamer/filter/reset', name: 'app_common_teamer_filter_reset')] + public function reset(Request $request): Response + { + $this->filterHandler->resetFilterSettings(); + $returnUrl = $this->getReturnUrl($request, 'app_admin_teamer_index'); + + return $this->redirect($returnUrl); + } +} \ No newline at end of file diff --git a/src/Form/TeamerFilterType.php b/src/Form/TeamerFilterType.php new file mode 100644 index 0000000..4f573ab --- /dev/null +++ b/src/Form/TeamerFilterType.php @@ -0,0 +1,70 @@ +add('name', TextType::class, [ + 'label' => 'Name', + 'required' => false, + ]) + ->add('dateFrom', DatepickerType::class, [ + 'label' => 'verfügbar ab', + ]) + ->add('dateTo', DatepickerType::class, [ + 'label' => 'verfügbar bis', + ]) + ->add('pickupId', BpnPickupType::class, [ + 'label' => 'Buszustieg', + 'required' => false, + ]) + ->add('skiLicense', CheckboxType::class, [ + 'label' => 'Skilehrer:innen-Lizenz', + 'required' => false, + ]) + ->add('snowboardLicense', CheckboxType::class, [ + 'label' => 'Snowboardlehrer:innen-Lizenz', + 'required' => false, + ]) + ->add('jobProfile', EntityType::class, [ + 'label' => 'Job-Profil', + 'class' => JobProfile::class, + 'required' => false, + 'choice_label' => 'name', + 'query_builder' => function (EntityRepository $repository) { + return $repository->createQueryBuilder('job_profile') + ->orderBy('job_profile.name', 'ASC'); + }, + ]) + ->add('apply', SubmitType::class, [ + 'label' => 'filtern', + ]) + ->add('reset', SubmitType::class, [ + 'label' => 'reset', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => TeamerFilterDto::class, + ]) + ; + } +} \ No newline at end of file diff --git a/src/Model/AbstractFilterDto.php b/src/Model/AbstractFilterDto.php new file mode 100644 index 0000000..41c4cf8 --- /dev/null +++ b/src/Model/AbstractFilterDto.php @@ -0,0 +1,27 @@ +getActiveFiltersCount() > 0; + } + + public function getActiveFiltersCount(): int + { + $freshInstance = new static(); + $filterCount = 0; + $reflection = new \ReflectionClass(static::class); + + foreach ($reflection->getProperties() as $property) { + $name = $property->getName(); + if ($this->$name !== $freshInstance->$name) { + ++$filterCount; + } + } + + return $filterCount; + } +} \ No newline at end of file diff --git a/src/Model/AssignmentFilterDto.php b/src/Model/AssignmentFilterDto.php index bf8068d..988665f 100644 --- a/src/Model/AssignmentFilterDto.php +++ b/src/Model/AssignmentFilterDto.php @@ -5,39 +5,18 @@ namespace App\Model; use App\Entity\Destination; use App\Entity\JobProfile; -class AssignmentFilterDto +class AssignmentFilterDto extends AbstractFilterDto { public const DURATION_WEEKEND = 1; public const DURATION_MID_WEEK = 2; public const DURATION_FULL_WEEK = 3; public const DURATION_MORE = 4; - private ?\DateTimeImmutable $dateFrom = null; - private ?\DateTimeImmutable $dateTo = null; - private ?JobProfile $jobProfile = null; - private ?Destination $destination = null; - private ?int $duration = null; - - public function isActive(): bool - { - return $this->getActiveFiltersCount() > 0; - } - - public function getActiveFiltersCount(): int - { - $freshInstance = new static(); - $filterCount = 0; - $reflection = new \ReflectionClass(static::class); - - foreach ($reflection->getProperties() as $property) { - $name = $property->getName(); - if ($this->$name !== $freshInstance->$name) { - ++$filterCount; - } - } - - return $filterCount; - } + protected ?\DateTimeImmutable $dateFrom = null; + protected ?\DateTimeImmutable $dateTo = null; + protected ?JobProfile $jobProfile = null; + protected ?Destination $destination = null; + protected ?int $duration = null; public function getDateFrom(): ?\DateTimeImmutable { @@ -98,9 +77,4 @@ class AssignmentFilterDto return $this; } - - public function isReset(): bool - { - return $this->reset; - } } \ No newline at end of file diff --git a/src/Model/TeamerFilterDto.php b/src/Model/TeamerFilterDto.php new file mode 100644 index 0000000..14b74a7 --- /dev/null +++ b/src/Model/TeamerFilterDto.php @@ -0,0 +1,100 @@ +name; + } + + public function setName(?string $name): static + { + $this->name = $name; + + return $this; + } + + public function getDateFrom(): ?\DateTimeImmutable + { + return $this->dateFrom; + } + + public function setDateFrom(?\DateTimeImmutable $dateFrom): static + { + $this->dateFrom = $dateFrom; + + return $this; + } + + public function getDateTo(): ?\DateTimeImmutable + { + return $this->dateTo; + } + + public function setDateTo(?\DateTimeImmutable $dateTo): static + { + $this->dateTo = $dateTo; + + return $this; + } + + public function getJobProfile(): ?JobProfile + { + return $this->jobProfile; + } + + public function setJobProfile(?JobProfile $jobProfile): static + { + $this->jobProfile = $jobProfile; + + return $this; + } + + public function getPickupId(): string + { + return $this->pickupId; + } + + public function setPickupId(string $pickupId): static + { + $this->pickupId = $pickupId; + + return $this; + } + + public function hasSkiLicense(): bool + { + return $this->skiLicense; + } + + public function setSkiLicense(bool $skiLicense): static + { + $this->skiLicense = $skiLicense; + + return $this; + } + + public function hasSnowboardLicense(): bool + { + return $this->snowboardLicense; + } + + public function setSnowboardLicense(bool $snowboardLicense): static + { + $this->snowboardLicense = $snowboardLicense; + + return $this; + } +} \ No newline at end of file diff --git a/src/Repository/TeamerRepository.php b/src/Repository/TeamerRepository.php index f09099c..7da027a 100644 --- a/src/Repository/TeamerRepository.php +++ b/src/Repository/TeamerRepository.php @@ -2,7 +2,10 @@ namespace App\Repository; +use App\Entity\License; use App\Entity\Teamer; +use App\Model\TeamerFilterDto; +use App\Repository\Traits\QueryHelperTrait; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; @@ -17,18 +20,74 @@ use Doctrine\Persistence\ManagerRegistry; */ class TeamerRepository extends ServiceEntityRepository { + use QueryHelperTrait; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Teamer::class); } - public function getListQuery(): Query + public function getListQuery(TeamerFilterDto $filterDto): Query { $qb = $this->createQueryBuilder('teamer'); - return $qb + $qb ->select('teamer', 'user') + ->leftJoin('teamer.availabilities', 'availability') + ->leftJoin('teamer.jobProfiles', 'job_profile') + ->leftJoin('teamer.licenses', 'license') ->leftJoin('teamer.user', 'user') - ->getQuery(); + ; + + if (null !== $filterDto->getName()) { + $qb + ->andWhere($qb->expr()->like('teamer.lastName', ':name')) + ->setParameter('name', '%'.$this->escapeLikeWildcards($filterDto->getName()).'%') + ; + } + + if (null !== $filterDto->getDateFrom()) { + $qb + ->andWhere($qb->expr()->gte('availability.dateFrom', ':dateFrom')) + ->setParameter('dateFrom', $filterDto->getDateFrom()) + ; + } + + if (null !== $filterDto->getDateTo()) { + $qb + ->andWhere($qb->expr()->lte('availability.dateTo', ':dateTo')) + ->setParameter('dateTo', $filterDto->getDateTo()) + ; + } + + if (null !== $filterDto->getJobProfile()) { + $qb + ->andWhere($qb->expr()->eq('job_profile', ':jobProfile')) + ->setParameter('jobProfile', $filterDto->getJobProfile()) + ; + } + + if ('' !== $filterDto->getPickupId()) { + $qb + ->andWhere($qb->expr()->like('teamer.pickups', ':pickup')) + ->setParameter('pickup', '%"'.$this->escapeLikeWildcards($filterDto->getPickupId()).'"%') + ; + } + + if (true === $filterDto->hasSkiLicense()) { + $qb + ->andWhere($qb->expr()->eq('license.type', ':licenseSki')) + ->setParameter('licenseSki', License::TYPE_SKI) + ; + } + + if (true === $filterDto->hasSnowboardLicense()) { + $qb + ->andWhere($qb->expr()->eq('license.type', ':licenseSnowboard')) + ->setParameter('licenseSnowboard', License::TYPE_SNOWBOARD) + ; + } + + return $qb->getQuery(); } } diff --git a/src/Service/Common/AssignmentFilterHandler.php b/src/Service/Common/AssignmentFilterHandler.php index de14b9f..213a638 100644 --- a/src/Service/Common/AssignmentFilterHandler.php +++ b/src/Service/Common/AssignmentFilterHandler.php @@ -55,10 +55,10 @@ class AssignmentFilterHandler $filterDto = new AssignmentFilterDto(); if (isset($data['date_from'])) { - $filterDto->setDateFrom(new \DateTimeImmutable($data['date_from'])); + $filterDto->setDateFrom($data['date_from']); } if (isset($data['date_to'])) { - $filterDto->setDateTo(new \DateTimeImmutable($data['date_to'])); + $filterDto->setDateTo($data['date_to']); } if (isset($data['job_profile']) && 0 < (int) $data['job_profile']) { $jobProfile = $this @@ -86,8 +86,8 @@ class AssignmentFilterHandler private function saveFilterSettings(AssignmentFilterDto $filterDto): void { $this->getSession()->set($this->namespace, [ - 'date_from' => $filterDto->getDateFrom()?->format('Y-m-d'), - 'date_to' => $filterDto->getDateTo()?->format('Y-m-d'), + 'date_from' => $filterDto->getDateFrom(), + 'date_to' => $filterDto->getDateTo(), 'job_profile' => $filterDto->getJobProfile()?->getId(), 'destination' => $filterDto->getDestination()?->getId(), 'duration' => $filterDto->getDuration(), diff --git a/src/Service/Common/TeamerFilterHandler.php b/src/Service/Common/TeamerFilterHandler.php new file mode 100644 index 0000000..311004f --- /dev/null +++ b/src/Service/Common/TeamerFilterHandler.php @@ -0,0 +1,94 @@ +namespace = $namespace; + + return $this; + } + + public function handleRequest(FormInterface $form): TeamerFilterDto + { + $filterDto = $form->getData(); + + if ($form->get('reset')->isClicked()) { + $filterDto = new TeamerFilterDto(); + $this->resetFilterSettings(); + } elseif ($form->get('apply')->isClicked()) { + $this->saveFilterSettings($filterDto); + } + + return $filterDto; + } + + public function resetFilterSettings(): void + { + $this->getSession()->remove($this->namespace); + } + + public function getFilterSettings(): TeamerFilterDto + { + if (null === $data = $this->getSession()->get($this->namespace)) { + return new TeamerFilterDto(); + } + + $filterDto = new TeamerFilterDto(); + + $filterDto + ->setName($data['name']) + ->setDateFrom($data['date_from']) + ->setDateTo($data['date_to']) + ->setPickupId($data['pickup_id']) + ->setSkiLicense($data['ski_license']) + ->setSnowboardLicense($data['snowboard_license']) + ; + + if (0 < (int) $data['job_profile']) { + $jobProfile = $this + ->entityManager + ->getRepository(JobProfile::class) + ->find($data['job_profile']) + ; + $filterDto->setJobProfile($jobProfile); + } + + return $filterDto; + } + + private function saveFilterSettings(TeamerFilterDto $filterDto): void + { + $this->getSession()->set($this->namespace, [ + 'name' => $filterDto->getName(), + 'date_from' => $filterDto->getDateFrom(), + 'date_to' => $filterDto->getDateTo(), + 'pickup_id' => $filterDto->getPickupId(), + 'ski_license' => $filterDto->hasSkiLicense(), + 'snowboard_license' => $filterDto->hasSnowboardLicense(), + 'job_profile' => $filterDto->getJobProfile()?->getId(), + ]); + } + + private function getSession(): SessionInterface + { + return $this->requestStack->getSession(); + } +} \ No newline at end of file diff --git a/templates/admin/teamer/index.html.twig b/templates/admin/teamer/index.html.twig index 2a826dd..ed6a5b0 100644 --- a/templates/admin/teamer/index.html.twig +++ b/templates/admin/teamer/index.html.twig @@ -3,9 +3,34 @@ {% block title %}Teamerübersicht{% endblock %} {% block content %} -

- Teamerübersicht -

+
+

+ Teamerübersicht +

+
+ + {% if filterDto.active %} + + Reset + + {% endif %} +
+
diff --git a/templates/common/teamer_filter.html.twig b/templates/common/teamer_filter.html.twig new file mode 100644 index 0000000..5cc4274 --- /dev/null +++ b/templates/common/teamer_filter.html.twig @@ -0,0 +1,16 @@ +{{ form_start(filterForm) }} +
+ {{ form_row(filterForm.name) }} + {{ form_row(filterForm.dateFrom) }} + {{ form_row(filterForm.dateTo) }} + {{ form_row(filterForm.jobProfile) }} + {{ form_row(filterForm.pickupId) }} + {{ form_row(filterForm.skiLicense) }} + {{ form_row(filterForm.snowboardLicense) }} +
+
+ {{ form_widget(filterForm.apply, { 'attr': { 'class': 'btn' } }) }} + {{ form_widget(filterForm.reset, { 'attr': { 'class': 'btn btn--secondary' } }) }} +
+{{ form_rest(filterForm) }} +{{ form_end(filterForm) }}