From a7ff5dc47179f1dead995765fdc4e38d9dad938e Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:15:54 +0800 Subject: [PATCH 01/17] style(complementary): Remove unnecessary newlines --- templates/complementary/index.html.twig | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/complementary/index.html.twig b/templates/complementary/index.html.twig index 80415c3..60d0b95 100644 --- a/templates/complementary/index.html.twig +++ b/templates/complementary/index.html.twig @@ -42,5 +42,4 @@ - {% endblock %} From 80c58cdeeab5b7139a086a4accfa390d59cc1fdb Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:14:48 +0800 Subject: [PATCH 02/17] refactor(admin): Simplify question controller --- src/Controller/Admin/QuestionCrudController.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Controller/Admin/QuestionCrudController.php b/src/Controller/Admin/QuestionCrudController.php index 5cd41af..7f742a5 100644 --- a/src/Controller/Admin/QuestionCrudController.php +++ b/src/Controller/Admin/QuestionCrudController.php @@ -61,9 +61,6 @@ public function reindex( $questionRepository->reindex($searchService); $this->addFlash('success', t('questions.reindex.success')); - return $this->redirect($adminUrlGenerator - ->setController(self::class) - ->setAction(Action::INDEX) - ->generateUrl()); + return $this->redirect($adminUrlGenerator->setAction(Crud::PAGE_INDEX)->generateUrl()); } } From a916f484190544124a438480aeb5ae7c367d8842 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:17:09 +0800 Subject: [PATCH 03/17] refactor(entity): Use UlidType::NAME instead of literal string --- src/Entity/BaseEvent.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Entity/BaseEvent.php b/src/Entity/BaseEvent.php index c39c91c..fc2e64d 100644 --- a/src/Entity/BaseEvent.php +++ b/src/Entity/BaseEvent.php @@ -6,6 +6,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; +use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Component\Uid\Ulid; #[ORM\MappedSuperclass] @@ -15,7 +16,7 @@ abstract class BaseEvent #[ORM\Id] #[ORM\CustomIdGenerator(class: UlidGenerator::class)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\Column(type: 'ulid', unique: true)] + #[ORM\Column(type: UlidType::NAME, unique: true)] protected ?Ulid $id = null; #[ORM\Column] From 8d86e7941d267235944ba081eb0cf8a7af9059a4 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:06:43 +0800 Subject: [PATCH 04/17] feat: Add footer in application --- assets/styles/app.scss | 30 +++++++++++++++++++ src/Controller/FeedbackController.php | 0 src/Entity/FeedbackStatus.php | 0 src/Entity/FeedbackType.php | 0 src/Entity/Form/MetadataDto.php | 0 src/Form/FeedbackMetadataModelTransformer.php | 0 src/Twig/Components/AppFooter.php | 12 ++++++++ templates/app.html.twig | 24 ++++++++------- templates/components/AppFooter.html.twig | 21 +++++++++++++ 9 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/Controller/FeedbackController.php create mode 100644 src/Entity/FeedbackStatus.php create mode 100644 src/Entity/FeedbackType.php create mode 100644 src/Entity/Form/MetadataDto.php create mode 100644 src/Form/FeedbackMetadataModelTransformer.php create mode 100644 src/Twig/Components/AppFooter.php create mode 100644 templates/components/AppFooter.html.twig diff --git a/assets/styles/app.scss b/assets/styles/app.scss index dbf1e79..94c0a5c 100644 --- a/assets/styles/app.scss +++ b/assets/styles/app.scss @@ -30,6 +30,36 @@ html { font-size: 14px; } +.app-footer { + @extend .mt-5; + + &__links { + display: inline; + list-style: none; + padding: 0; + margin: 0; + + li { + display: inline; + } + + li:not(:last-child)::after { + content: "|"; + } + + li > a { + color: inherit; + text-decoration: none; + } + + li > a:hover { + span { + text-decoration: underline; + } + } + } +} + // utils .v-center { height: 100vh; diff --git a/src/Controller/FeedbackController.php b/src/Controller/FeedbackController.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Entity/FeedbackStatus.php b/src/Entity/FeedbackStatus.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Entity/FeedbackType.php b/src/Entity/FeedbackType.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Entity/Form/MetadataDto.php b/src/Entity/Form/MetadataDto.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Form/FeedbackMetadataModelTransformer.php b/src/Form/FeedbackMetadataModelTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Twig/Components/AppFooter.php b/src/Twig/Components/AppFooter.php new file mode 100644 index 0000000..dcc945b --- /dev/null +++ b/src/Twig/Components/AppFooter.php @@ -0,0 +1,12 @@ + - {% block nav %} - - {% endblock %} - - {% endblock %} +
+ {% block header %} +
+ {% block nav %} + + {% endblock %} +
+ {% endblock %} -
- {% block app %} +
+ {% block app %} - {% endblock %} + {% endblock %} +
+ +
{% endblock %} diff --git a/templates/components/AppFooter.html.twig b/templates/components/AppFooter.html.twig new file mode 100644 index 0000000..8a64333 --- /dev/null +++ b/templates/components/AppFooter.html.twig @@ -0,0 +1,21 @@ + +
+
+ © 2024 資料庫練功房 +
+ +
+ From 4c8e65f5cdac3e9075369bdb70c81911f4714a3b Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:07:21 +0800 Subject: [PATCH 05/17] fix(navbar): Do not show user if there is no user --- templates/components/Navbar.html.twig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/components/Navbar.html.twig b/templates/components/Navbar.html.twig index d11a03a..92168e6 100644 --- a/templates/components/Navbar.html.twig +++ b/templates/components/Navbar.html.twig @@ -49,6 +49,7 @@ {% endfor %}
+ {% if app.user %}
+ {% endif %} From f562fc5b97bbe1c4a0d021f12cbf25e139022083 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:12:21 +0800 Subject: [PATCH 06/17] feat: Add feedback entity --- migrations/Version20241006071505.php | 41 ++++++ src/Entity/Feedback.php | 194 ++++++++++++++++++++++++++ src/Entity/FeedbackStatus.php | 28 ++++ src/Entity/FeedbackType.php | 24 ++++ src/Entity/User.php | 37 +++++ src/Repository/FeedbackRepository.php | 20 +++ 6 files changed, 344 insertions(+) create mode 100644 migrations/Version20241006071505.php create mode 100644 src/Entity/Feedback.php create mode 100644 src/Repository/FeedbackRepository.php diff --git a/migrations/Version20241006071505.php b/migrations/Version20241006071505.php new file mode 100644 index 0000000..a4854bc --- /dev/null +++ b/migrations/Version20241006071505.php @@ -0,0 +1,41 @@ +addSql('CREATE TABLE feedback (id UUID NOT NULL, sender_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, type VARCHAR(255) NOT NULL, metadata JSON NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, contact TEXT DEFAULT NULL, status VARCHAR(255) NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_D2294458F624B39D ON feedback (sender_id)'); + $this->addSql('CREATE INDEX IDX_D22944588CDE5729 ON feedback (type)'); + $this->addSql('CREATE INDEX IDX_D2294458F624B39D8CDE5729 ON feedback (sender_id, type)'); + $this->addSql('CREATE INDEX IDX_D22944587B00651C ON feedback (status)'); + $this->addSql('COMMENT ON COLUMN feedback.id IS \'(DC2Type:ulid)\''); + $this->addSql('COMMENT ON COLUMN feedback.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN feedback.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE feedback ADD CONSTRAINT FK_D2294458F624B39D FOREIGN KEY (sender_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE feedback DROP CONSTRAINT FK_D2294458F624B39D'); + $this->addSql('DROP TABLE feedback'); + } +} diff --git a/src/Entity/Feedback.php b/src/Entity/Feedback.php new file mode 100644 index 0000000..d347980 --- /dev/null +++ b/src/Entity/Feedback.php @@ -0,0 +1,194 @@ + $metadata the metadata for the feedback + */ + #[ORM\Column(type: 'json')] + private array $metadata = []; + + #[ORM\Column] + private \DateTimeImmutable $createdAt; + + #[ORM\ManyToOne(inversedBy: 'feedback')] + #[ORM\JoinColumn(nullable: true)] + private ?User $sender; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $contact = null; + + #[ORM\Column(enumType: FeedbackStatus::class)] + private FeedbackStatus $status = FeedbackStatus::New; + + #[ORM\Column] + private \DateTimeImmutable $updated_at; + + public function getId(): ?Ulid + { + return $this->id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(string $description): static + { + $this->description = $description; + + return $this; + } + + public function getType(): ?FeedbackType + { + return $this->type; + } + + public function setType(FeedbackType $type): static + { + $this->type = $type; + + return $this; + } + + /** + * @return array + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @param array $metadata + * + * @return $this + */ + public function setMetadata(array $metadata): static + { + $this->metadata = $metadata; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getSender(): ?User + { + return $this->sender; + } + + public function setSender(?User $sender): static + { + $this->sender = $sender; + + return $this; + } + + public function getContact(): ?string + { + return $this->contact; + } + + public function setContact(?string $contact): static + { + $this->contact = $contact; + + return $this; + } + + public function getStatus(): FeedbackStatus + { + return $this->status; + } + + public function setStatus(FeedbackStatus $status): static + { + $this->status = $status; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updated_at; + } + + public function setUpdatedAt(\DateTimeImmutable $updated_at): static + { + $this->updated_at = $updated_at; + + return $this; + } + + #[ORM\PrePersist] + public function updateCreatedAtValue(): void + { + $this->createdAt = new \DateTimeImmutable(); + $this->updateUpdatedAtValue(); + } + + #[ORM\PreUpdate] + public function updateUpdatedAtValue(): void + { + $this->updated_at = new \DateTimeImmutable(); + } +} diff --git a/src/Entity/FeedbackStatus.php b/src/Entity/FeedbackStatus.php index e69de29..d6d6bbd 100644 --- a/src/Entity/FeedbackStatus.php +++ b/src/Entity/FeedbackStatus.php @@ -0,0 +1,28 @@ + $translator->trans('feedback.status.backlog', locale: $locale), + self::New => $translator->trans('feedback.status.new', locale: $locale), + self::InProgress => $translator->trans('feedback.status.in_progress', locale: $locale), + self::Resolved => $translator->trans('feedback.status.resolved', locale: $locale), + self::Closed => $translator->trans('feedback.status.closed', locale: $locale), + }; + } +} diff --git a/src/Entity/FeedbackType.php b/src/Entity/FeedbackType.php index e69de29..d1fa85a 100644 --- a/src/Entity/FeedbackType.php +++ b/src/Entity/FeedbackType.php @@ -0,0 +1,24 @@ + $translator->trans('feedback.type.bugs', locale: $locale), + self::Improvements => $translator->trans('feedback.type.improvements', locale: $locale), + self::Others => $translator->trans('feedback.type.others', locale: $locale), + }; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index eb705a8..3d2c1e6 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -84,6 +84,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\OneToMany(targetEntity: LoginEvent::class, mappedBy: 'account', orphanRemoval: true)] private Collection $loginEvents; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: Feedback::class, mappedBy: 'sender')] + private Collection $feedback; + public function __construct() { $this->solutionEvents = new ArrayCollection(); @@ -92,6 +98,7 @@ public function __construct() $this->commentLikeEvents = new ArrayCollection(); $this->hintOpenEvents = new ArrayCollection(); $this->loginEvents = new ArrayCollection(); + $this->feedback = new ArrayCollection(); } public function getId(): ?int @@ -382,4 +389,34 @@ public function removeLoginEvent(LoginEvent $loginEvent): static return $this; } + + /** + * @return Collection + */ + public function getFeedback(): Collection + { + return $this->feedback; + } + + public function addFeedback(Feedback $feedback): static + { + if (!$this->feedback->contains($feedback)) { + $this->feedback->add($feedback); + $feedback->setSender($this); + } + + return $this; + } + + public function removeFeedback(Feedback $feedback): static + { + if ($this->feedback->removeElement($feedback)) { + // set the owning side to null + if ($feedback->getSender() === $this) { + $feedback->setSender(null); + } + } + + return $this; + } } diff --git a/src/Repository/FeedbackRepository.php b/src/Repository/FeedbackRepository.php new file mode 100644 index 0000000..26035c9 --- /dev/null +++ b/src/Repository/FeedbackRepository.php @@ -0,0 +1,20 @@ + + */ +class FeedbackRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Feedback::class); + } +} From 9a20aa7077a5de276bbf64e62141b8669ff92065 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:16:41 +0800 Subject: [PATCH 07/17] feat(feedback): Add feedback form --- src/Entity/Form/MetadataDto.php | 33 ++++++++++ src/Form/FeedbackFormType.php | 66 +++++++++++++++++++ src/Form/FeedbackMetadataModelTransformer.php | 40 +++++++++++ 3 files changed, 139 insertions(+) create mode 100644 src/Form/FeedbackFormType.php diff --git a/src/Entity/Form/MetadataDto.php b/src/Entity/Form/MetadataDto.php index e69de29..6178ee8 100644 --- a/src/Entity/Form/MetadataDto.php +++ b/src/Entity/Form/MetadataDto.php @@ -0,0 +1,33 @@ + + */ + public array $metadata; + + /** + * @return array + */ + public function getMetadata(): array + { + return $this->metadata; + } + + /** + * @param array $metadata + * + * @return $this + */ + public function setMetadata(array $metadata): self + { + $this->metadata = $metadata; + + return $this; + } +} diff --git a/src/Form/FeedbackFormType.php b/src/Form/FeedbackFormType.php new file mode 100644 index 0000000..fcb3e8a --- /dev/null +++ b/src/Form/FeedbackFormType.php @@ -0,0 +1,66 @@ +add('sender', TextType::class, [ + 'label' => t('feedback.form.account'), + 'disabled' => true, + ]) + ->add('type', EnumType::class, [ + 'class' => FeedbackType::class, + 'label' => t('feedback.form.type'), + ]) + ->add('title', TextType::class, [ + 'label' => t('feedback.form.subject'), + ]) + ->add('description', TextEditorType::class, [ + 'label' => t('feedback.form.description'), + 'help' => t('feedback.form.description_help'), + 'help_html' => true, + ]) + ->add('contact', TextType::class, [ + 'label' => t('feedback.form.contact'), + 'help' => t('feedback.form.contact_help'), + ]) + ->add('metadata', HiddenType::class) + ->add('submit', SubmitType::class, [ + 'label' => t('feedback.form.submit'), + ]) + ; + + $builder->get('metadata') + ->addModelTransformer($this->metadataModelTransformer); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Feedback::class, + ]); + } +} diff --git a/src/Form/FeedbackMetadataModelTransformer.php b/src/Form/FeedbackMetadataModelTransformer.php index e69de29..0523b35 100644 --- a/src/Form/FeedbackMetadataModelTransformer.php +++ b/src/Form/FeedbackMetadataModelTransformer.php @@ -0,0 +1,40 @@ +, string> + */ +final readonly class FeedbackMetadataModelTransformer implements DataTransformerInterface +{ + public function __construct( + private SerializerInterface $serializer, + ) { + } + + public function transform(mixed $value): string + { + \assert(\is_array($value)); + + return $this->serializer->serialize( + (new MetadataDto())->setMetadata($value), + 'json' + ); + } + + /** + * @return array + */ + public function reverseTransform(mixed $value): array + { + $deserialized = $this->serializer->deserialize($value, MetadataDto::class, 'json'); + + return $deserialized->getMetadata(); + } +} From 822ab78aa8a2bacc81b3fedc459cc8a74e3c109b Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:17:40 +0800 Subject: [PATCH 08/17] feat(feedback): Add feedback page --- config/packages/security.yaml | 5 +++ src/Controller/FeedbackController.php | 58 +++++++++++++++++++++++++++ templates/feedback/index.html.twig | 24 +++++++++++ 3 files changed, 87 insertions(+) create mode 100644 templates/feedback/index.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index b89bf28..1feba76 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -39,6 +39,11 @@ security: # login page - { route: app_login, roles: PUBLIC_ACCESS } + # feedback page + # Note that we provide the feedback form in login page, + # so we need to allow public access to this page. + - { route: app_feedback, roles: PUBLIC_ACCESS } + # admin - { path: ^/admin, roles: ROLE_ADMIN } diff --git a/src/Controller/FeedbackController.php b/src/Controller/FeedbackController.php index e69de29..9a8b7b5 100644 --- a/src/Controller/FeedbackController.php +++ b/src/Controller/FeedbackController.php @@ -0,0 +1,58 @@ +setSender($user) + ->setMetadata([ + 'url' => $url, + ]); + + $form = $this->createForm(FeedbackFormType::class, $feedback); + $form = $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // add more metadata that does not affect by requests (e.g. user agent) + $feedback->setMetadata(array_merge( + $feedback->getMetadata(), + [ + 'user_agent' => $request->headers->get('User-Agent'), + 'user' => $user?->getUserIdentifier(), + ], + )); + + $entityManager->persist($feedback); + $entityManager->flush(); + + return $this->render('feedback/index.html.twig', [ + 'form' => null, + ]); + } + + return $this->render('feedback/index.html.twig', [ + 'form' => $form, + ]); + } +} diff --git a/templates/feedback/index.html.twig b/templates/feedback/index.html.twig new file mode 100644 index 0000000..9a4497e --- /dev/null +++ b/templates/feedback/index.html.twig @@ -0,0 +1,24 @@ +{% extends 'app.html.twig' %} + +{% block nav %}{% endblock %} +{% block title %}留言一覽{% endblock %} + +{% block app %} +
+

