diff --git a/assets/controllers/multiselect_controller.js b/assets/controllers/multiselect_controller.js new file mode 100644 index 0000000..2dcca0e --- /dev/null +++ b/assets/controllers/multiselect_controller.js @@ -0,0 +1,30 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ 'label', 'dropdown', 'choice' ] + static values = { placeholder: String } + + connect() { + this.update() + } + + toggle() { + this.dropdownTarget.classList.toggle('hidden') + } + + update() { + let labels = [] + + this.choiceTargets.forEach((choice) => { + if (choice.checked) { + labels.push(choice.dataset.label) + } + this.labelTarget.innerText = labels.join(', ') + }) + + if (0 === labels.length) { + this.labelTarget.innerText = this.placeholderValue + } + } +} \ No newline at end of file diff --git a/src/Controller/Administrative/Assignment/CreateController.php b/src/Controller/Administrative/Assignment/CreateController.php index a7837c4..dacab1b 100644 --- a/src/Controller/Administrative/Assignment/CreateController.php +++ b/src/Controller/Administrative/Assignment/CreateController.php @@ -2,6 +2,7 @@ namespace App\Controller\Administrative\Assignment; +use App\Controller\Traits\ReturnUrlTrait; use App\Entity\Assignment; use App\Entity\User; use App\Form\AssignmentType; @@ -15,6 +16,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class CreateController extends AbstractController { + use ReturnUrlTrait; + public function __construct( private readonly EntityManagerInterface $entityManager, private readonly LoggerInterface $logger @@ -29,6 +32,7 @@ class CreateController extends AbstractController $user = $this->getUser(); $assignment = new Assignment(); $assignment->setOwner($user); + $returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index'); $form = $this->createForm( AssignmentType::class, @@ -51,11 +55,12 @@ class CreateController extends AbstractController 'job_profile' => $assignment->getJobProfile()->getName(), ]); - return $this->redirectToRoute('app_administrative_assignment_index'); + return $this->redirectToRoute($returnUrl); } return $this->render('administrative/assignment/create.html.twig', [ 'form' => $form->createView(), + 'returnUrl' => $returnUrl, ]); } } \ No newline at end of file diff --git a/src/Controller/Common/AssignmentFilterController.php b/src/Controller/Common/AssignmentFilterController.php index c39c24b..7f77b3e 100644 --- a/src/Controller/Common/AssignmentFilterController.php +++ b/src/Controller/Common/AssignmentFilterController.php @@ -2,7 +2,6 @@ namespace App\Controller\Common; -use App\Controller\Traits\ReturnUrlTrait; use App\Form\AssignmentFilterType; use App\Htmx\HxRedirectResponse; use App\Repository\AssignmentRepository; @@ -15,8 +14,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted; class AssignmentFilterController extends AbstractController { - use ReturnUrlTrait; - public function __construct( private readonly AssignmentFilterHandler $filterHandler, private readonly AssignmentRepository $assignmentRepository @@ -41,9 +38,8 @@ class AssignmentFilterController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $this->filterHandler->handleRequest($form); - $returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index'); - return new HxRedirectResponse($returnUrl); + return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_index')); } return $this->render('common/modal_assignment_filter.html.twig', [ @@ -56,8 +52,7 @@ class AssignmentFilterController extends AbstractController public function reset(Request $request): Response { $this->filterHandler->resetFilterSettings(); - $returnUrl = $this->getReturnUrl($request, 'app_administrative_assignment_index'); - return $this->redirect($returnUrl); + return $this->redirectToRoute('app_administrative_assignment_index'); } } \ No newline at end of file diff --git a/src/Form/AssignmentFilterType.php b/src/Form/AssignmentFilterType.php index 73ed0d7..501fd72 100644 --- a/src/Form/AssignmentFilterType.php +++ b/src/Form/AssignmentFilterType.php @@ -80,11 +80,11 @@ class AssignmentFilterType extends AbstractType if (0 < count($options['job_profiles'])) { $builder - ->add('jobProfile', EntityType::class, [ + ->add('jobProfiles', MultiselectEntityType::class, [ 'label' => 'Job-Profil', 'class' => JobProfile::class, 'required' => false, - 'placeholder' => 'nicht filtern', + 'empty_label' => 'nicht filtern', 'choice_label' => 'name', 'choices' => $options['job_profiles'], ]) @@ -96,10 +96,10 @@ class AssignmentFilterType extends AbstractType $choices[$item->getHotel()] = $item->getHotelCode(); } $builder - ->add('hotel', ChoiceType::class, [ + ->add('hotels', MultiselectType::class, [ 'label' => 'Haus', 'required' => false, - 'placeholder' => 'nicht filtern', + 'empty_label' => 'nicht filtern', 'choices' => $choices, ]) ; diff --git a/src/Form/AssignmentType.php b/src/Form/AssignmentType.php index 68152f0..c2324ba 100644 --- a/src/Form/AssignmentType.php +++ b/src/Form/AssignmentType.php @@ -67,7 +67,7 @@ class AssignmentType extends AbstractType } ]) ->add('jobProfileInfo', TextareaType::class, [ - 'label' => 'Jobprofil Zusatzinfo', + 'label' => 'Zusatzinfos', 'required' => false, 'attr' => [ 'rows' => 3, diff --git a/src/Form/MultiselectEntityType.php b/src/Form/MultiselectEntityType.php new file mode 100644 index 0000000..7a28c12 --- /dev/null +++ b/src/Form/MultiselectEntityType.php @@ -0,0 +1,36 @@ +vars['empty_label'] = $options['empty_label']; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'multiple' => true, + 'expanded' => true, + 'empty_label' => null, + ]); + } + + public function getBlockPrefix(): string + { + return 'multiselect'; + } +} \ No newline at end of file diff --git a/src/Form/MultiselectType.php b/src/Form/MultiselectType.php new file mode 100644 index 0000000..51526c3 --- /dev/null +++ b/src/Form/MultiselectType.php @@ -0,0 +1,36 @@ +vars['empty_label'] = $options['empty_label']; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'multiple' => true, + 'expanded' => true, + 'empty_label' => null, + ]); + } + + public function getBlockPrefix(): string + { + return 'multiselect'; + } +} \ No newline at end of file diff --git a/src/Model/AssignmentFilterDto.php b/src/Model/AssignmentFilterDto.php index e6d003b..5d50392 100644 --- a/src/Model/AssignmentFilterDto.php +++ b/src/Model/AssignmentFilterDto.php @@ -14,8 +14,8 @@ class AssignmentFilterDto extends AbstractFilterDto protected ?\DateTimeImmutable $dateFrom = null; protected ?\DateTimeImmutable $dateTo = null; - protected ?JobProfile $jobProfile = null; - protected ?string $hotel = null; + protected array $jobProfiles = []; + protected array $hotels = []; protected ?int $duration = null; protected array $status = []; protected bool $includePast = false; @@ -44,26 +44,26 @@ class AssignmentFilterDto extends AbstractFilterDto return $this; } - public function getJobProfile(): ?JobProfile + public function getJobProfiles(): array { - return $this->jobProfile; + return $this->jobProfiles; } - public function setJobProfile(?JobProfile $jobProfile): static + public function setJobProfiles(array $jobProfiles): static { - $this->jobProfile = $jobProfile; + $this->jobProfiles = $jobProfiles; return $this; } - public function getHotel(): ?string + public function getHotels(): ?array { - return $this->hotel; + return $this->hotels; } - public function setHotel(?string $hotel): static + public function setHotels(array $hotels): static { - $this->hotel = $hotel; + $this->hotels = $hotels; return $this; } diff --git a/src/Repository/AssignmentRepository.php b/src/Repository/AssignmentRepository.php index 18f7e3b..b0fe066 100644 --- a/src/Repository/AssignmentRepository.php +++ b/src/Repository/AssignmentRepository.php @@ -137,17 +137,17 @@ class AssignmentRepository extends ServiceEntityRepository ; } - if (null !== $jobProfile = $filterDto->getJobProfile()) { + if ($jobProfiles = $filterDto->getJobProfiles()) { $qb - ->andWhere($qb->expr()->eq('assignment.jobProfile', ':jobProfile')) - ->setParameter('jobProfile', $jobProfile) + ->andWhere($qb->expr()->in('assignment.jobProfile', ':jobProfiles')) + ->setParameter('jobProfiles', $jobProfiles) ; } - if (null !== $hotelCode = $filterDto->getHotel()) { + if ($hotelCodes = $filterDto->getHotels()) { $qb - ->andWhere($qb->expr()->eq('destination.hotelCode', ':hotelCode')) - ->setParameter('hotelCode', $hotelCode) + ->andWhere($qb->expr()->in('destination.hotelCode', ':hotelCodes')) + ->setParameter('hotelCodes', $hotelCodes) ; } diff --git a/src/Service/Common/AssignmentFilterHandler.php b/src/Service/Common/AssignmentFilterHandler.php index d42d62c..884a221 100644 --- a/src/Service/Common/AssignmentFilterHandler.php +++ b/src/Service/Common/AssignmentFilterHandler.php @@ -59,16 +59,16 @@ class AssignmentFilterHandler if (isset($data['date_to'])) { $filterDto->setDateTo($data['date_to']); } - if (isset($data['job_profile']) && 0 < (int) $data['job_profile']) { - $jobProfile = $this + if (isset($data['job_profiles']) && 0 < count($data['job_profiles'])) { + $jobProfiles = $this ->entityManager ->getRepository(JobProfile::class) - ->find($data['job_profile']) + ->findBy(['id' => $data['job_profiles']]) ; - $filterDto->setJobProfile($jobProfile); + $filterDto->setJobProfiles($jobProfiles); } - if (isset($data['hotel'])) { - $filterDto->setHotel($data['hotel']); + if (isset($data['hotels']) && 0 < count($data['hotels'])) { + $filterDto->setHotels($data['hotels']); } if (isset($data['duration']) && 0 < (int) $data['duration']) { $filterDto->setDuration($data['duration']); @@ -88,8 +88,10 @@ class AssignmentFilterHandler $this->getSession()->set($this->namespace, [ 'date_from' => $filterDto->getDateFrom(), 'date_to' => $filterDto->getDateTo(), - 'job_profile' => $filterDto->getJobProfile()?->getId(), - 'hotel' => $filterDto->getHotel(), + 'job_profiles' => array_map(function (JobProfile $jobProfile) { + return $jobProfile->getId(); + }, $filterDto->getJobProfiles()), + 'hotels' => $filterDto->getHotels(), 'duration' => $filterDto->getDuration(), 'status' => $filterDto->getStatus(), 'include_past' => $filterDto->isIncludePast(), diff --git a/templates/_partials/_assignment_info_compact.html.twig b/templates/_partials/_assignment_info_compact.html.twig index 6530008..014584a 100644 --- a/templates/_partials/_assignment_info_compact.html.twig +++ b/templates/_partials/_assignment_info_compact.html.twig @@ -31,7 +31,7 @@
- Anmerkungen + Anmerkungen intern
{{ assignment.remarks|nl2br|default('-') }} diff --git a/templates/common/modal_assignment_filter.html.twig b/templates/common/modal_assignment_filter.html.twig index db686c0..f1afe0c 100644 --- a/templates/common/modal_assignment_filter.html.twig +++ b/templates/common/modal_assignment_filter.html.twig @@ -10,8 +10,8 @@ {% endif %} {{ form_row(filterForm.dateFrom) }} {{ form_row(filterForm.dateTo) }} - {{ form_row(filterForm.jobProfile) }} - {{ form_row(filterForm.hotel) }} + {{ form_row(filterForm.jobProfiles) }} + {{ form_row(filterForm.hotels) }} {{ form_row(filterForm.duration) }} {{ form_row(filterForm.includePast) }}
diff --git a/templates/forms.html.twig b/templates/forms.html.twig index 74b0f13..b1929b1 100644 --- a/templates/forms.html.twig +++ b/templates/forms.html.twig @@ -216,4 +216,20 @@ {{ icon('search', 'w-4 h-4 text-gray-700') }} -{%- endblock autocomplete_widget %} +{%- endblock autocomplete_widget -%} + +{%- block multiselect_widget -%} +
+
+
+ {{ icon('dots', 'w-4 h-4 mt-2') }} +
+ +
+{%- endblock multiselect_widget -%}