Add quiz clearing and deletion functionality with UI enhancements

This commit introduces the ability to clear quiz results and delete quizzes directly from the backoffice. It includes new routes, controllers, modals for user confirmation, and updates to translations. The `QuizRepository` now supports dedicated methods for clearing results and deleting quizzes along with error handling. Related database migrations and front-end adjustments are also included.
This commit is contained in:
2025-06-07 20:49:21 +02:00
parent 79236d84e9
commit 6a77df402d
18 changed files with 327 additions and 63 deletions

View File

@@ -1,5 +1,5 @@
import * as bootstrap from 'bootstrap'
import './bootstrap.js'; import './bootstrap.js';
import 'bootstrap/dist/css/bootstrap.min.css' import 'bootstrap/dist/css/bootstrap.min.css'
import * as bootstrap from 'bootstrap'
import './styles/backoffice.scss'; import './styles/backoffice.scss';

View File

@@ -0,0 +1,17 @@
import {Controller} from '@hotwired/stimulus';
import * as bootstrap from 'bootstrap'
export default class extends Controller {
connect() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
}
clearQuiz() {
new bootstrap.Modal('#clearQuizModal').show();
}
deleteQuiz() {
new bootstrap.Modal('#deleteQuizModal').show();
}
}

View File

@@ -0,0 +1,41 @@
<?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 Version20250607154730 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(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT FK_F0E45BA96706D6B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT FK_F0E45BA96706D6B FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT fk_f0e45ba96706d6b
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT fk_f0e45ba96706d6b FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
}

View File

@@ -0,0 +1,53 @@
<?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 Version20250607184525 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(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT FK_5947284F853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT FK_5947284F853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT FK_9AC61A30853CD175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT FK_9AC61A30853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT fk_9ac61a30853cd175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT fk_9ac61a30853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT fk_5947284f853cd175
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT fk_5947284f853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
}

View File

@@ -27,6 +27,7 @@ return RectorConfig::configure()
phpunitCodeQuality: true, phpunitCodeQuality: true,
doctrineCodeQuality: true, doctrineCodeQuality: true,
symfonyCodeQuality: true, symfonyCodeQuality: true,
// naming: true
) )
->withAttributesSets(all: true) ->withAttributesSets(all: true)
->withComposerBased(twig: true, doctrine: true, phpunit: true, symfony: true) ->withComposerBased(twig: true, doctrine: true, phpunit: true, symfony: true)

View File

@@ -10,6 +10,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBase
abstract class AbstractController extends AbstractBaseController abstract class AbstractController extends AbstractBaseController
{ {
protected const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}'; protected const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
protected const string CANDIDATE_HASH_REGEX = '[\w\-=]+'; protected const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
#[\Override] #[\Override]

View File

@@ -39,9 +39,10 @@ final class PrepareEliminationController extends AbstractController
$elimination->updateFromInputBag($request->request); $elimination->updateFromInputBag($request->request);
$em->flush(); $em->flush();
if (true === $request->request->getBoolean('start')) { if ($request->request->getBoolean('start')) {
return $this->redirectToRoute('app_elimination', ['elimination' => $elimination->getId()]); return $this->redirectToRoute('app_elimination', ['elimination' => $elimination->getId()]);
} }
$this->addFlash('success', 'Elimination updated'); $this->addFlash('success', 'Elimination updated');
} }

View File

@@ -8,8 +8,10 @@ use App\Controller\AbstractController;
use App\Entity\Candidate; use App\Entity\Candidate;
use App\Entity\Quiz; use App\Entity\Quiz;
use App\Entity\Season; use App\Entity\Season;
use App\Exception\ErrorClearingQuizException;
use App\Repository\CandidateRepository; use App\Repository\CandidateRepository;
use App\Repository\QuizCandidateRepository; use App\Repository\QuizCandidateRepository;
use App\Repository\QuizRepository;
use App\Security\Voter\SeasonVoter; use App\Security\Voter\SeasonVoter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -19,6 +21,7 @@ use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Translation\TranslatorInterface;
#[AsController] #[AsController]
#[IsGranted('ROLE_USER')] #[IsGranted('ROLE_USER')]
@@ -26,6 +29,7 @@ class QuizController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly CandidateRepository $candidateRepository, private readonly CandidateRepository $candidateRepository,
private readonly TranslatorInterface $translator,
) {} ) {}
#[Route( #[Route(
@@ -61,6 +65,37 @@ class QuizController extends AbstractController
return $this->redirectToRoute('app_backoffice_season', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('app_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
} }
#[Route(
'/backoffice/quiz/{quiz}/clear',
name: 'app_backoffice_quiz_clear',
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function clearQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
try {
$quizRepository->clearQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz cleared'));
} catch (ErrorClearingQuizException) {
$this->addFlash('error', $this->translator->trans('Error clearing quiz'));
}
return $this->redirectToRoute('app_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
}
#[Route(
'/backoffice/quiz/{quiz}/delete',
name: 'app_backoffice_quiz_delete',
)]
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
public function deleteQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
$quizRepository->deleteQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz deleted'));
return $this->redirectToRoute('app_backoffice_season', ['seasonCode' => $quiz->getSeason()->getSeasonCode()]);
}
#[Route( #[Route(
'/backoffice/quiz/{quiz}/modify_correction/{candidate}', '/backoffice/quiz/{quiz}/modify_correction/{candidate}',
name: 'app_backoffice_modify_correction', name: 'app_backoffice_modify_correction',

