Add survey question drag-sort and backend assets

- Frontend editor: drag-to-sort questions with dedicated sort assets/JS
- Backend list/search refinements for tl_survey and tl_survey_content
- Repository helpers for survey/question/editor queries
- Translations and AI handover notes update
This commit is contained in:
Jürgen Mummert
2026-06-14 09:13:07 +02:00
parent 79c4402aee
commit 1b16675ae9
23 changed files with 928 additions and 40 deletions
@@ -188,6 +188,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
$template->set('questionForms', $questionFormViews);
$template->set('newQuestionForm', $newQuestionFormView);
$template->set('questions', $questions);
$template->set('canQuestionReorder', !$structureLocked && count($questions) > 1);
$conditionOverview = $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id);
$template->set('conditions', $this->buildJumpRuleOverview($questions, $conditionOverview));
@@ -279,6 +280,11 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
$this->addFlash('success', 'Die Frage wurde gelöscht.');
}
if ('reorder-questions' === $action) {
$this->surveyEditorService->reorderQuestions($survey, $this->parseQuestionOrder($request));
$this->addFlash('success', 'Die neue Reihenfolge der Fragen wurde gespeichert.');
}
if ('delete-condition' === $action) {
$this->surveyEditorService->deleteCondition($survey, $itemId);
$this->addFlash('success', 'Die Bedingung wurde gelöscht.');
@@ -290,6 +296,23 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
}
/**
* @return list<int>
*/
private function parseQuestionOrder(Request $request): array
{
$rawOrder = trim((string) $request->request->get('question_order', ''));
if ('' === $rawOrder) {
throw new \RuntimeException('Die neue Reihenfolge der Fragen fehlt.');
}
return array_values(array_filter(
array_map('intval', explode(',', $rawOrder)),
static fn (int $questionId): bool => $questionId > 0,
));
}
private function resolveActiveQuestion(Request $request, int $surveyId): mixed
{
$questionId = (int) $request->query->get('question', 0);
@@ -317,13 +340,15 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
private function buildQuestionChoices(int $surveyId, int $excludeQuestionId = 0): array
{
$choices = [];
$position = 1;
foreach ($this->surveyQuestionRepository->findAllBySurvey($surveyId) as $question) {
if ($excludeQuestionId > 0 && (int) $question->id === $excludeQuestionId) {
++$position;
continue;
}
$choices[(int) $question->id] = sprintf('#%d %s', (int) $question->id, (string) $question->question);
$choices[(int) $question->id] = sprintf('%d. %s', $position++, (string) $question->question);
}
return $choices;
@@ -90,9 +90,8 @@ final class MemberSurveyListController extends AbstractFrontendModuleController
{
$surveyId = (int) $request->request->get('item_id', 0);
$action = (string) $request->request->get('_survey_action');
$token = (string) $request->request->get('_token');
if ($surveyId <= 0 || !$this->isCsrfTokenValid($action.'-'.$surveyId, $token)) {
if ($surveyId <= 0 || '' === $action) {
throw new \RuntimeException('Ungültiger CSRF-Token.');
}