Refactor code for improved readability and consistency; add flash message handling and enhance quiz functionality
Some checks failed
CI / Tests (push) Failing after 9s
CI / Docker Lint (push) Successful in 4s

This commit is contained in:
2025-03-12 23:18:13 +01:00
parent 448daed6ea
commit acf5c06fcc
21 changed files with 309 additions and 80 deletions

View File

@@ -1,17 +1,20 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$finder = (new PhpCsFixer\Finder()) $finder = (new Finder())
->in(__DIR__) ->in(__DIR__)
->exclude('var') ->exclude('var')
; ;
return (new PhpCsFixer\Config()) return (new Config())
->setRules([ ->setRules([
'@Symfony' => true, '@Symfony' => true,
'@Symfony:risky' => true, '@Symfony:risky' => true,
'declare_strict_types' => true, 'declare_strict_types' => true,
'fully_qualified_strict_types' => ['import_symbols' => true],
'linebreak_after_opening_tag' => true, 'linebreak_after_opening_tag' => true,
'mb_str_functions' => true, 'mb_str_functions' => true,
'no_php4_constructor' => true, 'no_php4_constructor' => true,
@@ -19,11 +22,11 @@ return (new PhpCsFixer\Config())
'no_useless_else' => true, 'no_useless_else' => true,
'no_useless_return' => true, 'no_useless_return' => true,
'php_unit_strict' => true, 'php_unit_strict' => true,
'phpdoc_line_span' => ['const' => 'single', 'method' => 'single', 'property' => 'single'],
'phpdoc_order' => true, 'phpdoc_order' => true,
'single_line_empty_body' => true,
'strict_comparison' => true, 'strict_comparison' => true,
'strict_param' => true, 'strict_param' => true,
'blank_line_between_import_groups' => false,
'phpdoc_line_span' => ['const' => 'single', 'method' => 'single', 'property' => 'single'],
]) ])
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setFinder($finder) ->setFinder($finder)

View File

@@ -1,17 +1,28 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MakerBundle\MakerBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
use Symfony\UX\TwigComponent\TwigComponentBundle;
use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
return [ return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], MakerBundle::class => ['dev' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], TwigBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], SecurityBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], WebProfilerBundle::class => ['dev' => true, 'test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], TwigExtraBundle::class => ['all' => true],
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], TwigComponentBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true], EasyAdminBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
]; ];

View File

@@ -7,8 +7,8 @@ use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return static function (array $context): Kernel { return static function (array $context): Kernel {
$appEnv = !empty($context['APP_ENV']) ? (string) $context['APP_ENV'] : 'prod'; $appEnv = empty($context['APP_ENV']) ? 'prod' : (string) $context['APP_ENV'];
$appDebug = !empty($context['APP_DEBUG']) ? filter_var($context['APP_DEBUG'], \FILTER_VALIDATE_BOOL) : 'prod' !== $appEnv; $appDebug = empty($context['APP_DEBUG']) ? 'prod' !== $appEnv : filter_var($context['APP_DEBUG'], \FILTER_VALIDATE_BOOL);
return new Kernel($appEnv, $appDebug); return new Kernel($appEnv, $appDebug);
}; };

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Repository\CandidateRepository;
use App\Repository\QuizRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:test-command',
description: 'Add a short description for your command',
)]
class TestCommand extends Command
{
public function __construct(private readonly CandidateRepository $candidateRepository, private readonly QuizRepository $quizRepository)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
new SymfonyStyle($input, $output);
dd($this->candidateRepository->getScores($this->quizRepository->find('1effa06a-8aca-6c52-b52b-3974eda7eed7')));
return Command::SUCCESS;
}
}

View File

@@ -9,11 +9,13 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBase
abstract class AbstractController extends AbstractBaseController abstract class AbstractController extends AbstractBaseController
{ {
#[\Override]
protected function addFlash(FlashType|string $type, mixed $message): void protected function addFlash(FlashType|string $type, mixed $message): void
{ {
if ($type instanceof FlashType) { if ($type instanceof FlashType) {
$type = $type->value; $type = $type->value;
} }
parent::addFlash($type, $message); parent::addFlash($type, $message);
} }
} }

