From 91834d0b16381eea3e36be4d586b4363a3681653 Mon Sep 17 00:00:00 2001 From: Tom Hempel Date: Sat, 6 Jun 2026 16:07:06 +0200 Subject: [PATCH] Add support for app string translations in questionnaire imports, enhance UI strings, and improve question navigation in editor --- common.php | 20 +++++++++++++++ data/app_ui_strings.json | 8 ++++++ website/js/pages/editor.js | 50 ++++++++++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/common.php b/common.php index 2978646..7b2f2b4 100644 --- a/common.php +++ b/common.php @@ -2094,6 +2094,26 @@ function qdb_import_questionnaires_bundle(PDO $pdo, array $bundle, bool $replace if (!is_array($items) || !$items) { json_error('MISSING_FIELDS', 'bundle.questionnaires array is required', 400); } + foreach ($bundle['appStringTranslations'] ?? [] as $st) { + if (!is_array($st)) { + continue; + } + $sk = trim($st['stringKey'] ?? ''); + if ($sk === '') { + continue; + } + $map = qdb_normalize_bundle_translations($st['translations'] ?? []); + if (!$map) { + continue; + } + if (isset($map[QDB_SOURCE_LANGUAGE])) { + qdb_set_app_string_german_label($pdo, $sk, $map[QDB_SOURCE_LANGUAGE]); + unset($map[QDB_SOURCE_LANGUAGE]); + } + if ($map) { + qdb_apply_translation_map($pdo, 'app_string', $sk, $map); + } + } $results = []; foreach ($items as $item) { $results[] = qdb_import_questionnaire_bundle($pdo, $item, $replaceIfExists); diff --git a/data/app_ui_strings.json b/data/app_ui_strings.json index 8be0d8f..feda3ab 100644 --- a/data/app_ui_strings.json +++ b/data/app_ui_strings.json @@ -40,6 +40,10 @@ "june", "lay", "consultation_unlock_requires_rhs", + "enter_ages_ranges_hint", + "questionnaire_unlock_2_rhs", + "questionnaire_unlock_3_integration_index", + "questionnaire_unlock_4_consultation_results", "locked", "questionnaire_chip_edit", "questionnaire_chip_edit_pending", @@ -131,6 +135,10 @@ "june": "Juni", "lay": "liegen.", "consultation_unlock_requires_rhs": "Freischaltung nach Abschluss von Demografie und RHS", + "enter_ages_ranges_hint": "Bitte Alter als Bereich (z. B. 8–12) oder einzelne Jahre (z. B. 6, 9, 13) eingeben", + "questionnaire_unlock_2_rhs": "Verfügbar nach Abschluss von Demografie", + "questionnaire_unlock_3_integration_index": "Verfügbar nach Abschluss von Demografie und RHS", + "questionnaire_unlock_4_consultation_results": "Verfügbar nach Abschluss von Demografie (Einwilligung unterschrieben), RHS und IPL", "locked": "Gesperrt", "questionnaire_chip_edit": "Bearbeiten", "questionnaire_chip_edit_pending": "Bearbeiten · Upload ausstehend", diff --git a/website/js/pages/editor.js b/website/js/pages/editor.js index 95fa561..cd74632 100644 --- a/website/js/pages/editor.js +++ b/website/js/pages/editor.js @@ -193,19 +193,44 @@ function stringSpinnerOtherEnabled(config) { return Boolean(config.otherNextQuestionId || config.otherOptionKey); } -function stringSpinnerNextSectionHTML(config, prefix) { +/** Question types that use config.nextQuestionId (not per-option branching). */ +const QUESTION_LEVEL_NEXT_LAYOUTS = new Set([ + 'value_spinner', + 'slider_question', + 'free_text', + 'date_spinner', + 'multi_check_box_question', + 'glass_scale_question', + 'client_coach_code_question', + 'client_not_signed', + 'last_page', +]); + +function questionNextSectionHTML(config, prefix, { label, hint } = {}) { const next = config.nextQuestionId || ''; return ` -
- +
+ - Where to go after a normal spinner choice. Required when “Other” targets a question that sits next in order (so list picks can skip it). + ${hint || 'Where to go after this question. Leave empty to continue in list order.'}
`; } +function stringSpinnerNextSectionHTML(config, prefix) { + return questionNextSectionHTML(config, prefix, { + label: 'Next question (list selection)', + hint: 'Where to go after a normal spinner choice. Required when “Other” targets a question that sits next in order (so list picks can skip it).', + }); +} + +function readQuestionNextFromForm(prefix, config) { + const next = document.getElementById(`${prefix}_nextQuestionId`)?.value?.trim() || ''; + if (next) config.nextQuestionId = next; +} + function stringSpinnerOtherSectionHTML(config, prefix) { const enabled = stringSpinnerOtherEnabled(config); const otherKey = config.otherOptionKey || 'other_option'; @@ -288,7 +313,8 @@ function configFormHTML(layout, config, prefix) {
-
`; +
+

Branching: set Next question on each answer option below.

`; break; case 'multi_check_box_question': html = ` @@ -429,6 +455,9 @@ function configFormHTML(layout, config, prefix) { `; break; } + if (QUESTION_LEVEL_NEXT_LAYOUTS.has(layout)) { + html += questionNextSectionHTML(config, prefix); + } return html ? `
${html}
` : ''; } @@ -517,6 +546,9 @@ function readConfigFromForm(layout, prefix) { if (val('textKey')) config.textKey = val('textKey'); break; } + if (QUESTION_LEVEL_NEXT_LAYOUTS.has(layout) || layout === 'string_spinner') { + readQuestionNextFromForm(prefix, config); + } return JSON.stringify(config); } @@ -790,6 +822,7 @@ function showAddQuestionForm() {
+
+