From bcaab582cc7de0d17c04be3f6b0bb7eb09d98995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Tue, 27 Aug 2024 12:38:19 +0200 Subject: [PATCH] feat: filter documents --- assets/controllers/multiselect_controller.js | 1 - .../Document/IndexController.php | 9 +- .../Common/DocumentFilterController.php | 54 +++++ src/Form/DocumentFilterType.php | 64 ++++++ src/Model/DocumentFilterDto.php | 61 +++++ src/Repository/UploadRepository.php | 36 ++- src/Service/Common/AbstractFilterHandler.php | 60 +++++ .../Common/AssignmentFilterHandler.php | 49 +--- src/Service/Common/DocumentFilterHandler.php | 49 ++++ src/Service/Common/TeamerFilterHandler.php | 49 +--- .../administrative/document/index.html.twig | 216 ++++++++++-------- .../common/modal_document_filter.html.twig | 19 ++ 12 files changed, 476 insertions(+), 191 deletions(-) create mode 100644 src/Controller/Common/DocumentFilterController.php create mode 100644 src/Form/DocumentFilterType.php create mode 100644 src/Model/DocumentFilterDto.php create mode 100644 src/Service/Common/AbstractFilterHandler.php create mode 100644 src/Service/Common/DocumentFilterHandler.php create mode 100644 templates/common/modal_document_filter.html.twig diff --git a/assets/controllers/multiselect_controller.js b/assets/controllers/multiselect_controller.js index 40b0163..a822d63 100644 --- a/assets/controllers/multiselect_controller.js +++ b/assets/controllers/multiselect_controller.js @@ -31,7 +31,6 @@ export default class extends Controller { } clickOutside(e) { - e.preventDefault() this.dropdownTarget.classList.toggle('hidden', true) } } \ No newline at end of file diff --git a/src/Controller/Administrative/Document/IndexController.php b/src/Controller/Administrative/Document/IndexController.php index b7440d4..c0cf949 100644 --- a/src/Controller/Administrative/Document/IndexController.php +++ b/src/Controller/Administrative/Document/IndexController.php @@ -3,6 +3,7 @@ namespace App\Controller\Administrative\Document; use App\Repository\UploadRepository; +use App\Service\Common\DocumentFilterHandler; use Knp\Component\Pager\PaginatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -14,7 +15,8 @@ class IndexController extends AbstractController { public function __construct( private readonly UploadRepository $uploadRepository, - private readonly PaginatorInterface $paginator + private readonly PaginatorInterface $paginator, + private readonly DocumentFilterHandler $filterHandler ) { } @@ -22,9 +24,11 @@ class IndexController extends AbstractController #[IsGranted('ROLE_ADMINISTRATIVE')] public function index(Request $request): Response { + $filterDto = $this->filterHandler->getFilterSettings(); + $query = $this ->uploadRepository - ->getUploadListQuery() + ->getUploadListQuery($filterDto) ; $pagination = $this->paginator->paginate( @@ -35,6 +39,7 @@ class IndexController extends AbstractController return $this->render('administrative/document/index.html.twig', [ 'pagination' => $pagination, + 'filterDto' => $filterDto, ]); } } \ No newline at end of file diff --git a/src/Controller/Common/DocumentFilterController.php b/src/Controller/Common/DocumentFilterController.php new file mode 100644 index 0000000..ed7a38e --- /dev/null +++ b/src/Controller/Common/DocumentFilterController.php @@ -0,0 +1,54 @@ +filterHandler->getFilterSettings(); + + $form = $this->createForm(DocumentFilterType::class, $formData); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->filterHandler->handleRequest($form); + $returnUrl = $this->getReturnUrl($request, 'app_administrative_document_index'); + + return new HxRedirectResponse($returnUrl); + } + + return $this->render('common/modal_document_filter.html.twig', [ + 'filterForm' => $form->createView(), + 'filterDto' => $form->getData(), + ]); + } + + #[Route('/common/document/filter/reset', name: 'app_common_document_filter_reset')] + public function reset(Request $request): Response + { + $this->filterHandler->resetFilterSettings(); + $returnUrl = $this->getReturnUrl($request, 'app_administrative_document_index'); + + return $this->redirect($returnUrl); + } +} \ No newline at end of file diff --git a/src/Form/DocumentFilterType.php b/src/Form/DocumentFilterType.php new file mode 100644 index 0000000..ba6a97c --- /dev/null +++ b/src/Form/DocumentFilterType.php @@ -0,0 +1,64 @@ +add('teamer', AutocompleteEntityType::class, [ + 'label' => 'Teamer', + 'class' => Teamer::class, + 'label_property' => 'name', + 'label_function' => fn(Teamer $teamer) => (string) $teamer, + 'endpoint_route' => 'app_admin_autocomplete_teamer', + ]) + ->add('type', ChoiceType::class, [ + 'label' => 'Typ', + 'required' => false, + 'placeholder' => 'nicht filtern', + 'choices' => [ + 'Honorarnote' => Upload::TYPE_INVOICE, + 'Honorarvertrag' => Upload::TYPE_CONTRACT, + ], + ]) + ->add('dateFrom', DatepickerType::class, [ + 'label' => 'Einsatzzeitraum von', + 'attr' => [ + 'placeholder' => 'nicht filtern', + ], + ]) + ->add('dateTo', DatepickerType::class, [ + 'label' => 'Einsatzzeitraum bis', + 'attr' => [ + 'placeholder' => 'nicht filtern', + ], + ]) + ->add('apply', SubmitType::class, [ + 'label' => 'filtern', + ]) + ->add('reset', SubmitType::class, [ + 'label' => 'reset', + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => DocumentFilterDto::class, + ]) + ; + } +} \ No newline at end of file diff --git a/src/Model/DocumentFilterDto.php b/src/Model/DocumentFilterDto.php new file mode 100644 index 0000000..cf77739 --- /dev/null +++ b/src/Model/DocumentFilterDto.php @@ -0,0 +1,61 @@ +teamer; + } + + public function setTeamer(?Teamer $teamer): static + { + $this->teamer = $teamer; + + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): static + { + $this->type = $type; + + 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; + } +} \ No newline at end of file diff --git a/src/Repository/UploadRepository.php b/src/Repository/UploadRepository.php index b8547de..39fb9a3 100644 --- a/src/Repository/UploadRepository.php +++ b/src/Repository/UploadRepository.php @@ -3,6 +3,7 @@ namespace App\Repository; use App\Entity\Upload; +use App\Model\DocumentFilterDto; use App\Repository\Traits\QueryHelperTrait; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query; @@ -78,11 +79,11 @@ class UploadRepository extends ServiceEntityRepository return $data; } - public function getUploadListQuery(): Query + public function getUploadListQuery(DocumentFilterDto $filterDto): Query { $qb = $this->createQueryBuilder('upload'); - return $qb + $qb ->select('upload', 'disposition', 'assignment', 'job_profile', 'destination', 'teamer') ->innerJoin('upload.disposition', 'disposition') ->innerJoin('disposition.assignment', 'assignment') @@ -92,8 +93,37 @@ class UploadRepository extends ServiceEntityRepository ->addOrderBy('FIELD(upload.status, :new, :checked)', 'ASC') ->setParameter('new', Upload::STATUS_NEW) ->setParameter('checked', Upload::STATUS_CHECKED) - ->getQuery() ; + + if (null !== $teamer = $filterDto->getTeamer()) { + $qb + ->andWhere($qb->expr()->eq('upload.teamer', ':teamer')) + ->setParameter('teamer', $teamer) + ; + } + + if (null !== $type = $filterDto->getType()) { + $qb + ->andWhere($qb->expr()->eq('upload.type', ':type')) + ->setParameter('type', $type) + ; + } + + if (null !== $dateFrom = $filterDto->getDateFrom()) { + $qb + ->andWhere($qb->expr()->gte('destination.dateFrom', ':dateFrom')) + ->setParameter('dateFrom', $dateFrom) + ; + } + + if (null !== $dateTo = $filterDto->getDateTo()) { + $qb + ->andWhere($qb->expr()->lte('destination.dateTo', ':dateTo')) + ->setParameter('dateTo', $dateTo) + ; + } + + return $qb->getQuery(); } public function getNew(int $limit = 5): array diff --git a/src/Service/Common/AbstractFilterHandler.php b/src/Service/Common/AbstractFilterHandler.php new file mode 100644 index 0000000..9fe8a74 --- /dev/null +++ b/src/Service/Common/AbstractFilterHandler.php @@ -0,0 +1,60 @@ +namespace = $namespace; + + return $this; + } + + public function handleRequest(FormInterface $form): AbstractFilterDto + { + $filterDto = $form->getData(); + + if ($form->get('reset')->isClicked()) { + $filterDto = new AssignmentFilterDto(); + $this->resetFilterSettings(); + } elseif ($form->get('apply')->isClicked()) { + $this->saveFilterSettings($filterDto); + } + + return $filterDto; + } + + public function resetFilterSettings(): void + { + $this->getSession()->remove($this->namespace); + } + + public function getFilterSettings(): AbstractFilterDto + { + } + + protected function saveFilterSettings(AbstractFilterDto $filterDto): void + { + } + + protected function getSession(): SessionInterface + { + return $this->requestStack->getSession(); + } +} \ No newline at end of file diff --git a/src/Service/Common/AssignmentFilterHandler.php b/src/Service/Common/AssignmentFilterHandler.php index 884a221..8d58a06 100644 --- a/src/Service/Common/AssignmentFilterHandler.php +++ b/src/Service/Common/AssignmentFilterHandler.php @@ -3,47 +3,12 @@ namespace App\Service\Common; use App\Entity\JobProfile; +use App\Model\AbstractFilterDto; use App\Model\AssignmentFilterDto; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -class AssignmentFilterHandler +class AssignmentFilterHandler extends AbstractFilterHandler { - private string $namespace = 'filter:assignment'; - - public function __construct( - private readonly RequestStack $requestStack, - private readonly EntityManagerInterface $entityManager - ) { - } - - public function setNamespace(string $namespace): static - { - $this->namespace = $namespace; - - return $this; - } - - public function handleRequest(FormInterface $form): AssignmentFilterDto - { - $filterDto = $form->getData(); - - if ($form->get('reset')->isClicked()) { - $filterDto = new AssignmentFilterDto(); - $this->resetFilterSettings(); - } elseif ($form->get('apply')->isClicked()) { - $this->saveFilterSettings($filterDto); - } - - return $filterDto; - } - - public function resetFilterSettings(): void - { - $this->getSession()->remove($this->namespace); - } + protected string $namespace = 'filter:assignment'; public function getFilterSettings(): AssignmentFilterDto { @@ -83,8 +48,9 @@ class AssignmentFilterHandler return $filterDto; } - private function saveFilterSettings(AssignmentFilterDto $filterDto): void + protected function saveFilterSettings(AbstractFilterDto $filterDto): void { + /** @var AssignmentFilterDto $filterDto */ $this->getSession()->set($this->namespace, [ 'date_from' => $filterDto->getDateFrom(), 'date_to' => $filterDto->getDateTo(), @@ -97,9 +63,4 @@ class AssignmentFilterHandler 'include_past' => $filterDto->isIncludePast(), ]); } - - private function getSession(): SessionInterface - { - return $this->requestStack->getSession(); - } } \ No newline at end of file diff --git a/src/Service/Common/DocumentFilterHandler.php b/src/Service/Common/DocumentFilterHandler.php new file mode 100644 index 0000000..66bfb50 --- /dev/null +++ b/src/Service/Common/DocumentFilterHandler.php @@ -0,0 +1,49 @@ +getSession()->get($this->namespace)) { + return new DocumentFilterDto(); + } + + $filterDto = new DocumentFilterDto(); + + $filterDto + ->setType($data['type']) + ->setDateFrom($data['date_from']) + ->setDateTo($data['date_to']) + ; + + if (isset($data['teamer_id'])) { + $teamer = $this + ->entityManager + ->getRepository(Teamer::class) + ->find($data['teamer_id']) + ; + $filterDto->setTeamer($teamer); + } + + return $filterDto; + } + + protected function saveFilterSettings(AbstractFilterDto $filterDto): void + { + /** @var DocumentFilterDto $filterDto */ + $this->getSession()->set($this->namespace, [ + 'teamer_id' => $filterDto->getTeamer()?->getId(), + 'type' => $filterDto->getType(), + 'date_from' => $filterDto->getDateFrom(), + 'date_to' => $filterDto->getDateTo(), + ]); + } +} \ No newline at end of file diff --git a/src/Service/Common/TeamerFilterHandler.php b/src/Service/Common/TeamerFilterHandler.php index a6c0cd8..c76f410 100644 --- a/src/Service/Common/TeamerFilterHandler.php +++ b/src/Service/Common/TeamerFilterHandler.php @@ -3,47 +3,12 @@ namespace App\Service\Common; use App\Entity\JobProfile; +use App\Model\AbstractFilterDto; use App\Model\TeamerFilterDto; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -class TeamerFilterHandler +class TeamerFilterHandler extends AbstractFilterHandler { - private string $namespace = 'filter:teamer'; - - public function __construct( - private readonly RequestStack $requestStack, - private readonly EntityManagerInterface $entityManager - ) { - } - - public function setNamespace(string $namespace): static - { - $this->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); - } + protected string $namespace = 'filter:teamer'; public function getFilterSettings(): TeamerFilterDto { @@ -74,8 +39,9 @@ class TeamerFilterHandler return $filterDto; } - private function saveFilterSettings(TeamerFilterDto $filterDto): void + protected function saveFilterSettings(AbstractFilterDto $filterDto): void { + /** @var TeamerFilterDto $filterDto */ $this->getSession()->set($this->namespace, [ 'name' => $filterDto->getName(), 'available_at' => $filterDto->getAvailableAt(), @@ -88,9 +54,4 @@ class TeamerFilterHandler }, $filterDto->getJobProfiles()), ]); } - - private function getSession(): SessionInterface - { - return $this->requestStack->getSession(); - } } \ No newline at end of file diff --git a/templates/administrative/document/index.html.twig b/templates/administrative/document/index.html.twig index 44be4f5..554faa0 100644 --- a/templates/administrative/document/index.html.twig +++ b/templates/administrative/document/index.html.twig @@ -3,104 +3,126 @@ {% block title %}Dokumentenübersicht{% endblock %} {% block content %} -

+
+

Dokumentenübersicht

-
-
- - - - - - - - - - - - - - {% for upload in pagination %} - {% set disposition = upload.disposition %} - {% set assignment = disposition.assignment %} - - - - - - - - - - {% else %} - - - - {% endfor %} - -
- {{ knp_pagination_sortable(pagination, 'Einsatz­zeitraum', 'destination.dateFrom') }} - - {{ knp_pagination_sortable(pagination, 'Destination', 'destination.product') }} - - {{ knp_pagination_sortable(pagination, 'Jobprofil', 'job_profile.name') }} - - {{ knp_pagination_sortable(pagination, 'Dokument', 'upload.type') }} - - {{ knp_pagination_sortable(pagination, 'Teamer:in', 'teamer.lastName') }} - - {{ knp_pagination_sortable(pagination, 'Status', 'upload.status') }} -
- {{ assignment.destination.dateFrom|date('d.m.Y') }} - - - {{ assignment.destination.dateTo|date('d.m.Y') }} - -
- {{ icon('flag-' ~ assignment.destination.country, 'w-5 h-5 shrink-0') }} -
- {{ assignment.destination.product }} -
- {{ assignment.destination.hotel }} -
-
-
- {{ assignment.jobProfile.name }} - - {{ ('label.document.type.' ~ upload.type)|trans }} - - {{ disposition.teamer }} - - {{ upload.status|upload_status_badge }} - -
- {% if is_granted('DELETE', upload) %} - - {% endif %} - {% if is_granted('CHECK', upload) %} - - {% endif %} - - {{ icon('eye') }} - -
-
- Keine Daten... -
- {{ knp_pagination_render(pagination) }} -
+
+ + {% if filterDto.active %} + + Reset + + {% endif %}
+
+
+
+ + + + + + + + + + + + + + {% for upload in pagination %} + {% set disposition = upload.disposition %} + {% set assignment = disposition.assignment %} + + + + + + + + + + {% else %} + + + + {% endfor %} + +
+ {{ knp_pagination_sortable(pagination, 'Einsatz­zeitraum', 'destination.dateFrom') }} + + {{ knp_pagination_sortable(pagination, 'Destination', 'destination.product') }} + + {{ knp_pagination_sortable(pagination, 'Jobprofil', 'job_profile.name') }} + + {{ knp_pagination_sortable(pagination, 'Dokument', 'upload.type') }} + + {{ knp_pagination_sortable(pagination, 'Teamer:in', 'teamer.lastName') }} + + {{ knp_pagination_sortable(pagination, 'Status', 'upload.status') }} +
+ {{ assignment.destination.dateFrom|date('d.m.Y') }} + - + {{ assignment.destination.dateTo|date('d.m.Y') }} + +
+ {{ icon('flag-' ~ assignment.destination.country, 'w-5 h-5 shrink-0') }} +
+ {{ assignment.destination.product }} +
+ {{ assignment.destination.hotel }} +
+
+
+ {{ assignment.jobProfile.name }} + + {{ ('label.document.type.' ~ upload.type)|trans }} + + {{ disposition.teamer }} + + {{ upload.status|upload_status_badge }} + +
+ {% if is_granted('DELETE', upload) %} + + {% endif %} + {% if is_granted('CHECK', upload) %} + + {% endif %} + + {{ icon('eye') }} + +
+
+ Keine Daten... +
+ {{ knp_pagination_render(pagination) }} +
+
{% endblock %} \ No newline at end of file diff --git a/templates/common/modal_document_filter.html.twig b/templates/common/modal_document_filter.html.twig new file mode 100644 index 0000000..ee4047f --- /dev/null +++ b/templates/common/modal_document_filter.html.twig @@ -0,0 +1,19 @@ +{% extends 'htmx_modal.html.twig' %} + +{% block title %}Dokumente filtern{% endblock %} + +{% block content %} + {{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }} +
+ {{ form_row(filterForm.type) }} + {{ form_row(filterForm.teamer) }} + {{ form_row(filterForm.dateFrom) }} + {{ form_row(filterForm.dateTo) }} +
+
+ {{ form_widget(filterForm.apply, { 'attr': { 'class': 'btn' } }) }} + {{ form_widget(filterForm.reset, { 'attr': { 'class': 'btn btn--secondary' } }) }} +
+ {{ form_rest(filterForm) }} + {{ form_end(filterForm) }} +{% endblock %} \ No newline at end of file