Initial survey bundle import

This commit is contained in:
Jürgen Mummert
2026-05-17 17:31:28 +02:00
commit b8475e3a57
84 changed files with 7157 additions and 0 deletions
@@ -0,0 +1,410 @@
<style>
@font-face {
font-family: "PT Sans Narrow";
src: url("{{ asset('bundles/survey/fonts/PTSansNarrow-Regular.woff2') }}") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "PT Sans Narrow";
src: url("{{ asset('bundles/survey/fonts/PTSansNarrow-Bold.woff2') }}") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Blogger Sans";
src: url("{{ asset('bundles/survey/fonts/Blogger%20Sans-Medium.woff2') }}") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
:root {
--survey-blue: #0064ad;
--survey-orange: #ec7c32;
--survey-purple: #9d5276;
--survey-green: #00ae97;
--survey-rose: #fce4d6;
--survey-black: #222222;
--survey-gray-dark: #555e68;
--survey-gray-mid: #a2a8b4;
--survey-gray-soft: #e9e9eb;
--survey-surface: rgba(255, 255, 255, 0.92);
}
.survey-brand-strip {
display: flex;
align-items: center;
gap: 1.5rem;
justify-content: center;
margin: 0 0 1.75rem;
padding: 1rem 1.25rem 0.5rem;
}
.survey-brand-logo {
display: block;
object-fit: contain;
object-position: center;
}
.survey-brand-logo.primary {
width: auto;
max-width: min(100%, 320px);
height: 75px;
}
.survey-brand-logo.secondary {
width: auto;
max-width: min(100%, 420px);
height: 75px;
}
.survey-shell {
color: var(--survey-black);
font-family: "PT Sans Narrow", sans-serif;
letter-spacing: 0.3px;
background:
radial-gradient(circle at top left, rgba(0, 100, 173, 0.22), transparent 30%),
radial-gradient(circle at bottom right, rgba(0, 174, 151, 0.12), transparent 34%),
linear-gradient(180deg, rgba(0, 100, 173, 0.12) 0%, rgba(255, 255, 255, 0.98) 52%, rgba(0, 100, 173, 0.08) 100%);
border: 1px solid rgba(162, 168, 180, 0.45);
border-radius: 2rem;
box-shadow: 0 28px 80px rgba(34, 34, 34, 0.08);
padding: 1.5rem;
}
.survey-shell h2,
.survey-shell h3,
.survey-shell h4 {
font-family: "Blogger Sans", sans-serif;
font-weight: 500;
color: var(--survey-blue);
letter-spacing: 0.01em;
}
.survey-shell,
.survey-shell * {
font-style: normal;
}
.survey-grid {
display: grid;
gap: 1.5rem;
}
.survey-grid-columns {
display: grid;
gap: 1.5rem;
}
.survey-results-card {
overflow: hidden;
}
.survey-results-layout {
display: grid;
gap: 1.25rem;
margin-top: 1.1rem;
align-items: start;
}
.survey-results-layout.no-chart {
grid-template-columns: 1fr;
}
.survey-chart-panel {
position: relative;
min-height: 320px;
}
.survey-chart-panel::before {
content: "";
position: absolute;
inset: 0;
border-radius: 1.5rem;
background:
radial-gradient(circle at top left, rgba(0, 100, 173, 0.18), transparent 42%),
radial-gradient(circle at bottom right, rgba(236, 124, 50, 0.18), transparent 42%),
linear-gradient(135deg, rgba(0, 100, 173, 0.12) 0%, rgba(255, 255, 255, 0.9) 52%, rgba(236, 124, 50, 0.12) 100%);
border: 1px solid rgba(162, 168, 180, 0.28);
}
.survey-chart-frame {
position: relative;
z-index: 1;
min-height: 320px;
padding: 1rem;
}
.survey-chart-panel.is-line .survey-chart-frame {
min-height: 360px;
padding: 1rem 1rem 0.75rem;
}
.survey-chart-canvas {
width: 100%;
height: 100%;
}
.survey-results-panel {
display: grid;
gap: 1rem;
}
.survey-results-list {
display: grid;
gap: 0.9rem;
}
.survey-results-row {
display: grid;
gap: 0.45rem;
padding: 0.9rem 1rem;
border-radius: 1.15rem;
background: rgba(255, 255, 255, 0.78);
border: 1px solid rgba(162, 168, 180, 0.24);
}
.survey-results-responses {
max-height: 24rem;
overflow: auto;
padding-right: 0.25rem;
}
.survey-response-card {
margin: 0;
font-style: normal;
background: rgba(255, 255, 255, 0.72);
}
.survey-metric-grid {
display: grid;
gap: 0.85rem;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.survey-metric-card {
display: grid;
gap: 0.35rem;
padding: 0.95rem 1rem;
border-radius: 1.2rem;
background: rgba(252, 228, 214, 0.38);
border: 1px solid rgba(236, 124, 50, 0.16);
}
.survey-metric-card strong {
font-family: "Blogger Sans", sans-serif;
font-size: 1.35rem;
color: var(--survey-blue);
}
.survey-metric-label {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--survey-gray-dark);
font-weight: 700;
}
.survey-card {
background: var(--survey-surface);
border: 1px solid rgba(162, 168, 180, 0.34);
border-radius: 1.5rem;
padding: 1.25rem;
box-shadow: 0 12px 32px rgba(34, 34, 34, 0.06);
}
.survey-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.survey-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.65rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
background: rgba(162, 168, 180, 0.16);
}
.survey-badge.success { background: rgba(0, 174, 151, 0.16); color: var(--survey-green); }
.survey-badge.warning { background: rgba(236, 124, 50, 0.16); color: var(--survey-orange); }
.survey-badge.neutral { background: rgba(162, 168, 180, 0.16); color: var(--survey-gray-dark); }
.survey-button-row {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1rem;
}
.survey-button {
display: inline-flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
padding: 0.8rem 1rem;
border-radius: 999px;
border: 1px solid transparent;
text-decoration: none;
font-weight: 700;
cursor: pointer;
}
.survey-button.primary { background: var(--survey-blue); color: #fff; }
.survey-button.secondary { background: rgba(255,255,255,0.86); color: var(--survey-blue); border-color: rgba(0,100,173,0.18); }
.survey-button.danger { background: rgba(157, 82, 118, 0.12); color: var(--survey-purple); border-color: rgba(157, 82, 118, 0.22); }
.survey-form-grid {
display: grid;
gap: 1rem;
}
.survey-label {
display: grid;
gap: 0.4rem;
font-weight: 600;
}
.survey-label input,
.survey-label textarea,
.survey-label select {
width: 100%;
border-radius: 1rem;
border: 1px solid rgba(162,168,180,0.5);
padding: 0.8rem 0.9rem;
background: rgba(255,255,255,0.98);
color: var(--survey-black);
}
.survey-inline-check {
display: inline-flex;
gap: 0.6rem;
align-items: center;
border: 1px solid rgba(162,168,180,0.35);
border-radius: 1rem;
padding: 0.8rem 1rem;
background: rgba(252,228,214,0.42);
}
.survey-alert {
border-radius: 1.2rem;
padding: 1rem 1.1rem;
margin-bottom: 1rem;
background: rgba(255,255,255,0.78);
border: 1px solid rgba(162,168,180,0.35);
}
.survey-alert.error { border-color: rgba(157, 82, 118, 0.28); color: var(--survey-purple); }
.survey-alert.warning { border-color: rgba(236, 124, 50, 0.28); color: var(--survey-orange); }
.survey-kpis {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.survey-progress {
width: 100%;
height: 0.75rem;
border-radius: 999px;
overflow: hidden;
background: rgba(162,168,180,0.22);
}
.survey-progress > span {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--survey-blue) 0%, var(--survey-orange) 100%);
}
.survey-progress-bar {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--survey-blue) 0%, var(--survey-orange) 100%);
}
.survey-range-output {
font-size: 2rem;
font-weight: 800;
color: var(--survey-green);
}
@media (min-width: 960px) {
.survey-grid-columns {
grid-template-columns: 0.95fr 1.05fr;
}
.survey-results-layout {
grid-template-columns: minmax(320px, 0.95fr) minmax(0, 1.05fr);
}
}
@media (max-width: 767px) {
.survey-brand-strip {
padding: 0.75rem 0.75rem 0.25rem;
}
.survey-brand-logo.secondary {
height: 75px;
max-width: 100%;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-survey-range]').forEach(function (input) {
var targetId = input.getAttribute('data-survey-range-target');
var output = targetId ? document.getElementById(targetId) : null;
if (!output) {
return;
}
var update = function () {
output.textContent = input.value;
};
input.addEventListener('input', update);
update();
});
document.querySelectorAll('[data-survey-question-type]').forEach(function (select) {
var wrapper = select.closest('[data-survey-question-editor]');
if (!wrapper) {
return;
}
var toggle = function () {
var showRange = select.value === 'range';
var showYesNoMaybe = select.value === 'yes_no_maybe';
var showChoice = select.value === 'choice';
wrapper.querySelectorAll('.range-config-field').forEach(function (element) {
element.style.display = showRange ? '' : 'none';
});
wrapper.querySelectorAll('.yes-no-config-field').forEach(function (element) {
element.style.display = showYesNoMaybe ? '' : 'none';
});
wrapper.querySelectorAll('.choice-config-field').forEach(function (element) {
element.style.display = showChoice ? '' : 'none';
});
};
select.addEventListener('change', toggle);
toggle();
});
});
</script>
@@ -0,0 +1,3 @@
<div class="survey-brand-strip" aria-label="Projektlogos">
<img class="survey-brand-logo primary" src="{{ asset('bundles/survey/images/Logo_Landesverband.svg') }}" alt="Kinderschutzbund Sachsen">
</div>
@@ -0,0 +1,193 @@
{% include '@Survey/frontend/_survey_assets.html.twig' %}
{% include '@Survey/frontend/_survey_branding.html.twig' %}
<section class="survey-shell survey-grid">
{% if headline.text %}
<header>
<h2>{{ headline.text }}</h2>
</header>
{% endif %}
{% if loginRequired %}
<div class="survey-alert warning">Dieses Frontendmodul ist fuer eingeloggte Mitglieder gedacht. Nutzen Sie den normalen Contao-Mitgliederlogin auf einer geschuetzten Seite.</div>
{% elseif createMode %}
<div class="survey-card">
<h3>Neue Umfrage erstellen</h3>
<p>Die Umfrage wird nach dem Anlegen automatisch dem eingeloggten Mitglied zugewiesen.</p>
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
<label class="survey-label">Titel{{ form_widget(surveyForm.title) }}{{ form_errors(surveyForm.title) }}</label>
<label class="survey-label">Beschreibung{{ form_widget(surveyForm.description) }}</label>
<label class="survey-inline-check">{{ form_widget(surveyForm.published) }}<span>Direkt veroeffentlichen</span></label>
<div class="survey-button-row">
<button class="survey-button primary">Umfrage erstellen</button>
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">Zur Liste</a>{% endif %}
</div>
{{ form_end(surveyForm) }}
</div>
{% else %}
{% set locked = survey.isLocked == '1' %}
<div class="survey-button-row">
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">Zur Liste</a>{% endif %}
{% if publicUrl %}<a class="survey-button primary" href="{{ publicUrl }}" target="_blank" rel="noreferrer">Oeffentliche Ansicht oeffnen</a>{% endif %}
</div>
<div class="survey-kpis">
<article class="survey-card"><strong>Oeffentlicher Link</strong><div>{% if publicUrl %}<a href="{{ publicUrl }}" target="_blank" rel="noreferrer">{{ publicUrl }}</a>{% else %}Noch keine oeffentliche Seite hinterlegt.{% endif %}</div></article>
<article class="survey-card"><strong>Status</strong><div class="survey-meta"><span class="survey-badge {{ survey.published ? 'success' : 'neutral' }}">{{ survey.published ? 'veroeffentlicht' : 'entwurf' }}</span>{% if locked %}<span class="survey-badge warning">gesperrt</span>{% endif %}</div></article>
<article class="survey-card"><strong>Bearbeitende</strong><div>{{ editors|length }} Mitglied(er)</div></article>
</div>
{% if locked %}
<div class="survey-alert warning">Die Umfrage ist strukturell gesperrt, weil bereits Antworten vorliegen. Stammdaten koennen weiter gepflegt werden, der Ablauf und die Fragen jedoch nicht.</div>
{% endif %}
<div class="survey-grid-columns">
<div class="survey-grid">
<div class="survey-card">
<h3>Umfrage-Metadaten</h3>
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
<label class="survey-label">Titel{{ form_widget(surveyForm.title) }}{{ form_errors(surveyForm.title) }}</label>
<label class="survey-label">Beschreibung{{ form_widget(surveyForm.description) }}</label>
<label class="survey-inline-check">{{ form_widget(surveyForm.published) }}<span>Umfrage veroeffentlichen</span></label>
<div class="survey-button-row"><button class="survey-button primary">Speichern</button></div>
{{ form_end(surveyForm) }}
</div>
<div class="survey-card">
<h3>Bearbeitende Mitglieder</h3>
<div class="survey-grid">
{% for editor in editors %}
<div>
<strong>{{ [editor.firstname, editor.lastname]|join(' ')|trim ?: editor.email }}</strong><br>
<span>{{ editor.email }}</span>
</div>
{% else %}
<div>Keine Editoren hinterlegt.</div>
{% endfor %}
</div>
</div>
<div class="survey-card">
<h3>Antworten und Ergebnisse</h3>
<div class="survey-grid">
{% for submission in submissions %}
<article>
<div class="survey-meta">
<span class="survey-badge neutral">{{ submission.answerCount }} Antworten</span>
<span class="survey-badge {{ submission.isFinished ? 'success' : 'warning' }}">{{ submission.isFinished ? 'abgeschlossen' : 'offen' }}</span>
</div>
<div>Begonnen: {{ submission.startedAt ? submission.startedAt|date('d.m.Y H:i') : 'unbekannt' }}</div>
<div>Beendet: {{ submission.completedAt ? submission.completedAt|date('d.m.Y H:i') : 'unbekannt' }}</div>
<div>Teilnahme-Code: {{ submission.token }}</div>
</article>
{% else %}
<div>Es liegen noch keine Teilnahmen vor.</div>
{% endfor %}
</div>
</div>
</div>
<div class="survey-grid">
<div class="survey-card" data-survey-question-editor>
<div class="survey-meta"><h3>Fragen bearbeiten</h3>{% if activeQuestionId %}<span class="survey-badge neutral">Bearbeitungsmodus</span>{% endif %}</div>
{% if not locked %}
{% set questionType = questionForm.type.vars.value ?: 'yes_no_maybe' %}
{{ form_start(questionForm, {attr: {class: 'survey-form-grid'}}) }}
<label class="survey-label">Fragetyp{{ form_widget(questionForm.type, {attr: {'data-survey-question-type': '1'}}) }}</label>
<label class="survey-label">Frage{{ form_widget(questionForm.question) }}{{ form_errors(questionForm.question) }}</label>
<label class="survey-label">Beschreibung{{ form_widget(questionForm.description) }}</label>
<label class="survey-inline-check">{{ form_widget(questionForm.mandatory) }}<span>Pflichtfrage</span></label>
<label class="survey-inline-check">{{ form_widget(questionForm.published) }}<span>Aktiv</span></label>
<label class="survey-inline-check yes-no-config-field"{% if questionType != 'yes_no_maybe' %} style="display:none"{% endif %}>{{ form_widget(questionForm.allowMaybe) }}<span>Vielleicht anbieten</span></label>
<div class="yes-no-config-field survey-grid-columns"{% if questionType != 'yes_no_maybe' %} style="display:none;grid-template-columns: repeat(2, minmax(0, 1fr));"{% else %} style="grid-template-columns: repeat(2, minmax(0, 1fr));"{% endif %}>
<label class="survey-label">Zur Frage springen bei Auswahl ja{{ form_widget(questionForm.jumpOnYes) }}</label>
<label class="survey-label">Zur Frage springen bei Auswahl nein{{ form_widget(questionForm.jumpOnNo) }}</label>
</div>
<label class="survey-inline-check choice-config-field"{% if questionType != 'choice' %} style="display:none"{% endif %}>{{ form_widget(questionForm.allowMultiple) }}<span>Multiple-Choice aktivieren</span></label>
<div class="choice-config-field survey-grid-columns"{% if questionType != 'choice' %} style="display:none;grid-template-columns: repeat(2, minmax(0, 1fr));"{% else %} style="grid-template-columns: repeat(2, minmax(0, 1fr));"{% endif %}>
<label class="survey-label">Antwort 1{{ form_widget(questionForm.answerOption1) }}</label>
<label class="survey-label">Antwort 2{{ form_widget(questionForm.answerOption2) }}</label>
<label class="survey-label">Antwort 3{{ form_widget(questionForm.answerOption3) }}</label>
<label class="survey-label">Antwort 4{{ form_widget(questionForm.answerOption4) }}</label>
<label class="survey-label">Antwort 5{{ form_widget(questionForm.answerOption5) }}</label>
<label class="survey-label">Antwort 6{{ form_widget(questionForm.answerOption6) }}</label>
<label class="survey-label">Antwort 7{{ form_widget(questionForm.answerOption7) }}</label>
<label class="survey-label">Antwort 8{{ form_widget(questionForm.answerOption8) }}</label>
<label class="survey-label">Antwort 9{{ form_widget(questionForm.answerOption9) }}</label>
<label class="survey-label">Antwort 10{{ form_widget(questionForm.answerOption10) }}</label>
</div>
<div class="range-config-field survey-grid-columns"{% if questionType != 'range' %} style="display:none;grid-template-columns: repeat(3, minmax(0, 1fr));"{% else %} style="grid-template-columns: repeat(3, minmax(0, 1fr));"{% endif %}>
<label class="survey-label">Kleinster Wert{{ form_widget(questionForm.rangeMin) }}</label>
<label class="survey-label">Groesster Wert{{ form_widget(questionForm.rangeMax) }}</label>
<label class="survey-label">Schrittweite{{ form_widget(questionForm.rangeStep) }}</label>
</div>
<div class="survey-button-row"><button class="survey-button primary">{{ activeQuestionId ? 'Frage aktualisieren' : 'Frage speichern' }}</button></div>
{{ form_end(questionForm) }}
{% endif %}
<div class="survey-grid">
{% for question in questions %}
<article class="survey-card">
<div class="survey-meta">
<span class="survey-badge neutral">{{ question.type }}</span>
{% if question.mandatory %}<span class="survey-badge warning">Pflicht</span>{% endif %}
{% if loop.first %}<span class="survey-badge success">Start</span>{% endif %}
</div>
<h4>{{ question.question }}</h4>
<p>{{ question.description ?: 'Keine Zusatzbeschreibung.' }}</p>
{% if not locked %}
<div class="survey-button-row">
<a class="survey-button secondary" href="?survey={{ survey.id }}&question={{ question.id }}">Bearbeiten</a>
<form method="post">
<input type="hidden" name="_survey_action" value="delete-question">
<input type="hidden" name="item_id" value="{{ question.id }}">
<input type="hidden" name="_token" value="{{ csrf_token('delete-question-' ~ question.id) }}">
<button class="survey-button danger" onclick="return confirm('Frage wirklich loeschen?');">Loeschen</button>
</form>
</div>
{% endif %}
</article>
{% else %}
<div>Noch keine Fragen angelegt.</div>
{% endfor %}
</div>
</div>
<div class="survey-card">
<div class="survey-meta"><h3>Sprungregeln</h3>{% if activeConditionId %}<span class="survey-badge neutral">Bearbeitungsmodus</span>{% endif %}</div>
{% if not locked %}
{{ form_start(conditionForm, {attr: {class: 'survey-form-grid'}}) }}
<label class="survey-label">Ausgangsfrage{{ form_widget(conditionForm.sourceQuestion) }}</label>
<label class="survey-label">Antwort{{ form_widget(conditionForm.answerValue) }}</label>
<label class="survey-label">Naechste Frage{{ form_widget(conditionForm.targetQuestion) }}</label>
<div class="survey-button-row"><button class="survey-button primary">{{ activeConditionId ? 'Bedingung aktualisieren' : 'Bedingung speichern' }}</button></div>
{{ form_end(conditionForm) }}
{% endif %}
<div class="survey-grid">
{% for condition in conditions %}
<article class="survey-card">
<div><strong>Wenn Antwort = {{ condition.answerValue }}</strong></div>
<div>{{ condition.sourceQuestionLabel ?: ('Frage #' ~ condition.sourceQuestion) }}</div>
<div>weiter zu {{ condition.targetQuestionLabel ?: ('Frage #' ~ condition.targetQuestion) }}</div>
{% if not locked %}
<div class="survey-button-row">
<a class="survey-button secondary" href="?survey={{ survey.id }}&condition={{ condition.id }}">Bearbeiten</a>
<form method="post">
<input type="hidden" name="_survey_action" value="delete-condition">
<input type="hidden" name="item_id" value="{{ condition.id }}">
<input type="hidden" name="_token" value="{{ csrf_token('delete-condition-' ~ condition.id) }}">
<button class="survey-button danger" onclick="return confirm('Bedingung wirklich loeschen?');">Loeschen</button>
</form>
</div>
{% endif %}
</article>
{% else %}
<div>Noch keine Bedingungen definiert.</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
</section>
@@ -0,0 +1,52 @@
{% include '@Survey/frontend/_survey_assets.html.twig' %}
{% include '@Survey/frontend/_survey_branding.html.twig' %}
<section class="survey-shell survey-grid">
{% if headline.text %}
<header>
<h2>{{ headline.text }}</h2>
</header>
{% endif %}
{% if loginRequired %}
<div class="survey-alert warning">Dieser Bereich verwendet den normalen Contao-Mitgliederlogin. Bitte binden Sie das Modul auf einer geschuetzten Seite oder mit einer separaten Login-Seite ein.</div>
{% else %}
<div class="survey-button-row">
{% if createUrl %}
<a class="survey-button primary" href="{{ createUrl }}">Neue Umfrage anlegen</a>
{% endif %}
</div>
<div class="survey-grid">
{% for survey in surveys %}
<article class="survey-card">
<div class="survey-meta">
<h3>{{ survey.title }}</h3>
<span class="survey-badge {{ survey.published ? 'success' : 'neutral' }}">{{ survey.published ? 'veroeffentlicht' : 'Entwurf' }}</span>
{% if survey.isLocked %}
<span class="survey-badge warning">gesperrt</span>
{% endif %}
</div>
<p>{{ survey.description ?: 'Keine Beschreibung hinterlegt.' }}</p>
<div class="survey-meta">
<span class="survey-badge neutral">{{ survey.questionCount }} Fragen</span>
<span class="survey-badge neutral">{{ survey.answerCount }} Antworten</span>
</div>
<div class="survey-button-row">
{% if survey.editUrl %}
<a class="survey-button primary" href="{{ survey.editUrl }}">Bearbeiten</a>
{% endif %}
{% if survey.publicUrl %}
<a class="survey-button secondary" href="{{ survey.publicUrl }}" target="_blank" rel="noreferrer">Oeffentliche Ansicht</a>
{% endif %}
{% if survey.resultsUrl %}
<a class="survey-button secondary" href="{{ survey.resultsUrl }}" target="_blank" rel="noreferrer">Ergebnisse anzeigen</a>
{% endif %}
</div>
</article>
{% else %}
<div class="survey-card">Dem eingeloggten Mitglied ist aktuell keine Umfrage zugewiesen.</div>
{% endfor %}
</div>
{% endif %}
</section>
@@ -0,0 +1,84 @@
{% include '@Survey/frontend/_survey_assets.html.twig' %}
{% include '@Survey/frontend/_survey_branding.html.twig' %}
<section class="survey-shell survey-grid">
{% if headline.text %}
<header><h2>{{ headline.text }}</h2></header>
{% endif %}
{% if errorMessage %}
<div class="survey-alert error">{{ errorMessage }}</div>
{% elseif survey %}
<article class="survey-card">
<div class="survey-meta">
<h3>{{ survey.title }}</h3>
{% if progress.total > 0 %}<span class="survey-badge neutral">{{ progress.current }}/{{ progress.total }}</span>{% endif %}
</div>
<p>{{ survey.description ?: 'Bitte beantworten Sie die folgenden Fragen. Jede Frage wird einzeln angezeigt.' }}</p>
{% if progress.total > 0 %}
<div class="survey-progress"><span style="width: {{ progress.percentage }}%"></span></div>
{% endif %}
</article>
{% if isComplete %}
<article class="survey-card">
<h3>Vielen Dank fuers Ausfuellen.</h3>
<p>Ihre Antworten wurden anonym gespeichert. Es wurde keine Teilnehmeridentitaet mit diesem Umfrage-Durchlauf verknuepft.</p>
{% if answers %}
<div class="survey-grid">
{% for answer in answers %}
<article class="survey-card">
<div class="survey-badge neutral">{{ answer.questionType }}</div>
<h4>{{ answer.question }}</h4>
<p>{{ answer.value }}</p>
</article>
{% endfor %}
</div>
{% endif %}
</article>
{% elseif question %}
<article class="survey-card">
<div class="survey-meta"><span class="survey-badge neutral">Frage {{ progress.current }}</span></div>
<h3>{{ question.question }}</h3>
{% if question.description %}<p>{{ question.description }}</p>{% endif %}
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
{% if question.type == 'yes_no_maybe' %}
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
{% for child in surveyForm.answer %}
<label class="survey-card" style="cursor: pointer;">
<div class="survey-meta"><strong>{{ child.vars.label }}</strong>{{ form_widget(child) }}</div>
<div>Diese Antwort wird direkt fuer den weiteren Ablauf verwendet.</div>
</label>
{% endfor %}
</div>
{% elseif question.type == 'choice' %}
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
{% for child in surveyForm.answer %}
<label class="survey-card" style="cursor: pointer;">
<div class="survey-meta"><strong>{{ child.vars.label }}</strong>{{ form_widget(child) }}</div>
</label>
{% endfor %}
</div>
{% elseif question.type == 'range' %}
<div class="survey-grid">
<div class="survey-card">
<div>Aktueller Wert</div>
<div id="survey-range-output-{{ question.id }}" class="survey-range-output">{{ question.rangeMin }}</div>
</div>
{{ form_widget(surveyForm.answer, {attr: {'data-survey-range': '1', 'data-survey-range-target': 'survey-range-output-' ~ question.id}}) }}
<div class="survey-meta"><span>{{ question.rangeMin }}</span><span>{{ question.rangeMax }}</span></div>
</div>
{% else %}
{{ form_widget(surveyForm.answer) }}
{% endif %}
{{ form_errors(surveyForm.answer) }}
<div class="survey-button-row"><button class="survey-button primary">Weiter</button></div>
{{ form_end(surveyForm) }}
</article>
{% endif %}
{% endif %}
</section>
@@ -0,0 +1,162 @@
{% include '@Survey/frontend/_survey_assets.html.twig' %}
{% include '@Survey/frontend/_survey_branding.html.twig' %}
<section class="survey-shell survey-grid">
{% if headline.text %}
<header>
<h2>{{ headline.text }}</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 }}</h2>
<span class="survey-badge neutral">{{ completedSubmissionCount }} abgeschlossene Teilnahmen</span>
<span class="survey-badge neutral">{{ questionResults|length }} Fragen</span>
</div>
<p>{{ survey.description ?: 'Keine Beschreibung hinterlegt.' }}</p>
{% if downloadUrl or pdfDownloadUrl %}
<div class="survey-button-row">
{% if downloadUrl %}
<a class="survey-button primary" href="{{ downloadUrl }}">Ergebnisse als Excel herunterladen</a>
{% endif %}
{% if pdfDownloadUrl %}
<a class="survey-button secondary" href="{{ pdfDownloadUrl }}">Ergebnisse als PDF herunterladen</a>
{% endif %}
</div>
{% endif %}
</article>
<div class="survey-grid">
{% for question in questionResults %}
<article class="survey-card survey-results-card">
<div class="survey-meta">
<h3>{{ question.question }}</h3>
<span class="survey-badge neutral">{{ question.totalAnswers }} Antworten</span>
<span class="survey-badge neutral">{{ question.responseRate }}% Ruecklauf</span>
</div>
{% if question.description %}
<p>{{ question.description }}</p>
{% 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 }}</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">Minimum</span>
<strong>{{ question.minimum is not null ? question.minimum : '-' }}</strong>
</div>
<div class="survey-metric-card">
<span class="survey-metric-label">Maximum</span>
<strong>{{ question.maximum is not null ? question.maximum : '-' }}</strong>
</div>
<div class="survey-metric-card">
<span class="survey-metric-label">Durchschnitt</span>
<strong>{{ question.average is not null ? question.average : '-' }}</strong>
</div>
</div>
<div class="survey-results-list">
{% for option in question.distribution %}
<div class="survey-results-row">
<div class="survey-meta">
<strong>{{ option.label }}</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>
{% else %}
<p>Noch keine Bewertungen vorhanden.</p>
{% endfor %}
</div>
{% else %}
{% if question.responseSummary is defined and question.responseSummary %}
<div class="survey-results-list">
{% for option in question.responseSummary %}
<div class="survey-results-row">
<div class="survey-meta">
<strong>{{ option.label }}</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>
{% endif %}
<div class="survey-results-list survey-results-responses">
{% for response in question.responses %}
<blockquote class="survey-card survey-response-card">{{ response|nl2br }}</blockquote>
{% else %}
<p>Noch keine Freitextantworten vorhanden.</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</article>
{% else %}
<article class="survey-card">Fuer diese Umfrage sind noch keine Fragen angelegt.</article>
{% endfor %}
</div>
{% endif %}
</section>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/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>