feat: link ratings widget with feedback list filtered by teamer

addresses #869at5mdu
This commit is contained in:
Björn Fromme
2026-08-24 13:50:07 +02:00
parent 43968e4e7b
commit dfa3e63926
12 changed files with 83 additions and 11 deletions
@@ -2,6 +2,7 @@
namespace App\Controller\Administrative\Feedback;
use App\Entity\Teamer;
use App\Repository\FeedbackRepository;
use App\Service\Common\FeedbackFilterHandler;
use Knp\Component\Pager\PaginatorInterface;
@@ -46,4 +47,15 @@ class IndexController extends AbstractController
'filterDto' => $filterDto,
]);
}
#[Route('/administrative/feedback/teamer/{uuid}', name: 'app_administrative_feedback_index_teamer')]
#[IsGranted('ROLE_ADMINISTRATIVE')]
public function byTeamer(Teamer $teamer): Response
{
// The link means "show me this teamer's feedback", so previously stored
// criteria must not silently hide any of it.
$this->filterHandler->applyTeamerFilter($teamer);
return $this->redirectToRoute('app_administrative_feedback_index');
}
}
+9
View File
@@ -2,6 +2,7 @@
namespace App\Form;
use App\Entity\Teamer;
use App\Model\FeedbackFilterDto;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@@ -15,6 +16,14 @@ class FeedbackFilterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('teamer', AutocompleteEntityType::class, [
'label' => 'Teamer',
'required' => false,
'class' => Teamer::class,
'label_property' => 'name',
'label_function' => fn (Teamer $teamer) => (string) $teamer,
'endpoint_route' => 'app_admin_autocomplete_teamer',
])
->add('name', TextType::class, [
'label' => 'Name',
'required' => false,
+15
View File
@@ -2,13 +2,28 @@
namespace App\Model;
use App\Entity\Teamer;
class FeedbackFilterDto extends AbstractFilterDto
{
protected ?Teamer $teamer = null;
protected ?string $name = null;
protected ?\DateTimeImmutable $dateFrom = null;
protected ?\DateTimeImmutable $dateTo = null;
protected ?int $averageRating = null;
public function getTeamer(): ?Teamer
{
return $this->teamer;
}
public function setTeamer(?Teamer $teamer): static
{
$this->teamer = $teamer;
return $this;
}
public function getName(): ?string
{
return $this->name;
+7
View File
@@ -38,6 +38,13 @@ class FeedbackRepository extends ServiceEntityRepository
->innerJoin('teamer.user', 'user')
;
if (null !== $teamer = $filterDto->getTeamer()) {
$qb
->andWhere($qb->expr()->eq('feedback.teamer', ':teamer'))
->setParameter('teamer', $teamer)
;
}
if (null !== $name = $filterDto->getName()) {
$qb
->andWhere($qb->expr()->orX(
@@ -2,6 +2,7 @@
namespace App\Service\Common;
use App\Entity\Teamer;
use App\Model\AbstractFilterDto;
use App\Model\FeedbackFilterDto;
@@ -18,6 +19,15 @@ class FeedbackFilterHandler extends AbstractFilterHandler
return $filterDto;
}
if (isset($data['teamer_id'])) {
$teamer = $this
->entityManager
->getRepository(Teamer::class)
->find($data['teamer_id'])
;
$filterDto->setTeamer($teamer);
}
if (isset($data['name'])) {
$filterDto->setName($data['name']);
}
@@ -34,10 +44,16 @@ class FeedbackFilterHandler extends AbstractFilterHandler
return $filterDto;
}
public function applyTeamerFilter(Teamer $teamer): void
{
$this->saveFilterSettings((new FeedbackFilterDto())->setTeamer($teamer));
}
protected function saveFilterSettings(AbstractFilterDto $filterDto): void
{
/* @var FeedbackFilterDto $filterDto */
$this->getSession()->set($this->namespace, [
'teamer_id' => $filterDto->getTeamer()?->getId(),
'name' => $filterDto->getName(),
'date_from' => $filterDto->getDateFrom(),
'date_to' => $filterDto->getDateTo(),
+2
View File
@@ -2,10 +2,12 @@
namespace App\Twig\Components;
use App\Entity\Teamer;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent(template: 'components/rating_stars.html.twig')]
class RatingStars
{
public ?int $rating;
public ?Teamer $teamer = null;
}
@@ -51,7 +51,7 @@
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': application.teamer } %}
{{ icon('info', 'w-4 h-4') }}
</button>
<twig:RatingStars rating="{{ application.teamer.averageRating }}" />
<twig:RatingStars rating="{{ application.teamer.averageRating }}" :teamer="application.teamer" />
</td>
<td>
{{ ('label.application.status.' ~ application.status)|trans }}
@@ -162,7 +162,7 @@
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': disposition.teamer } %}
{{ icon('info', 'w-4 h-4') }}
</button>
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" />
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" :teamer="disposition.teamer" />
</td>
<td>
{{ ('label.disposition.status.' ~ disposition.status)|trans }}
@@ -142,7 +142,7 @@
<span>{{ assignment.activeDispositions|length }}/{{ assignment.availableDispositions }}</span>
<ul>
{% for disposition in assignment.activeDispositions %}
<li>
<li class="flex items-center space-x-1">
<div role="button"
title="Teamer:innen-Info anzeigen"
aria-label="Teamer:innen-Info anzeigen"
@@ -150,8 +150,8 @@
hx-target="body"
hx-swap="beforeend">
<span class="whitespace-nowrap">{{ disposition.teamer }}</span>
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" />
</div>
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" :teamer="disposition.teamer" />
</li>
{% endfor %}
</ul>
@@ -74,7 +74,7 @@
{{ icon('user', 'w-10 h-10 text-gray-200') }}
</div>
{% endif %}
<twig:RatingStars rating="{{ teamer.averageRating }}" />
<twig:RatingStars rating="{{ teamer.averageRating }}" :teamer="teamer" />
</div>
</td>
<td>
@@ -63,7 +63,7 @@
{{ icon('user', 'w-32 h-32 text-gray-200') }}
</div>
{% endif %}
<twig:RatingStars rating="{{ teamer.averageRating }}"/>
<twig:RatingStars rating="{{ teamer.averageRating }}" :teamer="teamer"/>
<ul class="pb-8 divide-y divide-gray-200">
{% if teamer.communication.phone %}
<li class="py-2">
@@ -5,6 +5,7 @@
{% block content %}
{{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
<div class="flex flex-col space-y-2 pb-4">
{{ form_row(filterForm.teamer) }}
{{ form_row(filterForm.name) }}
{{ form_row(filterForm.dateFrom) }}
{{ form_row(filterForm.dateTo) }}
@@ -1,5 +1,15 @@
{% set stars %}
<div class="inline-flex items-center">
{% for grade in range(1, 5) %}
{{ icon('star', html_classes('w-4 h-4', { 'text-gray-200': (rating/100)|round < grade, 'text-primary': (rating/100)|round >= grade })) }}
{% endfor %}
</div>
{% endset %}
{% if teamer is not null and rating and is_granted('ROLE_ADMINISTRATIVE') %}
<a href="{{ path('app_administrative_feedback_index_teamer', { 'uuid': teamer.uuid }) }}"
target="_blank"
rel="noopener"
title="Feedback anzeigen">{{ stars }}</a>
{% else %}
{{ stars }}
{% endif %}