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

@ -32,6 +32,9 @@ state([
'gallery' => [],
'website_url' => '',
'showroom' => '',
'originCity' => '',
'originDistance' => '',
'originRole' => '',
'is_published' => true,
'order' => 0,
]);
@ -70,7 +73,7 @@ $brands = computed(
$openCreate = function () {
$this->editingId = null;
$this->reset(['slug', 'brandName', 'tagline', 'teaser', 'summary', 'description', 'logo', 'image', 'website_url', 'showroom', 'widgetLabel', 'ctaTitle', 'ctaText']);
$this->reset(['slug', 'brandName', 'tagline', 'teaser', 'summary', 'description', 'logo', 'image', 'website_url', 'showroom', 'originCity', 'originDistance', 'originRole', 'widgetLabel', 'ctaTitle', 'ctaText']);
$this->sections = [];
$this->highlights = ['', '', ''];
$this->services = [['title' => '', 'description' => '']];
@ -89,6 +92,9 @@ $loadLocaleFields = function (CmsBrand $brand, string $locale) {
$this->widgetLabel = $brand->getTranslation('widget_label', $locale) ?? '';
$this->ctaTitle = $brand->getTranslation('cta_title', $locale) ?? '';
$this->ctaText = $brand->getTranslation('cta_text', $locale) ?? '';
$this->originCity = $brand->getTranslation('origin_city', $locale) ?? '';
$this->originDistance = $brand->getTranslation('origin_distance', $locale) ?? '';
$this->originRole = $brand->getTranslation('origin_role', $locale) ?? '';
};
$openEdit = function (int $id) {
@ -194,6 +200,9 @@ $save = function () {
'summary' => $this->summary,
'website_url' => $this->website_url,
'showroom' => $this->showroom,
'originCity' => $this->originCity,
'originDistance' => $this->originDistance,
'originRole' => $this->originRole,
'is_published' => $this->is_published,
'order' => $this->order,
], [
@ -204,6 +213,9 @@ $save = function () {
'summary' => 'nullable|string|max:4000',
'website_url' => 'nullable|url|max:500',
'showroom' => 'nullable|string|max:255',
'originCity' => 'nullable|string|max:255',
'originDistance' => 'nullable|string|max:255',
'originRole' => 'nullable|string|max:500',
'is_published' => 'boolean',
'order' => 'integer|min:0',
], [
@ -255,6 +267,11 @@ $save = function () {
$brand->setTranslation('cta_title', $this->editLocale, trim((string) $this->ctaTitle));
$brand->setTranslation('cta_text', $this->editLocale, trim((string) $this->ctaText));
// Herkunfts-Sektion: ohne Fertigungsort erscheint die Marke dort nicht.
$brand->setTranslation('origin_city', $this->editLocale, trim((string) ($validated['originCity'] ?? '')));
$brand->setTranslation('origin_distance', $this->editLocale, trim((string) ($validated['originDistance'] ?? '')));
$brand->setTranslation('origin_role', $this->editLocale, trim((string) ($validated['originRole'] ?? '')));
// normalizeSections() nimmt die Textblöcke entgegen und trennt sie an den Leerzeilen.
$brand->sections = CmsBrand::normalizeSections(collect($this->sections)->map(fn (array $s) => [
'title' => $s['title'] ?? '',
@ -365,6 +382,24 @@ $cancelForm = function () {
placeholder="Ausführlicher Text für die Markenübersicht. Leerzeile trennt Absätze." />
</div>
{{-- Fertigungsort für „Aus der Region gefertigt“ (Startseite und /marken) --}}
<div class="mt-6">
<flux:heading size="sm" class="mb-1">Fertigungsort</flux:heading>
<flux:text class="mb-3 text-sm">
Erscheint in der Sektion „Aus der Region gefertigt“ auf der Startseite und unter /marken.
Ohne Ort bleibt die Marke dort weg. Die Position in der Karte ist fest hinterlegt.
</flux:text>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<flux:input wire:model="originCity" label="Ort ({{ strtoupper($editLocale) }})" placeholder="Bünde" />
<flux:input wire:model="originDistance" label="Entfernung von Bielefeld ({{ strtoupper($editLocale) }})"
placeholder="rund 30 km" />
<div class="md:col-span-2">
<flux:input wire:model="originRole" label="Rolle des Standorts ({{ strtoupper($editLocale) }})"
placeholder="Küchen aus eigener Fertigung in Bünde – geplant bei uns im Küchenstudio." />
</div>
</div>
</div>
{{-- Drei Merkmale für die Übersichtsseite --}}
<div class="mt-6">
<div class="mb-2 flex items-center justify-between">