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:
2026-07-04 21:54:44 +02:00
parent f05f88bcc5
commit 8304d8680b
15 changed files with 329 additions and 23 deletions
@@ -1,10 +1,8 @@
<div class="row">
<div class="col-md-6 col-12">
<div class="d-flex align-items-center mb-3">
<h4 class="mb-0 pe-2">{{ 'Candidates'|trans }}</h4>
<a class="link"
href="{{ path('tvdt_backoffice_add_candidates', {seasonCode: season.seasonCode}) }}">{{ 'Add Candidate'|trans }}
</a>
<div class="mb-3">
<a class="btn btn-sm btn-outline-primary"
href="{{ path('tvdt_backoffice_add_candidates', {seasonCode: season.seasonCode}) }}">{{ 'Add Candidate'|trans }}</a>
</div>
<ul class="mb-3">
{% for candidate in season.candidates %}
@@ -1,6 +1,5 @@
<div class="d-flex align-items-center mb-3">
<h4 class="mb-0 pe-2">{{ 'Question bank'|trans }}</h4>
<a class="link" href="{{ path('tvdt_backoffice_question_bank_new', {seasonCode: season.seasonCode}) }}">{{ 'Add'|trans }}</a>
<div class="mb-3">
<a class="btn btn-sm btn-outline-primary" href="{{ path('tvdt_backoffice_question_bank_new', {seasonCode: season.seasonCode}) }}">{{ 'Add question'|trans }}</a>
</div>
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
@@ -51,7 +50,23 @@
{% endif %}
</td>
<td>
{{ bankQuestion.usages|map(usage => usage.quiz.name)|join(', ') }}
{% for usage in bankQuestion.usages %}
<div class="d-flex align-items-center gap-1">
<span>{{ usage.quiz.name }}</span>
<form action="{{ path('tvdt_backoffice_question_bank_unassign', {seasonCode: season.seasonCode, bankQuestion: bankQuestion.id, usage: usage.id}) }}"
method="POST" class="d-inline">
<input type="hidden" name="_token" value="{{ csrf_token('unassign_bank_question') }}">
<button type="submit" class="btn btn-sm btn-link p-0 text-danger" title="{{ 'Unassign'|trans }}">&times;</button>
</form>
{% if usage.quiz.isFinalized %}
<form action="{{ path('tvdt_backoffice_question_bank_sync', {seasonCode: season.seasonCode, bankQuestion: bankQuestion.id, usage: usage.id}) }}"
method="POST" class="d-inline">
<input type="hidden" name="_token" value="{{ csrf_token('sync_bank_question') }}">
<button type="submit" class="btn btn-sm btn-link p-0 text-primary" title="{{ 'Sync latest changes to this quiz'|trans }}">&#8635;</button>
</form>
{% endif %}
</div>
{% endfor %}
</td>
<td class="text-end">
<div class="d-inline-flex align-items-center gap-2">
@@ -1,6 +1,5 @@
<div class="row">
<div class="col-md-6 col-12">
<h4 class="mb-3">{{ 'Settings'|trans }}</h4>
{{ form(form) }}
</div>
</div>
@@ -1,9 +1,10 @@
<div class="row">
<div class="col-md-6 col-12">
<div class="d-flex align-items-center mb-3">
<h4 class="mb-0 pe-2">{{ 'Quizzes'|trans }}</h4>
<a class="link"
href="{{ path('tvdt_backoffice_quiz_add', {seasonCode: season.seasonCode}) }}">{{ 'Add'|trans }}</a>
<div class="d-flex align-items-center gap-2 mb-3">
<a class="btn btn-sm btn-outline-primary"
href="{{ path('tvdt_backoffice_quiz_add', {seasonCode: season.seasonCode}) }}">{{ 'Add from XLSX'|trans }}</a>
<a class="btn btn-sm btn-outline-secondary"
href="{{ path('tvdt_backoffice_quiz_add_blank', {seasonCode: season.seasonCode}) }}">{{ 'Add blank'|trans }}</a>
</div>
<div class="list-group mb-3">
{% for quiz in season.quizzes %}