From e5ae48c02ed63a0734c03c130b9d8526c9fcf767 Mon Sep 17 00:00:00 2001 From: Tom Hempel Date: Sat, 6 Jun 2026 16:28:10 +0200 Subject: [PATCH] improved questionnaire editor glass scales --- common.php | 101 ++++++++++++- data/app_ui_strings.json | 10 ++ handlers/questions.php | 21 +++ tests/Unit/CommonHelpersTest.php | 44 ++++++ website/css/style.css | 22 +++ website/js/pages/editor.js | 212 ++++++++++++++++++++++++---- website/js/questionnaire-preview.js | 8 +- 7 files changed, 388 insertions(+), 30 deletions(-) diff --git a/common.php b/common.php index 7b2f2b4..43b52e4 100644 --- a/common.php +++ b/common.php @@ -1145,6 +1145,85 @@ function qdb_parse_config_json($configJson): array { return $config; } +/** + * @return list + */ +function qdb_parse_glass_symptoms_body(array $body, array $config): array { + if (isset($body['glassSymptoms']) && is_array($body['glassSymptoms'])) { + $rows = []; + foreach ($body['glassSymptoms'] as $row) { + if (!is_array($row)) { + continue; + } + $key = trim((string)($row['key'] ?? '')); + if ($key === '') { + continue; + } + $key = qdb_validate_stable_key($key, 'Symptom key'); + $rows[] = [ + 'key' => $key, + 'labelDe' => trim((string)($row['labelDe'] ?? '')), + ]; + } + return $rows; + } + $out = []; + foreach ($config['symptoms'] ?? [] as $symptomKey) { + $key = trim((string)$symptomKey); + if ($key !== '') { + $out[] = ['key' => $key, 'labelDe' => '']; + } + } + return $out; +} + +/** Apply ordered symptom keys from editor rows into question config. */ +function qdb_apply_glass_symptoms_config(array $config, array $glassSymptoms): array { + $keys = []; + foreach ($glassSymptoms as $row) { + $keys[] = $row['key']; + } + if ($keys) { + $config['symptoms'] = $keys; + } else { + unset($config['symptoms']); + } + return $config; +} + +/** Upsert German labels for glass-scale symptom string keys. */ +function qdb_sync_glass_symptom_strings(PDO $pdo, array $glassSymptoms): void { + foreach ($glassSymptoms as $row) { + $key = $row['key'] ?? ''; + $label = trim((string)($row['labelDe'] ?? '')); + if ($key === '' || $label === '') { + continue; + } + qdb_put_translation($pdo, 'string', $key, QDB_SOURCE_LANGUAGE, $label); + } +} + +/** + * Symptom keys with German labels for editor / API consumers. + * + * @return list + */ +function qdb_glass_symptoms_with_labels(PDO $pdo, array $config): array { + $rows = []; + foreach ($config['symptoms'] ?? [] as $symptomKey) { + $key = trim((string)$symptomKey); + if ($key === '') { + continue; + } + $label = qdb_string_german_label($pdo, $key); + if ($label === $key) { + $label = ''; + } + $rows[] = ['key' => $key, 'labelDe' => $label]; + } + return $rows; +} + /** Sync note German text into string_translation rows for the app. */ function qdb_sync_question_note_strings(PDO $pdo, string $questionKey, array $config): void { if ($questionKey === '') { @@ -1360,6 +1439,8 @@ function qdb_translation_entry_lists(PDO $pdo, string $qnID): array { $stringKeys = []; $stringEntries = []; $contentEntries = []; + $addedSymptomKeys = []; + $appKeySet = qdb_app_string_key_set(); foreach ($dbQuestions as $dbQ) { $qOrder = (int)($dbQ['orderIndex'] ?? 0); @@ -1406,9 +1487,24 @@ function qdb_translation_entry_lists(PDO $pdo, string $qnID): array { $stringKeys[$config[$field]] = true; } } - if (!empty($config['symptoms']) && is_array($config['symptoms'])) { + if (($dbQ['type'] ?? '') === 'glass_scale_question' + && !empty($config['symptoms']) && is_array($config['symptoms'])) { + $symIdx = 0; foreach ($config['symptoms'] as $s) { - $stringKeys[$s] = true; + $sk = trim((string)$s); + if ($sk === '' || isset($appKeySet[$sk]) || isset($addedSymptomKeys[$sk])) { + continue; + } + $addedSymptomKeys[$sk] = true; + $parentLabel = $qKey !== '' ? $qKey : $qLocal; + $stringEntries[] = [ + 'key' => $sk, + 'displayKey' => $qLocal . ' · ' . $parentLabel . ' · ' . $sk, + 'type' => 'string', + 'entityId' => $sk, + 'sortOrder' => $qOrder * 1000 + 5 + $symIdx, + ]; + $symIdx++; } } if ($qKey !== '') { @@ -1421,7 +1517,6 @@ function qdb_translation_entry_lists(PDO $pdo, string $qnID): array { } } - $appKeySet = qdb_app_string_key_set(); $stringOrder = 0; $skList = array_keys($stringKeys); sort($skList, SORT_STRING); diff --git a/data/app_ui_strings.json b/data/app_ui_strings.json index feda3ab..243d7df 100644 --- a/data/app_ui_strings.json +++ b/data/app_ui_strings.json @@ -31,6 +31,7 @@ "error_not_found", "questionnaire_title", "exit_btn", + "extreme_glass", "february", "fill_all_fields", "fill_both_fields", @@ -39,6 +40,7 @@ "july", "june", "lay", + "little_glass", "consultation_unlock_requires_rhs", "enter_ages_ranges_hint", "questionnaire_unlock_2_rhs", @@ -52,8 +54,11 @@ "login_required", "march", "may", + "never_glass", "minutes_short", + "moderate_glass", "month", + "much_glass", "new_password_hint", "no_clients_assigned", "no_profile", @@ -126,6 +131,7 @@ "error_not_found": "Nicht gefunden", "questionnaire_title": "Fragebögen", "exit_btn": "Beenden", + "extreme_glass": "Extrem", "february": "Februar", "fill_all_fields": "Bitte alle Felder ausfüllen!", "fill_both_fields": "Bitte beide Felder ausfüllen!", @@ -134,6 +140,7 @@ "july": "Juli", "june": "Juni", "lay": "liegen.", + "little_glass": "Wenig", "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", @@ -147,8 +154,11 @@ "login_required": "Bitte zuerst einloggen", "march": "März", "may": "Mai", + "never_glass": "Nie", "minutes_short": "m", + "moderate_glass": "Mittel", "month": "Monat", + "much_glass": "Viel", "new_password_hint": "Neues Passwort", "no_clients_assigned": "Ihr Supervisor hat Ihnen noch keine Klienten zugewiesen.", "no_profile": "Dieser Klient ist noch nicht Teil der Datenbank", diff --git a/handlers/questions.php b/handlers/questions.php index 91d78d1..b2fa0cf 100644 --- a/handlers/questions.php +++ b/handlers/questions.php @@ -38,6 +38,9 @@ case 'GET': $opt['labelGerman'] = qdb_option_german_label($pdo, $opt['answerOptionID'], $opt['defaultText']); } unset($opt); + if (($q['type'] ?? '') === 'glass_scale_question') { + $q['glassSymptoms'] = qdb_glass_symptoms_with_labels($pdo, $cfg); + } $tr = $pdo->prepare("SELECT languageCode, text FROM question_translation WHERE questionID = :qid"); $tr->execute([':qid' => $q['questionID']]); $q['translations'] = $tr->fetchAll(PDO::FETCH_ASSOC); @@ -64,6 +67,10 @@ case 'POST': $order = (int)($body['orderIndex'] ?? 0); $req = (int)($body['isRequired'] ?? 0); $cfg = qdb_parse_config_json($body['configJson'] ?? '{}'); + $glassSymptoms = qdb_parse_glass_symptoms_body($body, $cfg); + if ($type === 'glass_scale_question') { + $cfg = qdb_apply_glass_symptoms_config($cfg, $glassSymptoms); + } $config = qdb_normalize_question_config( $cfg, $questionKey, @@ -101,12 +108,17 @@ case 'POST': ':o' => $order, ':r' => $req, ':cj' => $configJson]); qdb_upsert_source_translation($pdo, 'question', $id, $text); qdb_sync_question_note_strings($pdo, $questionKey, $config); + if ($type === 'glass_scale_question') { + qdb_sync_glass_symptom_strings($pdo, $glassSymptoms); + } qdb_save($tmpDb, $lockFp); json_success(['question' => [ 'questionID' => $id, 'questionnaireID' => $qnID, 'defaultText' => $text, 'questionKey' => $questionKey, 'localId' => $localId, 'type' => $type, 'orderIndex' => $order, 'isRequired' => $req, 'configJson' => $configJson, 'answerOptions' => [], 'translations' => [], + 'glassSymptoms' => $type === 'glass_scale_question' + ? qdb_glass_symptoms_with_labels($pdo, $config) : [], ]]); } catch (Throwable $e) { qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null); @@ -142,6 +154,10 @@ case 'PUT': json_error('MISSING_FIELDS', 'questionKey is required', 400); } $cfgIn = isset($body['configJson']) ? qdb_parse_config_json($body['configJson']) : $oldCfg; + $glassSymptoms = qdb_parse_glass_symptoms_body($body, $cfgIn); + if ($type === 'glass_scale_question') { + $cfgIn = qdb_apply_glass_symptoms_config($cfgIn, $glassSymptoms); + } $config = qdb_normalize_question_config( $cfgIn, $questionKey, @@ -153,12 +169,17 @@ case 'PUT': ->execute([':t' => $text, ':ty' => $type, ':o' => $order, ':r' => $req, ':cj' => $configJson, ':id' => $id]); qdb_upsert_source_translation($pdo, 'question', $id, $text); qdb_sync_question_note_strings($pdo, $questionKey, $config); + if ($type === 'glass_scale_question') { + qdb_sync_glass_symptom_strings($pdo, $glassSymptoms); + } qdb_save($tmpDb, $lockFp); json_success(['question' => [ 'questionID' => $id, 'questionnaireID' => $row['questionnaireID'], 'defaultText' => $text, 'questionKey' => $questionKey, 'type' => $type, 'orderIndex' => $order, 'isRequired' => $req, 'configJson' => $configJson, + 'glassSymptoms' => $type === 'glass_scale_question' + ? qdb_glass_symptoms_with_labels($pdo, $config) : [], ]]); } catch (Throwable $e) { qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null); diff --git a/tests/Unit/CommonHelpersTest.php b/tests/Unit/CommonHelpersTest.php index 7080182..0db7995 100644 --- a/tests/Unit/CommonHelpersTest.php +++ b/tests/Unit/CommonHelpersTest.php @@ -53,4 +53,48 @@ final class CommonHelpersTest extends TestCase $this->assertSame('moderate', qdb_glass_symptom_answer_value($json, 'pain')); $this->assertSame('', qdb_glass_symptom_answer_value($json, 'missing')); } + + public function testApplyGlassSymptomsConfig(): void + { + $config = ['scaleType' => 'glass']; + $rows = [ + ['key' => 'pain', 'labelDe' => 'Schmerz'], + ['key' => 'fatigue', 'labelDe' => 'Müdigkeit'], + ]; + $out = qdb_apply_glass_symptoms_config($config, $rows); + $this->assertSame(['pain', 'fatigue'], $out['symptoms']); + $this->assertSame('glass', $out['scaleType']); + } + + public function testApplyGlassSymptomsConfigClearsWhenEmpty(): void + { + $config = ['symptoms' => ['old_key'], 'scaleType' => 'thermometer']; + $out = qdb_apply_glass_symptoms_config($config, []); + $this->assertArrayNotHasKey('symptoms', $out); + } + + public function testParseGlassSymptomsBody(): void + { + $body = [ + 'glassSymptoms' => [ + ['key' => 'pain', 'labelDe' => 'Schmerz'], + ['key' => 'invalid key', 'labelDe' => 'x'], + ], + ]; + $rows = qdb_parse_glass_symptoms_body($body, []); + $this->assertSame([ + ['key' => 'pain', 'labelDe' => 'Schmerz'], + ['key' => 'invalid_key', 'labelDe' => 'x'], + ], $rows); + } + + public function testParseGlassSymptomsBodyFromConfigWhenMissing(): void + { + $config = ['symptoms' => ['fatigue', 'pain']]; + $rows = qdb_parse_glass_symptoms_body([], $config); + $this->assertSame([ + ['key' => 'fatigue', 'labelDe' => ''], + ['key' => 'pain', 'labelDe' => ''], + ], $rows); + } } diff --git a/website/css/style.css b/website/css/style.css index 899ccdb..bd195fb 100644 --- a/website/css/style.css +++ b/website/css/style.css @@ -2603,3 +2603,25 @@ select:disabled { text-align: center; color: var(--text-secondary); } + +/* Glass-scale symptom rows in editor */ +.glass-symptoms-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.glass-symptom-row { + padding: 10px 12px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; +} +.glass-symptoms-empty { + padding: 12px; + color: var(--text-secondary); + font-size: .85rem; + font-style: italic; +} diff --git a/website/js/pages/editor.js b/website/js/pages/editor.js index 9b16c80..62594d1 100644 --- a/website/js/pages/editor.js +++ b/website/js/pages/editor.js @@ -190,6 +190,145 @@ function readNotesFromForm(prefix) { return { noteBefore: val('noteBefore'), noteAfter: val('noteAfter') }; } +function glassSymptomsRowsFromQuestion(q) { + if (Array.isArray(q?.glassSymptoms) && q.glassSymptoms.length) { + return q.glassSymptoms.map(r => ({ + key: (r.key || '').trim(), + labelDe: (r.labelDe || '').trim(), + })); + } + const config = parseConfig(q); + return (config.symptoms || []).map(key => ({ key: String(key).trim(), labelDe: '' })); +} + +function suggestSymptomKey(label) { + let slug = (label || '').trim().toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, ''); + if (!slug) return ''; + if (!/^[a-z]/.test(slug)) slug = `sym_${slug}`; + if (!STABLE_KEY_RE.test(slug)) slug = `sym_${slug.replace(/^[^a-z]+/, '')}`; + return STABLE_KEY_RE.test(slug) ? slug : ''; +} + +function glassSymptomsSectionHTML(rows, prefix) { + const list = rows.length ? rows : []; + return ` +
+
+ + +
+

Each row is one table line in the app. German text is saved with the question; translate other languages on the Translations tab.

+
    + ${list.length ? list.map((row, i) => glassSymptomRowHTML(prefix, row, i)).join('') + : '
  • No symptoms yet.
  • '} +
+
`; +} + +function glassSymptomRowHTML(prefix, row, idx) { + return ` +
  • +
    +
    + + +
    +
    + + +
    + +
    +
  • `; +} + +function readGlassSymptomsFromForm(prefix) { + const container = document.getElementById(`${prefix}_glass_symptoms`); + if (!container) return []; + const rows = []; + container.querySelectorAll('.glass-symptom-row').forEach(rowEl => { + const key = rowEl.querySelector('.glass-symptom-key')?.value?.trim() || ''; + const labelDe = rowEl.querySelector('.glass-symptom-label')?.value?.trim() || ''; + if (key || labelDe) rows.push({ key, labelDe }); + }); + return rows; +} + +function validateGlassSymptoms(rows) { + const seen = new Set(); + for (const row of rows) { + if (!row.key) { + showToast('Each symptom needs a key', 'error'); + return false; + } + if (!validateStableKey(row.key, 'Symptom key')) return false; + if (!row.labelDe) { + showToast(`German label is required for symptom "${row.key}"`, 'error'); + return false; + } + if (seen.has(row.key)) { + showToast(`Duplicate symptom key "${row.key}"`, 'error'); + return false; + } + seen.add(row.key); + } + return true; +} + +function wireGlassSymptomsSection(prefix) { + const container = document.getElementById(`${prefix}_glass_symptoms`); + if (!container) return; + + const renderList = (rows) => { + const list = container.querySelector('.glass-symptoms-list'); + if (!list) return; + if (!rows.length) { + list.innerHTML = '
  • No symptoms yet.
  • '; + return; + } + list.innerHTML = rows.map((row, i) => glassSymptomRowHTML(prefix, row, i)).join(''); + bindRowEvents(); + }; + + const collectRows = () => readGlassSymptomsFromForm(prefix); + + const bindRowEvents = () => { + container.querySelectorAll('.glass-symptom-remove').forEach(btn => { + btn.addEventListener('click', () => { + const rowEl = btn.closest('.glass-symptom-row'); + rowEl?.remove(); + const list = container.querySelector('.glass-symptoms-list'); + if (list && !list.querySelector('.glass-symptom-row')) { + list.innerHTML = '
  • No symptoms yet.
  • '; + } + }); + }); + container.querySelectorAll('.glass-symptom-label').forEach(inp => { + inp.addEventListener('blur', () => { + const rowEl = inp.closest('.glass-symptom-row'); + const keyInp = rowEl?.querySelector('.glass-symptom-key'); + if (!keyInp || keyInp.value.trim()) return; + const suggested = suggestSymptomKey(inp.value); + if (suggested) keyInp.value = suggested; + }); + }); + }; + + container.querySelector('[data-action=add-glass-symptom]')?.addEventListener('click', () => { + const rows = collectRows(); + rows.push({ key: '', labelDe: '' }); + renderList(rows); + const lastKey = container.querySelector('.glass-symptom-row:last-child .glass-symptom-label'); + lastKey?.focus(); + }); + + bindRowEvents(); +} + function stringSpinnerOtherEnabled(config) { return Boolean(config.otherNextQuestionId || config.otherOptionKey); } @@ -306,7 +445,7 @@ function validateStringSpinnerConfig(prefix) { // ── Config form HTML for each layout type ──────────────────────────────── -function configFormHTML(layout, config, prefix) { +function configFormHTML(layout, config, prefix, opts = {}) { let html = ''; switch (layout) { case 'radio_question': @@ -327,6 +466,7 @@ function configFormHTML(layout, config, prefix) { break; case 'glass_scale_question': { const scaleType = config.scaleType || 'glass'; + const rows = opts.glassSymptoms || (config.symptoms || []).map(k => ({ key: k, labelDe: '' })); html = `
    @@ -335,10 +475,7 @@ function configFormHTML(layout, config, prefix) {
    -
    - - -
    `; + ${glassSymptomsSectionHTML(rows, prefix)}`; break; } case 'value_spinner': @@ -485,9 +622,6 @@ function readConfigFromForm(layout, prefix) { case 'glass_scale_question': { const st = val('scaleType') || 'glass'; if (st && st !== 'glass') config.scaleType = st; - const raw = val('symptoms'); - const syms = raw.split('\n').map(s => s.trim()).filter(Boolean); - if (syms.length) config.symptoms = syms; break; } case 'value_spinner': @@ -862,6 +996,7 @@ function showAddQuestionForm() { document.getElementById('aq_options_section').style.display = OPTION_TYPES.has(type) ? '' : 'none'; document.getElementById('aq_config_section').innerHTML = configFormHTML(type, {}, 'aq_cfg'); wireStringSpinnerOtherToggle('aq_cfg'); + if (type === 'glass_scale_question') wireGlassSymptomsSection('aq_cfg'); const notesEl = document.getElementById('aq_notes_section'); if (notesEl) notesEl.innerHTML = notesSectionHTML({}, 'aq_cfg', qKey); } @@ -933,8 +1068,24 @@ function showAddQuestionForm() { const text = document.getElementById('aq_text').value.trim(); const type = document.getElementById('aq_type').value; const isRequired = document.getElementById('aq_required').checked ? 1 : 0; + const btn = document.getElementById('aq_submit'); const notes = readNotesFromForm('aq_cfg'); const configJson = readConfigFromForm(type, 'aq_cfg'); + const postBody = { + questionnaireID: questionnaire.questionnaireID, + questionKey, + defaultText: text, + type, + isRequired, + configJson, + noteBefore: notes.noteBefore, + noteAfter: notes.noteAfter, + }; + if (type === 'glass_scale_question') { + const glassSymptoms = readGlassSymptomsFromForm('aq_cfg'); + if (glassSymptoms.length && !validateGlassSymptoms(glassSymptoms)) return; + postBody.glassSymptoms = glassSymptoms; + } if (!validateStableKey(questionKey, 'Question key')) return; if (!text) { showToast('German text is required', 'error'); return; } if (type === 'string_spinner' && !validateStringSpinnerConfig('aq_cfg')) return; @@ -943,20 +1094,10 @@ function showAddQuestionForm() { return; } - const btn = document.getElementById('aq_submit'); btn.disabled = true; btn.textContent = 'Adding...'; try { - const qData = await apiPost('questions.php', { - questionnaireID: questionnaire.questionnaireID, - questionKey, - defaultText: text, - type, - isRequired, - configJson, - noteBefore: notes.noteBefore, - noteAfter: notes.noteAfter, - }); + const qData = await apiPost('questions.php', postBody); if (!qData.success) throw new Error(qData.error || 'Failed'); const newQ = qData.question; @@ -1088,7 +1229,7 @@ function renderQuestionBody(q) { ${notesSectionHTML(config, `eq_cfg_${q.questionID}`, qKey)}
    - ${configFormHTML(layout, config, `eq_cfg_${q.questionID}`)} + ${configFormHTML(layout, config, `eq_cfg_${q.questionID}`, { glassSymptoms: glassSymptomsRowsFromQuestion(q) })}
    @@ -1108,6 +1249,13 @@ function renderQuestionBody(q) {

    ` : ''} ${Object.keys(config).length ? `

    Config: ${esc(JSON.stringify(config))}

    ` : ''} + ${layout === 'glass_scale_question' && glassSymptomsRowsFromQuestion(q).length ? ` +

    Symptoms:

    +
      + ${glassSymptomsRowsFromQuestion(q).map(r => + `
    • ${esc(r.key)} · ${esc(r.labelDe || '—')}
    • ` + ).join('')} +
    ` : ''}
    `} @@ -1135,10 +1283,7 @@ function renderQuestionBody(q) { const isReq = document.getElementById(`eq_req_${q.questionID}`).checked ? 1 : 0; const notes = readNotesFromForm(cfgPrefix); const cj = readConfigFromForm(type, cfgPrefix); - if (!validateStableKey(questionKey, 'Question key')) return; - if (!text) { showToast('German text is required', 'error'); return; } - if (type === 'string_spinner' && !validateStringSpinnerConfig(cfgPrefix)) return; - updateQuestion(q.questionID, { + const payload = { questionKey, defaultText: text, type, @@ -1146,7 +1291,16 @@ function renderQuestionBody(q) { configJson: cj, noteBefore: notes.noteBefore, noteAfter: notes.noteAfter, - }); + }; + if (type === 'glass_scale_question') { + const glassSymptoms = readGlassSymptomsFromForm(cfgPrefix); + if (glassSymptoms.length && !validateGlassSymptoms(glassSymptoms)) return; + payload.glassSymptoms = glassSymptoms; + } + if (!validateStableKey(questionKey, 'Question key')) return; + if (!text) { showToast('German text is required', 'error'); return; } + if (type === 'string_spinner' && !validateStringSpinnerConfig(cfgPrefix)) return; + updateQuestion(q.questionID, payload); }); document.getElementById(`eq_key_${q.questionID}`).addEventListener('input', () => { @@ -1161,11 +1315,17 @@ function renderQuestionBody(q) { document.getElementById(`eq_type_${q.questionID}`).addEventListener('change', () => { const newLayout = document.getElementById(`eq_type_${q.questionID}`).value; - document.getElementById(`eq_config_${q.questionID}`).innerHTML = configFormHTML(newLayout, {}, cfgPrefix); + const opts = newLayout === 'glass_scale_question' + ? { glassSymptoms: glassSymptomsRowsFromQuestion(q) } + : {}; + document.getElementById(`eq_config_${q.questionID}`).innerHTML = + configFormHTML(newLayout, {}, cfgPrefix, opts); wireStringSpinnerOtherToggle(cfgPrefix); + if (newLayout === 'glass_scale_question') wireGlassSymptomsSection(cfgPrefix); }); wireStringSpinnerOtherToggle(cfgPrefix); + if (layout === 'glass_scale_question') wireGlassSymptomsSection(cfgPrefix); body.querySelector('.del-q-btn').addEventListener('click', () => deleteQuestion(q)); diff --git a/website/js/questionnaire-preview.js b/website/js/questionnaire-preview.js index 1a9186c..3ac5af4 100644 --- a/website/js/questionnaire-preview.js +++ b/website/js/questionnaire-preview.js @@ -270,6 +270,12 @@ function visibleQuestionIndices(questions, sectionByIdx) { return visible; } +function symptomRowLabel(q, symptomKey) { + const rows = q.glassSymptoms || []; + const row = rows.find(r => r.key === symptomKey); + return (row?.labelDe || '').trim() || symptomKey; +} + function renderQuestionBody(q, sectionIdx) { const layout = q.type || ''; const config = parseConfig(q); @@ -382,7 +388,7 @@ function renderQuestionBody(q, sectionIdx) { for (const so of scaleOpts) inner += `${esc(so.label)}`; inner += ``; symptoms.forEach((sym, si) => { - inner += `${esc(sym)}`; + inner += `${esc(symptomRowLabel(q, sym))}`; scaleOpts.forEach(so => { inner += ``; });