'', 'showForm' => false, 'editingId' => null, 'editLocale' => 'de', 'slug' => '', 'brandName' => '', 'tagline' => '', 'teaser' => '', 'summary' => '', 'highlights' => [], 'description' => '', 'logo' => '', 'image' => '', 'services' => [], 'sections' => [], 'widgetLabel' => '', 'ctaTitle' => '', 'ctaText' => '', 'gallery' => [], 'website_url' => '', 'showroom' => '', 'is_published' => true, 'order' => 0, ]); on(['media-selected' => function ($mediaId, $url, $field) { $media = $mediaId ? CmsMedia::find($mediaId) : null; if (! $media) { return; } if ($field === 'brand_logo') { $this->logo = $media->filename; } if ($field === 'brand_image') { $this->image = $media->filename; } if (str_starts_with($field, 'brand_gallery:')) { $index = (int) substr($field, strlen('brand_gallery:')); if (isset($this->gallery[$index])) { $this->gallery[$index]['image'] = $media->filename; } } }]); $brands = computed( fn () => CmsBrand::query() ->when($this->search, fn ($q, $s) => $q->where('slug', 'like', "%{$s}%") ->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(name, '$.de')) LIKE ?", ["%{$s}%"])) ->ordered() ->get(), ); $openCreate = function () { $this->editingId = null; $this->reset(['slug', 'brandName', 'tagline', 'teaser', 'summary', 'description', 'logo', 'image', 'website_url', 'showroom', 'widgetLabel', 'ctaTitle', 'ctaText']); $this->sections = []; $this->highlights = ['', '', '']; $this->services = [['title' => '', 'description' => '']]; $this->gallery = []; $this->is_published = true; $this->order = (CmsBrand::max('order') ?? 0) + 1; $this->showForm = true; }; $loadLocaleFields = function (CmsBrand $brand, string $locale) { $this->brandName = $brand->getTranslation('name', $locale) ?? ''; $this->tagline = $brand->getTranslation('tagline', $locale) ?? ''; $this->teaser = $brand->getTranslation('teaser', $locale) ?? ''; $this->summary = $brand->getTranslation('summary', $locale) ?? ''; $this->description = CmsFluxEditorHtmlTransformer::toEditor((string) ($brand->getTranslation('description', $locale) ?? '')); $this->widgetLabel = $brand->getTranslation('widget_label', $locale) ?? ''; $this->ctaTitle = $brand->getTranslation('cta_title', $locale) ?? ''; $this->ctaText = $brand->getTranslation('cta_text', $locale) ?? ''; }; $openEdit = function (int $id) { $brand = CmsBrand::find($id); if (! $brand) { return; } $this->editingId = $id; $this->slug = $brand->slug; $this->logo = $brand->logo ?? ''; $this->image = $brand->image ?? ''; $this->website_url = $brand->website_url ?? ''; $this->showroom = $brand->showroom ?? ''; $this->is_published = $brand->is_published; $this->order = $brand->order ?? 0; $this->highlights = $brand->highlights ?: ['', '', '']; $this->services = $brand->services ?: [['title' => '', 'description' => '']]; $this->gallery = CmsBrand::normalizeGallery($brand->gallery); // Absätze im Formular als Textblock: eine Leerzeile trennt sie. $this->sections = collect(CmsBrand::normalizeSections($brand->sections)) ->map(fn (array $s) => [ 'title' => $s['title'], 'paragraphs' => implode("\n\n", $s['paragraphs']), 'claim' => $s['claim'], 'quote' => $s['quote'], 'quote_author' => $s['quote_author'], 'detail_title' => $s['detail']['title'] ?? '', 'detail_paragraphs' => implode("\n\n", $s['detail']['paragraphs'] ?? []), ]) ->all(); $this->loadLocaleFields($brand, $this->editLocale); $this->showForm = true; }; $switchLocale = function (string $locale) { $this->editLocale = $locale; if ($this->editingId) { $brand = CmsBrand::find($this->editingId); if ($brand) { $this->loadLocaleFields($brand, $locale); } } }; $addHighlight = fn () => $this->highlights[] = ''; $removeHighlight = function (int $index) { unset($this->highlights[$index]); $this->highlights = array_values($this->highlights); }; $addService = fn () => $this->services[] = ['title' => '', 'description' => '']; $removeService = function (int $index) { unset($this->services[$index]); $this->services = array_values($this->services); }; $addSection = fn () => $this->sections[] = [ 'title' => '', 'paragraphs' => '', 'claim' => '', 'quote' => '', 'quote_author' => '', 'detail_title' => '', 'detail_paragraphs' => '', ]; $removeSection = function (int $index) { unset($this->sections[$index]); $this->sections = array_values($this->sections); }; $moveSection = function (int $index, int $direction) { $target = $index + $direction; if (! isset($this->sections[$index], $this->sections[$target])) { return; } [$this->sections[$index], $this->sections[$target]] = [$this->sections[$target], $this->sections[$index]]; }; $addGalleryImage = function (string $category = CmsBrand::GALLERY_PROJECT) { $this->gallery[] = [ 'image' => '', 'category' => in_array($category, CmsBrand::GALLERY_CATEGORIES, true) ? $category : CmsBrand::GALLERY_INSPIRATION, 'caption' => '', ]; }; $removeGalleryImage = function (int $index) { unset($this->gallery[$index]); $this->gallery = array_values($this->gallery); }; $save = function () { $validated = validator([ 'slug' => $this->slug, 'brandName' => $this->brandName, 'tagline' => $this->tagline, 'teaser' => $this->teaser, 'summary' => $this->summary, 'website_url' => $this->website_url, 'showroom' => $this->showroom, 'is_published' => $this->is_published, 'order' => $this->order, ], [ 'slug' => 'required|string|max:255|regex:/^[a-z0-9-]+$/', 'brandName' => 'required|string|max:255', 'tagline' => 'nullable|string|max:255', 'teaser' => 'nullable|string|max:2000', 'summary' => 'nullable|string|max:4000', 'website_url' => 'nullable|url|max:500', 'showroom' => 'nullable|string|max:255', 'is_published' => 'boolean', 'order' => 'integer|min:0', ], [ 'slug.regex' => 'Der Slug darf nur Kleinbuchstaben, Ziffern und Bindestriche enthalten.', 'website_url.url' => 'Bitte eine vollständige URL angeben (mit https://).', ])->validate(); $duplicate = CmsBrand::query() ->where('slug', $validated['slug']) ->when($this->editingId, fn ($q) => $q->whereKeyNot($this->editingId)) ->exists(); if ($duplicate) { Flux::toast(variant: 'danger', heading: 'Slug vergeben', text: 'Es gibt bereits eine Marke mit diesem Slug.'); return; } $brand = $this->editingId ? CmsBrand::findOrFail($this->editingId) : CmsBrand::query()->make(); $brand->slug = $validated['slug']; $brand->setTranslation('name', $this->editLocale, $validated['brandName']); $brand->setTranslation('tagline', $this->editLocale, $validated['tagline'] ?? ''); $brand->setTranslation('teaser', $this->editLocale, $validated['teaser'] ?? ''); $brand->setTranslation('summary', $this->editLocale, $validated['summary'] ?? ''); $brand->setTranslation('description', $this->editLocale, CmsFluxEditorHtmlTransformer::fromEditor((string) $this->description)); $brand->logo = $this->logo ?: null; $brand->image = $this->image ?: null; $brand->website_url = $validated['website_url'] ?: null; $brand->showroom = $validated['showroom'] ?: null; $brand->highlights = collect($this->highlights) ->map(fn ($h) => trim((string) $h)) ->filter() ->values() ->toArray(); $brand->services = collect($this->services) ->filter(fn ($s) => ! empty($s['title']) || ! empty($s['description'])) ->values() ->toArray(); $brand->gallery = CmsBrand::normalizeGallery($this->gallery); $brand->setTranslation('widget_label', $this->editLocale, trim((string) $this->widgetLabel)); $brand->setTranslation('cta_title', $this->editLocale, trim((string) $this->ctaTitle)); $brand->setTranslation('cta_text', $this->editLocale, trim((string) $this->ctaText)); // 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'] ?? '', 'paragraphs' => $s['paragraphs'] ?? '', 'claim' => $s['claim'] ?? '', 'quote' => $s['quote'] ?? '', 'quote_author' => $s['quote_author'] ?? '', 'detail' => [ 'title' => $s['detail_title'] ?? '', 'paragraphs' => $s['detail_paragraphs'] ?? '', ], ])->all()); $brand->is_published = $validated['is_published']; $brand->order = $validated['order']; $brand->save(); $this->showForm = false; $this->editingId = null; Flux::toast(variant: 'success', heading: 'Gespeichert', text: 'Marke wurde erfolgreich gespeichert.'); }; $togglePublished = function (int $id) { $brand = CmsBrand::findOrFail($id); $brand->update(['is_published' => ! $brand->is_published]); Flux::toast(heading: 'Status geändert', text: $brand->is_published ? 'Veröffentlicht' : 'Entwurf'); }; $deleteBrand = function (int $id) { CmsBrand::findOrFail($id)->delete(); Flux::toast(variant: 'success', heading: 'Gelöscht', text: 'Marke wurde entfernt.'); }; $cancelForm = function () { $this->showForm = false; $this->editingId = null; }; ?>
Kernmarken
@foreach (config('flux-cms.locales', ['de' => 'DE', 'en' => 'EN']) as $code => $label) {{ strtoupper($code) }} @endforeach Neue Marke
Die Marken erscheinen als Kacheln auf der Startseite unter „Unsere Kernmarken" und bekommen jeweils eine eigene Detailseite unter /marken/<slug>. Die Reihenfolge steuert das Feld „Sortierung".
@if ($showForm)
{{ $editingId ? 'Marke bearbeiten' : 'Neue Marke' }}
@foreach (config('flux-cms.locales', ['de' => 'DE', 'en' => 'EN']) as $code => $label) {{ strtoupper($code) }} @endforeach
@error('slug') {{ $message }} @enderror @error('brandName') {{ $message }} @enderror
@error('website_url') {{ $message }} @enderror
{{-- Drei Merkmale für die Übersichtsseite --}}
Merkmale auf /marken Merkmal hinzufügen
@foreach ($highlights as $i => $highlight)
@endforeach
{{-- Bilder --}}
@if ($logo)
@endif

{{ $logo ?: 'Kein Logo' }}

@if ($image)
@endif

{{ $image ?: 'Kein Bild' }}

{{-- Einleitung --}}
Einleitung ({{ strtoupper($editLocale) }})
{{-- Leistungen --}}
Leistungen und Besonderheiten Punkt hinzufügen
@foreach ($services as $i => $service)
Punkt {{ $i + 1 }} @if (count($services) > 1) @endif
@endforeach
{{-- Abschnitte der Markenseite --}}
Abschnitte der Markenseite Abschnitt hinzufügen
Die Abschnitte erscheinen auf der Detailseite unter der Einleitung, in dieser Reihenfolge. Eine Leerzeile trennt zwei Absätze. Die Vertiefung wird auf der Seite als aufklappbarer Bereich dargestellt – leer lassen, wenn es keine gibt.
@if (empty($sections))

Noch keine Abschnitte.

@else
@foreach ($sections as $i => $section)
Abschnitt {{ $i + 1 }}

Vertiefung – erscheint aufklappbar

@endforeach
@endif
{{-- Abschlussblock und Beschriftung des Terminanfrage-Widgets --}}
Abschluss und Terminanfrage

Steht auf dem schwebenden Button, solange man auf dieser Markenseite ist. Leer lassen für die allgemeine Beschriftung.

{{-- Galerie --}}
Bildergalerie
Projekt Inspiration
Projekt = tatsächlich ausgeführter Auftrag. Inspiration = Entwurf, Rendering oder KI-Darstellung. Auf der Seite werden beide getrennt gezeigt und beschriftet – bitte nichts vermischen.
@if (empty($gallery))

Noch keine Bilder.

@else
@foreach ($gallery as $i => $item)
@if (!empty($item['image']))
@endif Realisiertes Projekt Inspiration / Entwurf
@endforeach
@endif
Speichern Abbrechen
@endif
@forelse ($this->brands as $brand)
@if ($brand->image)
@else
@endif
{{ $brand->getTranslation('name', $editLocale) }} @unless ($brand->is_published) Entwurf @endunless

{{ $brand->getTranslation('tagline', $editLocale) ?: '–' }} · /marken/{{ $brand->slug }}

@empty
Keine Marken Lege die erste Kernmarke mit dem Button oben an.
@endforelse