View File

@@ -22,6 +22,7 @@ use Symfony\Component\Routing\Attribute\Route;
class DashboardController extends AbstractDashboardController class DashboardController extends AbstractDashboardController
{ {
#[Route('/admin', name: 'admin')] #[Route('/admin', name: 'admin')]
#[\Override]
public function index(): Response public function index(): Response
{ {
// return parent::index(); // return parent::index();
@@ -44,12 +45,14 @@ class DashboardController extends AbstractDashboardController
// return $this->render('some/path/my-dashboard.html.twig'); // return $this->render('some/path/my-dashboard.html.twig');
} }
#[\Override]
public function configureDashboard(): Dashboard public function configureDashboard(): Dashboard
{ {
return Dashboard::new() return Dashboard::new()
->setTitle('TijdVoorDeTest'); ->setTitle('TijdVoorDeTest');
} }
#[\Override]
public function configureMenuItems(): iterable public function configureMenuItems(): iterable
{ {
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home'); yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');

View File

@@ -6,6 +6,7 @@ namespace App\Controller;
use App\Entity\Quiz; use App\Entity\Quiz;
use App\Entity\Season; use App\Entity\Season;
use App\Repository\CandidateRepository;
use App\Repository\SeasonRepository; use App\Repository\SeasonRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -13,14 +14,14 @@ use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
#[AsController] #[AsController]
#[Route('/backoffice', name: 'backoffice_')]
final class BackofficeController extends AbstractController final class BackofficeController extends AbstractController
{ {
public function __construct(private readonly SeasonRepository $seasonRepository) public function __construct(
{ private readonly SeasonRepository $seasonRepository,
} private readonly CandidateRepository $candidateRepository,
) {}
#[Route('/', name: 'index')] #[Route('/backoffice/', name: 'index')]
public function index(): Response public function index(): Response
{ {
$seasons = $this->seasonRepository->findAll(); $seasons = $this->seasonRepository->findAll();
@@ -30,7 +31,7 @@ final class BackofficeController extends AbstractController
]); ]);
} }
#[Route('/{seasonCode}', name: 'season')] #[Route('/backoffice/{seasonCode}', name: 'season')]
public function season(Season $season): Response public function season(Season $season): Response
{ {
return $this->render('backoffice/season.html.twig', [ return $this->render('backoffice/season.html.twig', [
@@ -38,12 +39,13 @@ final class BackofficeController extends AbstractController
]); ]);
} }
#[Route('/{seasonCode}/{quiz}', name: 'quiz')] #[Route('/backoffice/{seasonCode}/{quiz}', name: 'quiz')]
public function quiz(Season $season, Quiz $quiz): Response public function quiz(Season $season, Quiz $quiz): Response
{ {
return $this->render('backoffice/quiz.html.twig', [ return $this->render('backoffice/quiz.html.twig', [
'season' => $season, 'season' => $season,
'quiz' => $quiz, 'quiz' => $quiz,
'result' => $this->candidateRepository->getScores($quiz),
]); ]);
} }
} }

View File

@@ -28,6 +28,7 @@ use Symfony\Component\Routing\Attribute\Route;
final class QuizController extends AbstractController final class QuizController extends AbstractController
{ {
public const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}'; public const string SEASON_CODE_REGEX = '[A-Za-z\d]{5}';
private const string CANDIDATE_HASH_REGEX = '[\w\-=]+'; private const string CANDIDATE_HASH_REGEX = '[\w\-=]+';
#[Route(path: '/', name: 'select_season', methods: ['GET', 'POST'])] #[Route(path: '/', name: 'select_season', methods: ['GET', 'POST'])]
@@ -83,7 +84,7 @@ final class QuizController extends AbstractController
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash); $candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
if (!$candidate instanceof Candidate) { if (!$candidate instanceof Candidate) {
if (true === $season->isPreregisterCandidates()) { if ($season->isPreregisterCandidates()) {
$this->addFlash(FlashType::Danger, 'Candidate not found'); $this->addFlash(FlashType::Danger, 'Candidate not found');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\EliminationRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: EliminationRepository::class)]
class Elimination
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id = null;
#[ORM\Column(type: Types::JSON)]
private array $data = [];
public function getId(): ?int
{
return $this->id;
}
public function getData(): array
{
return $this->data;
}
public function setData(array $data): static
{
$this->data = $data;
return $this;
}
}

View File

@@ -96,4 +96,23 @@ class Question
return $this; return $this;
} }
public function getErrors(): ?string
{
if (0 === \count($this->answers)) {
return 'This question has no answers';
}
$correctAnswers = $this->answers->filter(static fn (Answer $answer): ?bool => $answer->isRightAnswer())->count();
if (0 === $correctAnswers) {
return 'This question has no correct answers';
}
if ($correctAnswers > 1) {
return 'This question has multiple correct answers';
}
return null;
}
} }

