diff --git a/src/Controller/Admin/Autocomplete/TeamerController.php b/src/Controller/Admin/Autocomplete/TeamerController.php new file mode 100644 index 0000000..25804b7 --- /dev/null +++ b/src/Controller/Admin/Autocomplete/TeamerController.php @@ -0,0 +1,32 @@ +getContent(), true, 512, JSON_THROW_ON_ERROR); + $queryString = $query['search']; + } catch (\JsonException $e) { + throw $this->createNotFoundException(); + } + + $dates = $this->teamerRepository->getAutocompletionData($queryString); + + return $this->json($dates); + } +} \ No newline at end of file diff --git a/src/Controller/Admin/Feedback/ProvideController.php b/src/Controller/Admin/Feedback/ProvideController.php new file mode 100644 index 0000000..0d91fb0 --- /dev/null +++ b/src/Controller/Admin/Feedback/ProvideController.php @@ -0,0 +1,70 @@ +getFeedbackForm(); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $feedback = $form->getData(); + + $this->entityManager->persist($feedback); + $this->entityManager->flush(); + + $this->logger->info('Provided feedback', [ + 'teamer' => $feedback->getTeamer()->getUuid(), + ]); + + return $this->redirectToRoute('app_administrative_feedback_index'); + } + + return $this->render('admin/feedback/provide.html.twig', [ + 'form' => $form->createView(), + ]); + } + + #[Route('/admin/feedback/provide/form', name: 'app_admin_feedback_provide_form')] + #[IsGranted('ROLE_ADMIN')] + public function form(Request $request): Response + { + $form = $this->getFeedbackForm(); + $form->handleRequest($request); + + return $this->renderBlock('admin/feedback/provide.html.twig', 'feedback_form', [ + 'form' => $form->createView(), + ]); + } + + private function getFeedbackForm(): FormInterface + { + /** @var User $user */ + $user = $this->getUser(); + $feedback = new Feedback($user); + + return $this->createForm(AdminFeedbackType::class, $feedback); + } +} \ No newline at end of file diff --git a/src/Entity/Feedback.php b/src/Entity/Feedback.php index fb2f201..3c16d60 100644 --- a/src/Entity/Feedback.php +++ b/src/Entity/Feedback.php @@ -32,6 +32,7 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface private ?string $status = self::STATUS_NEW; #[ORM\ManyToOne(inversedBy: 'feedback')] + #[Assert\NotNull(message: 'Bitte auswählen', groups: ['admin'])] private ?Teamer $teamer = null; #[ORM\Column(length: 255, nullable: true)] @@ -71,6 +72,10 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface #[ORM\Column(length: 255)] private ?string $author; + // Transient property for form only + #[Assert\NotNull(message: 'Bitte auswählen', groups: ['admin'])] + private ?FeedbackSet $feedbackSet = null; + public function __construct(User $author) { $this->uuid = Uuid::v4(); @@ -288,4 +293,16 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface return $this; } + + public function getFeedbackSet(): ?FeedbackSet + { + return $this->feedbackSet; + } + + public function setFeedbackSet(?FeedbackSet $feedbackSet): static + { + $this->feedbackSet = $feedbackSet; + + return $this; + } } diff --git a/src/Form/AdminFeedbackType.php b/src/Form/AdminFeedbackType.php new file mode 100644 index 0000000..d96a953 --- /dev/null +++ b/src/Form/AdminFeedbackType.php @@ -0,0 +1,103 @@ +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('feedbackSet', EntityType::class, [ + 'label' => 'Feedbackvorlage', + 'class' => FeedbackSet::class, + 'choice_label' => 'name', + 'placeholder' => 'Bitte wählen...', + ]) + ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + $data = $event->getData(); + $form = $event->getForm(); + + if (null === $feedbackSet = $data->getFeedbackSet()) { + return; + } + + $this->addDynamicForm($feedbackSet, $form); + }) + ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { + $data = $event->getData(); + $form = $event->getForm(); + + if (true === empty($data['feedbackSet'])) { + return; + } + + $feedbackSet = $this->feedbackSetRepository->find($data['feedbackSet']); + $this->addDynamicForm($feedbackSet, $form); + }) + ; + } + + private function addDynamicForm(FeedbackSet $feedbackSet, FormInterface $form): void + { + $form + ->add('ratings', FeedbackRatingsType::class, [ + 'label' => false, + 'feedback_set' => $feedbackSet, + ]) + ->add('comment', TextareaType::class, [ + 'label' => 'Kommentar (öffentlich)', + 'required' => false, + 'attr' => [ + 'rows' => 5, + ], + 'help' => 'Bitte formuliere deinen Kommentar so, dass er an das Team weitergegeben werden kann', + ]) + ->add('commentInternal', TextareaType::class, [ + 'label' => 'Kommentar (intern)', + 'required' => false, + 'attr' => [ + 'rows' => 5, + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => Feedback::class, + 'anti_xss' => true, + 'validation_groups' => [ + 'Default', + 'admin', + ] + ]) + ; + } +} \ No newline at end of file diff --git a/src/Form/FeedbackRatingsType.php b/src/Form/FeedbackRatingsType.php index f701bcd..9bcb863 100644 --- a/src/Form/FeedbackRatingsType.php +++ b/src/Form/FeedbackRatingsType.php @@ -3,6 +3,7 @@ namespace App\Form; use App\Entity\FeedbackSet; +use App\Form\DataTransformer\FeedbackToRatingsTransformer; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; @@ -20,6 +21,10 @@ class FeedbackRatingsType extends AbstractType 'choices' => array_combine(range(5, 1), range(5,1)), ]); } + + // This is most probably more suitable for DatamapperInterface + // but it works very well for the time being + $builder->addModelTransformer(new FeedbackToRatingsTransformer($options['feedback_set'])); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/FeedbackType.php b/src/Form/FeedbackType.php index 821446e..652b705 100644 --- a/src/Form/FeedbackType.php +++ b/src/Form/FeedbackType.php @@ -4,7 +4,6 @@ namespace App\Form; use App\Entity\Feedback; use App\Entity\FeedbackSet; -use App\Form\DataTransformer\FeedbackToRatingsTransformer; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; @@ -35,13 +34,6 @@ class FeedbackType extends AbstractType ], ]) ; - - // This is most probably more suitable for DatamapperInterface - // but it works very well for the time being - $builder - ->get('ratings') - ->addModelTransformer(new FeedbackToRatingsTransformer($options['feedback_set'])) - ; } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Repository/TeamerRepository.php b/src/Repository/TeamerRepository.php index f4aa768..f8e9127 100644 --- a/src/Repository/TeamerRepository.php +++ b/src/Repository/TeamerRepository.php @@ -103,4 +103,37 @@ class TeamerRepository extends ServiceEntityRepository return $qb->getQuery(); } + + public function getAutocompletionData(string $search): array + { + $qb = $this->createQueryBuilder('teamer'); + + $teamers = $qb + ->where($qb->expr()->orX( + $qb->expr()->like('teamer.lastName', ':search'), + $qb->expr()->like('teamer.firstName', ':search'), + )) + ->orderBy('teamer.lastName', 'ASC') + ->addOrderBy('teamer.firstName', 'ASC') + ->setParameter('search', '%'.$this->escapeLikeWildcards($search).'%') + ->getQuery() + ->getResult() + ; + + $data = [ + [ + 'value' => '', + 'text' => '...', + ], + ]; + + foreach ($teamers as $teamer) { + $data[] = [ + 'value' => $teamer->getId(), + 'text' => (string) $teamer, + ]; + } + + return $data; + } } diff --git a/templates/admin/feedback/provide.html.twig b/templates/admin/feedback/provide.html.twig new file mode 100644 index 0000000..3d33b9b --- /dev/null +++ b/templates/admin/feedback/provide.html.twig @@ -0,0 +1,106 @@ +{% extends 'admin/layout.html.twig' %} + +{% block title %}Feedback abgeben{% endblock %} + +{% block content %} +

