diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..79481d2 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(git add *)", + "Bash(git commit -q -m ' *)", + "Bash(git push *)" + ] + } +} diff --git a/AI_HANDOVER.md b/AI_HANDOVER.md index 22f06a7..6448250 100644 --- a/AI_HANDOVER.md +++ b/AI_HANDOVER.md @@ -63,6 +63,7 @@ Diese Entscheidung ist absichtlich so getroffen und kein Bug. - `tl_survey` - Umfrage-Stammdaten und Statusfelder + - Enthält zusätzlich die backend-only gepflegte `internalNote` - `tl_survey_content` - Fragen der Umfrage - `tl_survey_condition` @@ -115,10 +116,19 @@ Aktuelle Button- und Sichtbarkeitslogik pro Umfrage: - `Umfrage veröffentlichen` - Eigene Aktion in der Liste - Nach Veröffentlichung disabled und textlich als veröffentlicht markiert +- `Duplizieren` + - Legt eine Kopie der Umfrage für den Redakteur an + - Titelvergabe ist fortlaufend: `Titel - Kopie`, danach `Titel - Kopie 2`, `Titel - Kopie 3` usw. + - Bestehende `- Kopie`-Suffixe werden fachlich normalisiert, damit keine Titel wie `... - Kopie - Kopie` mehr neu entstehen - Kreis grau/grün - Schaltet `isActive` - Ist bewusst kein Auge mehr +Wichtiger technischer Punkt: + +- Die Frontend-Listenaktionen arbeiten nur noch mit dem Contao-`REQUEST_TOKEN` plus Berechtigungsprüfung. + - Die zusätzliche Symfony-Aktions-CSRF-Prüfung wurde bewusst entfernt, weil sie in diesem Frontend-Flow zu ungültigen Tokens führte. + ### 2. Frontend-Editor Controller: @@ -154,6 +164,9 @@ Wichtige Entscheidung: - Typ, Pflicht usw. sollen nicht schon im Header gezeigt werden. - Die Details sieht man erst nach dem Aufklappen. +- Die Sprungziel-Dropdowns im Frontend zeigen Positionsnummern der Umfrage (`1. ...`, `2. ...`) statt globaler Datenbank-IDs. + - Die aktuell bearbeitete Frage wird dort nicht als eigenes Sprungziel angeboten. + ### 3. Öffentliche Umfrageansicht Controller: @@ -232,6 +245,13 @@ Reihenfolge der Navigation: Wichtiger Detailpunkt: - In der Sprungregelübersicht im Frontend-Editor werden bewusst die Positionsnummern innerhalb der Umfrage angezeigt, nicht die globalen Datenbank-IDs der Fragen. +- Gleiches gilt für die Sprungziel-Dropdowns in Backend und Frontend. + +Wichtige technische Eigenschaft: + +- `jumpOnYes`, `jumpOnNo` und generische Bedingungen referenzieren Ziel-Fragen intern immer über die Frage-ID, nicht über die Position. +- Dadurch bleiben Sprünge bei einer reinen Umordnung der Fragen fachlich stabil an dieselbe Frage gebunden. +- Wenn eine Ziel-Frage an eine andere Position verschoben wird, zeigt die Oberfläche danach automatisch die neue Positionsnummer an. ## Excel-Export @@ -315,6 +335,26 @@ Wichtige Listenoperationen: - `Link kopieren` - `Ergebnisse anzeigen` +Aktuelle Listen- und Filterlogik: + +- Die mehrspaltige Umfragenliste zeigt aktuell: + - `Titel` + - `Zugewiesene Benutzer` + - `Vorlage` + - `Interne Notiz` +- `Kategorien` wurden bewusst aus der mehrspaltigen Liste und aus der rechten Filterspalte entfernt. +- Die Spalte `Zugewiesene Benutzer` wird in der Listenmetadatenlogik gekürzt: + - maximal 3 Namen direkt sichtbar + - danach Zusatz wie `... und X weitere` +- `internalNote` + - backend-only Feld in `tl_survey` + - nur im Backend bearbeitbar + - in der mehrspaltigen Liste sichtbar + - bei Duplikaten wird der Wert mitkopiert +- In der rechten Suchspalte ist `Interne Notiz` suchbar. +- `ID` wurde für die Umfragenliste bewusst aus dem Suchfeld entfernt. + - Das geschieht über einen kleinen backend-spezifischen JS-Workaround, weil Contao `id` im Core für `DC_Table` hart ergänzt. + Aktuelle Backend-Entscheidung: - Das Warnsymbol genügt als Hinweis bei veröffentlichten Umfragen @@ -329,6 +369,21 @@ Aktuelle Backend-Entscheidung: Hinweis: - `isLocked` wird in den Child-Headerfeldern nicht mehr angezeigt, weil der Backend-Lock bewusst keine führende Benutzerfunktion mehr ist. +- In `tl_survey_content` ist der Fragenlisten-Header zusätzlich reduziert: + - nur noch `Titel` und `Veröffentlicht` + - kein öffentlicher Link-Schlüssel mehr + - die rechte Header-Leiste mit Aktionen ist im Normalzustand ausgeblendet + - im aktiven Verschiebe-/Clipboard-Modus wird sie wieder eingeblendet, damit `Neu oben` bzw. die Einfügepositionen erreichbar bleiben + +Fragenliste `tl_survey_content`: + +- Direkt sichtbare Primäraktion pro Frage: + - `Bearbeiten`-Stift +- Verschieben: + - erfolgt im Backend über den normalen Contao-Paste-/Move-Flow + - die Aktion `Verschieben` liegt bewusst nur unter den drei Punkten + - dort wird der Contao-Pfeil `mover.svg` verwendet, nicht die blaue Schere + - nach Aktivierung des Clipboard-Zustands erscheinen die üblichen Einfügeziele wie `Nach dem Element ... einfügen` ### Systemeinstellungen @@ -356,6 +411,7 @@ Wichtige Settings: - `published` reversibel erlaubt - `isActive` per Standard-Contao-Toggle - nur Warnsymbol statt Backend-Sperre + - Admin darf Fragen auch nach Veröffentlichung oder nach vorhandenen Antworten umsortieren ## Wichtige Dateien nach Themengebiet @@ -398,12 +454,35 @@ Backend: - `ddev exec php bin/console lint:yaml ...` - `ddev exec php bin/console cache:clear` +## Offene technische Punkte / Deployment-Hinweise + +- Neue DB-Spalte in `tl_survey`: + - `internalNote` + - Typ aktuell: `varchar(255) NOT NULL default ''` + - Für andere Umgebungen muss diese Spalte per Migration/SQL vorhanden sein, sonst fehlen Backend-Feld, Listenanzeige und Suchfunktion fachlich bzw. führen zu Inkonsistenzen. + +- Nach Änderungen an DCA, Backend-CSS oder Backend-JS ist in dieser Installation typischerweise nötig: + - `php vendor/bin/contao-console assets:install public --symlink --relative` + - `php vendor/bin/contao-console cache:clear --env=prod` + +- Es gibt aktuell zwei bewusst eingebaute backend-spezifische UI-Workarounds über Bundle-Assets: + - In der Umfragenliste wird `ID` aus dem Suchfeld per JS entfernt, weil Contao das Feld im Core von `DC_Table` hart ergänzt. + - In der Fragenliste (`table=tl_survey_content`) wird die rechte Parent-Header-Leiste per CSS/JS im Normalzustand ausgeblendet und nur im aktiven Clipboard-/Verschiebe-Modus wieder eingeblendet. + +- Lokale DDEV-Besonderheit, die bei Fehleranalyse relevant sein kann: + - Das Bundle läuft hier als Path-Repository/Symlink. + - Wenn Backend-Übersetzungen oder Assets trotz korrektem Code unerwartet fehlen, zuerst Bundle-Pfad, veröffentlichte Assets und Prod-Cache prüfen. + ## Bekannte bewusste Trade-offs - Backend kann veröffentlichte Umfragen technisch weiterhin verändern. - Das ist bewusst akzeptiert, weil nur ein Admin arbeitet. - `isLocked` bleibt als internes Logikfeld bestehen, obwohl der Backend-Lock nicht mehr durchgesetzt wird. - PDF-Export hängt von Gotenberg ab und ist absichtlich optional. +- Eine reine Umordnung von Fragen ist für Sprungziele logisch unkritisch, weil Sprünge an Frage-IDs hängen. +- Ergebnisse vorhandener, weiterhin existierender Fragen folgen bei der Anzeige jedoch der aktuellen Sortierung der Frage. + - Historische Antwort-Snapshots speichern zwar `questionSorting`, die Ergebnisansicht sortiert vorhandene Fragen aber primär nach der aktuellen Frage-Sortierung. + - Das ist im toleranten Backend-Modell ein bewusst akzeptierter Side-Effect. ## Empfohlene Einstiegspunkte für künftige Änderungen diff --git a/contao/config/config.php b/contao/config/config.php index f80b051..8d24900 100644 --- a/contao/config/config.php +++ b/contao/config/config.php @@ -2,8 +2,9 @@ declare(strict_types=1); -$GLOBALS['TL_CSS'][] = 'assets/survey-backend.css|static'; +$GLOBALS['TL_CSS'][] = 'bundles/survey/css/survey-backend.css|static'; $GLOBALS['TL_CSS'][] = 'bundles/survey/css/auth-module.css|static'; +$GLOBALS['TL_JAVASCRIPT'][] = 'bundles/survey/js/survey-backend.js|static'; $GLOBALS['TL_MODELS']['tl_survey'] = Mummert\SurveyBundle\Model\SurveyModel::class; $GLOBALS['TL_MODELS']['tl_survey_category'] = Mummert\SurveyBundle\Model\SurveyCategoryModel::class; diff --git a/contao/dca/tl_survey.php b/contao/dca/tl_survey.php index 6b563a3..2df7a05 100644 --- a/contao/dca/tl_survey.php +++ b/contao/dca/tl_survey.php @@ -36,7 +36,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'panelLayout' => 'filter;sort,search,limit', ], 'label' => [ - 'fields' => ['title', 'categorySummary', 'assignedMembersSummary', 'templateSummary'], + 'fields' => ['title', 'assignedMembersSummary', 'templateSummary', 'internalNote'], 'showColumns' => true, ], 'global_operations' => [ @@ -47,22 +47,26 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], ], 'operations' => [ + 'editheader' => [ + 'label' => ['Umfrage bearbeiten', 'Umfrage bearbeiten'], + 'href' => 'act=edit', + 'icon' => 'edit.svg', + 'primary' => true, + 'showInHeader' => false, + ], 'edit' => [ 'label' => ['Fragen bearbeiten', 'Fragen der Umfrage bearbeiten'], 'href' => 'table=tl_survey_content', 'icon' => 'children.svg', - ], - 'editheader' => [ - 'label' => ['Umfrage bearbeiten', 'Umfrage bearbeiten'], - 'href' => 'act=edit', - 'icon' => 'header.svg', + 'primary' => true, + 'showInHeader' => false, ], 'toggle' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['toggleActive'], 'href' => 'act=toggle&field=isActive', 'icon' => 'visible.svg', 'primary' => true, - 'showInHeader' => true, + 'showInHeader' => false, ], 'publishedWarning' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['publishedWarning'], @@ -100,7 +104,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], ], 'palettes' => [ - 'default' => '{title_legend},title,category,description;{assignment_legend},assignedMembers;{publishing_legend},published,isActive,isTemplate;{meta_legend},createdAt,updatedAt', + 'default' => '{title_legend},title,category,description,internalNote;{assignment_legend},assignedMembers;{publishing_legend},published,isActive,isTemplate;{meta_legend},createdAt,updatedAt', ], 'fields' => [ 'id' => [ @@ -129,9 +133,15 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'eval' => ['rte' => 'tinyMCE', 'tl_class' => 'clr'], 'sql' => 'text NULL', ], + 'internalNote' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['internalNote'], + 'inputType' => 'text', + 'search' => true, + 'eval' => ['maxlength' => 255, 'tl_class' => 'clr w50'], + 'sql' => "varchar(255) NOT NULL default ''", + ], 'category' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['category'], - 'filter' => true, 'inputType' => 'select', 'foreignKey' => 'tl_survey_category.title', 'eval' => ['multiple' => true, 'chosen' => true, 'tl_class' => 'clr'], @@ -188,9 +198,9 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], 'assignedMembers' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['assignedMembers'], - 'inputType' => 'select', + 'inputType' => 'checkboxWizard', 'options_callback' => [Mummert\SurveyBundle\EventListener\SurveyDcaListener::class, 'getMemberOptions'], - 'eval' => ['multiple' => true, 'chosen' => true, 'tl_class' => 'clr'], + 'eval' => ['multiple' => true, 'class' => 'survey-assigned-members-checkbox-wizard', 'tl_class' => 'clr'], 'load_callback' => [[Mummert\SurveyBundle\EventListener\SurveyDcaListener::class, 'loadSurveyAssignedMembers']], 'save_callback' => [[Mummert\SurveyBundle\EventListener\SurveyDcaListener::class, 'saveSurveyAssignedMembers']], 'sql' => 'blob NULL', diff --git a/contao/dca/tl_survey_content.php b/contao/dca/tl_survey_content.php index 9cd7133..09bab69 100644 --- a/contao/dca/tl_survey_content.php +++ b/contao/dca/tl_survey_content.php @@ -32,7 +32,7 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [ 'sorting' => [ 'mode' => 4, 'fields' => ['sorting'], - 'headerFields' => ['title', 'alias', 'published'], + 'headerFields' => ['title', 'published'], 'child_record_class' => 'survey-question-record', 'panelLayout' => 'sort,search,limit', ], @@ -44,6 +44,13 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [ 'edit' => [ 'href' => 'act=edit', 'icon' => 'edit.svg', + 'primary' => true, + ], + 'cut' => [ + 'href' => 'act=paste&mode=cut', + 'method' => 'POST', + 'icon' => 'mover.svg', + 'attributes' => 'data-action="contao--scroll-offset#store"', ], 'copy' => [ 'href' => 'act=copy', diff --git a/contao/languages/de/tl_survey.php b/contao/languages/de/tl_survey.php index e1c7a66..414f4e7 100644 --- a/contao/languages/de/tl_survey.php +++ b/contao/languages/de/tl_survey.php @@ -11,6 +11,7 @@ $GLOBALS['TL_LANG']['tl_survey']['templateSummary'] = ['Vorlage', 'Zeigt an, ob $GLOBALS['TL_LANG']['tl_survey']['template_yes'] = 'Ja'; $GLOBALS['TL_LANG']['tl_survey']['template_no'] = 'Nein'; $GLOBALS['TL_LANG']['tl_survey']['description'] = ['Beschreibung', 'Einleitung für den Editor und das Frontend.']; +$GLOBALS['TL_LANG']['tl_survey']['internalNote'] = ['Interne Notiz', 'Nur im Backend sichtbare interne Notiz zur Umfrage.']; $GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Grunddaten'; $GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Bearbeitende Mitglieder'; $GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Sichtbarkeit'; diff --git a/contao/languages/en/tl_survey.php b/contao/languages/en/tl_survey.php index 6e8a029..024b1e7 100644 --- a/contao/languages/en/tl_survey.php +++ b/contao/languages/en/tl_survey.php @@ -11,6 +11,7 @@ $GLOBALS['TL_LANG']['tl_survey']['templateSummary'] = ['Template', 'Shows whethe $GLOBALS['TL_LANG']['tl_survey']['template_yes'] = 'Yes'; $GLOBALS['TL_LANG']['tl_survey']['template_no'] = 'No'; $GLOBALS['TL_LANG']['tl_survey']['description'] = ['Description', 'Introduction for the editor and the frontend.']; +$GLOBALS['TL_LANG']['tl_survey']['internalNote'] = ['Internal note', 'Internal note for the survey, visible in the backend only.']; $GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Basic data'; $GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Editing members'; $GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Visibility'; diff --git a/contao/templates/frontend/_survey_assets.html.twig b/contao/templates/frontend/_survey_assets.html.twig index 25d9910..0ca8f1c 100644 --- a/contao/templates/frontend/_survey_assets.html.twig +++ b/contao/templates/frontend/_survey_assets.html.twig @@ -708,6 +708,11 @@ margin: 0; } + .survey-accordion-header-row { + display: flex; + align-items: stretch; + } + .survey-accordion-toggle { display: flex; width: 100%; @@ -720,6 +725,82 @@ text-align: left; } + .survey-accordion-sort-handle { + position: relative; + display: inline-flex; + flex: 0 0 auto; + min-width: 3.5rem; + padding: 0 1rem; + border: 0; + border-left: 1px solid rgba(162, 168, 180, 0.18); + background: transparent; + cursor: grab; + align-items: center; + justify-content: center; + align-self: stretch; + transition: background 0.2s ease, color 0.2s ease; + } + + .survey-accordion-sort-handle:active { + cursor: grabbing; + } + + .survey-accordion-item .handorgel__header__button:hover + .survey-accordion-sort-handle, + .survey-accordion-item .handorgel__header__button:focus + .survey-accordion-sort-handle, + .survey-accordion-item .handorgel__header--opened .survey-accordion-sort-handle, + .survey-accordion-sort-handle:hover, + .survey-accordion-sort-handle:focus { + background: var(--survey-blue); + color: #fff; + } + + .survey-sort-icon { + display: inline-flex; + width: 0.95rem; + height: 1.28rem; + align-items: center; + justify-content: center; + color: #58a6da; + line-height: 0; + transform: translateY(1px); + } + + .survey-sort-icon svg { + display: block; + width: 100%; + height: 100%; + transform: translateY(4px); + } + + .survey-question-order-save-shell { + position: fixed; + top: 50%; + right: 1rem; + z-index: 40; + transform: translate(120%, -50%); + opacity: 0; + pointer-events: none; + transition: transform 0.26s ease, opacity 0.26s ease; + } + + .survey-question-order-save-shell.is-visible { + transform: translate(0, -50%); + opacity: 1; + pointer-events: auto; + } + + .survey-question-order-save { + box-shadow: 0 18px 40px rgba(0, 59, 102, 0.24); + } + + .survey-accordion-item.is-reordering { + box-shadow: 0 20px 44px rgba(0, 59, 102, 0.18); + } + + .survey-accordion-item.sortable-ghost { + opacity: 0.55; + } + .survey-accordion-item .handorgel__header__button, .survey-accordion-item .handorgel__header__button:hover, .survey-accordion-item .handorgel__header__button:focus { @@ -994,6 +1075,22 @@ padding: 0.95rem 1rem; } + .survey-accordion-sort-handle { + min-width: 3.1rem; + padding: 0 0.8rem; + } + + .survey-question-order-save-shell { + top: auto; + right: 0.85rem; + bottom: 0.85rem; + transform: translate(0, 130%); + } + + .survey-question-order-save-shell.is-visible { + transform: translate(0, 0); + } + } \ No newline at end of file diff --git a/contao/templates/frontend/_survey_question_sort_assets.html.twig b/contao/templates/frontend/_survey_question_sort_assets.html.twig new file mode 100644 index 0000000..af76e53 --- /dev/null +++ b/contao/templates/frontend/_survey_question_sort_assets.html.twig @@ -0,0 +1,119 @@ + + + \ No newline at end of file diff --git a/contao/templates/frontend/member_survey_edit.html.twig b/contao/templates/frontend/member_survey_edit.html.twig index 84c54e2..09e10b3 100644 --- a/contao/templates/frontend/member_survey_edit.html.twig +++ b/contao/templates/frontend/member_survey_edit.html.twig @@ -1,6 +1,7 @@ {% trans_default_domain 'messages' %} {% include '@Survey/frontend/_survey_assets.html.twig' %} +{% include '@Survey/frontend/_survey_question_sort_assets.html.twig' %} {% include '@Survey/frontend/_survey_branding.html.twig' %} @@ -47,6 +48,7 @@ {% else %} {% set surveyMetaFormId = 'survey-meta-form' %} {% set newQuestionFormId = 'survey-question-form-new' %} + {% set canQuestionReorder = canQuestionReorder|default(false) %}