mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-07 16:10:15 +02:00
e57ed79b35
- Add SeasonVoter::EDIT guard to QuizQuestionController::view() - Add full-count check in reorder() to reject partial ordering payloads - Retry once in _persistOrder(); lock drag and show dismissible alert on failure - Remove tabindex="-1" from correct-answer toggle button (accessibility) - Replace 'EMPTY'|trans placeholder with proper copy - Add data-modal-title to question bank Edit button to prevent stale title
178 lines
9.7 KiB
Twig
178 lines
9.7 KiB
Twig
{% macro confirm_modal(id, target, body, formAction, csrfToken) %}
|
|
<div class="modal fade" id="{{ id }}" data-bs-backdrop="static"
|
|
tabindex="-1"
|
|
data-bo--quiz-target="{{ target }}"
|
|
aria-labelledby="{{ id }}Label" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h1 class="modal-title fs-5" id="{{ id }}Label">{{ 'Please Confirm'|trans }}</h1>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">{{ body }}</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ 'No'|trans }}</button>
|
|
<form action="{{ formAction }}" method="POST">
|
|
<input type="hidden" name="_token" value="{{ csrfToken }}">
|
|
<button type="submit" class="btn btn-danger">{{ 'Yes'|trans }}</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endmacro %}
|
|
|
|
<div class="row">
|
|
<div class="col-md-8 col-12" data-controller="bo--quiz">
|
|
<h4 class="mb-3">
|
|
{{ 'Quick actions'|trans }}
|
|
{% if quiz.isFinalized %}
|
|
<span class="badge text-bg-success">{{ 'Finalized'|trans }}</span>
|
|
{% elseif quiz.isLocked %}
|
|
<span class="badge text-bg-warning">{{ 'Locked (answers given)'|trans }}</span>
|
|
{% else %}
|
|
<span class="badge text-bg-secondary">{{ 'Draft'|trans }}</span>
|
|
{% endif %}
|
|
</h4>
|
|
<div class="mb-3 btn-group">
|
|
|
|
{% if quiz is same as (season.activeQuiz) %}
|
|
<form action="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: 'null'}) }}" method="POST">
|
|
<input type="hidden" name="_token" value="{{ csrf_token('enable_quiz') }}">
|
|
<input type="hidden" name="redirect_quiz" value="{{ quiz.id }}">
|
|
<button type="submit" class="btn btn-secondary rounded-0 rounded-start">
|
|
{{ 'Deactivate Quiz'|trans }}
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<form action="{{ path('tvdt_backoffice_enable', {seasonCode: season.seasonCode, quiz: quiz.id}) }}" method="POST">
|
|
<input type="hidden" name="_token" value="{{ csrf_token('enable_quiz') }}">
|
|
<button type="submit" class="btn btn-primary rounded-0 rounded-start"
|
|
{% if not quiz.isFinalized %}disabled data-bs-toggle="tooltip"
|
|
title="{{ 'The quiz must be finalized before it can be activated'|trans }}"{% endif %}>
|
|
{{ 'Make active'|trans }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% if not quiz.isFinalized %}
|
|
<form action="{{ path('tvdt_backoffice_quiz_finalize', {quiz: quiz.id}) }}" method="POST">
|
|
<input type="hidden" name="_token" value="{{ csrf_token('finalize_quiz') }}">
|
|
<button type="submit" class="btn btn-success rounded-0"
|
|
data-bs-toggle="tooltip"
|
|
title="{{ 'Locks the quiz so it can no longer be edited and makes it ready for candidates to take.'|trans }}">
|
|
{{ 'Finalize'|trans }}
|
|
</button>
|
|
</form>
|
|
{% elseif not quiz.hasStartedCandidates and quiz is not same as (season.activeQuiz) %}
|
|
<form action="{{ path('tvdt_backoffice_quiz_unfinalize', {quiz: quiz.id}) }}" method="POST">
|
|
<input type="hidden" name="_token" value="{{ csrf_token('unfinalize_quiz') }}">
|
|
<button type="submit" class="btn btn-outline-success rounded-0"
|
|
data-bs-toggle="tooltip"
|
|
title="{{ 'Re-opens the quiz for editing. Candidates will no longer be able to take the quiz until it is finalized again.'|trans }}">
|
|
{{ 'Undo finalization'|trans }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
<button class="btn btn-danger rounded-0" data-action="click->bo--quiz#clearQuiz">
|
|
{{ 'Clear Quiz...'|trans }}
|
|
</button>
|
|
<button class="btn btn-danger rounded-0" data-action="click->bo--quiz#deleteQuiz">
|
|
{{ 'Delete Quiz...'|trans }}
|
|
</button>
|
|
<a class="btn btn-secondary rounded-0 rounded-end"
|
|
href="{{ path('tvdt_backoffice_quiz_export', {quiz: quiz.id}) }}">
|
|
{{ 'Export to XLSX'|trans }}
|
|
</a>
|
|
</div>
|
|
|
|
<div data-controller="bo--question-list bo--modal"
|
|
data-action="turbo:frame-load->bo--modal#frameLoad turbo:submit-end->bo--modal#frameSubmitEnd"
|
|
data-bo--question-list-reorder-url-value="{{ path('tvdt_backoffice_quiz_questions_reorder', {seasonCode: season.seasonCode, quiz: quiz.id}) }}"
|
|
data-bo--question-list-csrf-value="{{ csrf_token('question_reorder') }}"
|
|
data-bo--question-list-can-modify-value="{{ is_granted('QUIZ_MODIFY_CONTENT', quiz) ? 'true' : 'false' }}"
|
|
data-bo--question-list-saved-label-value="{{ 'Order saved'|trans }}"
|
|
data-bo--question-list-error-label-value="{{ 'Error saving order'|trans }}"
|
|
data-bo--question-list-error-hint-value="{{ 'Refresh the page to try again.'|trans }}">
|
|
|
|
<h4 class="mb-3 d-flex align-items-center gap-2">
|
|
{{ 'Questions'|trans }}
|
|
<span class="badge d-none fw-normal" style="font-size:.7rem;vertical-align:baseline" data-bo--question-list-target="status"></span>
|
|
</h4>
|
|
|
|
<div data-bo--question-list-target="list">
|
|
{%~ for question in quiz.questions ~%}
|
|
<div class="card mb-2"
|
|
data-bo--question-list-target="item"
|
|
data-question-id="{{ question.id }}">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center gap-2">
|
|
{% if is_granted('QUIZ_MODIFY_CONTENT', question) %}
|
|
<span class="text-muted" style="cursor:grab" data-drag-handle>
|
|
<i class="bi bi-grip-vertical"></i>
|
|
</span>
|
|
{% endif %}
|
|
<div class="flex-grow-1">
|
|
{% set questionError = questionErrors[question.id.toString] ?? null %}
|
|
<span class="badge rounded-pill me-1{% if questionError %} text-bg-danger{% else %} invisible{% endif %}"
|
|
{% if questionError %}data-bs-toggle="tooltip" title="{{ questionError }}"{% endif %}>!</span>
|
|
<strong><span data-question-number>{{ loop.index }}</span>. {{ question.question }}</strong>
|
|
</div>
|
|
{% if is_granted('QUIZ_MODIFY_CONTENT', question) %}
|
|
<button class="btn btn-sm btn-outline-secondary flex-shrink-0"
|
|
data-action="click->bo--modal#open"
|
|
data-src="{{ path('tvdt_backoffice_quiz_question_edit', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
|
data-modal-title="{{ 'Edit question'|trans }}">
|
|
<i class="bi bi-pencil"></i> {{ 'Edit'|trans }}
|
|
</button>
|
|
{% elseif quiz.isLocked %}
|
|
<button class="btn btn-sm btn-outline-secondary flex-shrink-0"
|
|
data-action="click->bo--modal#open"
|
|
data-src="{{ path('tvdt_backoffice_quiz_question_view', {seasonCode: season.seasonCode, quiz: quiz.id, question: question.id}) }}"
|
|
data-modal-title="{{ 'Question details'|trans }}">
|
|
<i class="bi bi-eye"></i> {{ 'View'|trans }}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
{{ 'No questions have been added to this quiz yet.'|trans }}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="modal fade" tabindex="-1"
|
|
data-bo--modal-target="modal"
|
|
aria-labelledby="questionEditModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="questionEditModalLabel">{{ 'Edit question'|trans }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<turbo-frame id="question-modal-frame" data-bo--modal-target="frame"></turbo-frame>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{ _self.confirm_modal(
|
|
'clearQuizModal',
|
|
'clearModal',
|
|
'Are you sure you want to clear all the results? This will also delete all the eliminations.'|trans,
|
|
path('tvdt_backoffice_quiz_clear', {quiz: quiz.id}),
|
|
csrf_token('clear_quiz'),
|
|
) }}
|
|
|
|
{{ _self.confirm_modal(
|
|
'deleteQuizModal',
|
|
'deleteModal',
|
|
'Are you sure you want to delete this quiz?'|trans,
|
|
path('tvdt_backoffice_quiz_delete', {quiz: quiz.id}),
|
|
csrf_token('delete_quiz'),
|
|
) }}
|
|
</div>
|
|
<div class="col-md-4 col-12">
|
|
{{ include('backoffice/help/quiz_overview.html.twig') }}
|
|
</div>
|
|
</div>
|