feat: link ratings widget with feedback list filtered by teamer
addresses #869at5mdu
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Controller\Administrative\Feedback;
|
namespace App\Controller\Administrative\Feedback;
|
||||||
|
|
||||||
|
use App\Entity\Teamer;
|
||||||
use App\Repository\FeedbackRepository;
|
use App\Repository\FeedbackRepository;
|
||||||
use App\Service\Common\FeedbackFilterHandler;
|
use App\Service\Common\FeedbackFilterHandler;
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
@@ -46,4 +47,15 @@ class IndexController extends AbstractController
|
|||||||
'filterDto' => $filterDto,
|
'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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Teamer;
|
||||||
use App\Model\FeedbackFilterDto;
|
use App\Model\FeedbackFilterDto;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
@@ -15,6 +16,14 @@ class FeedbackFilterType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$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, [
|
->add('name', TextType::class, [
|
||||||
'label' => 'Name',
|
'label' => 'Name',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|||||||
@@ -2,13 +2,28 @@
|
|||||||
|
|
||||||
namespace App\Model;
|
namespace App\Model;
|
||||||
|
|
||||||
|
use App\Entity\Teamer;
|
||||||
|
|
||||||
class FeedbackFilterDto extends AbstractFilterDto
|
class FeedbackFilterDto extends AbstractFilterDto
|
||||||
{
|
{
|
||||||
|
protected ?Teamer $teamer = null;
|
||||||
protected ?string $name = null;
|
protected ?string $name = null;
|
||||||
protected ?\DateTimeImmutable $dateFrom = null;
|
protected ?\DateTimeImmutable $dateFrom = null;
|
||||||
protected ?\DateTimeImmutable $dateTo = null;
|
protected ?\DateTimeImmutable $dateTo = null;
|
||||||
protected ?int $averageRating = 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
|
public function getName(): ?string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ class FeedbackRepository extends ServiceEntityRepository
|
|||||||
->innerJoin('teamer.user', 'user')
|
->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()) {
|
if (null !== $name = $filterDto->getName()) {
|
||||||
$qb
|
$qb
|
||||||
->andWhere($qb->expr()->orX(
|
->andWhere($qb->expr()->orX(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Service\Common;
|
namespace App\Service\Common;
|
||||||
|
|
||||||
|
use App\Entity\Teamer;
|
||||||
use App\Model\AbstractFilterDto;
|
use App\Model\AbstractFilterDto;
|
||||||
use App\Model\FeedbackFilterDto;
|
use App\Model\FeedbackFilterDto;
|
||||||
|
|
||||||
@@ -18,6 +19,15 @@ class FeedbackFilterHandler extends AbstractFilterHandler
|
|||||||
return $filterDto;
|
return $filterDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($data['teamer_id'])) {
|
||||||
|
$teamer = $this
|
||||||
|
->entityManager
|
||||||
|
->getRepository(Teamer::class)
|
||||||
|
->find($data['teamer_id'])
|
||||||
|
;
|
||||||
|
$filterDto->setTeamer($teamer);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($data['name'])) {
|
if (isset($data['name'])) {
|
||||||
$filterDto->setName($data['name']);
|
$filterDto->setName($data['name']);
|
||||||
}
|
}
|
||||||
@@ -34,10 +44,16 @@ class FeedbackFilterHandler extends AbstractFilterHandler
|
|||||||
return $filterDto;
|
return $filterDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function applyTeamerFilter(Teamer $teamer): void
|
||||||
|
{
|
||||||
|
$this->saveFilterSettings((new FeedbackFilterDto())->setTeamer($teamer));
|
||||||
|
}
|
||||||
|
|
||||||
protected function saveFilterSettings(AbstractFilterDto $filterDto): void
|
protected function saveFilterSettings(AbstractFilterDto $filterDto): void
|
||||||
{
|
{
|
||||||
/* @var FeedbackFilterDto $filterDto */
|
/* @var FeedbackFilterDto $filterDto */
|
||||||
$this->getSession()->set($this->namespace, [
|
$this->getSession()->set($this->namespace, [
|
||||||
|
'teamer_id' => $filterDto->getTeamer()?->getId(),
|
||||||
'name' => $filterDto->getName(),
|
'name' => $filterDto->getName(),
|
||||||
'date_from' => $filterDto->getDateFrom(),
|
'date_from' => $filterDto->getDateFrom(),
|
||||||
'date_to' => $filterDto->getDateTo(),
|
'date_to' => $filterDto->getDateTo(),
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Twig\Components;
|
namespace App\Twig\Components;
|
||||||
|
|
||||||
|
use App\Entity\Teamer;
|
||||||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
|
||||||
|
|
||||||
#[AsTwigComponent(template: 'components/rating_stars.html.twig')]
|
#[AsTwigComponent(template: 'components/rating_stars.html.twig')]
|
||||||
class RatingStars
|
class RatingStars
|
||||||
{
|
{
|
||||||
public ?int $rating;
|
public ?int $rating;
|
||||||
|
public ?Teamer $teamer = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': application.teamer } %}
|
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': application.teamer } %}
|
||||||
{{ icon('info', 'w-4 h-4') }}
|
{{ icon('info', 'w-4 h-4') }}
|
||||||
</button>
|
</button>
|
||||||
<twig:RatingStars rating="{{ application.teamer.averageRating }}" />
|
<twig:RatingStars rating="{{ application.teamer.averageRating }}" :teamer="application.teamer" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ ('label.application.status.' ~ application.status)|trans }}
|
{{ ('label.application.status.' ~ application.status)|trans }}
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': disposition.teamer } %}
|
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': disposition.teamer } %}
|
||||||
{{ icon('info', 'w-4 h-4') }}
|
{{ icon('info', 'w-4 h-4') }}
|
||||||
</button>
|
</button>
|
||||||
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" />
|
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" :teamer="disposition.teamer" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ ('label.disposition.status.' ~ disposition.status)|trans }}
|
{{ ('label.disposition.status.' ~ disposition.status)|trans }}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<span>{{ assignment.activeDispositions|length }}/{{ assignment.availableDispositions }}</span>
|
<span>{{ assignment.activeDispositions|length }}/{{ assignment.availableDispositions }}</span>
|
||||||
<ul>
|
<ul>
|
||||||
{% for disposition in assignment.activeDispositions %}
|
{% for disposition in assignment.activeDispositions %}
|
||||||
<li>
|
<li class="flex items-center space-x-1">
|
||||||
<div role="button"
|
<div role="button"
|
||||||
title="Teamer:innen-Info anzeigen"
|
title="Teamer:innen-Info anzeigen"
|
||||||
aria-label="Teamer:innen-Info anzeigen"
|
aria-label="Teamer:innen-Info anzeigen"
|
||||||
@@ -150,8 +150,8 @@
|
|||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend">
|
hx-swap="beforeend">
|
||||||
<span class="whitespace-nowrap">{{ disposition.teamer }}</span>
|
<span class="whitespace-nowrap">{{ disposition.teamer }}</span>
|
||||||
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<twig:RatingStars rating="{{ disposition.teamer.averageRating }}" :teamer="disposition.teamer" />
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
{{ icon('user', 'w-10 h-10 text-gray-200') }}
|
{{ icon('user', 'w-10 h-10 text-gray-200') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<twig:RatingStars rating="{{ teamer.averageRating }}" />
|
<twig:RatingStars rating="{{ teamer.averageRating }}" :teamer="teamer" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
{{ icon('user', 'w-32 h-32 text-gray-200') }}
|
{{ icon('user', 'w-32 h-32 text-gray-200') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<twig:RatingStars rating="{{ teamer.averageRating }}"/>
|
<twig:RatingStars rating="{{ teamer.averageRating }}" :teamer="teamer"/>
|
||||||
<ul class="pb-8 divide-y divide-gray-200">
|
<ul class="pb-8 divide-y divide-gray-200">
|
||||||
{% if teamer.communication.phone %}
|
{% if teamer.communication.phone %}
|
||||||
<li class="py-2">
|
<li class="py-2">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{{ form_start(filterForm, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' } }) }}
|
{{ 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">
|
<div class="flex flex-col space-y-2 pb-4">
|
||||||
|
{{ form_row(filterForm.teamer) }}
|
||||||
{{ form_row(filterForm.name) }}
|
{{ form_row(filterForm.name) }}
|
||||||
{{ form_row(filterForm.dateFrom) }}
|
{{ form_row(filterForm.dateFrom) }}
|
||||||
{{ form_row(filterForm.dateTo) }}
|
{{ form_row(filterForm.dateTo) }}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<div class="inline-flex items-center">
|
{% set stars %}
|
||||||
{% for grade in range(1, 5) %}
|
<div class="inline-flex items-center">
|
||||||
{{ icon('star', html_classes('w-4 h-4', { 'text-gray-200': (rating/100)|round < grade, 'text-primary': (rating/100)|round >= grade })) }}
|
{% for grade in range(1, 5) %}
|
||||||
{% endfor %}
|
{{ icon('star', html_classes('w-4 h-4', { 'text-gray-200': (rating/100)|round < grade, 'text-primary': (rating/100)|round >= grade })) }}
|
||||||
</div>
|
{% 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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user