mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-05 23:20:18 +02:00
feat: address PR review comments — unassign/sync bank questions, blank quiz creation, deactivate redirect, remove duplicate tab titles
- Use Symfony ObjectMapper for BankQuestion/BankAnswer → Question/Answer copy (#[Map(if: false)] on id, season, etc.) - Track created Question on BankQuestionUsage (nullable FK, onDelete: SET NULL) for unassign/sync support - Add unassign route: removes the Question copy + usage record - Add sync route: pushes bank question edits to a finalized-not-started quiz copy - Auto-sync non-finalized quiz copies on bank question edit; flash warning for finalized-not-started - Add blank quiz creation (no XLSX required) with new route + template - Deactivate quiz button now stays on the quiz overview page (redirect_quiz hidden field) - Remove duplicate h4 titles below the tab bar on all season tabs - Add migration for bank_question_usage.question_id - Add Dutch translations for all new strings
This commit is contained in:
@@ -19,6 +19,7 @@ use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Tvdt\Controller\AbstractController;
|
||||
use Tvdt\Entity\BankQuestion;
|
||||
use Tvdt\Entity\BankQuestionUsage;
|
||||
use Tvdt\Entity\QuestionLabel;
|
||||
use Tvdt\Entity\Quiz;
|
||||
use Tvdt\Entity\Season;
|
||||
@@ -121,6 +122,8 @@ class QuestionBankController extends AbstractController
|
||||
$this->applyAnswerOrdering($bankQuestion);
|
||||
$this->em->flush();
|
||||
|
||||
$this->syncUsagesAfterEdit($bankQuestion);
|
||||
|
||||
$this->addFlash(FlashType::Success, $this->translator->trans('Question updated'));
|
||||
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
@@ -245,6 +248,64 @@ class QuestionBankController extends AbstractController
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
}
|
||||
|
||||
#[IsCsrfTokenValid('unassign_bank_question')]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
#[Route(
|
||||
'/backoffice/season/{seasonCode:season}/question-bank/{bankQuestion}/unassign/{usage}',
|
||||
name: 'tvdt_backoffice_question_bank_unassign',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'bankQuestion' => Requirement::UUID, 'usage' => Requirement::UUID],
|
||||
methods: ['POST'],
|
||||
priority: 10,
|
||||
)]
|
||||
public function unassign(Season $season, BankQuestion $bankQuestion, BankQuestionUsage $usage): RedirectResponse
|
||||
{
|
||||
$this->assertSameSeason($season, $bankQuestion->season);
|
||||
|
||||
if ($usage->bankQuestion !== $bankQuestion) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
if ($usage->quiz->isLocked()) {
|
||||
$this->addFlash(FlashType::Danger, $this->translator->trans('This quiz can no longer be altered'));
|
||||
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
}
|
||||
|
||||
$this->questionBankService->unassignFromQuiz($usage);
|
||||
$this->addFlash(FlashType::Success, $this->translator->trans('Question removed from quiz %quiz%', ['%quiz%' => $usage->quiz->name]));
|
||||
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
}
|
||||
|
||||
#[IsCsrfTokenValid('sync_bank_question')]
|
||||
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
|
||||
#[Route(
|
||||
'/backoffice/season/{seasonCode:season}/question-bank/{bankQuestion}/sync/{usage}',
|
||||
name: 'tvdt_backoffice_question_bank_sync',
|
||||
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'bankQuestion' => Requirement::UUID, 'usage' => Requirement::UUID],
|
||||
methods: ['POST'],
|
||||
priority: 10,
|
||||
)]
|
||||
public function syncToQuiz(Season $season, BankQuestion $bankQuestion, BankQuestionUsage $usage): RedirectResponse
|
||||
{
|
||||
$this->assertSameSeason($season, $bankQuestion->season);
|
||||
|
||||
if ($usage->bankQuestion !== $bankQuestion) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
if ($usage->quiz->hasStartedCandidates()) {
|
||||
$this->addFlash(FlashType::Danger, $this->translator->trans('This quiz has already been filled in and can no longer be altered'));
|
||||
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
}
|
||||
|
||||
$this->questionBankService->syncToQuiz($bankQuestion, $usage);
|
||||
$this->addFlash(FlashType::Success, $this->translator->trans('Question synced to quiz %quiz%', ['%quiz%' => $usage->quiz->name]));
|
||||
|
||||
return $this->redirectToRoute('tvdt_backoffice_question_bank', ['seasonCode' => $season->seasonCode]);
|
||||
}
|
||||
|
||||
private function assertSameSeason(Season $season, Season $subjectSeason): void
|
||||
{
|
||||
if ($season !== $subjectSeason) {
|
||||
@@ -259,4 +320,26 @@ class QuestionBankController extends AbstractController
|
||||
$answer->ordering = $ordering++;
|
||||
}
|
||||
}
|
||||
|
||||
private function syncUsagesAfterEdit(BankQuestion $bankQuestion): void
|
||||
{
|
||||
$pendingNames = [];
|
||||
foreach ($bankQuestion->usages as $usage) {
|
||||
if (!$usage->quiz->isFinalized()) {
|
||||
$this->questionBankService->syncToQuiz($bankQuestion, $usage);
|
||||
} elseif (!$usage->quiz->hasStartedCandidates()) {
|
||||
$pendingNames[] = $usage->quiz->name;
|
||||
}
|
||||
}
|
||||
|
||||
if ([] !== $pendingNames) {
|
||||
$this->addFlash(
|
||||
FlashType::Warning,
|
||||
$this->translator->trans(
|
||||
'The question was not synced to finalized quiz(zes): %quizzes%. Use the Sync button to update them.',
|
||||
['%quizzes%' => implode(', ', $pendingNames)],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user