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

@ -45,6 +45,9 @@ class CmsBrand extends Model
'gallery',
'website_url',
'showroom',
'origin_city',
'origin_distance',
'origin_role',
'is_published',
'order',
];
@ -59,6 +62,9 @@ class CmsBrand extends Model
'widget_label',
'cta_title',
'cta_text',
'origin_city',
'origin_distance',
'origin_role',
];
protected function casts(): array
@ -139,6 +145,9 @@ class CmsBrand extends Model
'gallery' => [],
'website_url' => '',
'showroom' => '',
'origin_city' => '',
'origin_distance' => '',
'origin_role' => '',
'has_detail_page' => false,
], $brand, ['slug' => $slug, 'has_detail_page' => false]);
@ -311,6 +320,9 @@ class CmsBrand extends Model
'gallery' => self::normalizeGallery($this->gallery),
'website_url' => $this->website_url,
'showroom' => $this->showroom,
'origin_city' => (string) $this->origin_city,
'origin_distance' => (string) $this->origin_distance,
'origin_role' => (string) $this->origin_role,
'has_detail_page' => true,
];
}

115
app/Models/CmsReference.php Normal file
View file

@ -0,0 +1,115 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;
/**
* Referenzprojekt der Interior-Welt: ein realisierter Auftrag mit Titelbild, weiteren Bildern
* und den beteiligten Kernmarken (Nachtrag 13). Erscheint auf /referenzen.
*
* Nicht zu verwechseln mit {@see CmsProject} – das sind die Immobilienprojekte.
*/
class CmsReference extends Model
{
use HasFactory, HasTranslations;
protected $table = 'cms_references';
protected $fillable = [
'title',
'location',
'year',
'description',
'image',
'gallery',
'brand_slugs',
'is_published',
'order',
];
/** @var array<string> */
public array $translatable = ['title', 'description'];
protected function casts(): array
{
return [
'gallery' => 'array',
'brand_slugs' => 'array',
'is_published' => 'boolean',
'order' => 'integer',
];
}
public function scopePublished(Builder $query): Builder
{
return $query->where('is_published', true);
}
public function scopeOrdered(Builder $query): Builder
{
return $query->orderBy('order')->orderBy('id');
}
/**
* Galerie in eine feste Form bringen: nur Einträge mit Bild, Bildunterschrift getrimmt.
*
* @param mixed $gallery
* @return list<array{image: string, caption: string}>
*/
public static function normalizeGallery($gallery): array
{
if (! is_array($gallery)) {
return [];
}
$out = [];
foreach ($gallery as $item) {
$image = trim((string) (is_array($item) ? ($item['image'] ?? '') : $item));
if ($image === '') {
continue;
}
$out[] = [
'image' => $image,
'caption' => is_array($item) ? trim((string) ($item['caption'] ?? '')) : '',
];
}
return $out;
}
/**
* Beteiligte Marken als Liste mit Name und Link, aufgelöst gegen die veröffentlichten Marken.
*
* Eine unveröffentlichte oder gelöschte Marke fällt heraus.
*
* @param array<string, array<string, mixed>> $brands Ergebnis von CmsBrand::forFrontend()
* @return list<array{slug: string, name: string, link: ?string}>
*/
public function resolveBrands(array $brands): array
{
$out = [];
foreach ($this->brand_slugs ?? [] as $slug) {
$slug = (string) $slug;
if (! isset($brands[$slug])) {
continue;
}
$out[] = [
'slug' => $slug,
'name' => (string) ($brands[$slug]['name'] ?? $slug),
'link' => ! empty($brands[$slug]['has_detail_page']) ? '/marken/'.$slug : null,
];
}
return $out;
}
}