+ Neues Feedback +

+
+
+ {{ form_start(form) }} +
+ {{ form_row(form.teamer) }} +
+
+ {{ form_row(form.feedbackSet, { 'attr': { + 'hx-post': path('app_admin_feedback_provide_form'), + 'hx-target': '#feedback-form', + 'hx-swap': 'innerHTML', + } }) }} +
+
+ {% block feedback_form %} + {% if form.ratings is defined %} +
+ {% for child in form.ratings.children %} +
+

+ {{ child.vars.label }} +

+ {{ form_widget(child) }} +
+ {% endfor %} +
+ {{ form_row(form.comment) }} +
+
+ {{ form_row(form.commentInternal) }} +
+
+ {% endif %} + {% endblock %} +
+ + {{ form_rest(form) }} + {{ form_end(form) }} +
+
+

+ Bewertungsskala +

+ + + + + + + + + + + + + + + + + + + + + +
+ 5 Punkte + + Der/die Wochenteamer:in hat hervorragende Arbeit geleistet und ist beim Team und den Gästen + sehr gut angekommen, es gibt nichts auszusetzen. +
+ 4 Punkte + + Der/die Wochenteamer:in hat einen sehr guten Job geleistet. Wenn es etwas zu beanstanden oder + Ideen zur Verbesserung gibt, dann sind es nur Kleinigkeiten (diese gerne in den Kommentar + schreiben). +
+ 3 Punkte + + Der/die Wochenteamer:in hat einen ganz guten Job erledigt. Es gibt einige Dinge, die + verbesserungswürdig sind (diese gerne in den Kommentaren vermerken). Der/die Wochenteamer:in + ist aber nicht negativ aufgefallen. +
+ 2 Punkte + + Der/die Wocheteamer:in hat keinen guten Job geleistet. Der Einsatz hat das Team eher belastet, + als es zu unterstützen. Der/die Wochenteamer:in bekommt noch eine Chance sich zu beweisen + (hierfür gerne ein schriftliches Feedback im Kommentarfeld hinterlassen). +
+ 1 Punkt + + Der/die Wochenteamer:in hat einen schlechten Job erledigt, ist Team und/oder Gästen negativ + aufgefallen und soll keine weiteren Einsätze erhalten. +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/administrative/feedback/index.html.twig b/templates/administrative/feedback/index.html.twig index ff399a4..398cfef 100644 --- a/templates/administrative/feedback/index.html.twig +++ b/templates/administrative/feedback/index.html.twig @@ -77,4 +77,9 @@ {{ knp_pagination_render(pagination) }} + {% if is_granted('ROLE_ADMIN') %} + + Neu + + {% endif %} {% endblock %} \ No newline at end of file