View File

@@ -11,9 +11,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class EnterNameType extends AbstractType class EnterNameType extends AbstractType
{ {
public function __construct(private TranslatorInterface $translator) public function __construct(private readonly TranslatorInterface $translator) {}
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {

View File

@@ -8,9 +8,7 @@ use Safe\Exceptions\UrlException;
class Base64 class Base64
{ {
private function __construct() private function __construct() {}
{
}
public static function base64_url_encode(string $input): string public static function base64_url_encode(string $input): string
{ {

View File

@@ -5,14 +5,20 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Entity\Candidate; use App\Entity\Candidate;
use App\Entity\Correction;
use App\Entity\Quiz;
use App\Entity\Season; use App\Entity\Season;
use App\Helpers\Base64; use App\Helpers\Base64;
use DateInterval;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Safe\Exceptions\UrlException; use Safe\Exceptions\UrlException;
/** /**
* @extends ServiceEntityRepository<Candidate> * @extends ServiceEntityRepository<Candidate>
*
* @phpstan-type ResultArray array<string, array{0: Candidate, correct: int, time: DateInterval, corrections?: float, score: float}>
*/ */
class CandidateRepository extends ServiceEntityRepository class CandidateRepository extends ServiceEntityRepository
{ {
@@ -41,8 +47,54 @@ class CandidateRepository extends ServiceEntityRepository
{ {
$this->getEntityManager()->persist($candidate); $this->getEntityManager()->persist($candidate);
if (true === $flush) { if ($flush) {
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
} }
} }
/** @return ResultArray */
public function getScores(Quiz $quiz): array
{
$scoreTimeQb = $this->createQueryBuilder('c', 'c.id')
->select('c', 'sum(case when a.isRightAnswer = true then 1 else 0 end) as correct', 'max(ga.created) - min(ga.created) as time')
->join('c.givenAnswers', 'ga')
->join('ga.answer', 'a')
->where('ga.quiz = :quiz')
->groupBy('c.id')
->setParameter('quiz', $quiz);
$correctionsQb = $this->createQueryBuilder('c', 'c.id')
->select('c', 'cor.amount as corrections')
->innerJoin(Correction::class, 'cor', Join::WITH, 'cor.candidate = c and cor.quiz = :quiz')
->setParameter('quiz', $quiz);
$merged = array_merge_recursive($scoreTimeQb->getQuery()->getArrayResult(), $correctionsQb->getQuery()->getArrayResult());
return $this->sortResults($this->calculateScore($merged));
}
/**
* @param array<string, array{0: Candidate, correct: int, time: \DateInterval, corrections?: float}> $in
*
* @return ResultArray
*/
private function calculateScore(array $in): array
{
return array_map(static fn ($candidate): array => [
...$candidate,
'score' => $candidate['correct'] + ($candidate['corrections'] ?? 0.0),
], $in);
}
/**
* @param ResultArray $results
*
* @return ResultArray
*/
private function sortResults(array $results): array
{
usort($results, static fn ($a, $b): int => $b['score'] <=> $a['score']);
return $results;
}
} }

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\Elimination;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Elimination>
*/
class EliminationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Elimination::class);
}
// /**
// * @return Elimination[] Returns an array of Elimination objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('e')
// ->andWhere('e.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('e.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Elimination
// {
// return $this->createQueryBuilder('e')
// ->andWhere('e.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -22,7 +22,7 @@ class GivenAnswerRepository extends ServiceEntityRepository
{ {
$this->getEntityManager()->persist($givenAnswer); $this->getEntityManager()->persist($givenAnswer);
if (true === $flush) { if ($flush) {
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
} }
} }

View File

@@ -18,7 +18,5 @@ class QuizRepository extends ServiceEntityRepository
parent::__construct($registry, Quiz::class); parent::__construct($registry, Quiz::class);
} }
public function quizReault(Quiz $quiz): array public function quizReault(Quiz $quiz): array {}
{
}
} }

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Repository\CandidateRepository;
/**
* @phpstan-import-type ResultArray from CandidateRepository
*/
class EliminationService
{
/** @phpstan-param ResultArray $result */
public function createEliminationFromResult(array $result): void {}
}

View File

@@ -14,7 +14,7 @@
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if 'backoffice_index' == app.current_route() %} active{% endif %}" <a class="nav-link{% if 'backoffice_index' == app.current_route() %} active{% endif %}"
href="{{ path('backoffice_index') }}">{{ t('Seasons') }}</a> href="{{ path('backoffice_index') }}">{{ 'Seasons'|trans }}</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -2,11 +2,11 @@
{% block body %} {% block body %}
<p> <p>
<h2>{{ t('Quiz') }}: {{ quiz.season.name }} - {{ quiz.name }}</h2> <h2>{{ 'Quiz'|trans }}: {{ quiz.season.name }} - {{ quiz.name }}</h2>
</p> </p>
<div id="questions"> <div id="questions">
<p> <p>
<h4>{{ t('Questions') }}</h4> <h4>{{ 'Questions'|trans }}</h4>
</p> </p>
<div class="accordion"> <div class="accordion">
{% for question in quiz.questions %} {% for question in quiz.questions %}
@@ -17,24 +17,25 @@
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#question-{{ loop.index0 }}" data-bs-target="#question-{{ loop.index0 }}"
aria-controls="question-{{ loop.index0 }}"> aria-controls="question-{{ loop.index0 }}">
{# {% with question_error = question.errors %} #} {% set questionErrors = question.getErrors %}
{# {% if question_error %} #} {% if questionErrors %}
{# <span data-bs-toggle="tooltip" #} <span data-bs-toggle="tooltip"
{# title="{{ question_error }}" #} title="{{ questionErrors }}"
{# class="badge text-bg-danger rounded-pill me-2">!</span> #} class="badge text-bg-danger rounded-pill me-2">!</span>
{# {% endif %} #} {% endif %}
{# {% endwith %} #}
{{ loop.index }}. {{ question.question }} {{ loop.index }}. {{ question.question }}
</button> </button>
</h2> </h2>
<div id="question-{{ loop.index0 }}" <div id="question-{{ loop.index0 }}"
class="accordion-collapse collapse"> class="accordion-collapse collapse">
<div class="accordion-body"> <div class="accordion-body">
{% for answer in question.answers %} <ul>
<li {% if answer.isRightAnswer %}class="text-decoration-underline"{% endif %}>{{ answer.text }}</li> {% for answer in question.answers %}
{% else %} <li {% if answer.isRightAnswer %}class="text-decoration-underline"{% endif %}>{{ answer.text }}</li>
{{ t('There are no answers for this question') }} {% else %}
{% endfor %} {{ 'There are no answers for this question'|trans }}
{% endfor %}
</ul>
</div> </div>
</div> </div>
</div> </div>
@@ -45,51 +46,54 @@
</div> </div>
<div class="scores"> <div class="scores">
<p> <p>
<h4>{{ t('Score') }}</h4> <h4>{{ 'Score'|trans }}</h4>
</p> </p>
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group btn-group-lg me-2"> <div class="btn-group btn-group-lg me-2">
<a class="btn btn-primary">{{ t('Start Elimination') }}</a> <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a>
</div> </div>
<div class="btn-group btn-group-lg"> <div class="btn-group btn-group-lg">
<a class="btn btn-secondary">{{ t('Prepare Custom Elimination') }}</a> <a class="btn btn-secondary">{{ 'Prepare Custom Elimination'|trans }}</a>
<a class="btn btn-secondary">{{ t('Load Prepared Elimination') }}</a> <a class="btn btn-secondary">{{ 'Load Prepared Elimination'|trans }}</a>
</div> </div>
</div> </div>
<p>{{ t('Number of dropouts:') }} {{ quiz.dropouts }} </p> <p>{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p>
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">{{ t('Candidate') }}</th> <th scope="col">{{ 'Candidate'|trans }}</th>
<th scope="col">{{ t('Correct Answers') }}</th> <th scope="col">{{ 'Correct Answers'|trans }}</th>
<th scope="col">{{ t('Corrections') }}</th> <th scope="col">{{ 'Corrections'|trans }}</th>
<th scope="col">{{ t('Score') }}</th> <th scope="col">{{ 'Score'|trans }}</th>
<th scope="col">{{ t('Time') }}</th> <th scope="col">{{ 'Time'|trans }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{# {% with result = quiz.get_score %} #} {% for candidate in result %}
{# {% for candidate in result %} #} <tr class="table-{% if loop.revindex > quiz.dropouts %}success{% else %}danger{% endif %}">
{# <tr class="table-{% if forloop.revcounter > quiz.dropouts %}success{% else %}danger{% endif %}"> #} <td>{{ candidate.0.name }}</td>
{# <td>{{ candidate.name }}</td> #} <td>{{ candidate.correct|default('0') }}</td>
{# <td>{{ candidate.correct }}</td> #} <td>{{ candidate.corrections|default('0') }}</td>
{# <td>{{ candidate.corrections }}</td> #} <td>{{ candidate.score|default('x') }}</td>
{# <td>{{ candidate.score }}</td> #} <td>{{ candidate.time }}</td>
{# <td>{{ candidate.time }}</td> #} </tr>
{# </tr> #} {% else %}
{# {% empty %} #} <tr>
{# {% endfor %} #} <td colspan="5">{{ 'No results'|trans }}</td>
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
{# {% endwith %} #}
</div> </div>
{% endblock %} {% endblock %}
{% block script %} {% block javascripts %}
<script> <script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') document.addEventListener('DOMContentLoaded', function () {
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
});
</script> </script>
{% endblock script %} {% endblock javascripts %}
{% block title %} {% block title %}
{% endblock %} {% endblock %}

View File

@@ -1,11 +1,11 @@
{% extends 'backoffice/base.html.twig' %} {% extends 'backoffice/base.html.twig' %}
{% block body %} {% block body %}
<p> <p>
<h2>{{ t('Season') }}: {{ season.name }}</h2> <h2>{{ 'Season'|trans }}: {{ season.name }}</h2>
</p> </p>
<div class="row"> <div class="row">
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
<h4>{{ t('Quizzes') }}</h4> <h4>{{ 'Quizzes'|trans }}</h4>
<div class="list-group"> <div class="list-group">
{% for quiz in season.quizzes %} {% for quiz in season.quizzes %}
<a class="list-group-item list-group-item-action{% if season.activeQuiz == quiz %} active{% endif %}" <a class="list-group-item list-group-item-action{% if season.activeQuiz == quiz %} active{% endif %}"
@@ -16,7 +16,7 @@
</div> </div>
</div> </div>
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
<h4>{{ t('Candidates') }}</h4> <h4>{{ 'Candidates'|trans }}</h4>
<ul> <ul>
{% for candidate in season.candidates %} {% for candidate in season.candidates %}
<li>{{ candidate.name }}</li>{% endfor %} <li>{{ candidate.name }}</li>{% endfor %}

View File

@@ -8,6 +8,7 @@ Corrections: Jokers
Manage: Beheren Manage: Beheren
Name: Naam Name: Naam
'No active quiz': 'Geen actieve test' 'No active quiz': 'Geen actieve test'
'No results': 'Geen resultaten'
'Number of dropouts:': 'Aantal afvallers:' 'Number of dropouts:': 'Aantal afvallers:'
'Prepare Custom Elimination': 'Bereid aangepaste eliminatie voor' 'Prepare Custom Elimination': 'Bereid aangepaste eliminatie voor'
'Preregister?': 'Voorregistreren?' 'Preregister?': 'Voorregistreren?'