CMS: Standorte an der Marke, Seitenbaum mit Vorschau, Referenzen-Modul

Nachtrag 11: Herkunfts-Sektion liest Ort, Entfernung und Rolle aus der
Markenverwaltung (drei neue Felder an cms_brands, BrandOriginSeeder),
Kartenpositionen im Code, „Marken“ in der Sidebar.

Nachtrag 12: Inhalte-Editor zeigt die Seiten der Website mit ihren
Sektionen in Reihenfolge (config/cms_pages.php, CmsPageTree), Felder in
Klartext, fehlende Sektionen aus der Sprachdatei anlegbar, Vorschau der
echten Seite im iframe mit Sprung zur Sektion; Anker an allen lebenden
Sektionen nachgezogen.

Nachtrag 13: Referenzen als eigenes Modul (CmsReference, Portal-Seite,
/referenzen mit Fallback auf die Markengalerien); Immobilien-Projekte
aus Sidebar und CMS-Übersicht genommen, Route bleibt.

Dazu .gitignore für _b2in_interior_shop/ und docs/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-15 14:58:45 +00:00
parent 7953a8c9a1
commit 90aa079cf7
50 changed files with 2580 additions and 324 deletions

View file

@ -40,7 +40,7 @@ ausführlichen Text. `php artisan test --compact`: **873 grün** (3217 Assertion
| Welten-Trennung: Navigation, Magazin, Über uns, FAQ, Widget je Welt | ✅ (Nachtrag 7, Erweiterung zu 1.1) |
| Arbeitsplan 14.09. · 1.2 Tischlerei als vierte Marke (Platzhalter) | ✅ (Nachtrag 9) |
| Arbeitsplan 14.09. · 1.4 Akkordeon und Widget-Beschriftung je Marke | ✅ (bereits seit 03.09., CABINET-Text fehlt im CMS) |
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10, Ablauf CABINET von Marcel bestätigen lassen) |
| Arbeitsplan 14.09. · 1.3 Herkunfts-Sektion „Aus der Region gefertigt“ | ✅ (Nachtrag 10 + 11: Standorte an der Marke, Ablauf CABINET von Marcel bestätigen lassen) |
### Nächste Schritte
@ -849,7 +849,7 @@ als Datenquelle". → **Einbettung des vorhandenen Players im 9:16-Container.**
**Tests:** `DisplayWebFeedTest` (10 Fälle: Embed liefert die Datei, kein Token im Ausgabe-Inhalt,
404 bei inaktiv/Entwurf/leer/unbekannt, Einbindung auf der Startseite, CMS-Auswahl und Fallback).
### AP5 · Rubrik-Seite „Referenzen" — ✅ umgesetzt
### AP5 · Rubrik-Seite „Referenzen" — ✅ umgesetzt (Pflege seit Nachtrag 13 als eigenes Modul)
**Korrektur gegenüber Revision 1:** Revision 1 schlug einen neuen `DisplayVersionType`-Case vor.
Die Umsetzungsliste sagt „zweites, separates **Display** im Display-CMS" – das ist ein weiterer
@ -1270,9 +1270,14 @@ php artisan db:seed --class=WorldSplitCmsSeeder --force
# 8. Vierte Kernmarke „Tischlerei“ als Platzhalter (Nachtrag 9) – legt nur an, was fehlt.
php artisan db:seed --class=CmsBrandSeeder --force
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie fehlt.
# 9. Herkunfts-Sektion „Aus der Region gefertigt“ (Nachtrag 10) – legt die Sektion nur an, wenn sie
# fehlt. Entfernt aus einem bestehenden Eintrag nur den veralteten Schlüssel „locations“ (Nachtrag 11).
php artisan db:seed --class=OriginSectionCmsSeeder --force
# 10. Fertigungsort je Kernmarke (Nachtrag 11) – setzt Ort, Entfernung und Rolle nur bei Marken,
# deren Ort noch leer ist. Die Migration davor legt die drei Spalten an.
php artisan db:seed --class=BrandOriginSeeder --force
php artisan config:cache
php artisan view:clear
@ -1598,13 +1603,12 @@ Tischlerei?). Die Sektion ist so formuliert, dass sie diesem Verständnis folgt.
Markenübersicht und „Mehr als Marken: unser Netzwerk“. Anker `#herkunft`.
- **Schematische Karte** (SVG, ab `md`): Bielefeld als Drehscheibe in Ocker, die vier Standorte
als Punkte in Tanne mit gestrichelten Speichen und Entfernungsangabe, gestrichelte Ellipse
„Ostwestfalen“ um die drei OWL-Standorte. Keine Geografie, nur Richtung und Entfernung –
Positionen (`x`/`y` in der viewBox 640×400) und Beschriftungsseite (`label`) stehen im
Inhalt und sind im Admin änderbar. Unter `md` entfällt die Karte, die Kacheln tragen alles.
„Ostwestfalen“ um die drei OWL-Standorte. Keine Geografie, nur Richtung und Entfernung.
Positionen und Beschriftungsseite lagen zunächst im Inhalt; seit Nachtrag 11 stehen sie im
Code, die Standorte kommen aus den Marken. Unter `md` entfällt die Karte, die Kacheln tragen alles.
- **Vier Standort-Kacheln** (2×2 ab `sm`): Stadt, Marke, Rolle, Entfernung „von Bielefeld“ und
Link „Zur Marke“. Der Link kommt **nicht** aus dem CMS, sondern aus den veröffentlichten
Marken (`brand_slug`, ersatzweise Namensabgleich): eine unveröffentlichte Marke verliert den
Link, ihr Standort bleibt sichtbar.
Link „Zur Marke“. Ursprünglich lagen die Standorte im Sektions-JSON, der Link kam aus den
veröffentlichten Marken. Seit Nachtrag 11 gilt: Standort = Marke, siehe dort.
- **Ablauf-Block „Gefertigt in Köln, geplant in Bielefeld“**: drei nummerierte Schritte am
Beispiel CABINET (Beratung und Planung in Bielefeld → Fertigung in Köln → Aufbau vor Ort mit
Sonderwünschen aus der Tischlerei). Das ist die saubere Trennung „gefertigt“ / „vertrieben“
@ -1620,4 +1624,169 @@ fehlt, überschreibt nichts). Beim Deployment mit erledigen (Abschnitt 8, Schrit
**Offen:** Bestätigung des CABINET-Ablaufs durch Marcel; Wortlaut der Tischlerei-Kachel folgt
dem Platzhalter aus Nachtrag 9 und wandert mit dem echten Markennamen.
**Tests:** `tests/Feature/OriginSectionTest.php` (12 Fälle).
**Tests:** `tests/Feature/OriginSectionTest.php` (12 Fälle, mit Nachtrag 11 umgeschrieben).
---
## Nachtrag 11 · Standorte an der Marke, Marken in der Sidebar (15.09.2026)
Prüfung der Inhaltspflege im Admin auf Kevins Frage, ob Marken wie das Magazin einen eigenen
Punkt brauchen. Ergebnis: **Die Markenverwaltung gab es schon** („Kernmarken verwalten“, AP2),
sie war nur nicht in der Sidebar verlinkt – erreichbar allein über die Kachel auf der
CMS-Übersicht. Die Herkunfts-Sektion aus Nachtrag 10 lag dagegen komplett im generischen
Inhalte-Editor, inklusive der Kartenkoordinaten `x`/`y` und der Beschriftungsseite als nackte
Felder. Das war für Marcel nicht pflegbar. Abgestimmt mit Kevin: **Weg 1**, Standort an die Marke.
### Was sich ändert
- **Sidebar:** neuer Eintrag „Marken“ unter CMS → Website-Inhalte, zwischen Projekte und
Magazin. Die Gruppe klappt auf der Markenseite jetzt auch auf.
- **Drei neue Felder je Marke** (Migration `add_origin_to_cms_brands_table`, übersetzbar wie die
übrigen Textfelder): `origin_city` (Fertigungsort), `origin_distance` (Entfernung als Text,
„rund 30 km“), `origin_role` (ein Satz zur Rolle). Im Admin als Block „Fertigungsort“ unter
den Stammdaten, mit Hinweis: ohne Ort erscheint die Marke nicht in der Sektion.
- **`OriginSection` liest die Standorte aus `CmsBrand::forFrontend()`**: nur veröffentlichte
Marken mit Ort, in der Reihenfolge der Marken. Eine unveröffentlichte Marke fällt damit ganz
heraus (vorher blieb ihr Standort ohne Link stehen – jetzt gibt es keinen Standort ohne
Marke mehr). Der Link „Zur Marke“ hängt an `has_detail_page`, der Text `link_text` liegt
einmal auf Sektionsebene statt je Standort.
- **Kartenpositionen im Code:** `OriginSection::MAP_POSITIONS`, je Slug `x`/`y`/`label`. Eine
neue Marke mit Ort, aber ohne Position, erscheint in den Kacheln, nicht in der Karte – dafür
muss ein Entwickler eine Position ergänzen. Das ist gewollt: die Karte ist Zeichnung, kein
Inhalt.
- **Sektions-JSON `home/origin_section`** enthält nur noch Überschrift, Untertitel, Alt-Text,
Regionslabel, Entfernungs-Suffix, Drehscheibe, Link-Text und Ablauf-Block. Der alte Schlüssel
`locations` wird von `OriginSectionCmsSeeder` aus einem bestehenden Eintrag entfernt, damit er
im Inhalte-Editor nicht mehr als Pflegefeld auftaucht (idempotent, sonst weiterhin nur
anlegen-wenn-fehlt).
- **Lang-Fallback:** die vier Einträge in `core_brands.brands` tragen jetzt `slug` und die drei
Herkunftsfelder, damit die Sektion auch ohne gepflegte Marke steht – dann ohne Links, wie die
Kacheln der Kernmarken.
- **Seeder:** `CmsBrandSeeder` bringt die Herkunft für frische Installationen mit; neu
`BrandOriginSeeder` trägt sie bei bestehenden Marken nach, nur wenn der Ort leer ist (Muster
wie `BrandLogoSeeder`). Keine Dubletten möglich, beide arbeiten über den Slug.
- Ohne Marke mit Ort bleiben Überschrift und Ablauf-Block stehen, Karte und Kacheln entfallen.
**Lokal ausgeführt:** `php artisan migrate`, `db:seed --class=BrandOriginSeeder` (4 gesetzt),
`db:seed --class=OriginSectionCmsSeeder` (`locations` entfernt). Beim Deployment: Abschnitt 8,
Schritte 9 und 10, die Migration läuft mit `migrate` vorweg.
**Offen:** wie Nachtrag 10 (Bestätigung des CABINET-Ablaufs durch Marcel). Der generische
Inhalte-Editor bleibt für die übrigen Sektionen ohne Schema; wo Listen mit technischen Feldern
auftauchen, ist derselbe Weg möglich: Daten an ein Modell hängen, Darstellung in den Code.
**Tests:** `tests/Feature/OriginSectionTest.php` (15 Fälle), drei neue Fälle in
`tests/Feature/Admin/Cms/CmsBrandsIndexTest.php` (Herkunft je Sprache speichern, Ort leeren,
Sidebar-Link).
---
## Nachtrag 12 · Inhalte-Editor: Seitenbaum und Vorschau (15.09.2026)
Kevins Rückmeldung nach der Prüfung des CMS: der Punkt „Inhalte“ zeigt Datenbank-Gruppen
alphabetisch, ohne Bezug zur Seite, mit JSON-Schlüsseln als Beschriftung – zu abstrakt für
Marcel. Abgestimmt: Stufe 1 (Seitenbaum, Klartext) und Stufe 2 (Vorschau im Rahmen) umsetzen,
Stufe 3 (Stift-Markierungen in der Vorschau, die den Editor öffnen) erst nach Marcels Feedback.
### Stufe 1 · Seitenbaum statt Gruppen
- **Registry `config/cms_pages.php`:** je Seite (Startseite, Marken, Referenzen, Magazin, Über
uns, Kontakt, FAQ, „Auf jeder Seite“, Immobilien-Welt, Rechtliches) die Sektionen in der
Reihenfolge, wie sie auf der Website stehen, mit Titel, einem Satz Beschreibung, Pfad und
Sprungmarke. Dazu ein Wörterbuch `field_labels` (`button_text` → „Button-Beschriftung“,
`x`/`y` → „Position (Entwickler)“ …), je Sektion überschreibbar über `fields`.
- **`App\Services\CmsPageTree`** verbindet die Registry mit der Datenbank: welche Sektion
existiert (`content_id`), welche fehlt, was in der Datenbank liegt, aber auf keiner Seite
eingetragen ist. Letzteres landet als Sammelpunkt **„Nicht mehr verwendet“** ganz unten
(Netzwerk-, Partner- und alte Immobilien-Sektionen, `home/synergie_section` & Co.) – nichts
geht verloren, nichts steht im Weg.
- **Editor (`admin.cms.content-index`):** linke Spalte zeigt den Seitenbaum, nummeriert von
oben nach unten. Ein Klick wählt genau eine Sektion (`selectSection`), der Kopf zeigt Titel,
Beschreibung, Seite und den technischen Schlüssel klein darunter. Felder tragen die
Klartext-Beschriftung, der Schlüssel steht als `code` daneben; Typ-Badges heißen „Text“,
„Formatierter Text“, „Bild“, „Liste“. Der Listen-Editor (Overlay) beschriftet seine Felder
genauso. `selectGroup` bleibt für die Gruppenansicht erhalten.
- **Fehlende Sektion anlegen:** steht eine Sektion in der Registry, aber nicht in der
Datenbank (heute z. B. das Terminanfrage-Widget), erscheint sie grau mit „+“. Ein Klick
legt sie über `CmsContentSeeder` mit den Texten aus `b2in.php` an. Rechtliches und die
Kontaktdaten (`global/contact`) haben keine Vorlage, dort erscheint ein Hinweis.
### Stufe 2 · Vorschau
- Rechts neben dem Editor ein **iframe mit der echten Website** (Domain aus
`config('domains')`, Pfad der Seite, `?cms-preview=<n>` als Cache-Brecher, `#anker`). Beim
Wechsel der Sektion springt die Vorschau zur Sprungmarke, nach jedem Speichern lädt sie
neu (`previewVersion`). Buttons: neu laden, in neuem Tab öffnen, Vorschau aus/ein.
Keine Anmeldung nötig, die Website ist öffentlich; Frame-Header setzt die App keine.
- **Sprungmarken nachgezogen:** `id` an den Wurzelelementen der lebenden Sektionen (`hero`,
`gruender`, `wege`, `marken`, `herkunft`, `vision`, `engagement`, `displays`, `immobilien`,
`cta`, `seitenkopf`, `netzwerk`, `wachstum`, `marken-cta`, `projekte`, `magazin`,
`geschichte`, `bild`, `werte`, `kontakt`, `faq`, `header`, `footer`). Beim Display-Feed
sitzt der Anker am Wurzel-`div`, weil die Sektion ohne veröffentlichtes Display leer bleibt.
- Sektionen ohne Seite (Beitragsseite, Widget, Rechtliches) zeigen die Seite ohne Sprung.
### Erkenntnisse beim Kartieren
- `/immobilien` rendert `web/dev/immobilien-azizi.blade.php` mit statischen Texten aus
`immobilien-azizi.json`; die CMS-Sektionen `immobilien/hero`, `warum_dubai`, `kaufprozess`
usw. gehören zur stillgelegten `web/immobilien.blade.php` und stehen deshalb unter „Nicht
mehr verwendet“. Gleiches gilt für `about/leadership_team`.
- `display_feed` liegt in der Gruppe `referenzen`, steht aber zuerst auf der Startseite –
im Baum unter Startseite mit Hinweis auf /referenzen.
**Tests:** `tests/Feature/Cms/CmsPageTreeTest.php` (9 Fälle, darunter: jede Sprungmarke der
Registry existiert auf der jeweiligen Seite, jede Sektion mit Vorlage ist in `b2in.php`
auffindbar) und sechs neue Fälle in `tests/Feature/Cms/CmsAdminTest.php` (Seitenbaum,
Sektion wählen mit Klartext-Labels und Vorschau-Adresse, Sammelpunkt, Anlegen aus Vorlage,
Anlegen ohne Vorlage, Vorschau ausblenden). Kein Deployment-Schritt nötig: Registry und
Editor sind Code, die Datenbank bleibt unverändert.
**Offen:** Stufe 3 nach Marcels Rückmeldung. Neue Sektionen müssen in `cms_pages.php`
eingetragen werden, sonst landen sie unter „Nicht mehr verwendet“ – als Erinnerung steht das
im Kopf der Datei.
---
## Nachtrag 13 · Referenzen als eigenes Modul, Immobilien-Projekte ausgeblendet (15.09.2026)
Kevin nach der Abnahme von Nachtrag 12: „Projekte“ im Portal (die Immobilienprojekte aus
Dubai) ist nicht freigegeben und kommt raus; an die Stelle gehören die Projekte/Referenzen der
Interior-Welt. Das ist eine **Korrektur zu AP5**, das die Referenzen bewusst aus den
Markengalerien zusammengesammelt hatte, um keine zweite Pflegestelle zu schaffen. Ein
realisiertes Projekt hat aber mehr als ein Bild und oft mehr als eine Marke (CABINET plus
Tischlerei), darum jetzt ein eigener Eintrag.
### Was sich ändert
- **Neues Modell `CmsReference`** (Tabelle `cms_references`): Titel und Beschreibung
übersetzbar, Ort und Jahr als Text, Titelbild, weitere Bilder mit Bildunterschrift,
beteiligte Marken als Liste von Slugs aus `cms_brands`, Veröffentlicht, Sortierung.
- **Portal → CMS → „Referenzen“** (`admin.cms.references-index`, Route
`cms.references.index`): Liste mit Vorschaubild, Marken-Badges und Bildanzahl; Formular
nach dem Muster des Magazins mit Sprachumschalter, Marken als Checkboxen, Titelbild und
Galerie über den Media-Picker, Galeriebilder sortierbar. Der Kopf der Seite sagt die Regel:
nur ausgeführte Aufträge, Entwürfe gehören in die Galerie der Marke.
- **Sidebar und CMS-Übersicht:** „Referenzen“ ersetzt „Projekte“. Die Route
`cms.projects.index` bleibt bestehen und ist per Adresse erreichbar, nur nicht mehr
verlinkt – die Dubai-Daten bleiben unangetastet, falls die Freigabe später kommt.
- **`/referenzen`** zeigt die veröffentlichten Referenzen: Titelbild, Titel, „Ort · Jahr“,
Beschreibung, weitere Bilder als kleine Reihe, darunter die beteiligten Marken verlinkt auf
ihre Markenseiten (unveröffentlichte Marken fallen heraus). Ohne Titelbild dient das erste
Galeriebild als Titelbild, ohne jedes Bild wird die Referenz übersprungen.
- **Fallback bleibt:** Solange keine Referenz veröffentlicht ist, greift der Stand aus AP5
(Bilder der Rubrik „Realisiertes Projekt“ aus den Markengalerien). Der Leerzustand von AP5
gilt weiter, wenn beides leer ist. So sieht die Preview beim Deployment keinen Unterschied,
bis Marcel die erste Referenz anlegt.
- Registry `cms_pages.php`: Beschreibung von „Realisierte Projekte“ verweist auf das neue Modul.
**Deployment:** nur `php artisan migrate --force` (Abschnitt 8.2, Schritt 1 deckt die neue
Tabelle ab). Kein Seeder, keine Daten.
**Offen:** Marcel legt die ersten Referenzen selbst an; die bisherigen Galeriebilder der Marken
bleiben dort, wo sie sind. Ob die Bilder aus den Markengalerien später automatisch als Referenz
übernommen werden sollen, klärt sich mit den ersten echten Einträgen.
**Tests:** `tests/Feature/Admin/Cms/CmsReferencesIndexTest.php` (9 Fälle), vier neue Fälle in
`tests/Feature/ReferenzenPageTest.php` (Referenzen vor Galerien, Entwurf ausgeblendet und
Fallback, unveröffentlichte Marke und Referenz ohne Bild, Galeriebild als Titelbild),
Dashboard-Statistik in `CmsAdminTest` auf Referenzen umgestellt.