Fix customer-reported survey results and list issues
Behebt die Fehler aus der Kunden-Testrunde (Praktikantin, Juli 2026):
- Ergebnisreihenfolge: Fragen werden vor dem Finalisieren nach _sorting
sortiert. finalizeQuestionResult entfernt _sorting, daher fiel die
bisherige Nachsortierung auf den alphabetischen Fallback zurück. Online,
PDF und Excel folgen jetzt derselben Frage-Sortierung.
- Excel-Export: Eigene Fragetext-Zeile über den Antwortspalten (je Frage
über die zugehörigen Spalten zusammengeführt). Zuvor fehlte der Fragetext.
- Range-Ergebnis: Verteilung deckt die komplette Skala rangeMin..rangeMax in
rangeStep-Schritten ab, damit die X-Achse linear und vollständig ist
(vorher nur tatsächlich vorkommende Werte in gleichen Abständen).
- Teilnahmen/Rücklauf pro Frage als absolute Zahlen ("X von Y beantwortet",
"Z übersprungen") statt reiner Prozent-Rücklaufquote.
- Freitextantworten gruppiert: jede Antwort einmal mit exakter Häufigkeit,
ohne Prozentangaben und ohne Doppel-Auflistung.
- Frontend-Liste: "Link kopieren"-Button (absolute Reader-URL) für
veröffentlichte Umfragen; Aktiv-Umschalter mit Textlabel statt nur Punkt;
Aktionslinks öffnen im selben Tab statt in neuen Tabs.
- Duplizieren: Titel-Kollisionsprüfung ohne Ausschluss der Quell-ID, damit
aus "... - Kopie" korrekt "... - Kopie 2" wird statt eines Dubletten-Titels.
Der internalNote-Fehler beim Anlegen/Duplizieren war ein fehlendes
DB-Schema-Update (Spalte tl_survey.internalNote) und wurde per
contao:migrate auf der Testumgebung nachgezogen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -440,15 +440,36 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.survey-visibility-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.35rem 0.9rem 0.35rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(162, 168, 180, 0.5);
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
color: var(--survey-gray-dark);
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
|
||||
.survey-visibility-toggle.is-on {
|
||||
border-color: rgba(10, 131, 46, 0.45);
|
||||
color: #0a6e28;
|
||||
}
|
||||
|
||||
.survey-visibility-dot {
|
||||
aspect-ratio: 1;
|
||||
height: 42px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
background: #a6adb7;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
@@ -456,6 +477,10 @@
|
||||
background: #0a832e;
|
||||
}
|
||||
|
||||
.survey-visibility-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.survey-button-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -70,13 +70,22 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if survey.publicUrl and survey.published %}
|
||||
<a class="survey-button secondary" href="{{ survey.publicUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.public_view'|trans }}</a>
|
||||
<a class="survey-button secondary" href="{{ survey.publicUrl }}">{{ 'survey.list.public_view'|trans }}</a>
|
||||
{% endif %}
|
||||
{% if survey.copyUrl and survey.published %}
|
||||
<button
|
||||
type="button"
|
||||
class="survey-button secondary"
|
||||
data-copy-text="{{ survey.copyUrl }}"
|
||||
data-copy-label="{{ 'survey.list.copy_link'|trans }}"
|
||||
data-copy-success="{{ 'survey.list.copy_link_done'|trans }}"
|
||||
>{{ 'survey.list.copy_link'|trans }}</button>
|
||||
{% endif %}
|
||||
{% if survey.draftUrl and not survey.published %}
|
||||
<a class="survey-button secondary" href="{{ survey.draftUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.draft_view'|trans }}</a>
|
||||
<a class="survey-button secondary" href="{{ survey.draftUrl }}">{{ 'survey.list.draft_view'|trans }}</a>
|
||||
{% endif %}
|
||||
{% if survey.resultsUrl %}
|
||||
<a class="survey-button secondary" href="{{ survey.resultsUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.show_results'|trans }}</a>
|
||||
<a class="survey-button secondary" href="{{ survey.resultsUrl }}">{{ 'survey.list.show_results'|trans }}</a>
|
||||
{% endif %}
|
||||
<form method="post" class="survey-question-action-form">
|
||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
||||
@@ -118,7 +127,10 @@
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('toggle-active-' ~ survey.id) }}">
|
||||
<input type="hidden" name="_survey_action" value="toggle-active">
|
||||
<input type="hidden" name="item_id" value="{{ survey.id }}">
|
||||
<button type="submit" class="survey-visibility-dot{{ survey.isActive ? ' is-on' : '' }}" title="{{ survey.isActive ? 'survey.edit.deactivate_survey'|trans : 'survey.edit.activate_survey'|trans }}" aria-label="{{ survey.isActive ? 'survey.edit.deactivate_survey'|trans : 'survey.edit.activate_survey'|trans }}"></button>
|
||||
<button type="submit" class="survey-visibility-toggle{{ survey.isActive ? ' is-on' : '' }}" title="{{ survey.isActive ? 'survey.edit.deactivate_survey'|trans : 'survey.edit.activate_survey'|trans }}" aria-label="{{ survey.isActive ? 'survey.edit.deactivate_survey'|trans : 'survey.edit.activate_survey'|trans }}">
|
||||
<span class="survey-visibility-dot{{ survey.isActive ? ' is-on' : '' }}" aria-hidden="true"></span>
|
||||
<span class="survey-visibility-label">{{ survey.isActive ? 'survey.list.active_state'|trans : 'survey.list.inactive_state'|trans }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,8 +41,10 @@
|
||||
<article class="survey-card survey-results-card">
|
||||
<div class="survey-meta">
|
||||
<h3>{{ question.question|striptags|trim }}</h3>
|
||||
<span class="survey-badge neutral">{{ 'survey.results.participations'|trans({'%count%': completedSubmissionCount}) }}</span>
|
||||
<span class="survey-badge neutral">{{ 'survey.results.response_rate'|trans({'%rate%': question.responseRate}) }}</span>
|
||||
<span class="survey-badge neutral">{{ 'survey.results.answered'|trans({'%count%': question.totalAnswers, '%total%': completedSubmissionCount}) }}</span>
|
||||
{% if question.skippedCount > 0 %}
|
||||
<span class="survey-badge neutral">{{ 'survey.results.skipped'|trans({'%count%': question.skippedCount}) }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if question.description|striptags|trim %}
|
||||
@@ -107,25 +109,13 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% if question.responseSummary is defined and question.responseSummary %}
|
||||
<div class="survey-text-summary-grid">
|
||||
{% for option in question.responseSummary %}
|
||||
<article class="survey-text-summary-card">
|
||||
<strong>{{ option.label|striptags|trim }}</strong>
|
||||
<div class="survey-meta">
|
||||
<span class="survey-badge neutral">{{ option.count }}</span>
|
||||
<span class="survey-badge neutral">{{ option.percentage }}%</span>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Eine Liste statt Doppel-Auflistung: jede Antwort einmal,
|
||||
mit exakter Anzahl statt Prozentwerten. #}
|
||||
<div class="survey-text-response-grid survey-results-responses">
|
||||
{% for response in question.responses %}
|
||||
{% for option in question.responseSummary|default([]) %}
|
||||
<article class="survey-card survey-response-card">
|
||||
<span class="survey-response-card__index">{{ loop.index }}</span>
|
||||
<blockquote>{{ response|striptags|trim|nl2br }}</blockquote>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user