From f106d282279071701356d79f047830f8a1e5f205 Mon Sep 17 00:00:00 2001 From: jstet Date: Fri, 25 Sep 2026 13:59:47 +0200 Subject: [PATCH] wizard: replace the reasoning list with a real survey preview (#38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The on-page result used to be a flat list of label + rationale + source per question — no type, no choices, no hint, no relevance. After a run the user had to download the .xlsx to actually see the questionnaire. New SurveyView.svelte renders one card per question with: * a colour-coded type badge (text/select/number/date/note) * the label as the heading, hint in muted text below * a choices table with code + label columns (exclusive answers tagged) * a relevance pill when a skip condition is present * a collapsed 'Details' block for name, rationale, source and which RQs the question serves The audit-trail reasoning and the (multi) research-question list sit at the top of the same section. Welcome and end notes become callouts around the question list. Demographics get their own divider if the wizard appended any, so the AI-written questions don't get mixed in. +page.svelte shrinks by ~85 lines (the inline reasoning markup and its CSS move into the component); the result is reusable for any future 'preview this survey' surface. New i18n keys in EN/DE. --- src/lib/components/SurveyView.svelte | 441 +++++++++++++++++++++++++++ src/lib/i18n.ts | 26 ++ src/routes/+page.svelte | 85 +----- 3 files changed, 469 insertions(+), 83 deletions(-) create mode 100644 src/lib/components/SurveyView.svelte diff --git a/src/lib/components/SurveyView.svelte b/src/lib/components/SurveyView.svelte new file mode 100644 index 0000000..0425be9 --- /dev/null +++ b/src/lib/components/SurveyView.svelte @@ -0,0 +1,441 @@ + + +
+
+

{survey.title}

+ {#if survey.formId}

form_id: {survey.formId}

{/if} +
+ + {#if hasMultipleRqs} +
+ {$t('surveyView.researchQuestionsHeading')} ({rqs.length}) +
    + {#each rqs as rq, i (i)} +
  1. {$t('wizard.researchShort')} {i + 1}{rq}
  2. + {/each} +
+
+ {/if} + + {#if survey.reasoning} +
+ {$t('surveyView.reasoningHeading')} +

{survey.reasoning}

+
+ {/if} + + {#if welcomeNote} + + {/if} + +
    + {#each bodyQuestions as q, i (q.name)} + {@const isDemographic = demographicsStart >= 0 && i >= demographicsStart} +
  1. + {#if isDemographic && i === demographicsStart} +
    + {$t('surveyView.demographicsHeading')} +
    + {/if} +
    + {i + 1} + {q.type} +
    +

    {q.label}

    + {#if q.hint} +

    {q.hint}

    + {/if} + {#if q.choices && q.choices.length > 0} + + + + + + + + + {#each q.choices as c (c.name)} + + + + + {/each} + +
    {$t('surveyView.choiceCode')}{$t('surveyView.choiceLabel')}
    {c.name} + {c.label} + {#if c.exclusive} + {$t('surveyView.exclusive')} + {/if} +
    + {/if} + {#if q.relevant} +
    + {$t('surveyView.relevant')}: + {q.relevant} +
    + {/if} + {#if q.rationale || q.source || (q.researchQuestions && hasMultipleRqs)} +
    + {$t('surveyView.details')} +
    +
    {$t('surveyView.name')}
    +
    {q.name}
    + {#if q.rationale} +
    {$t('surveyView.rationale')}
    +
    {q.rationale}
    + {/if} + {#if q.source} +
    {$t('surveyView.source')}
    +
    {q.source}
    + {/if} + {#if q.researchQuestions?.length && hasMultipleRqs} +
    {$t('wizard.reasoningServes')}
    +
    + {q.researchQuestions.map((n) => `${$t('wizard.researchShort')} ${n}`).join(', ')} +
    + {/if} +
    +
    + {/if} +
  2. + {/each} +
+ + {#if endNote} + + {/if} +
+ + diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 452449f..43b8a81 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -96,6 +96,19 @@ const translations: Record> = { 'wizard.reasoningIntro': 'The generator’s own account of its selection. It is also written to the “explanations” sheet of the downloaded file.', 'wizard.reasoningSource': 'Source', + 'surveyView.researchQuestionsHeading': 'Research questions', + 'surveyView.reasoningHeading': 'Audit trail', + 'surveyView.intro': 'Intro', + 'surveyView.outro': 'Outro', + 'surveyView.demographicsHeading': 'Demographic questions (added automatically)', + 'surveyView.choiceCode': 'Code', + 'surveyView.choiceLabel': 'Answer option', + 'surveyView.exclusive': 'exclusive', + 'surveyView.relevant': 'shown when', + 'surveyView.details': 'Details', + 'surveyView.name': 'Variable name', + 'surveyView.rationale': 'Rationale', + 'surveyView.source': 'Source', 'wizard.apiKeyMissing': 'Please enter an API key at the top first.', 'wizard.apiKeyWarning': 'API key missing: enter an API key at the top to generate questionnaires.', @@ -244,6 +257,19 @@ const translations: Record> = { 'wizard.reasoningIntro': 'Die Begründung des Generators für seine Auswahl. Sie steht auch im Blatt „explanations“ der heruntergeladenen Datei.', 'wizard.reasoningSource': 'Quelle', + 'surveyView.researchQuestionsHeading': 'Forschungsfragen', + 'surveyView.reasoningHeading': 'Begründung', + 'surveyView.intro': 'Einleitung', + 'surveyView.outro': 'Abschluss', + 'surveyView.demographicsHeading': 'Demografische Fragen (automatisch ergänzt)', + 'surveyView.choiceCode': 'Code', + 'surveyView.choiceLabel': 'Antwortoption', + 'surveyView.exclusive': 'exklusiv', + 'surveyView.relevant': 'angezeigt wenn', + 'surveyView.details': 'Details', + 'surveyView.name': 'Variablenname', + 'surveyView.rationale': 'Begründung', + 'surveyView.source': 'Quelle', 'wizard.apiKeyMissing': 'Bitte trage zuerst oben einen API-Key ein.', 'wizard.apiKeyWarning': 'API-Key fehlt: Trage oben einen API-Key ein, um Fragebögen zu generieren.', diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 511609b..f7d37f8 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -6,6 +6,7 @@ import { locale, t } from '$lib/i18n'; import { get } from 'svelte/store'; import StepResults from '$lib/components/StepResults.svelte'; + import SurveyView from '$lib/components/SurveyView.svelte'; import { descriptionHtml } from 'virtual:cdl-content'; import { LeadAgent, @@ -462,42 +463,7 @@ {/if} {#if generatedSurvey && (generatedSurvey.reasoning || generatedSurvey.questions.some((q) => q.rationale))} -
- {$t('wizard.reasoningHeading')} -
-

{$t('wizard.reasoningIntro')}

- {#if (generatedSurvey.researchQuestions?.length ?? 0) > 1} -
    - {#each generatedSurvey.researchQuestions ?? [] as rq, i (i)} -
  1. {rq}
  2. - {/each} -
- {/if} - {#if generatedSurvey.reasoning} -

{generatedSurvey.reasoning}

- {/if} -
    - {#each generatedSurvey.questions as q (q.name)} -
  • - {q.label} - {#if q.rationale} - {q.rationale} - {/if} - {#if q.source} - {$t('wizard.reasoningSource')}: {q.source} - {/if} - {#if q.researchQuestions?.length && (generatedSurvey.researchQuestions?.length ?? 0) > 1} - {$t('wizard.reasoningServes')}: {q.researchQuestions - .map((n) => `${$t('wizard.researchShort')} ${n}`) - .join(', ')} - {/if} -
  • - {/each} -
-
-
+ {/if} {#if generatedFile && !qwacAvailable} @@ -904,41 +870,6 @@ color: color-mix(in srgb, var(--color-text-primary) 70%, white); } - .reasoning-section { - background: var(--color-white); - border: var(--dimension-border-width) solid var(--color-text-primary); - border-radius: var(--radius-lg); - padding: var(--spacing-base) var(--spacing-lg); - } - - .reasoning-section summary { - cursor: pointer; - font-weight: var(--font-weight-semibold); - } - - .reasoning-text { - white-space: pre-wrap; - } - - .reasoning-list { - list-style: none; - padding: 0; - margin: var(--spacing-sm) 0 0; - display: flex; - flex-direction: column; - gap: var(--spacing-sm); - } - - .reasoning-list li { - border-left: 3px solid var(--color-tertiary); - padding-left: var(--spacing-sm); - } - - .reasoning-label { - display: block; - font-weight: var(--font-weight-medium); - } - .research-row { display: flex; gap: var(--spacing-xs); @@ -984,16 +915,4 @@ .guide-hint a { color: var(--color-secondary); } - - .reasoning-rqs { - margin: var(--spacing-sm) 0; - padding-left: 1.5rem; - } - - .reasoning-why, - .reasoning-source { - display: block; - font-size: 0.85rem; - color: color-mix(in srgb, var(--color-text-primary) 70%, white); - }