Compare commits

..

4 Commits

Author SHA1 Message Date
Marijn 4918f49445 WIP 2026-03-09 19:56:25 +01:00
Marijn 6d3402b8e3 Add Penalty Seconds on tests 2026-03-07 21:56:19 +01:00
Marijn ac46f380cb Remove worker (#71)
CI / Tests (push) Failing after 39s
CI / Build and deploy to ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }} (push) Has been skipped
* Remove worker config

* Do not copy worker.Caddyfile
2026-02-18 16:50:04 +01:00
Marijn b281a199d3 Remove worker config (#70) 2026-02-18 16:39:17 +01:00
23 changed files with 356 additions and 213 deletions
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="----------------------------------------&#10;1:0:7c4a7798-5e31-4429-bc70-035404e2d83b&#10;2:0:d8fd3863-4972-4e82-ae01-bcafe182faad&#10;3:0:b9828d40-0e5d-43de-8e43-47a6795649b1&#10;" />
</component>
</project>
-2
View File
@@ -87,12 +87,10 @@ FROM frankenphp_base AS frankenphp_prod
RUN rm -rf /var/lib/apt/lists/* RUN rm -rf /var/lib/apt/lists/*
ENV APP_ENV=prod ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/ COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/
COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile
# prevent the reinstallation of vendors at every changes in the source code # prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./ COPY --link composer.* symfony.* ./
+6
View File
@@ -0,0 +1,6 @@
# yaml-language-server: $schema=../../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
when@dev:
maker:
root_namespace: 'Tvdt'
generate_final_classes: true
generate_final_entities: false
-4
View File
@@ -1,4 +0,0 @@
worker {
file ./public/index.php
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
}
+31
View File
@@ -0,0 +1,31 @@
<?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 Version20260125191247 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 quiz_candidate ADD penalty_seconds SMALLINT DEFAULT 0 NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE quiz_candidate DROP penalty_seconds');
}
}
+21 -2
View File
@@ -76,7 +76,7 @@ class QuizController extends AbstractController
{ {
try { try {
$this->quizRepository->clearQuiz($quiz); $this->quizRepository->clearQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz cleared')); $this->addFlash('success', $this->translator->trans('quiz cleared'));
} catch (ErrorClearingQuizException) { } catch (ErrorClearingQuizException) {
$this->addFlash('error', $this->translator->trans('Error clearing quiz')); $this->addFlash('error', $this->translator->trans('Error clearing quiz'));
} }
@@ -94,7 +94,7 @@ class QuizController extends AbstractController
{ {
$this->quizRepository->deleteQuiz($quiz); $this->quizRepository->deleteQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz deleted')); $this->addFlash('success', $this->translator->trans('quiz deleted'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->season->seasonCode]); return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->season->seasonCode]);
} }
@@ -117,4 +117,23 @@ class QuizController extends AbstractController
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]); return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
#[Route(
'/backoffice/quiz/{quiz}/candidate/{candidate}/modify_penalty',
name: 'tvdt_backoffice_modify_penalty',
requirements: ['quiz' => Requirement::UUID, 'candidate' => Requirement::UUID],
)]
public function modifyPenalty(Quiz $quiz, Candidate $candidate, Request $request): RedirectResponse
{
if (!$request->isMethod('POST')) {
throw new MethodNotAllowedHttpException(['POST']);
}
$penalty = (int) $request->request->get('penalty');
$this->quizCandidateRepository->setPenaltyForCandidate($quiz, $candidate, $penalty);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]);
}
} }
@@ -105,7 +105,7 @@ class SeasonController extends AbstractController
$this->em->persist($quiz); $this->em->persist($quiz);
$this->em->flush(); $this->em->flush();
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz Added!')); $this->addFlash(FlashType::Success, $this->translator->trans('quiz Added!'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]); return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]);
} }
+1 -1
View File
@@ -118,7 +118,7 @@ final class QuizController extends AbstractController
// Keep creating flash here based on the return type of service call // Keep creating flash here based on the return type of service call
if (!$question instanceof Question) { if (!$question instanceof Question) {
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz completed')); $this->addFlash(FlashType::Success, $this->translator->trans('quiz completed'));
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->seasonCode]); return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->seasonCode]);
} }
+2 -2
View File
@@ -56,7 +56,7 @@ final class KrtekFixtures extends Fixture implements FixtureGroupInterface
private function createQuiz1(Season $season): Quiz private function createQuiz1(Season $season): Quiz
{ {
$quiz = new Quiz(); $quiz = new Quiz();
$quiz->name = 'Quiz 1'; $quiz->name = 'quiz 1';
$quiz->season = $season; $quiz->season = $season;
$q = new Question(); $q = new Question();
@@ -223,7 +223,7 @@ final class KrtekFixtures extends Fixture implements FixtureGroupInterface
private function createQuiz2(Season $season): Quiz private function createQuiz2(Season $season): Quiz
{ {
$quiz = new Quiz(); $quiz = new Quiz();
$quiz->name = 'Quiz 2'; $quiz->name = 'quiz 2';
$quiz->season = $season; $quiz->season = $season;
$q = new Question(); $q = new Question();
+1
View File
@@ -13,6 +13,7 @@ final readonly class Result
public string $name, public string $name,
public int $correct, public int $correct,
public float $corrections, public float $corrections,
public int $penaltySeconds,
public \DateInterval $time, public \DateInterval $time,
public float $score, public float $score,
) {} ) {}
+3
View File
@@ -24,6 +24,9 @@ class QuizCandidate
#[ORM\Column] #[ORM\Column]
public float $corrections = 0; public float $corrections = 0;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
public int $penaltySeconds = 0;
#[Gedmo\Timestampable(on: 'create')] #[Gedmo\Timestampable(on: 'create')]
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)] #[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)]
public private(set) \DateTimeImmutable $created; public private(set) \DateTimeImmutable $created;
+2 -2
View File
@@ -22,11 +22,11 @@ class UploadQuizFormType extends AbstractType
{ {
$builder $builder
->add('name', TextType::class, [ ->add('name', TextType::class, [
'label' => $this->translator->trans('Quiz name'), 'label' => $this->translator->trans('quiz name'),
'translation_domain' => false, 'translation_domain' => false,
]) ])
->add('sheet', FileType::class, [ ->add('sheet', FileType::class, [
'label' => $this->translator->trans('Quiz (xlsx)'), 'label' => $this->translator->trans('quiz (xlsx)'),
'mapped' => false, 'mapped' => false,
'required' => true, 'required' => true,
'translation_domain' => false, 'translation_domain' => false,
+12 -1
View File
@@ -38,10 +38,21 @@ class QuizCandidateRepository extends ServiceEntityRepository
{ {
$quizCandidate = $this->findOneBy(['candidate' => $candidate, 'quiz' => $quiz]); $quizCandidate = $this->findOneBy(['candidate' => $candidate, 'quiz' => $quiz]);
if (!$quizCandidate instanceof QuizCandidate) { if (!$quizCandidate instanceof QuizCandidate) {
throw new \InvalidArgumentException('Quiz candidate not found'); throw new \InvalidArgumentException('quiz candidate not found');
} }
$quizCandidate->corrections = $corrections; $quizCandidate->corrections = $corrections;
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
} }
public function setPenaltyForCandidate(Quiz $quiz, Candidate $candidate, int $penalty): void
{
$quizCandidate = $this->findOneBy(['candidate' => $candidate, 'quiz' => $quiz]);
if (!$quizCandidate instanceof QuizCandidate) {
throw new \InvalidArgumentException('quiz candidate not found');
}
$quizCandidate->penaltySeconds = $penalty;
$this->getEntityManager()->flush();
}
} }
+2
View File
@@ -81,6 +81,7 @@ class QuizRepository extends ServiceEntityRepository
c.name, c.name,
sum(case when a.isRightAnswer = true then 1 else 0 end) as correct, sum(case when a.isRightAnswer = true then 1 else 0 end) as correct,
qd.corrections, qd.corrections,
qd.penaltySeconds,
max(ga.created) as end_time, max(ga.created) as end_time,
qd.created as start_time, qd.created as start_time,
(sum(case when a.isRightAnswer = true then 1 else 0 end) + qd.corrections) as score (sum(case when a.isRightAnswer = true then 1 else 0 end) + qd.corrections) as score
@@ -99,6 +100,7 @@ class QuizRepository extends ServiceEntityRepository
name: $row['name'], name: $row['name'],
correct: (int) $row['correct'], correct: (int) $row['correct'],
corrections: $row['corrections'], corrections: $row['corrections'],
penaltySeconds: $row['penaltySeconds'],
time: $row['start_time']->diff(new DateTimeImmutable($row['end_time'])), time: $row['start_time']->diff(new DateTimeImmutable($row['end_time'])),
score: $row['score'], score: $row['score'],
), $result); ), $result);
+1 -1
View File
@@ -19,7 +19,7 @@
<th scope="col">{{ 'Owner(s)'|trans }}</th> <th scope="col">{{ 'Owner(s)'|trans }}</th>
{% endif %} {% endif %}
<th scope="col">{{ 'Name'|trans }}</th> <th scope="col">{{ 'Name'|trans }}</th>
<th scope="col">{{ 'Active Quiz'|trans }}</th> <th scope="col">{{ 'Active quiz'|trans }}</th>
<th scope="col">{{ 'Season Code'|trans }}</th> <th scope="col">{{ 'Season Code'|trans }}</th>
<th scope="col">{{ 'Manage'|trans }}</th> <th scope="col">{{ 'Manage'|trans }}</th>
</tr> </tr>
+30 -155
View File
@@ -4,163 +4,38 @@
{% 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" data-controller="bo--quiz"> <ul class="nav nav-tabs pt-2" id="quizTab" role="tablist">
<a class="btn btn-primary {% if quiz is same as(season.activeQuiz) %}disabled{% endif %}" <li class="nav-item" role="presentation">
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ 'Make active'|trans }}</a> <button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview-tab-pane"
{% if quiz is same as (season.activeQuiz) %} type="button" role="tab" aria-controls="overview-tab-pane" aria-selected="true">
<a class="btn btn-secondary" {{ 'Overview'|trans }}
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}">{{ 'Deactivate Quiz'|trans }}</a> </button>
{% endif %} </li>
<button class="btn btn-danger" data-action="click->bo--quiz#clearQuiz"> <li class="nav-item">
{{ 'Clear quiz...'|trans }} <button class="nav-link" id="result-tab" data-bs-toggle="tab" data-bs-target="#result-tab-pane"
</button> type="button" role="tab" aria-controls="result-tab-pane" aria-selected="true">
<button class="btn btn-danger" data-action="click->bo--quiz#deleteQuiz"> {{ 'Results & Elimination'|trans }}
{{ 'Delete Quiz...'|trans }} </button>
</button> </li>
</div> <li class="nav-item">
<button class="nav-link" id="candidates-tab" data-bs-toggle="tab" data-bs-target="#candidates-tab-pane"
<div id="questions"> type="button" role="tab" aria-controls="candidates-tab-pane" aria-selected="true">
<h4 class="py-2">{{ 'Questions'|trans }}</h4> {{ 'Candidates'|trans }}
<div class="accordion"> </button>
{%~ for question in quiz.questions ~%} </li>
<div class="accordion-item"> </ul>
<h2 class="accordion-header"> <div class="tab-content" id="quizTabContent">
<button class="accordion-button collapsed" <div class="tab-pane fade show active" id="overview-tab-pane" role="tabpanel" aria-labelledby="overview-tab"
type="button" tabindex="0">
data-bs-toggle="collapse" {% include 'backoffice/quiz/tab_overview.html.twig' %}
data-bs-target="#question-{{ loop.index0 }}"
aria-controls="question-{{ loop.index0 }}">
{% set questionErrors = question.getErrors %}
{%~ if questionErrors -%}
<span data-bs-toggle="tooltip"
title="{{ questionErrors }}"
class="badge text-bg-danger rounded-pill me-2">!</span>
{% endif %}
{{~ loop.index -}}. {{ question.question -}}
</button>
</h2>
<div id="question-{{ loop.index0 }}"
class="accordion-collapse collapse">
<div class="accordion-body">
<ul>
{%~ for answer in question.answers %}
<li{% if answer.isRightAnswer %} class="text-decoration-underline"{% endif %}>{{ answer.text -}}</li>
{%~ else %}
{{ 'There are no answers for this question'|trans -}}
{%~ endfor %}
</ul>
</div>
</div>
</div>
{% else %}
{{ 'EMPTY'|trans }}
{% endfor %}
</div> </div>
</div> <div class="tab-pane fade" id="result-tab-pane" role="tabpanel" aria-labelledby="result-tab"
<div class="scores"> tabindex="0">
<h4 class="py-2">{{ 'Score'|trans }}</h4> {% include 'backoffice/quiz/tab_result.html.twig' %}
<div class="btn-toolbar" role="toolbar">
<div class="btn-group btn-group-lg me-2">
{# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #}
<a href="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
class="btn btn-secondary">{{ 'Prepare Custom Elimination'|trans }}</a>
{%~ if not quiz.eliminations.empty %}
<button class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown">{{ 'Load Prepared Elimination'|trans }}</button>
<ul class="dropdown-menu">
{%~ for elimination in quiz.eliminations %}
<li><a class="dropdown-item"
href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.created|format_datetime() }}</a>
</li>
{%~ endfor %}
</ul>
{% endif %}
</div>
</div> </div>
<p>{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p> <div class="tab-pane fade" id="candidates-tab-pane" role="tabpanel" aria-labelledby="candidates-tab"
<table class="table table-hover"> tabindex="0">
<thead> {% include 'backoffice/quiz/tab_candidates.html.twig' %}
<tr>
<th scope="col">{{ 'Candidate'|trans }}</th>
<th style="width: 15%" scope="col">{{ 'Correct Answers'|trans }}</th>
<th style="width: 20%" scope="col">{{ 'Corrections'|trans }}</th>
<th style="width: 10%" scope="col">{{ 'Score'|trans }}</th>
<th style="width: 20%" scope="col">{{ 'Time'|trans }}</th>
</tr>
</thead>
<tbody>
{%~ for candidate in result ~%}
<tr class="table-{% if loop.revindex > quiz.dropouts %}success{% else %}danger{% endif %}">
<td>{{ candidate.name }}</td>
<td>{{ candidate.correct|default('0') }}</td>
<td>
<form method="post"
action="{{ path('tvdt_backoffice_modify_correction', {quiz: quiz.id, candidate: candidate.id}) }}">
<div class="row">
<div class="col-8">
<input class="form-control form-control-sm" type="number"
value="{{ candidate.corrections }}" step="0.5"
name="corrections">
</div>
<div class="col-2">
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
</div>
</div>
</form>
</td>
<td>{{ candidate.score|default('x') }}</td>
<td>{{ candidate.time.format('%i:%S') }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">{{ 'No results'|trans }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# 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'|trans }}</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.'|trans }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
<a href="{{ path('tvdt_backoffice_quiz_clear', {quiz: quiz.id}) }}"
class="btn btn-danger">{{ 'Yes'|trans }}</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'|trans }}</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?'|trans }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
<a href="{{ path('tvdt_backoffice_quiz_delete', {quiz: quiz.id}) }}"
class="btn btn-danger">{{ 'Yes'|trans }}</a>
</div>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@@ -0,0 +1,5 @@
{% for question in quiz.questions %}
{% for answer in question.answers %}
{{ answer.text }}<br>
{% endfor %}
{% endfor %}
@@ -0,0 +1,100 @@
<h4 class="py-2">Quick actions</h4>
<div class="py-2 btn-group" data-controller="bo--quiz">
{% if quiz is same as (season.activeQuiz) %}
<a class="btn btn-secondary"
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}">{{ 'Deactivate quiz'|trans }}</a>
{% else %}
<a class="btn btn-primary"
href="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}">{{ 'Make active'|trans }}</a>
{% 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 id="questions">
<h4 class="py-2">{{ 'Questions'|trans }}</h4>
<div class="accordion">
{%~ for question in quiz.questions ~%}
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#question-{{ loop.index0 }}"
aria-controls="question-{{ loop.index0 }}">
{% set questionErrors = question.getErrors %}
{%~ if questionErrors -%}
<span data-bs-toggle="tooltip"
title="{{ questionErrors }}"
class="badge text-bg-danger rounded-pill me-2">!</span>
{% endif %}
{{~ loop.index -}}. {{ question.question -}}
</button>
</h2>
<div id="question-{{ loop.index0 }}"
class="accordion-collapse collapse">
<div class="accordion-body">
<ul>
{%~ for answer in question.answers %}
<li{% if answer.isRightAnswer %} class="text-decoration-underline"{% endif %}>{{ answer.text -}}</li>
{%~ else %}
{{ 'There are no answers for this question'|trans -}}
{%~ endfor %}
</ul>
</div>
</div>
</div>
{% else %}
{{ 'EMPTY'|trans }}
{% endfor %}
</div>
</div>
{# 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'|trans }}</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.'|trans }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
<a href="{{ path('tvdt_backoffice_quiz_clear', {quiz: quiz.id}) }}"
class="btn btn-danger">{{ 'Yes'|trans }}</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'|trans }}</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?'|trans }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
<a href="{{ path('tvdt_backoffice_quiz_delete', {quiz: quiz.id}) }}"
class="btn btn-danger">{{ 'Yes'|trans }}</a>
</div>
</div>
</div>
</div>
@@ -0,0 +1,78 @@
<div class="scores">
<h4 class="py-2">{{ 'Score'|trans }}</h4>
<div class="btn-toolbar" role="toolbar">
<div class="btn-group btn-group-lg me-2">
{# <a class="btn btn-primary">{{ 'Start Elimination'|trans }}</a> #}
<a href="{{ path('tvdt_prepare_elimination', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
class="btn btn-secondary">{{ 'Prepare Custom Elimination'|trans }}</a>
{%~ if not quiz.eliminations.empty %}
<button class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown">{{ 'Load Prepared Elimination'|trans }}</button>
<ul class="dropdown-menu">
{%~ for elimination in quiz.eliminations %}
<li><a class="dropdown-item"
href="{{ path('tvdt_prepare_elimination_view', {elimination: elimination.id}) }}">{{ elimination.created|format_datetime() }}</a>
</li>
{%~ endfor %}
</ul>
{% endif %}
</div>
</div>
<p>{{ 'Number of dropouts:'|trans }} {{ quiz.dropouts }} </p>
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{{ 'Candidate'|trans }}</th>
<th style="width: 15%" scope="col">{{ 'Correct Answers'|trans }}</th>
<th style="width: 20%" scope="col">{{ 'Corrections'|trans }}</th>
<th style="width: 20%" scope="col">{{ 'Penalty'|trans }}</th>
<th style="width: 10%" scope="col">{{ 'Score'|trans }}</th>
<th style="width: 20%" scope="col">{{ 'Time'|trans }}</th>
</tr>
</thead>
<tbody>
{%~ for candidate in result ~%}
<tr class="table-{% if loop.revindex > quiz.dropouts %}success{% else %}danger{% endif %}">
<td>{{ candidate.name }}</td>
<td>{{ candidate.correct|default('0') }}</td>
<td>
<form method="post"
action="{{ path('tvdt_backoffice_modify_correction', {quiz: quiz.id, candidate: candidate.id}) }}">
<div class="row">
<div class="col-8">
<input class="form-control form-control-sm" type="number"
value="{{ candidate.corrections }}" step="0.5"
name="corrections">
</div>
<div class="col-2">
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
</div>
</div>
</form>
</td>
<td>
<form method="post"
action="{{ path('tvdt_backoffice_modify_penalty', {quiz: quiz.id, candidate: candidate.id}) }}">
<div class="row">
<div class="col-8">
<input class="form-control form-control-sm" type="number"
value="{{ candidate.penaltySeconds }}" step="1"
name="penalty">
</div>
<div class="col-2">
<button class="btn btn-sm btn-primary" type="submit">{{ 'Save'|trans }}</button>
</div>
</div>
</form>
</td>
<td>{{ candidate.score|default('x') }}</td>
<td>{{ candidate.time.format('%i:%S') }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">{{ 'No results'|trans }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends 'quiz/base.html.twig' %} {% extends 'quiz/base.html.twig' %}
{% block body %} {% block body %}
{{ form(form) }} {{ form(form) }}
<a href="{{ path('tvdt_backoffice_index') }}" class="btn btn-outline-secondary">{{ 'Manage Quiz'|trans }}</a> <a href="{{ path('tvdt_backoffice_index') }}" class="btn btn-outline-secondary">{{ 'Manage quiz'|trans }}</a>
{% endblock body %} {% endblock body %}
+1 -1
View File
@@ -59,7 +59,7 @@ final class QuizRepositoryTest extends DatabaseTestCase
$quiz = $krtekSeason->activeQuiz; $quiz = $krtekSeason->activeQuiz;
$this->assertInstanceOf(Quiz::class, $quiz); $this->assertInstanceOf(Quiz::class, $quiz);
// Start Quiz // Start quiz
$qc = new QuizCandidate($quiz, $candidate); $qc = new QuizCandidate($quiz, $candidate);
$this->entityManager->persist($qc); $this->entityManager->persist($qc);
$this->entityManager->flush(); $this->entityManager->flush();
+1 -1
View File
@@ -62,7 +62,7 @@ final class SeasonVoterTest extends TestCase
$quiz = self::createStub(Quiz::class); $quiz = self::createStub(Quiz::class);
$quiz->season = $season; $quiz->season = $season;
yield 'Quiz' => [$quiz]; yield 'quiz' => [$quiz];
$elimination = self::createStub(Elimination::class); $elimination = self::createStub(Elimination::class);
$elimination->quiz = $quiz; $elimination->quiz = $quiz;
+51 -39
View File
@@ -5,9 +5,9 @@
<tool tool-id="symfony" tool-name="Symfony"/> <tool tool-id="symfony" tool-name="Symfony"/>
</header> </header>
<body> <body>
<trans-unit id="uyMngrK" resname="Active Quiz"> <trans-unit id="_dC510i" resname="Active quiz">
<source>Active Quiz</source> <source>Active quiz</source>
<target>Actieve test</target> <target>Actieve Test</target>
</trans-unit> </trans-unit>
<trans-unit id="g.hyGgB" resname="Add"> <trans-unit id="g.hyGgB" resname="Add">
<source>Add</source> <source>Add</source>
@@ -85,13 +85,13 @@
<source>Create an account</source> <source>Create an account</source>
<target>Maak een account aan</target> <target>Maak een account aan</target>
</trans-unit> </trans-unit>
<trans-unit id="w9AyAnn" resname="Deactivate Quiz"> <trans-unit id="XDCiNJX" resname="Deactivate quiz">
<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..."> <trans-unit id=".mDzL17" resname="Delete quiz...">
<source>Delete Quiz...</source> <source>Delete quiz...</source>
<target>Test verwijderen...</target> <target>Verwijder test...</target>
</trans-unit> </trans-unit>
<trans-unit id="R9yHzHv" resname="Download Template"> <trans-unit id="R9yHzHv" resname="Download Template">
<source>Download Template</source> <source>Download Template</source>
@@ -141,22 +141,22 @@
<source>Manage</source> <source>Manage</source>
<target>Beheren</target> <target>Beheren</target>
</trans-unit> </trans-unit>
<trans-unit id="LZzySF." resname="Manage Quiz"> <trans-unit id="FyWuzOy" resname="Manage quiz">
<source>Manage Quiz</source> <source>Manage quiz</source>
<target>Beheer test</target> <target>Beheer test</target>
</trans-unit> </trans-unit>
<trans-unit id="wbMeKOh" resname="Name"> <trans-unit id="wbMeKOh" resname="Name">
<source>Name</source> <source>Name</source>
<target>Naam</target> <target>Naam</target>
</trans-unit> </trans-unit>
<trans-unit id="wd1MvZW" resname="No">
<source>No</source>
<target>Nee</target>
</trans-unit>
<trans-unit id="gefhnBC" resname="Next"> <trans-unit id="gefhnBC" resname="Next">
<source>Next</source> <source>Next</source>
<target>Volgende</target> <target>Volgende</target>
</trans-unit> </trans-unit>
<trans-unit id="wd1MvZW" resname="No">
<source>No</source>
<target>Nee</target>
</trans-unit>
<trans-unit id="nOHriCl" resname="No active quiz"> <trans-unit id="nOHriCl" resname="No active quiz">
<source>No active quiz</source> <source>No active quiz</source>
<target>Geen actieve test</target> <target>Geen actieve test</target>
@@ -169,6 +169,10 @@
<source>Number of dropouts:</source> <source>Number of dropouts:</source>
<target>Aantal afvallers:</target> <target>Aantal afvallers:</target>
</trans-unit> </trans-unit>
<trans-unit id="HmgPmMV" resname="Overview">
<source>Overview</source>
<target>Overzicht</target>
</trans-unit>
<trans-unit id="PywqOf4" resname="Owner(s)"> <trans-unit id="PywqOf4" resname="Owner(s)">
<source>Owner(s)</source> <source>Owner(s)</source>
<target>Eigena(a)r(en)</target> <target>Eigena(a)r(en)</target>
@@ -177,6 +181,10 @@
<source>Password</source> <source>Password</source>
<target>Wachtwoord</target> <target>Wachtwoord</target>
</trans-unit> </trans-unit>
<trans-unit id="1ne1Zlc" resname="Penalty">
<source>Penalty</source>
<target>Straf</target>
</trans-unit>
<trans-unit id="VbgD9L8" resname="Please Confirm"> <trans-unit id="VbgD9L8" resname="Please Confirm">
<source>Please Confirm</source> <source>Please Confirm</source>
<target>Bevestig Alsjeblieft</target> <target>Bevestig Alsjeblieft</target>
@@ -205,30 +213,6 @@
<source>Quiz</source> <source>Quiz</source>
<target>Test</target> <target>Test</target>
</trans-unit> </trans-unit>
<trans-unit id="4BzxOP3" resname="Quiz (xlsx)">
<source>Quiz (xlsx)</source>
<target>Test (xlsx)</target>
</trans-unit>
<trans-unit id="nU5BEhV" resname="Quiz Added!">
<source>Quiz Added!</source>
<target>Test toegevoegd!</target>
</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">
<source>Quiz completed</source>
<target>Test voltooid</target>
</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">
<source>Quiz name</source>
<target>Testnaam</target>
</trans-unit>
<trans-unit id="bggVfH9" resname="Quizzes"> <trans-unit id="bggVfH9" resname="Quizzes">
<source>Quizzes</source> <source>Quizzes</source>
<target>Tests</target> <target>Tests</target>
@@ -249,6 +233,10 @@
<source>Repeat Password</source> <source>Repeat Password</source>
<target>Herhaal wachtwoord</target> <target>Herhaal wachtwoord</target>
</trans-unit> </trans-unit>
<trans-unit id="7UvBPrb" resname="Results &amp; Elimination">
<source>Results &amp; Elimination</source>
<target><![CDATA[Resultaat & Eliminatie]]></target>
</trans-unit>
<trans-unit id="z9OKodR" resname="Save"> <trans-unit id="z9OKodR" resname="Save">
<source>Save</source> <source>Save</source>
<target>Opslaan</target> <target>Opslaan</target>
@@ -321,6 +309,30 @@
<source>Your email address has been verified.</source> <source>Your email address has been verified.</source>
<target>Je e-mailadres is geverifieerd.</target> <target>Je e-mailadres is geverifieerd.</target>
</trans-unit> </trans-unit>
<trans-unit id="w9XpE_6" resname="quiz (xlsx)">
<source>quiz (xlsx)</source>
<target>test (xlsx)</target>
</trans-unit>
<trans-unit id="4XnEy8T" resname="quiz Added!">
<source>quiz Added!</source>
<target>test toegevoegd!</target>
</trans-unit>
<trans-unit id="d5xdgOJ" resname="quiz cleared">
<source>quiz cleared</source>
<target>test geleegd</target>
</trans-unit>
<trans-unit id="o39z9fJ" resname="quiz completed">
<source>quiz completed</source>
<target>test voltooid</target>
</trans-unit>
<trans-unit id="09fqK71" resname="quiz deleted">
<source>quiz deleted</source>
<target>test verwijderd</target>
</trans-unit>
<trans-unit id="ddYX3As" resname="quiz name">
<source>quiz name</source>
<target>test name</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>