Symfony's required default forced an answer to every question no matter what the mandatory checkbox said. Optional choice questions now offer a dedicated 'Keine Antwort' option, optional range and text questions a 'Keine Angaben machen' checkbox (preselected for ranges, because a slider always submits a value); the side that is not chosen gets dimmed but stays clickable. Mandatory ranges start their value display at '?' and reject the submit until the slider was actually used, so the browser's midpoint default no longer leaks into the results. Empty answers are stored as a deliberate 'no answer', kept out of counts, averages and charts, and reported separately on the results page and in the PDF export. Also gives the reader textarea its full width and field styling, adds grid overflow guards for unbreakable content and styles the form error box. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
172 lines
9.7 KiB
Twig
172 lines
9.7 KiB
Twig
{% trans_default_domain 'messages' %}
|
|
|
|
{% include '@Survey/frontend/_survey_assets.html.twig' %}
|
|
{% include '@Survey/frontend/_survey_branding.html.twig' %}
|
|
|
|
{% set moduleHeadline = (headline is iterable ? headline.text|default('') : headline|default(''))|striptags|trim %}
|
|
|
|
<section class="survey-shell survey-grid">
|
|
{% if moduleHeadline %}
|
|
<header>
|
|
<h2>{{ moduleHeadline }}</h2>
|
|
</header>
|
|
{% endif %}
|
|
|
|
{% if errorMessage %}
|
|
<div class="survey-alert warning">{{ errorMessage }}</div>
|
|
{% elseif survey %}
|
|
<article class="survey-card">
|
|
<div class="survey-meta">
|
|
<h2>{{ survey.title|striptags|trim }}</h2>
|
|
<span class="survey-badge neutral">{{ 'survey.results.participations'|trans({'%count%': completedSubmissionCount}) }}</span>
|
|
<span class="survey-badge neutral">{{ 'survey.results.questions'|trans({'%count%': questionResults|length}) }}</span>
|
|
</div>
|
|
|
|
{% if survey.description|survey_has_text %}
|
|
<div class="survey-rich-text">{{ survey.description|survey_rich }}</div>
|
|
{% else %}
|
|
<p>{{ 'survey.results.no_description'|trans }}</p>
|
|
{% endif %}
|
|
|
|
{% if downloadUrl or pdfDownloadUrl %}
|
|
<div class="survey-button-row">
|
|
{% if downloadUrl %}
|
|
<a class="survey-button primary" href="{{ downloadUrl }}">{{ 'survey.results.download_excel'|trans }}</a>
|
|
{% endif %}
|
|
{% if pdfDownloadUrl %}
|
|
<a class="survey-button secondary" href="{{ pdfDownloadUrl }}">{{ 'survey.results.download_pdf'|trans }}</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</article>
|
|
|
|
<div class="survey-grid">
|
|
{% set previousSectionId = null %}
|
|
{% for question in questionResults %}
|
|
{% set sectionId = question.section.id|default(0) %}
|
|
{% if hasSections|default(false) and sectionId != previousSectionId %}
|
|
<header class="survey-results-section-head">
|
|
<span class="survey-section-kicker">{{ 'survey.results.section_kicker'|trans }}</span>
|
|
<h3>{{ question.section.title|default('') ?: 'survey.results.section_default'|trans }}</h3>
|
|
</header>
|
|
{% set previousSectionId = sectionId %}
|
|
{% endif %}
|
|
<article class="survey-card survey-results-card">
|
|
<div class="survey-meta">
|
|
<h3>{{ question.question|striptags|trim }}</h3>
|
|
<span class="survey-badge neutral">{{ 'survey.results.answered'|trans({'%count%': question.totalAnswers, '%total%': completedSubmissionCount}) }}</span>
|
|
{% if question.noAnswerCount|default(0) > 0 %}
|
|
<span class="survey-badge neutral">{{ 'survey.results.no_answer'|trans({'%count%': question.noAnswerCount}) }}</span>
|
|
{% endif %}
|
|
{% if question.skippedCount > 0 %}
|
|
<span class="survey-badge neutral">{{ 'survey.results.skipped'|trans({'%count%': question.skippedCount}) }}</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if question.description|survey_has_text %}
|
|
<div class="survey-rich-text">{{ question.description|survey_rich }}</div>
|
|
{% endif %}
|
|
|
|
<div class="survey-results-layout{% if not question.chart %} no-chart{% endif %}">
|
|
{% if question.chart %}
|
|
<div class="survey-chart-panel{% if question.chart.type == 'line' %} is-line{% endif %}">
|
|
<div class="survey-chart-frame">
|
|
<canvas class="survey-chart-canvas" data-survey-chart="{{ question.chart|json_encode|e('html_attr') }}"></canvas>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="survey-results-panel">
|
|
{% if question.type in ['yes_no_maybe', 'choice'] %}
|
|
<div class="survey-results-list">
|
|
{% for option in question.options %}
|
|
<div class="survey-results-row">
|
|
<div class="survey-meta">
|
|
<strong>{{ option.label|striptags|trim }}</strong>
|
|
<span class="survey-badge neutral">{{ option.count }}</span>
|
|
<span class="survey-badge neutral">{{ option.percentage }}%</span>
|
|
</div>
|
|
<div class="survey-progress">
|
|
<span class="survey-progress-bar" style="width: {{ option.percentage }}%"></span>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% elseif question.type == 'range' %}
|
|
<div class="survey-metric-grid">
|
|
<div class="survey-metric-card">
|
|
<span class="survey-metric-label">{{ 'survey.results.minimum'|trans }}</span>
|
|
<strong>{{ question.minimum is not null ? question.minimum : '-' }}</strong>
|
|
</div>
|
|
<div class="survey-metric-card">
|
|
<span class="survey-metric-label">{{ 'survey.results.maximum'|trans }}</span>
|
|
<strong>{{ question.maximum is not null ? question.maximum : '-' }}</strong>
|
|
</div>
|
|
<div class="survey-metric-card">
|
|
<span class="survey-metric-label">{{ 'survey.results.average'|trans }}</span>
|
|
<strong>{{ question.average is not null ? question.average : '-' }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="survey-results-list">
|
|
{% for option in question.distributionRows|default([]) %}
|
|
<div class="survey-results-row">
|
|
<div class="survey-meta">
|
|
<strong>{{ option.label|striptags|trim }}</strong>
|
|
<span class="survey-badge neutral">{{ 'survey.results.answer_count'|trans({'%count%': option.count}) }}</span>
|
|
<span class="survey-badge neutral">{{ option.percentage }}%</span>
|
|
</div>
|
|
<div class="survey-progress">
|
|
<span class="survey-progress-bar" style="width: {{ option.percentage }}%"></span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<p>{{ 'survey.results.no_ratings'|trans }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
{# Eine Liste statt Doppel-Auflistung: jede Antwort einmal,
|
|
mit exakter Anzahl statt Prozentwerten. #}
|
|
<div class="survey-text-response-grid survey-results-responses">
|
|
{% for option in question.responseSummary|default([]) %}
|
|
<article class="survey-card survey-response-card">
|
|
<span class="survey-badge neutral">{{ 'survey.results.answer_count'|trans({'%count%': option.count}) }}</span>
|
|
<blockquote>{{ option.label|striptags|trim|nl2br }}</blockquote>
|
|
</article>
|
|
{% else %}
|
|
<p>{{ 'survey.results.no_text_responses'|trans }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{% else %}
|
|
<article class="survey-card">{{ 'survey.results.no_questions'|trans }}</article>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<script src="{{ asset('bundles/survey/js/chart.umd.min.js') }}"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
if (typeof Chart === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
document.querySelectorAll('[data-survey-chart]').forEach(function (canvas) {
|
|
try {
|
|
var chartConfig = JSON.parse(canvas.getAttribute('data-survey-chart') || '{}');
|
|
|
|
if (!chartConfig.type || !chartConfig.data) {
|
|
return;
|
|
}
|
|
|
|
new Chart(canvas, chartConfig);
|
|
} catch (error) {
|
|
console.error('Survey chart could not be initialized.', error);
|
|
}
|
|
});
|
|
});
|
|
</script> |