意見回饋

+ +

感謝您提出的意見回饋!請填寫下方的意見回饋表單,我們會在 24 小時內回覆您。

+

注意這份表單已經附上您目前的瀏覽器及環境資訊、回報的連結和使用者資訊,不過您也可以補充上其他您覺得有必要的資訊。

+ + + {% if form is not null %} + {{ form(form) }} + {% else %} + + {% endif %} + +
+{% endblock %} From b54dd37f152c3d504ea0528d8b052ac5fd41482d Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:17:53 +0800 Subject: [PATCH 09/17] feat(security): Add feedback button --- templates/security/login.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig index 22bd60b..db6ce09 100644 --- a/templates/security/login.html.twig +++ b/templates/security/login.html.twig @@ -59,7 +59,9 @@ From 6dfae09b9a5506d98d0b001a299e0a8978f6b3f7 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:18:11 +0800 Subject: [PATCH 10/17] feat(admin): Add feedback admin panel --- src/Controller/Admin/DashboardController.php | 4 + .../Admin/FeedbackCrudController.php | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/Controller/Admin/FeedbackCrudController.php diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index c6c8eb1..df4afb8 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -6,6 +6,7 @@ use App\Entity\Comment; use App\Entity\CommentLikeEvent; +use App\Entity\Feedback; use App\Entity\Group; use App\Entity\HintOpenEvent; use App\Entity\LoginEvent; @@ -56,5 +57,8 @@ public function configureMenuItems(): iterable yield MenuItem::linkToCrud('SolutionVideoEvent', 'fa fa-video', SolutionVideoEvent::class); yield MenuItem::linkToCrud('HintOpenEvent', 'fa fa-lightbulb', HintOpenEvent::class); yield MenuItem::linkToCrud('LoginEvent', 'fa fa-sign-in', LoginEvent::class); + + yield MenuItem::section('Feedback'); + yield MenuItem::linkToCrud('Feedback', 'fa fa-comments', Feedback::class); } } diff --git a/src/Controller/Admin/FeedbackCrudController.php b/src/Controller/Admin/FeedbackCrudController.php new file mode 100644 index 0000000..8733ba1 --- /dev/null +++ b/src/Controller/Admin/FeedbackCrudController.php @@ -0,0 +1,119 @@ +setDisabled(), + AssociationField::new('sender')->hideWhenUpdating(), + TextField::new('title')->hideWhenUpdating(), + TextEditorField::new('description')->hideWhenUpdating(), + ChoiceField::new('type')->hideWhenUpdating(), + TextField::new('contact')->hideWhenUpdating(), + ArrayField::new('metadata')->hideWhenUpdating(), + ChoiceField::new('status'), + DateTimeField::new('createdAt', 'Created at')->setDisabled(), + DateTimeField::new('updatedAt', 'Updated at')->setDisabled(), + ]; + } + + public function configureFilters(Filters $filters): Filters + { + return $filters->add('sender')->add('type')->add('status'); + } + + public function configureActions(Actions $actions): Actions + { + $actions->add(Crud::PAGE_INDEX, Action::DETAIL); + $actions->addBatchAction( + Action::new('mark_resolved', icon: 'fa fa-check') + ->linkToUrl( + $this + ->adminUrlGenerator + ->unsetAll() + ->setController(self::class) + ->setAction('markStatus') + ->set('status', FeedbackStatus::Resolved->value) + ->generateUrl() + ) + ); + $actions->addBatchAction( + Action::new('mark_closed', icon: 'fa fa-xmark') + ->linkToUrl( + $this + ->adminUrlGenerator + ->unsetAll() + ->setController(self::class) + ->setAction('markStatus') + ->set('status', FeedbackStatus::Closed->value) + ->generateUrl() + ) + ); + + return $actions; + } + + public function markStatus( + BatchActionDto $batchActionDto, + EntityManagerInterface $entityManager, + #[MapQueryParameter] FeedbackStatus $status, + ): Response { + $repository = $entityManager->getRepository(Feedback::class); + + foreach ($batchActionDto->getEntityIds() as $entityId) { + $feedback = $repository->find($entityId); + if (null === $feedback) { + continue; + } + + $feedback->setStatus($status); + $entityManager->persist($feedback); + } + + $entityManager->flush(); + $this->addFlash('success', t('feedback.marked', ['%status%' => $status])); + + return $this->redirect($batchActionDto->getReferrerUrl()); + } + + public function configureCrud(Crud $crud): Crud + { + return $crud->setDefaultSort(['createdAt' => 'DESC']); + } +} From 40ef6878930f6e433e171008b0d43f2efe39332a Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:18:35 +0800 Subject: [PATCH 11/17] feat(feedback): Add translation --- translations/messages.zh_TW.yaml | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/translations/messages.zh_TW.yaml b/translations/messages.zh_TW.yaml index 56fd815..053782c 100644 --- a/translations/messages.zh_TW.yaml +++ b/translations/messages.zh_TW.yaml @@ -46,6 +46,12 @@ HintOpenEvent: 提示打開事件 Response: 回應 LoginEvent: 登入事件 Account: 帳號 +Feedback: 回饋 +Sender: 寄件者 +Contact: 聯絡方式 +Metadata: 中繼資料 +Mark Resolved: 標記為已解決 +Mark Closed: 標記為已關閉 challenge.error-type.user: 輸入錯誤 challenge.error-type.server: 伺服器錯誤 @@ -93,3 +99,38 @@ charts: instruction: hint: no_hint: 沒有提示。 + +feedback: + type: + bugs: 錯誤報告 + improvements: 功能建議 + others: 其他意見 + + form: + account: 意見回應帳號 + subject: 主旨 + description: 詳細說明 + type: 回饋類型 + description_help: | +

「詳細說明」應該包括:

+
    +
  1. 問題的清楚描述,比如「錯誤的 SQL 語法會導致伺服器錯誤」。
  2. +
  3. 如何重現問題,比如「在答案欄輸入 'SELECT * FROM' 後按下提交」。
  4. +
  5. 預期的行為,比如「應該顯示『語法錯誤』」。
  6. +
  7. 實際的行為,比如「顯示『伺服器錯誤』」。
  8. +
+

「重現問題」最好包括螢幕截圖和螢幕錄影。你可以上傳到雲端空間,然後將連結複製給我們。

+ contact: 聯絡方式 + contact_help: | + 如果有其他需要確認,或者是你想要收到問題的回應進度,請留下你的聯絡方式。 + 如果不填寫,則使用你帳號的電子信箱進行回覆(如果沒有登入則不會回覆)。 + submit: 送出回饋 + + status: + backlog: 待處理 + new: 新問題 + in_progress: 處理中 + resolved: 已解決 + closed: 已關閉 + + marked: 已經將選擇的回饋標記為「%status%」。 From dac320ea219fcadec9f70beda046645bd42d727a Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:34:39 +0800 Subject: [PATCH 12/17] refactor(footer): Remove unused PHP twig class --- src/Twig/Components/AppFooter.php | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/Twig/Components/AppFooter.php diff --git a/src/Twig/Components/AppFooter.php b/src/Twig/Components/AppFooter.php deleted file mode 100644 index dcc945b..0000000 --- a/src/Twig/Components/AppFooter.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Sun, 6 Oct 2024 16:34:58 +0800 Subject: [PATCH 13/17] fix(feedback): Contacts is not required --- src/Form/FeedbackFormType.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Form/FeedbackFormType.php b/src/Form/FeedbackFormType.php index fcb3e8a..2311471 100644 --- a/src/Form/FeedbackFormType.php +++ b/src/Form/FeedbackFormType.php @@ -46,6 +46,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ->add('contact', TextType::class, [ 'label' => t('feedback.form.contact'), 'help' => t('feedback.form.contact_help'), + 'required' => false, ]) ->add('metadata', HiddenType::class) ->add('submit', SubmitType::class, [ From 64cc681d7369878be3ced4103cb5199aa27a2707 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sun, 6 Oct 2024 16:35:19 +0800 Subject: [PATCH 14/17] feat(feedback): Apply field-sizing to description --- templates/feedback/index.html.twig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/templates/feedback/index.html.twig b/templates/feedback/index.html.twig index 9a4497e..3631ff2 100644 --- a/templates/feedback/index.html.twig +++ b/templates/feedback/index.html.twig @@ -13,6 +13,13 @@ {% if form is not null %} {{ form(form) }} + + {% else %}