WIP: Implement feedback functionality

This commit is contained in:
Björn Fromme
2023-11-03 09:20:48 +01:00
parent d024e11f8f
commit d016ee043a
12 changed files with 123 additions and 73 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20231103081749 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE disposition ADD feedback_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE disposition ADD CONSTRAINT FK_4C58BF60D249A887 FOREIGN KEY (feedback_id) REFERENCES feedback (id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_4C58BF60D249A887 ON disposition (feedback_id)');
$this->addSql('ALTER TABLE feedback DROP FOREIGN KEY FK_D2294458D19302F8');
$this->addSql('DROP INDEX IDX_D2294458D19302F8 ON feedback');
$this->addSql('ALTER TABLE feedback DROP assignment_id');
$this->addSql('ALTER TABLE feedback_set ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL, CHANGE items ratings JSON NOT NULL COMMENT \'(DC2Type:json)\'');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE disposition DROP FOREIGN KEY FK_4C58BF60D249A887');
$this->addSql('DROP INDEX UNIQ_4C58BF60D249A887 ON disposition');
$this->addSql('ALTER TABLE disposition DROP feedback_id');
$this->addSql('ALTER TABLE feedback ADD assignment_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D2294458D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE SET NULL');
$this->addSql('CREATE INDEX IDX_D2294458D19302F8 ON feedback (assignment_id)');
$this->addSql('ALTER TABLE feedback_set DROP created_by, DROP updated_by, CHANGE ratings items JSON NOT NULL COMMENT \'(DC2Type:json)\'');
}
}
@@ -20,6 +20,7 @@ class DeleteController extends AbstractController
#[Route('/admin/system/feedback-set/delete/{id}', name: 'app_admin_system_feedback_set_delete', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')]
#[IsGranted('DELETE', subject: 'feedbackSet')]
public function index(FeedbackSet $feedbackSet): Response
{
$this->entityManager->remove($feedbackSet);
-34
View File
@@ -84,9 +84,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
#[ORM\ManyToOne]
private ?User $owner = null;
#[ORM\OneToMany(mappedBy: 'assignment', targetEntity: Feedback::class)]
private Collection $feedbacks;
public function __construct()
{
$this->uuid = Uuid::v4();
@@ -96,7 +93,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
$this->teamers = new ArrayCollection();
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
$this->documents = new ArrayCollection();
$this->feedbacks = new ArrayCollection();
}
public static function duplicate(Assignment $assignment): static
@@ -440,34 +436,4 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
return $this;
}
/**
* @return Collection<int, Feedback>
*/
public function getFeedbacks(): Collection
{
return $this->feedbacks;
}
public function addFeedback(Feedback $feedback): static
{
if (!$this->feedbacks->contains($feedback)) {
$this->feedbacks->add($feedback);
$feedback->setAssignment($this);
}
return $this;
}
public function removeFeedback(Feedback $feedback): static
{
if ($this->feedbacks->removeElement($feedback)) {
// set the owning side to null (unless already changed)
if ($feedback->getAssignment() === $this) {
$feedback->setAssignment(null);
}
}
return $this;
}
}
+15
View File
@@ -48,6 +48,9 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
#[ORM\OneToMany(mappedBy: 'disposition', targetEntity: Upload::class, cascade: ['persist', 'remove'])]
private Collection $documents;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
private ?Feedback $feedback = null;
public function __construct(Application $application)
{
$this->uuid = Uuid::v4();
@@ -155,4 +158,16 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
return $this;
}
public function getFeedback(): ?Feedback
{
return $this->feedback;
}
public function setFeedback(?Feedback $feedback): static
{
$this->feedback = $feedback;
return $this;
}
}
-16
View File
@@ -26,10 +26,6 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
#[ORM\ManyToOne(inversedBy: 'feedback')]
private ?Teamer $teamer = null;
#[ORM\ManyToOne(inversedBy: 'feedbacks')]
#[ORM\JoinColumn(onDelete: 'SET NULL')]
private ?Assignment $assignment = null;
#[ORM\Column(length: 255)]
private ?string $assignmentDestination = null;
@@ -66,18 +62,6 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
return $this;
}
public function getAssignment(): ?Assignment
{
return $this->assignment;
}
public function setAssignment(?Assignment $assignment): static
{
$this->assignment = $assignment;
return $this;
}
public function getAssignmentDestination(): ?string
{
return $this->assignmentDestination;
+8 -6
View File
@@ -2,14 +2,16 @@
namespace App\Entity;
use App\Entity\Traits\BlameableEntity;
use App\Entity\Traits\TimestampableEntity;
use App\Repository\FeedbackSetRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: FeedbackSetRepository::class)]
class FeedbackSet implements TimestampableEntityInterface
class FeedbackSet implements BlameableEntityInterface, TimestampableEntityInterface
{
use BlameableEntity;
use TimestampableEntity;
#[ORM\Id]
@@ -23,7 +25,7 @@ class FeedbackSet implements TimestampableEntityInterface
#[ORM\Column]
#[Assert\Count(min: 1, minMessage: 'Bitte füge mindestens eine Bewertung hinzu')]
private array $items = [];
private array $ratings = [];
public function getId(): ?int
{
@@ -42,14 +44,14 @@ class FeedbackSet implements TimestampableEntityInterface
return $this;
}
public function getItems(): array
public function getRatings(): array
{
return $this->items;
return $this->ratings;
}
public function setItems(array $items): static
public function setRatings(array $ratings): static
{
$this->items = $items;
$this->ratings = $ratings;
return $this;
}
+1
View File
@@ -40,6 +40,7 @@ class JobProfile implements BlameableEntityInterface, TimestampableEntityInterfa
#[ORM\ManyToOne]
#[ORM\JoinColumn(onDelete: 'SET NULL')]
#[Assert\NotNull(message: 'Bitte ordne eine Feedback-Vorlage zu')]
private ?FeedbackSet $feedbackSet = null;
public function __construct()
+1 -1
View File
@@ -17,7 +17,7 @@ class FeedbackSetType extends AbstractType
->add('name', TextType::class, [
'label' => 'Bezeichnung',
])
->add('items', CollectionType::class, [
->add('ratings', CollectionType::class, [
'label' => 'Bewertungen',
'entry_type' => TextType::class,
'allow_add' => true,
+1 -1
View File
@@ -50,7 +50,7 @@ class JobProfileType extends AbstractType
'label' => 'Feedback-Vorlage',
'class' => FeedbackSet::class,
'choice_label' => 'name',
'placeholder' => 'Kein Feedback',
'placeholder' => '',
])
;
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Security\Voter;
use App\Entity\FeedbackSet;
use App\Repository\JobProfileRepository;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class FeedbackSetVoter extends Voter
{
public const DELETE = 'DELETE';
public function __construct(private readonly JobProfileRepository $jobProfileRepository)
{
}
protected function supports(string $attribute, mixed $subject): bool
{
return $subject instanceof FeedbackSet && static::DELETE === $attribute;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
/** @var FeedbackSet $feedbackSet */
$feedbackSet = $subject;
// Feedback sets may only be deleted when not in use
$result = $this
->jobProfileRepository
->findOneBy(['feedbackSet' => $feedbackSet])
;
return null === $result;
}
}
@@ -13,13 +13,13 @@
{{ form_start(form) }}
<div class="flex flex-col space-y-4 pb-8">
{{ form_row(form.name) }}
<div {{ stimulus_controller('form-collection', { 'prototype': _self.collectionRow(form.items.vars.prototype)|json_encode }) }}>
<div {{ stimulus_controller('form-collection', { 'prototype': _self.collectionRow(form.ratings.vars.prototype)|json_encode }) }}>
<h4 class="font-bold pb-2">
Bewertungen
</h4>
<div {{ stimulus_target('form-collection', 'fields')}} class="flex flex-col space-y-4 mb-4">
{% do form.items.setRendered %}
{%- for item in form.items -%}{{- _self.collectionRow(item) -}}{%- endfor -%}
{% do form.ratings.setRendered %}
{%- for item in form.ratings -%}{{- _self.collectionRow(item) -}}{%- endfor -%}
</div>
<div class="flex justify-end">
<button type="button"
@@ -28,7 +28,7 @@
{{ icon('plus', 'w-4 h-4 pointer-events-none') }}
</button>
</div>
{{ form_errors(form.items) }}
{{ form_errors(form.ratings) }}
</div>
</div>
<div class="flex items-center space-x-2">
@@ -27,20 +27,22 @@
{{ feedbackSet.name }}
</td>
<td>
{{ feedbackSet.items|length }}
{{ feedbackSet.ratings|length }}
</td>
<td>
<div class="flex items-center space-x-2 justify-end">
<button type="button"
class="text-red-500"
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
{{ stimulus_action('modal-button', 'confirmation', null, {
'title': 'Bist du sicher?',
'content': 'Möchtest du das Job-Profil wirklich löschen?',
'target-url': path('app_admin_system_feedback_set_delete', { 'id': feedbackSet.id })
}) }}>
{{ icon('delete') }}
</button>
{% if is_granted('DELETE', feedbackSet) %}
<button type="button"
class="text-red-500"
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
{{ stimulus_action('modal-button', 'confirmation', null, {
'title': 'Bist du sicher?',
'content': 'Möchtest du das Job-Profil wirklich löschen?',
'target-url': path('app_admin_system_feedback_set_delete', { 'id': feedbackSet.id })
}) }}>
{{ icon('delete') }}
</button>
{% endif %}
<a href="{{ path('app_admin_system_feedback_set_edit', { 'id': feedbackSet.id }) }}">
{{ icon('edit') }}
</a>