View File

@@ -26,7 +26,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
#[IsGranted('ROLE_USER')] #[IsGranted('ROLE_USER')]
class SeasonController extends AbstractController class SeasonController extends AbstractController
{ {
public function __construct(private readonly TranslatorInterface $translator, private EntityManagerInterface $em, public function __construct(private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $em,
) {} ) {}
#[Route( #[Route(

View File

@@ -18,6 +18,7 @@ use Symfony\Component\Uid\Uuid;
class Elimination class Elimination
{ {
public const string SCREEN_GREEN = 'green'; public const string SCREEN_GREEN = 'green';
public const string SCREEN_RED = 'red'; public const string SCREEN_RED = 'red';
#[ORM\Id] #[ORM\Id]
@@ -35,7 +36,7 @@ class Elimination
public function __construct( public function __construct(
#[ORM\ManyToOne(inversedBy: 'eliminations')] #[ORM\ManyToOne(inversedBy: 'eliminations')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz, private Quiz $quiz,
) {} ) {}
@@ -66,7 +67,7 @@ class Elimination
/** @param InputBag<bool|float|int|string> $inputBag */ /** @param InputBag<bool|float|int|string> $inputBag */
public function updateFromInputBag(InputBag $inputBag): self public function updateFromInputBag(InputBag $inputBag): self
{ {
foreach ($this->data as $name => $screenColour) { foreach (array_keys($this->data) as $name) {
$newColour = $inputBag->get('colour-'.mb_strtolower($name)); $newColour = $inputBag->get('colour-'.mb_strtolower($name));
if (\is_string($newColour)) { if (\is_string($newColour)) {
$this->data[$name] = $inputBag->get('colour-'.mb_strtolower($name)); $this->data[$name] = $inputBag->get('colour-'.mb_strtolower($name));

View File

@@ -31,7 +31,7 @@ class GivenAnswer
private Candidate $candidate, private Candidate $candidate,
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz, private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')] #[ORM\ManyToOne(inversedBy: 'givenAnswers')]

View File

@@ -43,6 +43,7 @@ class Season
private Collection $owners; private Collection $owners;
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?Quiz $ActiveQuiz = null; private ?Quiz $ActiveQuiz = null;
public function __construct() public function __construct()

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exception;
class ErrorClearingQuizException extends \Exception {}

View File

@@ -4,17 +4,59 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Entity\Elimination;
use App\Entity\GivenAnswer;
use App\Entity\Quiz; use App\Entity\Quiz;
use App\Entity\QuizCandidate;
use App\Exception\ErrorClearingQuizException;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
/** /**
* @extends ServiceEntityRepository<Quiz> * @extends ServiceEntityRepository<Quiz>
*/ */
class QuizRepository extends ServiceEntityRepository class QuizRepository extends ServiceEntityRepository
{ {
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry, private readonly LoggerInterface $logger)
{ {
parent::__construct($registry, Quiz::class); parent::__construct($registry, Quiz::class);
} }
/** @throws ErrorClearingQuizException */
public function clearQuiz(Quiz $quiz): void
{
$em = $this->getEntityManager();
$em->beginTransaction();
try {
$em->createQueryBuilder()
->delete()->from(QuizCandidate::class, 'qc')
->where('qc.quiz = :quiz')
->setParameter('quiz', $quiz)
->getQuery()->execute();
$em->createQueryBuilder()
->delete()->from(GivenAnswer::class, 'ga')
->where('ga.quiz = :quiz')
->setParameter('quiz', $quiz)
->getQuery()->execute();
$em->createQueryBuilder()
->delete()->from(Elimination::class, 'e')
->where('e.quiz = :quiz')
->setParameter('quiz', $quiz)
->getQuery()->execute();
} catch (\Throwable $throwable) {
$this->logger->error($throwable->getMessage());
$em->rollback();
throw new ErrorClearingQuizException(previous: $throwable);
}
$em->commit();
}
public function deleteQuiz(Quiz $quiz): void
{
$this->getEntityManager()->remove($quiz);
$this->getEntityManager()->flush();
}
} }

View File

@@ -11,6 +11,7 @@
{{ 'Add'|trans }} {{ 'Add'|trans }}
</a> </a>
</div> </div>
{% if seasons %}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@@ -45,9 +46,10 @@
<a href="{{ path('app_backoffice_season', {seasonCode: season.seasonCode}) }}">{{ 'Manage'|trans }}</a> <a href="{{ path('app_backoffice_season', {seasonCode: season.seasonCode}) }}">{{ 'Manage'|trans }}</a>
</td> </td>
</tr> </tr>
{% else %}
EMPTY
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
{{ 'You have no seasons yet.'|trans }}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -2,13 +2,19 @@
{% block body %} {% block body %}
<h2 class="py-2">{{ 'Quiz'|trans }}: {{ quiz.season.name }} - {{ quiz.name }}</h2> <h2 class="py-2">{{ 'Quiz'|trans }}: {{ quiz.season.name }} - {{ quiz.name }}</h2>
<div class="py-2 btn-group"> <div class="py-2 btn-group" data-controller="bo--quiz">
<a class="btn btn-primary {% if quiz is same as(season.activeQuiz) %}disabled{% endif %}" <a class="btn btn-primary {% if quiz is same as(season.activeQuiz) %}disabled{% endif %}"
href="{{ path('app_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ 'Make active'|trans }}</a> href="{{ path('app_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ 'Make active'|trans }}</a>
{% if quiz is same as (season.activeQuiz) %} {% if quiz is same as (season.activeQuiz) %}
<a class="btn btn-secondary" <a class="btn btn-secondary"
href="{{ path('app_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}">{{ 'Deactivate Quiz'|trans }}</a> href="{{ path('app_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}">{{ 'Deactivate Quiz'|trans }}</a>
{% endif %} {% endif %}
<button class="btn btn-danger" data-action="click->bo--quiz#clearQuiz">
{{ 'Clear quiz...'|trans }}
</button>
<button class="btn btn-danger" data-action="click->bo--quiz#deleteQuiz">
{{ 'Delete Quiz...'|trans }}
</button>
</div> </div>
<div id="questions"> <div id="questions">
@@ -111,16 +117,48 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function () {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
});
</script>
{% endblock javascripts %}
{% block title %}
{# Modal Clear #}
<div class="modal fade" id="clearQuizModal" data-bs-backdrop="static"
tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Please Confirm</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to clear all the results? This will also delete al the eliminations.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
<a href="{{ path('app_backoffice_quiz_clear', {quiz: quiz.id}) }}" class="btn btn-danger">Yes</a>
</div>
</div>
</div>
</div>
{# Modal Delete #}
<div class="modal fade" id="deleteQuizModal" data-bs-backdrop="static"
tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Please Confirm</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete this quiz?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
<a href="{{ path('app_backoffice_quiz_delete', {quiz: quiz.id}) }}" class="btn btn-danger">Yes</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block title %}
{% endblock %} {% endblock %}

View File

@@ -18,19 +18,19 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
class SeasonVoterTest extends TestCase final class SeasonVoterTest extends TestCase
{ {
private SeasonVoter $seasonVoter; private SeasonVoter $seasonVoter;
private TokenInterface&Stub $token; private TokenInterface&Stub $token;
private User&Stub $user;
protected function setUp(): void protected function setUp(): void
{ {
$this->seasonVoter = new SeasonVoter(); $this->seasonVoter = new SeasonVoter();
$this->token = $this->createStub(TokenInterface::class); $this->token = $this->createStub(TokenInterface::class);
$this->user = $this->createStub(User::class); $user = $this->createStub(User::class);
$this->token->method('getUser')->willReturn($this->user); $this->token->method('getUser')->willReturn($user);
} }
#[DataProvider('typesProvider')] #[DataProvider('typesProvider')]

View File

@@ -49,6 +49,10 @@
<source>Candidates</source> <source>Candidates</source>
<target>Kandidaten</target> <target>Kandidaten</target>
</trans-unit> </trans-unit>
<trans-unit id="FNY513f" resname="Clear quiz...">
<source>Clear quiz...</source>
<target>Test leegmaken...</target>
</trans-unit>
<trans-unit id="sFpB4C2" resname="Correct Answers"> <trans-unit id="sFpB4C2" resname="Correct Answers">
<source>Correct Answers</source> <source>Correct Answers</source>
<target>Goede antwoorden</target> <target>Goede antwoorden</target>
@@ -77,6 +81,10 @@
<source>Deactivate Quiz</source> <source>Deactivate Quiz</source>
<target>Deactiveer test</target> <target>Deactiveer test</target>
</trans-unit> </trans-unit>
<trans-unit id="p9GNNI3" resname="Delete Quiz...">
<source>Delete Quiz...</source>
<target>Test verwijderen...</target>
</trans-unit>
<trans-unit id="R9yHzHv" resname="Download Template"> <trans-unit id="R9yHzHv" resname="Download Template">
<source>Download Template</source> <source>Download Template</source>
<target>Download sjabloon</target> <target>Download sjabloon</target>
@@ -93,6 +101,10 @@
<source>Enter your name</source> <source>Enter your name</source>
<target>Voor je naam in</target> <target>Voor je naam in</target>
</trans-unit> </trans-unit>
<trans-unit id="HNMwvRn" resname="Error clearing quiz">
<source>Error clearing quiz</source>
<target>Fout bij leegmaken test</target>
</trans-unit>
<trans-unit id="OGiIhMH" resname="Green"> <trans-unit id="OGiIhMH" resname="Green">
<source>Green</source> <source>Green</source>
<target>Groen</target> <target>Groen</target>
@@ -177,10 +189,18 @@
<source>Quiz Added!</source> <source>Quiz Added!</source>
<target>Test toegevoegd!</target> <target>Test toegevoegd!</target>
</trans-unit> </trans-unit>
<trans-unit id="vXN8b2w" resname="Quiz cleared">
<source>Quiz cleared</source>
<target>Test leeggemaakt</target>
</trans-unit>
<trans-unit id="LbVe.2c" resname="Quiz completed"> <trans-unit id="LbVe.2c" resname="Quiz completed">
<source>Quiz completed</source> <source>Quiz completed</source>
<target>Test voltooid</target> <target>Test voltooid</target>
</trans-unit> </trans-unit>
<trans-unit id="XdfTTMD" resname="Quiz deleted">
<source>Quiz deleted</source>
<target>Test verwijderd</target>
</trans-unit>
<trans-unit id="frxoIkW" resname="Quiz name"> <trans-unit id="frxoIkW" resname="Quiz name">
<source>Quiz name</source> <source>Quiz name</source>
<target>Testnaam</target> <target>Testnaam</target>
@@ -237,10 +257,6 @@
<source>Sign in</source> <source>Sign in</source>
<target>Log in</target> <target>Log in</target>
</trans-unit> </trans-unit>
<trans-unit id="2QO7aYC" resname="Start Elimination">
<source>Start Elimination</source>
<target>Start eliminatie</target>
</trans-unit>
<trans-unit id="9m8DOBg" resname="Submit"> <trans-unit id="9m8DOBg" resname="Submit">
<source>Submit</source> <source>Submit</source>
<target>Verstuur</target> <target>Verstuur</target>
@@ -253,10 +269,18 @@
<source>There are no answers for this question</source> <source>There are no answers for this question</source>
<target>Er zijn geen antwoorden voor deze vraag</target> <target>Er zijn geen antwoorden voor deze vraag</target>
</trans-unit> </trans-unit>
<trans-unit id=".LrcTyU" resname="There is no active quiz">
<source>There is no active quiz</source>
<target>Er is geen test actief</target>
</trans-unit>
<trans-unit id="Dptvysv" resname="Time"> <trans-unit id="Dptvysv" resname="Time">
<source>Time</source> <source>Time</source>
<target>Tijd</target> <target>Tijd</target>
</trans-unit> </trans-unit>
<trans-unit id="0afY1NF" resname="You have no seasons yet.">
<source>You have no seasons yet.</source>
<target>Je hebt nog geen seizoenen.</target>
</trans-unit>
<trans-unit id="vVQAP9A" resname="Your Seasons"> <trans-unit id="vVQAP9A" resname="Your Seasons">
<source>Your Seasons</source> <source>Your Seasons</source>
<target>Jouw seizoenen</target> <target>Jouw seizoenen</target>