'', '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; }; ?>
/marken/<slug>.
Die Reihenfolge steuert das Feld „Sortierung".
{{ $logo ?: 'Kein Logo' }}
{{ $image ?: 'Kein Bild' }}
Noch keine Abschnitte.
@elseVertiefung – erscheint aufklappbar
Steht auf dem schwebenden Button, solange man auf dieser Markenseite ist. Leer lassen für die allgemeine Beschriftung.
Noch keine Bilder.
@else{{ $brand->getTranslation('tagline', $editLocale) ?: '–' }} · /marken/{{ $brand->slug }}