WIP: Implement feedback functionality
This commit is contained in:
@@ -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'])]
|
#[Route('/admin/system/feedback-set/delete/{id}', name: 'app_admin_system_feedback_set_delete', methods: ['POST'])]
|
||||||
#[IsGranted('ROLE_ADMIN')]
|
#[IsGranted('ROLE_ADMIN')]
|
||||||
|
#[IsGranted('DELETE', subject: 'feedbackSet')]
|
||||||
public function index(FeedbackSet $feedbackSet): Response
|
public function index(FeedbackSet $feedbackSet): Response
|
||||||
{
|
{
|
||||||
$this->entityManager->remove($feedbackSet);
|
$this->entityManager->remove($feedbackSet);
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
private ?User $owner = null;
|
private ?User $owner = null;
|
||||||
|
|
||||||
#[ORM\OneToMany(mappedBy: 'assignment', targetEntity: Feedback::class)]
|
|
||||||
private Collection $feedbacks;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
@@ -96,7 +93,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
$this->teamers = new ArrayCollection();
|
$this->teamers = new ArrayCollection();
|
||||||
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
|
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
|
||||||
$this->documents = new ArrayCollection();
|
$this->documents = new ArrayCollection();
|
||||||
$this->feedbacks = new ArrayCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function duplicate(Assignment $assignment): static
|
public static function duplicate(Assignment $assignment): static
|
||||||
@@ -440,34 +436,4 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
|
|
||||||
return $this;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
#[ORM\OneToMany(mappedBy: 'disposition', targetEntity: Upload::class, cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(mappedBy: 'disposition', targetEntity: Upload::class, cascade: ['persist', 'remove'])]
|
||||||
private Collection $documents;
|
private Collection $documents;
|
||||||
|
|
||||||
|
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
|
||||||
|
private ?Feedback $feedback = null;
|
||||||
|
|
||||||
public function __construct(Application $application)
|
public function __construct(Application $application)
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
@@ -155,4 +158,16 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFeedback(): ?Feedback
|
||||||
|
{
|
||||||
|
return $this->feedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFeedback(?Feedback $feedback): static
|
||||||
|
{
|
||||||
|
$this->feedback = $feedback;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
#[ORM\ManyToOne(inversedBy: 'feedback')]
|
#[ORM\ManyToOne(inversedBy: 'feedback')]
|
||||||
private ?Teamer $teamer = null;
|
private ?Teamer $teamer = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'feedbacks')]
|
|
||||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
|
||||||
private ?Assignment $assignment = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $assignmentDestination = null;
|
private ?string $assignmentDestination = null;
|
||||||
|
|
||||||
@@ -66,18 +62,6 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAssignment(): ?Assignment
|
|
||||||
{
|
|
||||||
return $this->assignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAssignment(?Assignment $assignment): static
|
|
||||||
{
|
|
||||||
$this->assignment = $assignment;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAssignmentDestination(): ?string
|
public function getAssignmentDestination(): ?string
|
||||||
{
|
{
|
||||||
return $this->assignmentDestination;
|
return $this->assignmentDestination;
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Entity\Traits\BlameableEntity;
|
||||||
use App\Entity\Traits\TimestampableEntity;
|
use App\Entity\Traits\TimestampableEntity;
|
||||||
use App\Repository\FeedbackSetRepository;
|
use App\Repository\FeedbackSetRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: FeedbackSetRepository::class)]
|
#[ORM\Entity(repositoryClass: FeedbackSetRepository::class)]
|
||||||
class FeedbackSet implements TimestampableEntityInterface
|
class FeedbackSet implements BlameableEntityInterface, TimestampableEntityInterface
|
||||||
{
|
{
|
||||||
|
use BlameableEntity;
|
||||||
use TimestampableEntity;
|
use TimestampableEntity;
|
||||||
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
@@ -23,7 +25,7 @@ class FeedbackSet implements TimestampableEntityInterface
|
|||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
#[Assert\Count(min: 1, minMessage: 'Bitte füge mindestens eine Bewertung hinzu')]
|
#[Assert\Count(min: 1, minMessage: 'Bitte füge mindestens eine Bewertung hinzu')]
|
||||||
private array $items = [];
|
private array $ratings = [];
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
@@ -42,14 +44,14 @@ class FeedbackSet implements TimestampableEntityInterface
|
|||||||
return $this;
|
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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class JobProfile implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||||
|
#[Assert\NotNull(message: 'Bitte ordne eine Feedback-Vorlage zu')]
|
||||||
private ?FeedbackSet $feedbackSet = null;
|
private ?FeedbackSet $feedbackSet = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class FeedbackSetType extends AbstractType
|
|||||||
->add('name', TextType::class, [
|
->add('name', TextType::class, [
|
||||||
'label' => 'Bezeichnung',
|
'label' => 'Bezeichnung',
|
||||||
])
|
])
|
||||||
->add('items', CollectionType::class, [
|
->add('ratings', CollectionType::class, [
|
||||||
'label' => 'Bewertungen',
|
'label' => 'Bewertungen',
|
||||||
'entry_type' => TextType::class,
|
'entry_type' => TextType::class,
|
||||||
'allow_add' => true,
|
'allow_add' => true,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class JobProfileType extends AbstractType
|
|||||||
'label' => 'Feedback-Vorlage',
|
'label' => 'Feedback-Vorlage',
|
||||||
'class' => FeedbackSet::class,
|
'class' => FeedbackSet::class,
|
||||||
'choice_label' => 'name',
|
'choice_label' => 'name',
|
||||||
'placeholder' => 'Kein Feedback',
|
'placeholder' => '',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) }}
|
{{ form_start(form) }}
|
||||||
<div class="flex flex-col space-y-4 pb-8">
|
<div class="flex flex-col space-y-4 pb-8">
|
||||||
{{ form_row(form.name) }}
|
{{ 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">
|
<h4 class="font-bold pb-2">
|
||||||
Bewertungen
|
Bewertungen
|
||||||
</h4>
|
</h4>
|
||||||
<div {{ stimulus_target('form-collection', 'fields')}} class="flex flex-col space-y-4 mb-4">
|
<div {{ stimulus_target('form-collection', 'fields')}} class="flex flex-col space-y-4 mb-4">
|
||||||
{% do form.items.setRendered %}
|
{% do form.ratings.setRendered %}
|
||||||
{%- for item in form.items -%}{{- _self.collectionRow(item) -}}{%- endfor -%}
|
{%- for item in form.ratings -%}{{- _self.collectionRow(item) -}}{%- endfor -%}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
{{ icon('plus', 'w-4 h-4 pointer-events-none') }}
|
{{ icon('plus', 'w-4 h-4 pointer-events-none') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{ form_errors(form.items) }}
|
{{ form_errors(form.ratings) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
|||||||
@@ -27,20 +27,22 @@
|
|||||||
{{ feedbackSet.name }}
|
{{ feedbackSet.name }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ feedbackSet.items|length }}
|
{{ feedbackSet.ratings|length }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center space-x-2 justify-end">
|
<div class="flex items-center space-x-2 justify-end">
|
||||||
<button type="button"
|
{% if is_granted('DELETE', feedbackSet) %}
|
||||||
class="text-red-500"
|
<button type="button"
|
||||||
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
|
class="text-red-500"
|
||||||
{{ stimulus_action('modal-button', 'confirmation', null, {
|
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
|
||||||
'title': 'Bist du sicher?',
|
{{ stimulus_action('modal-button', 'confirmation', null, {
|
||||||
'content': 'Möchtest du das Job-Profil wirklich löschen?',
|
'title': 'Bist du sicher?',
|
||||||
'target-url': path('app_admin_system_feedback_set_delete', { 'id': feedbackSet.id })
|
'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>
|
{{ icon('delete') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
<a href="{{ path('app_admin_system_feedback_set_edit', { 'id': feedbackSet.id }) }}">
|
<a href="{{ path('app_admin_system_feedback_set_edit', { 'id': feedbackSet.id }) }}">
|
||||||
{{ icon('edit') }}
|
{{ icon('edit') }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user